embulk-input-athena 0.1.0 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/README.md +21 -9
  4. data/build.gradle +22 -11
  5. data/docker-compose.yml +7 -1
  6. data/src/main/java/org/embulk/input/athena/AthenaInputConnection.java +3 -0
  7. data/src/main/java/org/embulk/input/athena/AthenaInputPlugin.java +182 -33
  8. data/src/main/resources/log4j.properties +4 -0
  9. metadata +25 -49
  10. data/src/main/java/org/embulk/input/athena/AthenaInputPlugin.java.tmp1 +0 -192
  11. data/src/main/java/org/embulk/input/jdbc/AbstractJdbcInputPlugin.java +0 -674
  12. data/src/main/java/org/embulk/input/jdbc/JdbcColumn.java +0 -58
  13. data/src/main/java/org/embulk/input/jdbc/JdbcColumnOption.java +0 -31
  14. data/src/main/java/org/embulk/input/jdbc/JdbcInputConnection.java +0 -397
  15. data/src/main/java/org/embulk/input/jdbc/JdbcLiteral.java +0 -38
  16. data/src/main/java/org/embulk/input/jdbc/JdbcSchema.java +0 -55
  17. data/src/main/java/org/embulk/input/jdbc/Ssl.java +0 -37
  18. data/src/main/java/org/embulk/input/jdbc/ToString.java +0 -54
  19. data/src/main/java/org/embulk/input/jdbc/ToStringMap.java +0 -35
  20. data/src/main/java/org/embulk/input/jdbc/getter/AbstractColumnGetter.java +0 -105
  21. data/src/main/java/org/embulk/input/jdbc/getter/AbstractIncrementalHandler.java +0 -45
  22. data/src/main/java/org/embulk/input/jdbc/getter/AbstractTimestampColumnGetter.java +0 -38
  23. data/src/main/java/org/embulk/input/jdbc/getter/BigDecimalColumnGetter.java +0 -59
  24. data/src/main/java/org/embulk/input/jdbc/getter/BooleanColumnGetter.java +0 -56
  25. data/src/main/java/org/embulk/input/jdbc/getter/ColumnGetter.java +0 -21
  26. data/src/main/java/org/embulk/input/jdbc/getter/ColumnGetterFactory.java +0 -207
  27. data/src/main/java/org/embulk/input/jdbc/getter/DateColumnGetter.java +0 -37
  28. data/src/main/java/org/embulk/input/jdbc/getter/DoubleColumnGetter.java +0 -66
  29. data/src/main/java/org/embulk/input/jdbc/getter/FloatColumnGetter.java +0 -66
  30. data/src/main/java/org/embulk/input/jdbc/getter/JsonColumnGetter.java +0 -57
  31. data/src/main/java/org/embulk/input/jdbc/getter/LongColumnGetter.java +0 -70
  32. data/src/main/java/org/embulk/input/jdbc/getter/StringColumnGetter.java +0 -96
  33. data/src/main/java/org/embulk/input/jdbc/getter/TimeColumnGetter.java +0 -37
  34. data/src/main/java/org/embulk/input/jdbc/getter/TimestampColumnGetter.java +0 -36
  35. data/src/main/java/org/embulk/input/jdbc/getter/TimestampWithTimeZoneIncrementalHandler.java +0 -83
  36. data/src/main/java/org/embulk/input/jdbc/getter/TimestampWithoutTimeZoneIncrementalHandler.java +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3fb336d83e38770353f87f111859738619bb3e6
4
- data.tar.gz: 7fe07d1b0fd72c8e08ba963e21b590c26242da79
3
+ metadata.gz: df05f5540bab30f61316b799b7cb80b849da32d6
4
+ data.tar.gz: 0a854b7fc362bdff91f8423bd788a119426a4ff0
5
5
  SHA512:
