embulk v0.10 APIを利用する私的メモ

11 min read読了の目安(約10500字

はじめに

Embulkのプラグインを0.9から0.10対応にするためにのメモ。
基本的にはここJava plugins to catch up with Embulk v0.10 from v0.9に書いてあることを読めば良い。普段Javaを書かない自分用のメモ(お気づきの点があればぜひ教えてください)
@dmikurubeさんに教えてもらったものをまとめただけともいう

ゴール

  • プラグインが "embulk-core" に依存しなくなること(本体のjacksonのライブラリとぶつかったり等の問題を回避して、プラグインで自由にライブラリを利用できるようにする等)

新しいAPIで書かれているプラグイン

gradle-embulk-plugins

v0.10のAPIを使ってプラグインを開発するには、gradleのプラグインとして提供されるgradle-embulk-pluginsを使う必要がある、こちらのプラグインはgradleのバージョン6を必要とするため、まずgradleのバージョンをあげなければならない。

gradleのアップデート方法

gradleのバージョンは6.Xを利用する。近々gradle 7がリリースされるようだが、gradle-embulk-pluginsはまだ7.0に対応していないので、6.X系のバージョンを利用する。
最新のバージョンはここ でチェックする

./gradlew wrapper --gradle-version 6.8.2

のように実行する。

build.gradleの書き換え

How to migrate old-style build.gradle of your Embulk plugins を読みながらプラグインのbuild.gradleを書き換えする。書いてあるとおりにしたがっていけばよい。

4. Add required testCompile if depending on embulk-core:0.9.22+.の部分について、embulk-0.10.10以降は次のように記載する。

    testCompile "org.embulk:embulk-deps:0.10.26"

以下の部分は将来プラグインをrubygemsではなくmaven centralから取得できる仕組みで利用する。プラグインをMaven Centralにアップロードできるようになるまではコメントにしておくのがよい(と思う)

//publishing {
//    publications {
//        embulkPluginMaven(MavenPublication) {  // Publish it with "publishEmbulkPluginMavenPublicationToMavenRepository".
//            from components.java  // Must be "components.java". The dependency modification works only for it.
//        }
//    }
//    repositories {
//        maven {
//            url = "${project.buildDir}/mavenPublishLocal"
//        }
//    }
//}

Javaコンパイルの際にdeprecatedの警告を出力するには以下の設定を追記する。

tasks.withType(JavaCompile) {
    options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked"
}

gradle-embulk-pluginsはプラグインロードに必要なRubyファイルを自動生成するようになっており、従来のプラグインにあったプラグイン名.rbというファイルは不要になっている。
諸般の事情(guessをRubyで実装している等)がある場合には以下のような記述をして独自のRubyファイルをgemに含めることができる。

gem {

    generateRubyCode = false  // Avoiding generate lib/embulk/parser/jsonpath.rb
    into("lib/embulk/parser/") {
      from "lib/embulk/parser/jsonpath.rb"
    }
    into("lib/embulk/guess/") {
      from "lib/embulk/guess/jsonpath.rb"
    }

Javaの書き換え

importの書き換え

従来embulk-coreの中にまとまっていたクラス群は、embulk-util-configembulk-util-timestampなど別のライブラリに分割された。import文をそれぞれに置き換えなければならない。

以下のようなスクリプトを作成して、機械的に変換をおこなっている。

#!/bin/bash

set -ue

fatal(){
    echo "Fatal Error: $*" >&2
    exit 1
}

if [ ! -d src ] ; then
  fatal "invalid exec directory"
fi

find src -name '*.java' -print0 | xargs -0 \
  perl -i -pe 's/import org.embulk.config.Config;/import org.embulk.util.config.Config;/;
               s/import org.embulk.config.ConfigDefault;/import org.embulk.util.config.ConfigDefault;/;
               s/import org.embulk.config.Task;/import org.embulk.util.config.Task;/;
               s/import org.embulk.spi.ColumnConfig;/import org.embulk.util.config.units.ColumnConfig;/;
               s/import org.embulk.spi.SchemaConfig;/import org.embulk.util.config.units.SchemaConfig;/;
               s/import com.google.common.base.Optional;/import java.util.Optional;/;
               s/import org.embulk.spi.util.FileInputInputStream;/import org.embulk.util.file.FileInputInputStream;/;
               s/import org.embulk.spi.time.TimestampParser;/import org.embulk.util.timestamp.TimestampFormatter;/;
'

Exec.getModelManager()

今後なくなっていく予定だが、当面はExecInternalを利用することで暫定的に回避することができる。ModelManagerは近々削除される予定になっており、いずれ別の書き方に変える必要がある。

Exec.getModelManager()
ExecInternal.getModelManager

ExecInternalを利用するには、build.gradleにcompileOnly "embulk-core"する必要があるが、それ自体非推奨になっている。つまりできるだけembulk-coreをdependenciesに入れるのは避けるべきである。

テストでは、ExecInternalはしばらく利用できる(v0.11開発時に検討される模様)
以下の記述は、テストだけでExecInternalを利用するようにするための設定

dependencies {
    testCompile "org.embulk:embulk-core:0.10.28"
}    

loadConfig, loadTask

ConfigMapper,TaskMapperそれぞれのクラスで提供されるmapメソッドを利用する。

@@ -43,6 +46,7 @@ public class JsonpathParserPlugin
 {
 
     private static final Logger logger = LoggerFactory.getLogger(JsonpathParserPlugin.class);
+    private static final ConfigMapperFactory CONFIG_MAPPER_FACTORY = ConfigMapperFactory.builder().addDefaultModules().build();
 
     private static final Configuration JSON_PATH_CONFIG = Configuration
             .builder()
@@ -94,18 +98,22 @@ public class JsonpathParserPlugin
     @Override
     public void transaction(ConfigSource config, ParserPlugin.Control control)
     {
-        PluginTask task = config.loadConfig(PluginTask.class);
+        final ConfigMapper configMapper = CONFIG_MAPPER_FACTORY.createConfigMapper();
+        final PluginTask task = configMapper.map(config, PluginTask.class);
 
         Schema schema = getSchemaConfig(task).toSchema();
 
-        control.run(task.dump(), schema);
+        control.run(task.toTaskSource(), schema); // v0.10 only
     }
 
     @Override
     public void run(TaskSource taskSource, Schema schema,
             FileInput input, PageOutput output)
     {
-        PluginTask task = taskSource.loadTask(PluginTask.class);
+        final TaskMapper taskMapper = CONFIG_MAPPER_FACTORY.createTaskMapper();
+        final PluginTask task = taskMapper.map(taskSource, PluginTask.class);
+

依存ライブラリの固定

./gradlew dependencies --write-locks を使って利用しているバージョンの依存関係を固定する

プラグインがjacksonを利用している場合、build.gradleに明示的に書かなければならなくなった。バージョンはembulk-coreで利用しているバージョンと同じバージョンを指定する

    // They are once excluded from transitive dependencies of other dependencies,
    // and added explicitly with versions exactly the same with embulk-core:0.10.19.
    compile "com.fasterxml.jackson.core:jackson-annotations:2.6.7"
    compile "com.fasterxml.jackson.core:jackson-core:2.6.7"
    compile "com.fasterxml.jackson.core:jackson-databind:2.6.7"
    compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.6.7"
    compile "javax.validation:validation-api:1.1.0.Final"

API互換

ドキュメントのsinceのところが0.10と書かれていたら新しいAPI、v0.9を利用する場合このメソッドは使ってはいけない

TypeModule

次のようなエラーが出た場合、ConfigMapperFactory を作るときに TypeModule を add する必要がある

Caused by: java.lang.IllegalArgumentException: 
  Can not construct instance of org.embulk.spi.type.Type,
   problem: abstract types either need to be mapped to concrete types,
    private static final ConfigMapperFactory CONFIG_MAPPER_FACTORY = ConfigMapperFactory
            .builder()
            .addDefaultModules()
            .addModule(new TypeModule()) // <-- これ
            .build();

参考

脱Guava

ImmutableMapの書き換え

diff --git a/src/main/java/org/embulk/parser/jsonpath/JsonpathParserPlugin.java b/src/main/java/org/embulk/parser/jsonpath/JsonpathParserPlugin.java
index 1946175..5bc9746 100644
--- a/src/main/java/org/embulk/parser/jsonpath/JsonpathParserPlugin.java
+++ b/src/main/java/org/embulk/parser/jsonpath/JsonpathParserPlugin.java
@@ -2,8 +2,10 @@ package org.embulk.parser.jsonpath;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.JsonNodeType;
+
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Optional;
-import com.google.common.collect.ImmutableMap;
 import com.jayway.jsonpath.Configuration;
 import com.jayway.jsonpath.InvalidJsonException;
 import com.jayway.jsonpath.JsonPath;
@@ -186,15 +188,15 @@ public class JsonpathParserPlugin
 
     private Map<Column, String> createJsonPathMap(PluginTask task, Schema schema)
     {
-        ImmutableMap.Builder<Column, String> builder = ImmutableMap.builder();
+        Map<Column, String> columnMap = new HashMap<>();
         for (int i = 0; i < schema.size(); i++) {
             ColumnConfig config = getSchemaConfig(task).getColumn(i);
             JsonpathColumnOption option = config.getOption().loadConfig(JsonpathColumnOption.class);
             if (option.getPath().isPresent()) {
-                builder.put(schema.getColumn(i), option.getPath().get());
+                columnMap.put(schema.getColumn(i), option.getPath().get());
             }
         }
-        return builder.build();
+        return Collections.unmodifiableMap(columnMap);
     }
 
     private void skipOrThrow(DataException cause, boolean stopOnInvalidRecord)

ImmutableSet

diff --git a/src/main/java/org/embulk/parser/jsonpath/ColumnVisitorImpl.java b/src/main/java/org/embulk/parser/jsonpath/ColumnVisitorImpl.java
index d846ffe..d5aef46 100644
--- a/src/main/java/org/embulk/parser/jsonpath/ColumnVisitorImpl.java
+++ b/src/main/java/org/embulk/parser/jsonpath/ColumnVisitorImpl.java
@@ -3,8 +3,9 @@ package org.embulk.parser.jsonpath;
 import com.fasterxml.jackson.databind.JsonNode;
 
 import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Optional;
-import com.google.common.collect.ImmutableList;
 import org.embulk.parser.jsonpath.JsonpathParserPlugin.PluginTask;
 import org.embulk.parser.jsonpath.JsonpathParserPlugin.TypecastColumnOption;
 import org.embulk.spi.Column;
@@ -31,8 +32,8 @@ public class ColumnVisitorImpl
         implements ColumnVisitor
 {
     private static final JsonParser JSON_PARSER = new JsonParser();
-    private static final List<String> BOOL_TRUE_STRINGS = ImmutableList.of("true", "1", "yes", "on", "y", "t");
-    private static final List<String> BOOL_FALSE_STRINGS = ImmutableList.of("false", "0", "no", "off", "n", "f");
+    private static final List<String> BOOL_TRUE_STRINGS = Collections.unmodifiableList(Arrays.asList("true", "1", "yes", "on", "y", "t"));
+    private static final List<String> BOOL_FALSE_STRINGS = Collections.unmodifiableList(Arrays.asList("false", "0", "no", "off", "n", "f"));
 
     protected final PluginTask task;
     protected final Schema schema;

テスト

embulk 0.10.1 ~ 0.10.29まで、-L-Iのオプションの挙動が0.9と異なっていた。
embulk 0.10.30(多分)でこの挙動は修正される。
(v0.9と若干やり方が違っており)
ここを参照(後日追加)