HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)

#はじめに
こんにちは。よろず相談担当　すぎもん:yum:です。
今回は、HULFT IoT EdgeStreamingのSDKを使用したコンポーネント（アダプタ）の開発をやってみようと思います。全3部作で完結するよう記載してます。

#今回やること
今回は、第２弾としてHULFT IoT EdgeStreaming Plugin SDKを使用してプラグインを開発してみます。他にセットアップ編、実行編の記事もあるので是非参考にしてみてください。

:arrow_forward: [HULFT IoT EdgeStreamingでプラグインを作成してみた【セットアップ編】(1/3)]
(https://qiita.com/sugimon/items/cf7503479c6e4c46c3b3)
:arrow_forward: HULFT IoT EdgeStreamingでプラグインを作成してみた【開発編】(2/3)
:arrow_forward: [HULFT IoT EdgeStreamingでプラグインを作成してみた【実行編】(3/3)](https://qiita.com/sugimon/items/93f47d7bd472a8b18e54)

#プラグインの作成概要
EdgeStreamingのプラグイン作成の構成は、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458288/b0173151-a038-408b-fe7c-2d8585851cae.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458288/efc6b237-4b32-6f46-5333-bb6d7be0df3d.png)



###実行環境構成
Studioから指定されたStreaming処理を実行するRuntime部分になります。

**SourceOperation**
`Streamingデータ（Tuple）を生成するオペレーションです。「入力処理」となります。
 ※JSON形式となります`

**SinkOperation**
`Streamingデータ（Tuple）を外部へ出力するオペレーションです。「出力処理」となります。`

**UDSFOperation**
`Streamingデータ（Tuple）の変換を行うオペレーションです。
また、変換されたStreming（Tuple）を出力します。「入出力(変換)処理」`


###開発環境構成
RunTimeにStreaming処理をどのように行うかを指示するBQL（SQLライクな文法）を生成します。

**AdapterModuleComponent**
`コンポーネント（アダプタ）を表すクラスです。オペレーションと多対1となります。`

**BQLPluginSourceOperationFactory**
`SourceOperartionのプロパティを定義します。「入力処理」
Sourceオペレーションを生成するBQL（Create Source文）を出力します。`

**BQLPluginOutputOperationFactory**
`SinkOperationのプロパティを定義します。「出力処理」
Sinkオペレーションを生成するBQL（Create Sink文）を出力します。`

**BQLPluginUDSFOperationFactory**
`UDSFのプロパティを定義します。「入出力(変換)処理」
UDSFオペレーションを生成するBQL（Select文）を出力します。`

#プラグインの作成
今回は、以下のような形でプラグイン作成をしてみました。

**・基本情報**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458288/92434957-6435-50ad-6384-b7833e965e6d.png)

**・オペレーション情報**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458288/eb58ed26-97f6-0a8c-6d5c-6c82555d0273.png)


#実行環境側(Golang)の実装
Studioから指定されたStreaming処理の実装をします。

**環境準備**
1. モジュールディレクトリの作成
・`$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample`
　※ここに、ソースファイルを格納しました。(source.go, sink.go, udsf.go)
2. pluginディレクトリの作成
・`$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample/plugin`
3. externalディレクトリの作成
・`$SDK_HOME/dev/go/src/github.sis.saison.co.jp/sherpa/es-agent/sample/external`

**ソースファイルの作成**
モジュールディレクトリ（ここではsample）に以下のファイル名でソースファイルを作成していってみます。

・ファイル構成は、以下のとおりです。

```
├─sample
│   ├─source.go（入力処理）
│   ├─sink.go（出力処理）
│   ├─udsf.go（入出力変換処理）
│   │ 
│   ├─external
│   │   ├─plugin_main.go（main関数）
│   │   │ 
│   └─plugin
│   │   ├─plugin.go（各sample配下のソースファイルを登録）
│   │   │ 
```

では、ソースファイルを作成していってみましょう。

**・source.go（入力処理）**
一定時間間隔で疑似乱数を生成する処理を作成します。
主な流れは、以下のとおりです。
1. 整数型のパラメータ”interval”を受け取る
2. intervalの時間間隔で疑似乱数を生成する
3. JSONスキーマのデータを持つTupleを生成する

```json:Tuple
{
  "type": "object",
  "required": ["payload"],
  "properties": {
    "payload": {
      "type": "object",
      "required": ["value"],
      "properties": {
        "value": {
          "type": "number"
        }
      }
    }
  }
}
```