6
- metadata.gz: 4483e0985f1741775a6abbf8a9183231be12f52b5f28dc278c850fbf926fba5a2090e9fd660fd6d5b93782dec551b644828e6b767c096254ccc269deb755728f
7
- data.tar.gz: bb989515c90ebdd73c651099fcfe6b136d1bfcdc1a5c30c979bbb5f0b3e2d782b8212110cb282f3129888bd347cc6e9878ae5ab2deb4118d108d58201a765f2c
6
+ metadata.gz: b84f7b31683d9d239bd41bde61d35ee9d003fe5d0537b96b9f06310ac9123a752288197b671d7d11d335a215bb9334a4589013d9ac65d7cddef3cd033d53371f
7
+ data.tar.gz: c6ea2231c7e1dc107f89b4d0fdf3ba1ee7e495b1be14de86e3c75782fb2219b0818fa094e3d14ac23a492748b7acc5a5781e4ed798ed2a41115b8a783c3f1ca6
data/.gitignore CHANGED
@@ -10,4 +10,5 @@ build/
10
10
  /.metadata/
11
11
  .classpath
12
12
  .project
13
- bin
13
+ bin/
14
+ .vscode/
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Athena input plugin for Embulk
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/embulk-input-athena.svg)](https://badge.fury.io/rb/embulk-input-athena)
4
+ [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
5
+
3
6
  Athena input plugin for Embulk loads records from Athena(AWS).
4
7
 
5
8
  ## Overview
@@ -11,14 +14,16 @@ Athena input plugin for Embulk loads records from Athena(AWS).
11
14
 
12
15
  ## Configuration
13
16
 
14
- * **database**: description (string, required)
15
- * **athena_url**: description (string, required)
16
- * **s3_staging_dir**: description (string, required)
17
- * **access_key**: description (string, required)
18
- * **secret_key**: description (string, required)
19
- * **query**: description (string, required)
20
- * **columns**: description (string, required)
21
- * **options**: description (string, default: {})
17
+ * **driver_path**: path to the jar file of the Athena JDBC driver. If not set, the bundled JDBC driver(AthenaJDBC41-1.1.0.jar) will be used. (string)
18
+ * **database**: database name (string, required)
19
+ * **athena_url**: Athena url (string, required)
20
+ * **s3_staging_dir**: The S3 location to which your query output is written, for example s3://query-results-bucket/folder/. (string, required)
21
+ * **access_key**: AWS access key (string, required)
22
+ * **secret_key**: AWS secret key (string, required)
23
+ * **query**: SQL to run (string, required)
24
+ * **columns**: columns (string, required)
25
+ * **options**: extra JDBC properties (string, default: {})
26
+ * **null_to_zero**: if true, convert long, double and boolean value from null to zero (boolean, default: false)
22
27
 
23
28
  ## Example
24
29
 
@@ -35,12 +40,19 @@ in:
35
40
  columns:
36
41
  - {name: uid, type: string}
37
42
  - {name: created_at, type: timestamp}
43
+ null_to_zero: true
38
44
  ```
39
45
 
40
46
  ## Build
41
47
 
42
- ```
48
+ ```bash
43
49
  $ docker-compose up -d
44
50
  $ docker-compose exec embulk bash
45
51
  embulk>$ ./gradlew gem # -t to watch change of files and rebuild continuously
52
+
53
+ embulk>$ embulk preview -I lib sample.yml
54
+ embulk>$ embulk preview -L . sample.yml
55
+
56
+ curl -u shinji19 https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials
57
+ embulk>$ ./gradlew gemPush
46
58
  ```
data/build.gradle CHANGED
@@ -1,21 +1,22 @@
1
1
  plugins {
2
2
  id "com.jfrog.bintray" version "1.1"
3
- id "com.github.jruby-gradle.base" version "0.1.5"
3
+ id "com.github.jruby-gradle.base" version "1.5.0"
4
4
  id "java"
5
5
  id "checkstyle"
6
+ // for task download
7
+ id "de.undercouch.download" version "3.4.2"
6
8
  }
7
9
  import com.github.jrubygradle.JRubyExec
8
10
  repositories {
9
11
  mavenCentral()
10
12
  jcenter()
11
- // for athena jdbc
12
- maven { url "https://maven.atlassian.com/repository/public" }
13
+ maven { url "https://dl.bintray.com/embulk-input-jdbc/maven" }
13
14
  }
14
15
  configurations {
15
16
  provided
16
17
  }
17
18
 
18
- version = "0.1.0"
19
+ version = "0.1.6"
19
20
 
20
21
  sourceCompatibility = 1.8
21
22
  targetCompatibility = 1.8
@@ -23,16 +24,24 @@ targetCompatibility = 1.8
23
24
  dependencies {
24
25
  compile "org.embulk:embulk-core:0.8.39"
25
26
  provided "org.embulk:embulk-core:0.8.39"
26
- // https://mvnrepository.com/artifact/com.amazonaws.athena.jdbc/AthenaJDBC41
27
- // compile group: 'com.amazonaws.athena.jdbc', name: 'AthenaJDBC41', version: '1.0.1-atlassian-hosted'
27
+ // TODO: maven...
28
28
  compile files ('build/AthenaJDBC41-1.1.0.jar')
29
29
  compile group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.301'
30
+ // compile group: 'com.amazonaws', name: 'aws-java-sdk-athena', version: '1.11.301'
31
+ compile 'org.embulk.input.jdbc:embulk-input-jdbc:0.9.1'
30
32
  testCompile "junit:junit:4.+"
31
33
  }
32
34
 
33
- task classpath(type: Copy, dependsOn: ["jar"]) {
35
+ task downloadFile(type: Download) {
36
+ src 'https://s3.amazonaws.com/athena-downloads/drivers/AthenaJDBC41-1.1.0.jar'
37
+ dest buildDir
38
+ onlyIfModified true
39
+ }
40
+
41
+ task classpath(type: Copy, dependsOn: ["downloadFile", "jar"]) {
34
42
  doFirst { file("classpath").deleteDir() }
35
43
  from (configurations.runtime - configurations.provided + files(jar.archivePath))
44
+ from ("build/AthenaJDBC41-1.1.0.jar'")
36
45
  into "classpath"
37
46
  }
38
47
  clean { delete "classpath" }
@@ -55,14 +64,16 @@ task checkstyle(type: Checkstyle) {
55
64
  }
56
65
 
57
66
  task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) {
58
- jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "build"
59
- script "${project.name}.gemspec"
67
+ jrubyArgs "-S"
68
+ script "gem"
69
+ scriptArgs "build", "${project.name}.gemspec"
60
70
  doLast { ant.move(file: "${project.name}-${project.version}.gem", todir: "pkg") }
61
71
  }
62
72
 
63
73
  task gemPush(type: JRubyExec, dependsOn: ["gem"]) {
64
- jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "push"
65
- script "pkg/${project.name}-${project.version}.gem"
74
+ jrubyArgs "-S"
75
+ script "gem"
76
+ scriptArgs "push", "pkg/${project.name}-${project.version}.gem"
66
77
  }
67
78
 
68
79
  task "package"(dependsOn: ["gemspec", "classpath"]) {
data/docker-compose.yml CHANGED
@@ -7,4 +7,10 @@ services:
7
7
  working_dir: /root/embulk-input-athena
8
8
  volumes:
9
9
  - ./:/root/embulk-input-athena
10
- command: tail -f /dev/null
10
+ tty: true
11
+ postgres:
12
+ image: postgres
13
+ ports:
14
+ - 5432:5432
15
+ environment:
16
+ - POSTGRES_PASSWORD=postgres
@@ -1,3 +1,4 @@
1
+ /*
1
2
  package org.embulk.input.athena;
2
3
 
3
4
  import java.util.List;
@@ -17,6 +18,7 @@ public class AthenaInputConnection
17
18
  throws SQLException
18
19
  {
19
20
  super(connection, null);
21
+ connection.setAutoCommit(true);
20
22
  }
21
23
 
22
24
  @Override
@@ -47,3 +49,4 @@ public class AthenaInputConnection
47
49
  return new SingleSelect(stmt);
48
50
  }
49
51
  }
52
+ */
@@ -1,9 +1,17 @@
1
1
  package org.embulk.input.athena;
2
2
 
3
+ import com.google.common.base.Optional;
4
+
5
+ import java.io.File;
6
+ import java.io.FileFilter;
7
+ import java.net.MalformedURLException;
8
+ import java.net.URISyntaxException;
9
+ import java.net.URL;
10
+ import java.nio.file.Path;
11
+ import java.nio.file.Paths;
3
12
  import java.sql.Connection;
4
13
  import java.sql.DriverManager;
5
14
  import java.sql.ResultSet;
6
- import java.sql.ResultSetMetaData;
7
15
  import java.sql.SQLException;
8
16
  import java.sql.Statement;
9
17
  import java.util.List;
@@ -12,12 +20,14 @@ import java.util.Properties;
12
20
  import org.embulk.config.Config;
13
21
  import org.embulk.config.ConfigDefault;
14
22
  import org.embulk.config.ConfigDiff;
23
+ import org.embulk.config.ConfigException;
15
24
  import org.embulk.config.ConfigInject;
16
25
  import org.embulk.config.ConfigSource;
17
26
  import org.embulk.config.Task;
18
27
  import org.embulk.config.TaskReport;
19
28
  import org.embulk.config.TaskSource;
20
29
  import org.embulk.input.jdbc.ToStringMap;
30
+ import org.embulk.plugin.PluginClassLoader;
21
31
  import org.embulk.spi.BufferAllocator;
22
32
  import org.embulk.spi.Column;
23
33
  import org.embulk.spi.ColumnVisitor;
@@ -28,9 +38,18 @@ import org.embulk.spi.PageOutput;
28
38
  import org.embulk.spi.Schema;
29
39
  import org.embulk.spi.SchemaConfig;
30
40
  import org.embulk.spi.time.Timestamp;
41
+ import org.slf4j.Logger;
42
+
43
+ public class AthenaInputPlugin implements InputPlugin
44
+ {
45
+ protected final Logger logger = Exec.getLogger(getClass());
46
+
47
+ public interface PluginTask extends Task
48
+ {
49
+ @Config("driver_path")
50
+ @ConfigDefault("null")
51
+ public Optional<String> getDriverPath();
31
52
 
32
- public class AthenaInputPlugin implements InputPlugin {
33
- public interface PluginTask extends Task {
34
53
  // database (required string)
35
54
  @Config("database")
36
55
  public String getDatabase();
@@ -63,12 +82,17 @@ public class AthenaInputPlugin implements InputPlugin {
63
82
  @ConfigDefault("{}")
64
83
  public ToStringMap getOptions();
65
84
 
85
+ @Config("null_to_zero")
86
+ @ConfigDefault("false")
87
+ public boolean getNullToZero();
88
+
66
89
  @ConfigInject
67
90
  BufferAllocator getBufferAllocator();
68
91
  }
69
92
 
70
93
  @Override
71
- public ConfigDiff transaction(ConfigSource config, InputPlugin.Control control) {
94
+ public ConfigDiff transaction(ConfigSource config, InputPlugin.Control control)
95
+ {
72
96
  PluginTask task = config.loadConfig(PluginTask.class);
73
97
 
74
98
  Schema schema = task.getColumns().toSchema();
@@ -78,17 +102,20 @@ public class AthenaInputPlugin implements InputPlugin {
78
102
  }
79
103
 
80
104
  @Override
81
- public ConfigDiff resume(TaskSource taskSource, Schema schema, int taskCount, InputPlugin.Control control) {
105
+ public ConfigDiff resume(TaskSource taskSource, Schema schema, int taskCount, InputPlugin.Control control)
106
+ {
82
107
  control.run(taskSource, schema, taskCount);
83
108
  return Exec.newConfigDiff();
84
109
  }
85
110
 
86
111
  @Override
87
- public void cleanup(TaskSource taskSource, Schema schema, int taskCount, List<TaskReport> successTaskReports) {
112
+ public void cleanup(TaskSource taskSource, Schema schema, int taskCount, List<TaskReport> successTaskReports)
113
+ {
88
114
  }
89
115
 
90
116
  @Override
91
- public TaskReport run(TaskSource taskSource, Schema schema, int taskIndex, PageOutput output) {
117
+ public TaskReport run(TaskSource taskSource, Schema schema, int taskIndex, PageOutput output)
118
+ {
92
119
  PluginTask task = taskSource.loadTask(PluginTask.class);
93
120
  BufferAllocator allocator = task.getBufferAllocator();
94
121
  PageBuilder pageBuilder = new PageBuilder(allocator, schema, output);
@@ -101,58 +128,93 @@ public class AthenaInputPlugin implements InputPlugin {
101
128
  connection = getAthenaConnection(task);
102
129
  statement = connection.createStatement();
103
130
  ResultSet resultSet = statement.executeQuery(task.getQuery());
131
+ boolean nullToZero = task.getNullToZero();
104
132
 
105
- ResultSetMetaData m = resultSet.getMetaData();
106
133
  while (resultSet.next()) {
107
- schema.visitColumns(new ColumnVisitor() {
134
+ schema.visitColumns(new ColumnVisitor()
135
+ {
108
136
  @Override
109
- public void timestampColumn(Column column) {
137
+ public void timestampColumn(Column column)
138
+ {
110
139
  try {
111
140
  java.sql.Timestamp t = resultSet.getTimestamp(column.getName());
112
141
  pageBuilder.setTimestamp(column, Timestamp.ofEpochMilli(t.getTime()));
113
- } catch (SQLException e) {
142
+ }
143
+ catch (SQLException e) {
114
144
  e.printStackTrace();
145
+ throw new RuntimeException(e);
115
146
  }
116
147
  }
117
148
 
118
149
  @Override
119
- public void stringColumn(Column column) {
150
+ public void stringColumn(Column column)
151
+ {
120
152
  try {
121
153
  pageBuilder.setString(column, resultSet.getString(column.getName()));
122
- } catch (SQLException e) {
154
+ }
155
+ catch (SQLException e) {
123
156
  e.printStackTrace();
157
+ throw new RuntimeException(e);
124
158
  }
125
159
  }
126
160
 
127
161
  @Override
128
- public void longColumn(Column column) {
162
+ public void longColumn(Column column)
163
+ {
129
164
  try {
130
- pageBuilder.setLong(column, resultSet.getLong(column.getName()));
131
- } catch (SQLException e) {
165
+ long ret = resultSet.getLong(column.getName());
166
+ if (resultSet.wasNull() && !nullToZero){
167
+ pageBuilder.setNull(column);
168
+ }
169
+ else {
170
+ pageBuilder.setLong(column, ret);
171
+ }
172
+ }
173
+ catch (SQLException e) {
132
174
  e.printStackTrace();
175
+ throw new RuntimeException(e);
133
176
  }
134
177
  }
135
178
 
136
179
  @Override
137
- public void doubleColumn(Column column) {
180
+ public void doubleColumn(Column column)
181
+ {
138
182
  try {
139
- pageBuilder.setDouble(column, resultSet.getDouble(column.getName()));
140
- } catch (SQLException e) {
183
+ double ret = resultSet.getDouble(column.getName());
184
+ if (resultSet.wasNull() && !nullToZero){
185
+ pageBuilder.setNull(column);
186
+ }
187
+ else {
188
+ pageBuilder.setDouble(column, ret);
189
+ }
190
+ }
191
+ catch (SQLException e) {
141
192
  e.printStackTrace();
193
+ throw new RuntimeException(e);
142
194
  }
143
195
  }
144
196
 
145
197
  @Override
146
- public void booleanColumn(Column column) {
198
+ public void booleanColumn(Column column)
199
+ {
147
200
  try {
148
- pageBuilder.setBoolean(column, resultSet.getBoolean(column.getName()));
149
- } catch (SQLException e) {
201
+ boolean ret = resultSet.getBoolean(column.getName());
202
+ if (resultSet.wasNull() && !nullToZero){
203
+ pageBuilder.setNull(column);
204
+ }
205
+ else {
206
+ pageBuilder.setBoolean(column, ret);
207
+ }
208
+ }
209
+ catch (SQLException e) {
150
210
  e.printStackTrace();
211
+ throw new RuntimeException(e);
151
212
  }
152
213
  }
153
214
 
154
215
  @Override
155
- public void jsonColumn(Column column) {
216
+ public void jsonColumn(Column column)
217
+ {
156
218
  // TODO:
157
219
  }
158
220
  });
@@ -164,19 +226,24 @@ public class AthenaInputPlugin implements InputPlugin {
164
226
  pageBuilder.close();
165
227
  resultSet.close();
166
228
  connection.close();
167
- } catch (Exception e) {
229
+ }
230
+ catch (Exception e) {
168
231
  e.printStackTrace();
169
- } finally {
232
+ throw new RuntimeException(e);
233
+ }
234
+ finally {
170
235
  try {
171
- if (statement != null)
236
+ if (statement != null) {
172
237
  statement.close();
173
- } catch (Exception ex) {
174
-
238
+ }
175
239
  }
240
+ catch (Exception ex) { }
176
241
  try {
177
- if (connection != null)
242
+ if (connection != null) {
178
243
  connection.close();
179
- } catch (Exception ex) {
244
+ }
245
+ }
246
+ catch (Exception ex) {
180
247
  ex.printStackTrace();
181
248
  }
182
249
  }
@@ -185,12 +252,14 @@ public class AthenaInputPlugin implements InputPlugin {
185
252
  }
186
253
 
187
254
  @Override
188
- public ConfigDiff guess(ConfigSource config) {
255
+ public ConfigDiff guess(ConfigSource config)
256
+ {
189
257
  return Exec.newConfigDiff();
190
258
  }
191
259
 
192
- protected Connection getAthenaConnection(PluginTask task) throws ClassNotFoundException, SQLException {
193
- Class.forName("com.amazonaws.athena.jdbc.AthenaDriver");
260
+ protected Connection getAthenaConnection(PluginTask task) throws ClassNotFoundException, SQLException
261
+ {
262
+ loadDriver("com.amazonaws.athena.jdbc.AthenaDriver", task.getDriverPath());
194
263
  Properties properties = new Properties();
195
264
  properties.put("s3_staging_dir", task.getS3StagingDir());
196
265
  properties.put("user", task.getAccessKey());
@@ -199,4 +268,84 @@ public class AthenaInputPlugin implements InputPlugin {
199
268
 
200
269
  return DriverManager.getConnection(task.getAthenaUrl(), properties);
201
270
  }
271
+
272
+ //
273
+ // copy from embulk-input-jdbc
274
+ //
275
+
276
+ protected void loadDriver(String className, Optional<String> driverPath)
277
+ {
278
+ if (driverPath.isPresent()) {
279
+ addDriverJarToClasspath(driverPath.get());
280
+ }
281
+ else {
282
+ try {
283
+ // Gradle test task will add JDBC driver to classpath
284
+ Class.forName(className);
285
+ }
286
+ catch (ClassNotFoundException ex) {
287
+ File root = findPluginRoot();
288
+ File driverLib = new File(root, "default_jdbc_driver");
289
+ File[] files = driverLib.listFiles(new FileFilter() {
290
+ @Override
291
+ public boolean accept(File file)
292
+ {
293
+ return file.isFile() && file.getName().endsWith(".jar");
294
+ }
295
+ });
296
+ if (files == null || files.length == 0) {
297
+ throw new RuntimeException("Cannot find JDBC driver in '" + root.getAbsolutePath() + "'.");
298
+ }
299
+ else {
300
+ for (File file : files) {
301
+ logger.info("JDBC Driver = " + file.getAbsolutePath());
302
+ addDriverJarToClasspath(file.getAbsolutePath());
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ // Load JDBC Driver
309
+ try {
310
+ Class.forName(className);
311
+ }
312
+ catch (ClassNotFoundException ex) {
313
+ throw new RuntimeException(ex);
314
+ }
315
+ }
316
+
317
+ protected void addDriverJarToClasspath(String glob)
318
+ {
319
+ // TODO match glob
320
+ PluginClassLoader loader = (PluginClassLoader) getClass().getClassLoader();
321
+ Path path = Paths.get(glob);
322
+ if (!path.toFile().exists()) {
323
+ throw new ConfigException("The specified driver jar doesn't exist: " + glob);
324
+ }
325
+ loader.addPath(Paths.get(glob));
326
+ }
327
+
328
+ protected File findPluginRoot()
329
+ {
330
+ try {
331
+ URL url = getClass().getResource("/" + getClass().getName().replace('.', '/') + ".class");
332
+ if (url.toString().startsWith("jar:")) {
333
+ url = new URL(url.toString().replaceAll("^jar:", "").replaceAll("![^!]*$", ""));
334
+ }
335
+
336
+ File folder = new File(url.toURI()).getParentFile();
337
+ for (;; folder = folder.getParentFile()) {
338
+ if (folder == null) {
339
+ throw new RuntimeException("Cannot find 'embulk-input-xxx' folder.");
340
+ }
341
+
342
+ if (folder.getName().startsWith("embulk-input-")) {
343
+ return folder;
344
+ }
345
+ }
346
+ }
347
+ catch (MalformedURLException | URISyntaxException e) {
348
+ throw new RuntimeException(e);
349
+ }
350
+ }
202
351
  }