出力されるTupleのJSONデータは、以下のような形になります。

`{"payload": {"value": 3.5423242}}`

source.goは、以下のように作成してみました。

```golang:source.go
package sample

import (
	"math/rand"
	"time"

	"gopkg.in/sensorbee/sensorbee.v0/bql"
	"gopkg.in/sensorbee/sensorbee.v0/core"
	"gopkg.in/sensorbee/sensorbee.v0/data"
)

type source struct {
	interval time.Duration
	term     chan struct{}
}

func (s *source) GenerateStream(ctx *core.Context, w core.Writer) error {
	rand.Seed(time.Now().UnixNano())

	next := time.Now()
	for {
		val := rand.Float64()
		m := data.Map{"value": data.Float(val)}
		t := core.NewTuple(data.Map{"payload": m})
		if s.interval > 0 {
			t.Timestamp = next
		}
		ctx.Log().Debug("generation: ", val)
		if err := w.Write(ctx, t); err != nil {
			return err
		}

		if s.interval > 0 {
			now := time.Now()
			next = next.Add(s.interval)
			if next.Before(now) {
				next = now.Add(s.interval)
			}

			select {
			case <-s.term:
				return core.ErrSourceStopped
			case <-time.After(next.Sub(now)):
			}
		}
	}
	return nil
}

func (s *source) Stop(ctx *core.Context) error {
	s.term <- struct{}{}
	return nil
}

func CreateSource(ctx *core.Context, ioParams *bql.IOParams, params data.Map) (core.Source, error) {
	interval, err := getInterval(params)
	if err != nil {
		return nil, err
	}

	return &source{
		interval: interval,
		term:     make(chan struct{}),
	}, nil
}

func getInterval(params data.Map) (time.Duration, error) {
	interval := 1 * time.Second
	if v, ok := params["interval"]; ok {
		i, err := data.ToDuration(v)
		if err != nil {
			return interval, err
		}
		interval = i
	}
	return interval, nil
}

```

**・sink.go（出力処理）**
有効小数点桁数で切り捨ててログに出力する処理を作成します。

主な流れは、以下のとおりです。
1. 整数型のパラメータ「decimal」を受け取る
2. 「decimal」の有効小数点桁数で、受け取った値を標準出力に出力
3. JSONデータのスキーマのデータを持つTupleを受け取る`

```json:Tuple
{
  "type": "object",
  "required": ["payload"],
  "properties": {
    "payload": {
      "type": "object",
      "required": ["value", "formula"],
      "properties": {
        "value": {
          "type": "number"
        }
        "formula": {
          "type": "string"
        }
      }
    }
  }
}
```

sink.goは、以下のように作成してみました。

```golang:sink.go
package sample

import (
	"fmt"
	"math"

	"gopkg.in/sensorbee/sensorbee.v0/bql"
	"gopkg.in/sensorbee/sensorbee.v0/core"
	"gopkg.in/sensorbee/sensorbee.v0/data"
)

type sink struct {
	decimal int
}

func (s *sink) Write(ctx *core.Context, tuple *core.Tuple) error {
	p, ok := tuple.Data["payload"]
	if !ok {
		return fmt.Errorf("the tuple doesn't have the required field: payload")
	}

	payload, err := data.AsMap(p)
	if err != nil {
		return err
	}

	v, ok := payload["value"]
	if !ok {
		return fmt.Errorf("the tuple doesn't have the required field: value")
	}

	value, err := data.AsFloat(v)
	if err != nil {
		return err
	}

	f, ok := payload["formula"]
	if !ok {
		return fmt.Errorf("the tuple doesn't have the required field: formula")
	}

	formula, err := data.AsString(f)
	if err != nil {
		return err
	}
	shift := math.Pow(10, float64(s.decimal))
	value = math.Floor(value*shift) / shift
	ctx.Log().Infof("formula: %s", formula)
	ctx.Log().Infof("value: %f", value)
	return nil
}

func (s *sink) Close(ctx *core.Context) error {
	return nil
}

func CreateSink(ctx *core.Context, ioParams *bql.IOParams, params data.Map) (core.Sink, error) {
	decimal, err := getDecimal(params)
	if err != nil {
		return nil, err
	}

	return &sink{
		decimal: decimal,
	}, nil
}

func getDecimal(params data.Map) (int, error) {
	node, ok := params["decimal"]
	if !ok {
		return 0, fmt.Errorf("decimal is required")
	}
	decimal, err := data.AsInt(node)
	if err != nil {
		return 0, fmt.Errorf("decimal must be a int:%s", err)
	}
	return int(decimal), nil
}

```

**・udsf.go（入出力変換処理）**
udsfオブジェクトは、以下をデータを受け取ります。
- 文字列型のパラメータ：stream_name(入力Stream名)
- operaror(演算子)
- 浮動小数点型パラメータ： initial_value(初期値)

udsfオブジェクトは、受け取ったTupleのvalue要素の値を、現在の値(スタート時はinitial_value(初期値))に指定した演算子で、演算し続けます。


udsf.goは、以下のように作成してみました。


```golang:udsf.go
package sample

import (
	"fmt"

	"gopkg.in/sensorbee/sensorbee.v0/bql/udf"
	"gopkg.in/sensorbee/sensorbee.v0/core"
	"gopkg.in/sensorbee/sensorbee.v0/data"
)

type operator byte

const (
	none    = ' '
	plus    = '+'
	minus   = '-'
	times   = '*'
	divided = '/'
)

type udsf struct {
	cur float64
	ope operator
}

func (u *udsf) Process(ctx *core.Context, tuple *core.Tuple, w core.Writer) error {
	p, ok := tuple.Data["payload"]
	if !ok {
		return fmt.Errorf("the tuple doesn't have the required field: payload")
	}

	payload, err := data.AsMap(p)
	if err != nil {
		return err
	}

	v, ok := payload["value"]
	if !ok {
		return fmt.Errorf("the tuple doesn't have the required field: value")
	}

	value, err := data.AsFloat(v)
	if err != nil {
		return err
	}

	var formula string
	newVal := u.cur
	switch u.ope {
	case plus:
		newVal += value
	case minus:
		newVal -= value
	case times:
		newVal *= value
	case divided:
		newVal /= value
	}
	formula = fmt.Sprintf("%f %s %f", u.cur, string(u.ope), value)
	ctx.Log().Debug("calculate: " + formula)
	m := data.Map{
		"value":   data.Float(newVal),
		"formula": data.String(formula),
	}
	if err := w.Write(ctx, core.NewTuple(data.Map{"payload": m})); err != nil {
		return err
	}
	u.cur = newVal
	return nil
}

func (u *udsf) Terminate(ctx *core.Context) error {
	return nil
}

func CreateUDSF(decl udf.UDSFDeclarer, params data.Map) (udf.UDSF, error) {
	inputStream, err := getStreamName(params)
	if err != nil {
		return nil, err
	}

	operator, err := getOperator(params)
	if err != nil {
		return nil, err
	}

	initialValue, err := getInitialValue(params)
	if err != nil {
		return nil, err
	}

	if err := decl.Input(inputStream, nil); err != nil {
		return nil, err
	}

	return &udsf{
		ope: operator,
		cur: initialValue,
	}, nil
}

func getStreamName(params data.Map) (string, error) {
	node, ok := params["stream_name"]
	if !ok {
		return "", fmt.Errorf("stream_name is required")
	}
	streamName, err := data.AsString(node)
	if err != nil {
		return "", fmt.Errorf("stream_name must be a string:%s", err)
	}
	return streamName, nil
}

func getOperator(params data.Map) (operator, error) {
	node, ok := params["operator"]
	if !ok {
		return none, fmt.Errorf("operator is required")
	}
	operatorStr, err := data.AsString(node)
	if err != nil {
		return none, fmt.Errorf("operator must be a string:%s", err)
	}

	switch operatorStr {
	case "plus":
		return plus, nil
	case "minus":
		return minus, nil
	case "times":
		return times, nil
	case "divided":
		return divided, nil
	default:
		return none, fmt.Errorf("invalid oparator")
	}
}

func getInitialValue(params data.Map) (float64, error) {
	initialValue := 0.0
	node, ok := params["initial_value"]
	if !ok {
		return initialValue, nil
	}
	initialValue, err := data.AsFloat(node)
	if err != nil {
		return initialValue, fmt.Errorf("initial_value is invalid")
	}
	return initialValue, nil
}

```

**ソースファイルの登録**
pluginディレクトリに、plugin.goを作成し、Source、Sink、 UDSF Operationの登録処理(BQLとして使用するため)を実装します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458288/57c12fa2-3751-0678-c8f0-894bf8d2d1f4.png)

plugin.goは、以下のように作成してみました。

```Golang:plugin.go
package plugin

import (
	"github.sis.saison.co.jp/sherpa/es-agent/sample"
	"gopkg.in/sensorbee/sensorbee.v0/bql"
	"gopkg.in/sensorbee/sensorbee.v0/bql/udf"
)

func init() {
	bql.MustRegisterGlobalSourceCreator("sample_source", bql.SourceCreatorFunc(sample.CreateSource))
	bql.MustRegisterGlobalSinkCreator("sample_sink", bql.SinkCreatorFunc(sample.CreateSink))
	udf.MustRegisterGlobalUDSFCreator("sample_udsf", udf.MustConvertToUDSFCreator(sample.CreateUDSF))
}

```

**main関数の作成**
最後に、単体の実行モジュールとして処理を呼び出すmain関数をexternalディレクトリに、plugin_main.goを作成し実装します。

plugin_main.goは、以下のように作成してみました。

```Golang:plugin_main.go

package main

import (
	"os"

	"github.sis.saison.co.jp/sherpa/es-agent/external/plugin"
	_ "github.sis.saison.co.jp/sherpa/es-agent/sample/plugin"
)

func main() {
	if err := plugin.NewServer().ListenAndServe(); err != nil {
		os.Exit(1)
	}
}

```

ここまでで、Runtime側の準備が完了しました。
次に、開発環境側の実装をしていきます。

#開発環境側(Java)の実装
RuntimeにStreaming処理をどのように行うかを指示するBQL（SQLライクな文法）の生成を実装します。

**環境準備**
**モジュールディレクトリの作成**
・`$SDK_HOME/dev/sample_adapter`ディレクトリを作成します。

**ファイルのコピー**
・`$SDK_HOME/dev/conf`ディレクトリにある、build.xml、config.propertiesをモジュールディレクトリにコピーします。
`$SDK_HOME/dev/conf/build.xml` 
⇒ `$SDK_HOME/dev/sample_adapter/build.xml`
`$SDK_HOME/dev/conf/config.properties`
⇒ `$SDK_HOME/dev/sample_adapter/config.propertites`

コピーしたconfig.propertiesファイルを編集します。

```
Implementation-Title=SampleAdapter
Implementation-Vendor=sugimon
Implementation-Version=0

module.category=Sample
module.label=Sample Plugin
display.name=Sample Plugin Adapter

plugin.name=sample_plugin
esagent.plugin.package=github.sis.saison.co.jp/sherpa/es-agent/sample
```

**ソースファイル用ディレクトリを作成**
   `$SDK_HOME/dev/sample_adapter`にsrcディレクトリを作成します。
   ($SDK_HOME/dev/sample_adapter/src)

　 次に作成するjavaファイルのパッケージ`com/appresso/ds/dp/modules/adapter/sample`
   になるように、以下のようなパッケージ用のディレクトリを作成します。
   ($SDK_HOME/dev/sample_adapter/src/com/appresso/ds/dp/modules/adapter/sample)


**ソースファイルの作成**
   パッケージディレクトリに以下のファイル名でソースファイルを作成します。

   ・SampleAdapterModuleComponent.java
   ・SampleSinkOperationFactory.java
   ・SampleSourceOperationFactory.java
   ・SampleUDSFOperationFactory.java

```
├─ sample_adapter
│      │  build.xml
│      │  config.properties
│      ├─ src
│      │   └com
│      │     └appresso
│      │       └ds
│      │         └dp
│      │           └modules
│      │              └adapter
│      │                 └sample
│      │                    SampleAdapterModuleComponent.java
│      │                    SampleSinkOperationFactory.java
│      │                    SampleSourceOperationFactory.java
│      │                    SampleUDSFOperationFactory.java

```

こちらもそれぞれ、ソースファイルを作成していってみましょう。

**・SampleSourceOperationFactory.java（入力処理）**
Sourceオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。（継承元クラス：BQLPluginSourceOperationFactoryクラス）

SampleSourceOperationFactory.javaは、以下のように作成してみました。

```java:SampleSourceOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginSourceOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import org.xml.sax.SAXException;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.FLOAT;

public class SampleSourceOperationFactory extends BQLPluginSourceOperationFactory {

	@Override
	protected String getLabel() {
		return "Sample source";
	}

	@Override
	protected String getPluginName() {
		return "sample_plugin";
	}

	@Override
	public String getOperationName() {
		return "sample_source";
	}

	@Override
	protected String getTypeName() {
		return "sample_source";
	}

	@Override
	protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
		operationConfigurator.addSimpleParameter(createIntervalParameter());
	}

	@Override
	protected void setupOutputSchema(XmlHandler handler, OperationConfiguration conf) throws Exception {
		handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
		writeElement(handler, "value");
		handler.endElement("", "payload", "payload");
	}

	protected void writeElement(XmlHandler handler, String name) throws SAXException {
		handler.startElement("", name, name, EMPTY_ATTRIBUTE);
		handler.endElement("", name, name);
	}

	static SimpleParameter createIntervalParameter() {
		NumberFillin fillin = new NumberFillin();
		fillin.setMinValue(0.001);
		fillin.setMaxValue(1314000);
		fillin.setAllowMin(true);
		fillin.setAllowMax(true);
		fillin.setPrecision(10);
		fillin.setDecimal(3);
		fillin.setAllowDouble(true);
		fillin.setLabel("Interval[sec]");
		fillin.setRequired(true);
		return new SimpleParameter(FLOAT.toParameterKey("interval"), fillin);
	}
}

```

**・SampleSinkOperationFactory.java（出力処理）**
Sinkオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。（継承元クラス：BQLPluginSinkOperationFactoryクラス）

SampleSinkOperationFactory.javaは、以下のように作成してみました。

```java:SampleSinkOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginOutputOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import com.appresso.ds.dp.spi.OperationContext;
import org.xml.sax.SAXException;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.INTEGER;

public class SampleSinkOperationFactory extends BQLPluginOutputOperationFactory {
	@Override
	protected String getLabel() {
		return "Sample sink";
	}

	@Override
	protected String getPluginName() {
		return "sample_plugin";
	}

	@Override
	public String getOperationName() {
		return "sample_sink";
	}

	@Override
	protected String getTypeName() {
		return "sample_sink";
	}

	@Override
	protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
		operationConfigurator.addSimpleParameter(createDecimalParameter());
	}

	protected void setupInputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
			throws Exception {
		handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
		writeElement(handler, "formula");
		writeElement(handler, "value");
		handler.endElement("", "payload", "payload");
	}

	protected void writeElement(XmlHandler handler, String name) throws SAXException {
		handler.startElement("", name, name, EMPTY_ATTRIBUTE);
		handler.endElement("", name, name);
	}

	static SimpleParameter createDecimalParameter() {
		NumberFillin fillin = new NumberFillin();
		fillin.setMinValue(0);
		fillin.setMaxValue(10);
		fillin.setAllowMin(true);
		fillin.setAllowMax(true);
		fillin.setLabel("Decimal");
		fillin.setRequired(true);
		return new SimpleParameter(INTEGER.toParameterKey("decimal"), fillin);
	}
}


```

**・SampleUDSFOperationFactory**
UDSFオペレーションのプロパティを保持するオブジェクトを返したり、オペレーションオブジェクトを返したりします。

SampleUDSFOperationFactory.javaは、以下のように作成してみました。

```java:SampleUDSFOperationFactory.java
package com.appresso.ds.dp.modules.adapter.sample;

import com.appresso.ds.common.bql.UDSFFromArgument;
import com.appresso.ds.common.bql.UDSFFromTemplate;
import com.appresso.ds.common.spi.constraint.Item;
import com.appresso.ds.common.spi.constraint.Multi;
import com.appresso.ds.common.spi.constraint.NumberFillin;
import com.appresso.ds.common.spi.param.SimpleParameter;
import com.appresso.ds.common.xmlfw.xml.XmlHandler;
import com.appresso.ds.dp.share.adapter.bql.common.BQLPluginUDSFOperationFactory;
import com.appresso.ds.dp.spi.OperationConfiguration;
import com.appresso.ds.dp.spi.OperationConfigurator;
import com.appresso.ds.dp.spi.OperationContext;
import org.xml.sax.SAXException;

import java.util.stream.Stream;

import static com.appresso.ds.common.bql.BQLSimpleParameterType.FLOAT;
import static com.appresso.ds.common.bql.BQLSimpleParameterType.STRING;

public class SampleUDSFOperationFactory extends BQLPluginUDSFOperationFactory {
	@Override
	protected String getLabel() {
		return "Sample UDSF";
	}

	@Override
	public String getPluginName() {
		return "sample_plugin";
	}

	@Override
	protected String getTypeName() {
		return "sample_udsf";
	}

	@Override
	public String getOperationName() {
		return "sample_udsf";
	}

	@Override
	protected void addArgs(UDSFFromTemplate template) {
		template.addArg(new UDSFFromArgument(STRING.toParameterKey("operator")));
		template.addArg(new UDSFFromArgument(FLOAT.toParameterKey("initial_value")));
	}

	@Override
	protected void setupOperationConfigurator(OperationConfigurator operationConfigurator) {
		setStreamConfigurationParameter(operationConfigurator);
		operationConfigurator.addSimpleParameter(createOperatorParameter());
		operationConfigurator.addSimpleParameter(createInitialValueParameter());
	}

	@Override
	protected void setupInputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
			throws Exception {
		handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
		writeElement(handler, "value");
		handler.endElement("", "payload", "payload");
	}

	@Override
	protected void setupOutputSchema(XmlHandler handler, OperationConfiguration conf, OperationContext context)
			throws Exception {
		handler.startElement("", "payload", "payload", EMPTY_ATTRIBUTE);
		writeElement(handler, "formula");
		writeElement(handler, "value");
		handler.endElement("", "payload", "payload");
	}

	protected void writeElement(XmlHandler handler, String name) throws SAXException {
		handler.startElement("", name, name, EMPTY_ATTRIBUTE);
		handler.endElement("", name, name);
	}

	static SimpleParameter createInitialValueParameter() {
		NumberFillin fillin = new NumberFillin();
		fillin.setPrecision(10);
		fillin.setDecimal(3);
		fillin.setAllowDouble(true);
		fillin.setLabel("Initial value");
		fillin.setRequired(true);
		return new SimpleParameter(FLOAT.toParameterKey("initial_value"), fillin);
	}

	static SimpleParameter createOperatorParameter(){
		Multi multi = new Multi(Operator.getItems());
		multi.setLabel("Operator");
		multi.setRequired(true);
		SimpleParameter param = new SimpleParameter(STRING.toParameterKey("operator"), multi);
		return param;
	}

	enum Operator {
		Plus("+","plus"),
		Minus("-","minus"),
		Times("*","times"),
		Divided("/","divided");

		public String getDisplayName() {
			return displayName;
		}

		public String getValue() {
			return value;
		}

		private final String displayName;

		private final String value;

		private Operator(String displayName, String value) {
			this.displayName = displayName;
			this.value=value;
		}

		Item toItem(){
			return new Item(value,displayName);
		}

		static Item[] getItems(){
			return Stream.of(Operator.values()).map(s->s.toItem()).toArray(Item[]::new);
		}
	}
}

```

**・SampleAdapterModuleComponent**
コンポーネント（アダプタ）を表すクラスです。

SampleAdapterModuleComponent.javaは、以下のように作成してみました。


```java:SampleAdapterModuleComponent.java
package com.appresso.ds.dp.modules.adapter.sample;

import java.util.ArrayList;
import java.util.List;

import com.appresso.ds.common.kernel.modules.LicenseManager;
import com.appresso.ds.common.license.LicensePackageType;
import com.appresso.ds.dp.spi.AdapterModuleComponent;
import com.appresso.ds.dp.spi.OperationFactory;
import com.appresso.ds.dp.spi.ResourceFactory;

public class SampleAdapterModuleComponent extends AdapterModuleComponent {

	private static final String MODULE_COMPONENT_NAME = "Sample Adapter";

	@Override
	public OperationFactory[] getOperationFactories() throws Exception {
		List<OperationFactory> operationFactories = new ArrayList<>();
		operationFactories.add(new SampleSourceOperationFactory());
		operationFactories.add(new SampleUDSFOperationFactory());
		operationFactories.add(new SampleSinkOperationFactory());
		return operationFactories.toArray(new OperationFactory[operationFactories.size()]);
	}

	@Override
	public ResourceFactory[] getResourceFactories() throws Exception {
		return new ResourceFactory[]{};
	}

	public void checkLicense() throws Exception {
		LicenseManager licenseManager = getContext().getProxy(LicenseManager.class);
		licenseManager.checkLicense(getModuleComponentName(), getPermittedPackageType());
	}

	private String getModuleComponentName() {
		return MODULE_COMPONENT_NAME;
	}

	private int[] getPermittedPackageType() {
		return new int[]{LicensePackageType.TYPE_BASIC_SERVER};
	}
}

```


#最後に
今回は、実際に実行環境側と開発環境側に分けて処理の実装をしてみました。ここまででプラグインの作成処理が完了しました。次回はこれらをビルドして実行をしてみたいと思います。

このブログでは、今後も技術の「よろず相談窓口」で相談を受けた内容や、誕生したワザをご紹介していけたらな、と思っています。

これからも是非チェックいただき、宜しければフォローをお願いします。

それでは、また！