embulk-input-td 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
 
2
2
  Gem::Specification.new do |spec|
3
3
  spec.name = "embulk-input-td"
4
- spec.version = "0.2.0"
4
+ spec.version = "0.2.1"
5
5
  spec.authors = ["Muga Nishizawa"]
6
6
  spec.summary = %[Td input plugin for Embulk]
7
7
  spec.description = %[Loads records from Td.]
@@ -1,11 +1,5 @@
1
1
  package org.embulk.input.td;
2
2
 
3
- import java.io.IOException;
4
- import java.io.InputStream;
5
- import java.util.List;
6
- import java.util.Properties;
7
- import java.util.zip.GZIPInputStream;
8
-
9
3
  import com.fasterxml.jackson.databind.JsonNode;
10
4
  import com.fasterxml.jackson.databind.ObjectMapper;
11
5
  import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -19,17 +13,25 @@ import com.treasuredata.client.TDClientBuilder;
19
13
  import com.treasuredata.client.model.TDJob;
20
14
  import com.treasuredata.client.model.TDJobRequest;
21
15
  import com.treasuredata.client.model.TDJobSummary;
22
- import org.embulk.config.ConfigException;
23
- import org.embulk.config.ConfigInject;
24
- import org.embulk.config.TaskReport;
16
+ import com.treasuredata.client.model.TDResultFormat;
17
+ import java.io.IOException;
18
+ import java.io.InputStream;
19
+ import java.util.List;
20
+ import java.util.Locale;
21
+ import java.util.Properties;
22
+ import java.util.zip.GZIPInputStream;
25
23
  import org.embulk.config.Config;
26
24
  import org.embulk.config.ConfigDefault;
27
25
  import org.embulk.config.ConfigDiff;
26
+ import org.embulk.config.ConfigException;
27
+ import org.embulk.config.ConfigInject;
28
28
  import org.embulk.config.ConfigSource;
29
29
  import org.embulk.config.Task;
30
+ import org.embulk.config.TaskReport;
30
31
  import org.embulk.config.TaskSource;
31
32
  import org.embulk.input.td.writer.BooleanValueWriter;
32
33
  import org.embulk.input.td.writer.DoubleValueWriter;
34
+ import org.embulk.input.td.writer.JsonValueWriter;
33
35
  import org.embulk.input.td.writer.LongValueWriter;
34
36
  import org.embulk.input.td.writer.StringValueWriter;
35
37
  import org.embulk.input.td.writer.ValueWriter;
@@ -42,130 +44,62 @@ import org.embulk.spi.PageBuilder;
42
44
  import org.embulk.spi.PageOutput;
43
45
  import org.embulk.spi.Schema;
44
46
  import org.embulk.spi.type.Type;
47
+ import org.embulk.spi.type.Types;
45
48
  import org.msgpack.core.MessagePack;
46
49
  import org.msgpack.core.MessageUnpacker;
47
50
  import org.msgpack.value.ArrayValue;
48
51
  import org.msgpack.value.Value;
49
52
  import org.slf4j.Logger;
50
53
 
51
- import static com.google.common.base.Optional.fromNullable;
52
- import static com.treasuredata.client.model.TDResultFormat.MESSAGE_PACK_GZ;
53
- import static java.lang.Integer.parseInt;
54
- import static java.util.Locale.ENGLISH;
55
- import static org.embulk.spi.Exec.getLogger;
56
- import static org.embulk.spi.Exec.newConfigDiff;
57
- import static org.embulk.spi.Exec.newTaskReport;
58
- import static org.embulk.spi.type.Types.BOOLEAN;
59
- import static org.embulk.spi.type.Types.DOUBLE;
60
- import static org.embulk.spi.type.Types.JSON;
61
- import static org.embulk.spi.type.Types.LONG;
62
- import static org.embulk.spi.type.Types.STRING;
63
- import static org.embulk.spi.type.Types.TIMESTAMP;
64
-
65
54
  public class TdInputPlugin
66
- implements InputPlugin
67
- {
68
- public interface PluginTask
69
- extends Task
70
- {
71
- @Config("apikey")
72
- public String getApiKey();
73
-
74
- @Config("endpoint")
75
- @ConfigDefault("\"api.treasuredata.com\"")
76
- public String getEndpoint();
77
-
78
- @Config("use_ssl")
79
- @ConfigDefault("true")
80
- public boolean getUseSsl();
81
-
82
- @Config("http_proxy")
83
- @ConfigDefault("null")
84
- public Optional<HttpProxyTask> getHttpProxy();
85
-
86
- // TODO timeout
87
- // TODO query, database
88
-
89
- @Config("query")
90
- @ConfigDefault("null")
91
- public Optional<String> getQuery();
92
-
93
- @Config("database")
94
- @ConfigDefault("null")
95
- public Optional<String> getDatabase();
96
-
97
- @Config("job_id")
98
- @ConfigDefault("null")
99
- public Optional<String> getJobId();
100
-
101
- @Config("stop_on_invalid_record")
102
- @ConfigDefault("false")
103
- public boolean getStopOnInvalidRecord();
104
-
105
- // TODO column_options
106
-
107
- @ConfigInject
108
- BufferAllocator getBufferAllocator();
109
- }
110
-
111
- public interface HttpProxyTask
112
- extends Task
113
- {
114
- @Config("host")
115
- public String getHost();
116
-
117
- @Config("port")
118
- public int getPort();
119
-
120
- @Config("use_ssl")
121
- @ConfigDefault("false")
122
- public boolean getUseSsl();
123
-
124
- @Config("user")
125
- @ConfigDefault("null")
126
- public Optional<String> getUser();
127
-
128
- @Config("password")
129
- @ConfigDefault("null")
130
- public Optional<String> getPassword();
131
- }
55
+ implements InputPlugin {
132
56
 
133
57
  private final Logger log;
134
58
 
135
59
  @Inject
136
- public TdInputPlugin()
137
- {
138
- this.log = getLogger(this.getClass());
60
+ public TdInputPlugin() {
61
+ this.log = Exec.getLogger(this.getClass());
62
+ }
63
+
64
+ private static JsonNode toJsonNode(final String schema) {
65
+ try {
66
+ return new ObjectMapper().readTree(schema);
67
+ } catch (IOException e) {
68
+ throw new ConfigException(String.format(Locale.ENGLISH,
69
+ "Failed to parse job result schema as JSON: %s", schema));
70
+ }
139
71
  }
140
72
 
141
73
  @Override
142
- public ConfigDiff transaction(ConfigSource config, InputPlugin.Control control)
143
- {
144
- PluginTask task = config.loadConfig(PluginTask.class);
145
- try (TDClient client = newTDClient(task)) {
146
- TDJob job = getTDJob(task, client);
74
+ public ConfigDiff transaction(final ConfigSource config, final InputPlugin.Control control) {
75
+ final PluginTask task = config.loadConfig(PluginTask.class);
76
+ try (final TDClient client = newTdClient(task)) {
77
+ final TDJob job = getTdJob(task, client);
147
78
 
148
- Optional<String> jobResultSchema = job.getResultSchema();
79
+ final Optional<String> jobResultSchema = job.getResultSchema();
149
80
  if (!jobResultSchema.isPresent()) {
150
- throw new ConfigException(String.format("Not found result schema of job %s", job.getJobId()));
81
+ throw new ConfigException(String.format(
82
+ "Not found result schema of job %s", job.getJobId()));
151
83
  }
152
84
 
153
- Schema inputSchema = convertSchema(job.getType(), toJsonNode(jobResultSchema.get()));
154
- newValueWriters(inputSchema); // validate if value writers can be created according to the input schema
85
+ final JsonNode jobResultSchemaJsonNode = toJsonNode(jobResultSchema.get());
86
+ final Schema inputSchema = convertSchema(job.getType(), jobResultSchemaJsonNode);
87
+ // validate if value writers can be created according to the input schema
88
+ newValueWriters(inputSchema);
155
89
 
156
- TaskSource taskSource = task.dump().set("job_id", job.getJobId()); // overwrite job_id
90
+ // overwrite job_id
91
+ final TaskSource taskSource = task.dump().set("job_id", job.getJobId());
157
92
  return resume(taskSource, inputSchema, 1, control);
158
93
  }
159
94
  }
160
95
 
161
- private TDClient newTDClient(PluginTask task)
162
- {
163
- TDClientBuilder builder = TDClient.newBuilder();
96
+ private TDClient newTdClient(final PluginTask task) {
97
+ final TDClientBuilder builder = TDClient.newBuilder();
164
98
  builder.setApiKey(task.getApiKey());
165
99
  builder.setEndpoint(task.getEndpoint());
166
100
  builder.setUseSSL(task.getUseSsl());
167
101
 
168
- Optional<ProxyConfig>proxyConfig = newProxyConfig(task.getHttpProxy());
102
+ final Optional<ProxyConfig> proxyConfig = newProxyConfig(task.getHttpProxy());
169
103
  if (proxyConfig.isPresent()) {
170
104
  builder.setProxy(proxyConfig.get());
171
105
  }
@@ -173,42 +107,48 @@ public class TdInputPlugin
173
107
  return builder.build();
174
108
  }
175
109
 
176
- private Optional<ProxyConfig> newProxyConfig(Optional<HttpProxyTask> task)
177
- {
178
- // This plugin searches http proxy settings and configures them to TDClient. The order of proxy setting searching is:
110
+ private Optional<ProxyConfig> newProxyConfig(final Optional<HttpProxyTask> task) {
111
+ // This plugin searches http proxy settings and configures them to TDClient.
112
+ // The order of proxy setting searching is:
179
113
  // 1. System properties
180
114
  // 2. http_proxy config option provided by this plugin
181
115
 
182
- Properties props = System.getProperties();
116
+ final Properties props = System.getProperties();
183
117
  if (props.containsKey("http.proxyHost") || props.containsKey("https.proxyHost")) {
184
- boolean useSsl = props.containsKey("https.proxyHost");
185
- String proto = !useSsl ? "http" : "https";
186
- String host = props.getProperty(proto + ".proxyHost");
187
- int port = parseInt(props.getProperty(proto + ".proxyPort", !useSsl ? "80" : "443"));
188
- Optional<String> user = fromNullable(props.getProperty(proto + ".proxyUser"));
189
- Optional<String> password = fromNullable(props.getProperty(proto + ".proxyPassword"));
118
+ final boolean useSsl = props.containsKey("https.proxyHost");
119
+ final String proto = !useSsl ? "http" : "https";
120
+ final String hostKey = proto + ".proxyHost";
121
+ final String portKey = proto + ".proxyPort";
122
+ final String userKey = proto + ".proxyUser";
123
+ final String passwordKey = proto + ".proxyPassword";
124
+ final String host = props.getProperty(hostKey);
125
+ final String defaultPort = !useSsl ? "80" : "443";
126
+ final int port = Integer.parseInt(props.getProperty(portKey, defaultPort));
127
+ final Optional<String> user = Optional.fromNullable(props.getProperty(userKey));
128
+ final Optional<String> password = Optional.fromNullable(props.getProperty(passwordKey));
190
129
  return Optional.of(new ProxyConfig(host, port, useSsl, user, password));
191
- }
192
- else if (task.isPresent()) {
193
- HttpProxyTask proxyTask = task.get();
194
- return Optional.of(new ProxyConfig(proxyTask.getHost(), proxyTask.getPort(), proxyTask.getUseSsl(),
195
- proxyTask.getUser(), proxyTask.getPassword()));
196
- }
197
- else {
130
+ } else if (task.isPresent()) {
131
+ final HttpProxyTask proxyTask = task.get();
132
+ final String host = proxyTask.getHost();
133
+ final int port = proxyTask.getPort();
134
+ final boolean useSsl = proxyTask.getUseSsl();
135
+ final Optional<String> user = proxyTask.getUser();
136
+ final Optional<String> password = proxyTask.getPassword();
137
+ return Optional.of(new ProxyConfig(host, port, useSsl, user, password));
138
+ } else {
198
139
  return Optional.absent();
199
140
  }
200
141
  }
201
142
 
202
- private TDJob getTDJob(PluginTask task, TDClient client)
203
- {
204
- String jobId;
143
+ private TDJob getTdJob(final PluginTask task, final TDClient client) {
144
+ final String jobId;
205
145
  if (!task.getJobId().isPresent()) {
206
146
  if (!task.getQuery().isPresent() || !task.getDatabase().isPresent()) {
207
- throw new ConfigException("Must specify both of 'query' and 'database' options if 'job_id' option is not used.");
147
+ throw new ConfigException("Must specify both of 'query' and 'database' options "
148
+ + "if 'job_id' option is not used.");
208
149
  }
209
150
  jobId = submitJob(task, client);
210
- }
211
- else {
151
+ } else {
212
152
  jobId = task.getJobId().get();
213
153
  }
214
154
 
@@ -216,24 +156,23 @@ public class TdInputPlugin
216
156
  return client.jobInfo(jobId);
217
157
  }
218
158
 
219
- private String submitJob(PluginTask task, TDClient client)
220
- {
221
- String query = task.getQuery().get();
222
- String database = task.getDatabase().get();
159
+ private String submitJob(final PluginTask task, final TDClient client) {
160
+ final String query = task.getQuery().get();
161
+ final String database = task.getDatabase().get();
223
162
 
224
- log.info(String.format(ENGLISH, "Submit a query for database '%s': %s", database, query));
225
- String jobId = client.submit(TDJobRequest.newPrestoQuery(database, query));
226
- log.info(String.format(ENGLISH, "Job %s is queued.", jobId));
163
+ log.info(String.format(Locale.ENGLISH,
164
+ "Submit a query for database '%s': %s", database, query));
165
+ final String jobId = client.submit(TDJobRequest.newPrestoQuery(database, query));
166
+ log.info(String.format(Locale.ENGLISH, "Job %s is queued.", jobId));
227
167
  return jobId;
228
168
  }
229
169
 
230
- private void waitJobCompletion(PluginTask task, TDClient client, String jobId)
231
- {
170
+ private void waitJobCompletion(final PluginTask task, final TDClient client, String jobId) {
232
171
  TDJobSummary js;
233
172
  long waitTime = 5 * 1000; // 5 secs
234
173
 
235
174
  // wait for job finish
236
- log.info(String.format(ENGLISH, "Confirm that job %s finished", jobId));
175
+ log.info(String.format(Locale.ENGLISH, "Confirm that job %s finished", jobId));
237
176
  while (true) {
238
177
  js = client.jobStatus(jobId);
239
178
  if (js.getStatus().isFinished()) {
@@ -243,93 +182,154 @@ public class TdInputPlugin
243
182
  log.debug("Wait for job finished");
244
183
  try {
245
184
  Thread.sleep(waitTime);
246
- }
247
- catch (InterruptedException ignored) {
185
+ } catch (InterruptedException ignored) {
186
+ // can ignore
248
187
  }
249
188
  }
250
189
 
251
190
  // confirm if the job status is 'success'
252
191
  if (js.getStatus() != TDJob.Status.SUCCESS) {
253
- throw new ConfigException(String.format(ENGLISH, "Cannot download job result because the job was '%s'.", js.getStatus()));
254
- }
255
- }
256
-
257
- private static JsonNode toJsonNode(String schema)
258
- {
259
- try {
260
- return new ObjectMapper().readTree(schema);
261
- }
262
- catch (IOException e) {
263
- throw new ConfigException(String.format(ENGLISH, "Failed to parse job result schema as JSON: %s", schema));
192
+ throw new ConfigException(String.format(Locale.ENGLISH,
193
+ "Cannot download job result because the job was '%s'.",
194
+ js.getStatus()));
264
195
  }
265
196
  }
266
197
 
267
- private Schema convertSchema(TDJob.Type jobType, JsonNode from)
268
- {
269
- Schema.Builder schema = new Schema.Builder();
270
- ArrayNode a = (ArrayNode) from;
198
+ private Schema convertSchema(final TDJob.Type jobType, final JsonNode from) {
199
+ final Schema.Builder schema = new Schema.Builder();
200
+ final ArrayNode a = (ArrayNode) from;
271
201
  for (int i = 0; i < a.size(); i++) {
272
- ArrayNode column = (ArrayNode)a.get(i);
273
- String name = column.get(0).asText();
274
- Type type = convertColumnType(jobType, column.get(1).asText());
202
+ final ArrayNode column = (ArrayNode) a.get(i);
203
+ final String name = column.get(0).asText();
204
+ final Type type = convertColumnType(jobType, column.get(1).asText());
275
205
  schema.add(name, type);
276
206
  }
277
207
  return schema.build();
278
208
  }
279
209
 
280
- private Type convertColumnType(TDJob.Type jobType, String from)
281
- {
210
+ private Type convertColumnType(final TDJob.Type jobType, final String from) {
282
211
  switch (jobType) {
283
- case PRESTO:
284
- return convertPrestoColumnType(from);
285
- case HIVE:
286
- default:
287
- throw new ConfigException(String.format(ENGLISH, "Unsupported job type '%s'. Supported types are [presto].", jobType)); // TODO hive
212
+ case PRESTO:
213
+ return convertPrestoColumnType(new Parser(from));
214
+ case HIVE:
215
+ default:
216
+ throw new ConfigException(String.format(Locale.ENGLISH,
217
+ "Unsupported job type '%s'. Supported types are [presto].",
218
+ jobType)); // TODO hive
288
219
  }
289
220
  }
290
221
 
291
- private Type convertPrestoColumnType(String from)
292
- {
293
- String t = from.toUpperCase(ENGLISH);
294
- if (t.equals("BOOLEAN")) {
295
- return BOOLEAN;
222
+ private Type convertPrestoColumnType(final Parser p) {
223
+ if (p.scan("BOOLEAN")) {
224
+ return Types.BOOLEAN;
225
+ } else if (p.scan("INTEGER")) {
226
+ return Types.LONG;
227
+ } else if (p.scan("BIGINT")) {
228
+ return Types.LONG;
229
+ } else if (p.scan("DOUBLE")) {
230
+ return Types.DOUBLE;
231
+ } else if (p.scan("DECIMAL")) {
232
+ return Types.DOUBLE;
233
+ } else if (p.scan("VARCHAR")) {
234
+ // TODO VARCHAR(n)
235
+ return Types.STRING;
236
+ } else if (p.scan("ARRAY")) {
237
+ if (!p.scan("(")) {
238
+ throw new IllegalArgumentException(
239
+ "Cannot parse type: expected '(' for array type: " + p.getOriginalString());
240
+ }
241
+ convertPrestoColumnType(p);
242
+ if (!p.scan(")")) {
243
+ throw new IllegalArgumentException(
244
+ "Cannot parse type: expected ')' for array type: " + p.getOriginalString());
245
+ }
246
+ return Types.JSON;
247
+ } else if (p.scan("MAP")) {
248
+ if (!p.scan("(")) {
249
+ throw new IllegalArgumentException(
250
+ "Cannot parse type: expected '(' for map type: " + p.getOriginalString());
251
+ }
252
+ convertPrestoColumnType(p);
253
+ if (!p.scan(",")) {
254
+ throw new IllegalArgumentException(
255
+ "Cannot parse type: expected ',' for map type: " + p.getOriginalString());
256
+ }
257
+ convertPrestoColumnType(p);
258
+ if (!p.scan(")")) {
259
+ throw new IllegalArgumentException(
260
+ "Cannot parse type: expected ')' for map type: " + p.getOriginalString());
261
+ }
262
+ return Types.JSON;
263
+ } else {
264
+ throw new ConfigException(String.format(Locale.ENGLISH,
265
+ "Unsupported presto type '%s'", p.getOriginalString())); // TODO other types
296
266
  }
297
- else if (t.equals("BIGINT")) {
298
- return LONG;
267
+ }
268
+
269
+ private static class Parser {
270
+ private final String original;
271
+ private final String string;
272
+ private int offset;
273
+
274
+ public Parser(final String original) {
275
+ this.original = original;
276
+ this.string = original.toUpperCase(Locale.ENGLISH);
299
277
  }
300
- else if (t.equals("DOUBLE") || t.equals("DECIMAL") || t.startsWith("DECIMAL")) {
301
- return DOUBLE;
278
+
279
+ public String getOriginalString() {
280
+ return original;
302
281
  }
303
- else if (t.equals("VARCHAR") || t.startsWith("VARCHAR")) {
304
- return STRING;
282
+
283
+ public String getString() {
284
+ return string;
305
285
  }
306
- else {
307
- throw new ConfigException(String.format(ENGLISH, "Unsupported presto type '%s'", from)); // TODO other types
286
+
287
+ public boolean scan(final String s) {
288
+ skipSpaces();
289
+ if (string.startsWith(s, offset)) {
290
+ offset += s.length();
291
+ return true;
292
+ }
293
+ return false;
294
+ }
295
+
296
+ public boolean eof() {
297
+ skipSpaces();
298
+ return string.length() <= offset;
299
+ }
300
+
301
+ private void skipSpaces() {
302
+ while (string.startsWith(" ", offset)) {
303
+ offset++;
304
+ }
308
305
  }
309
306
  }
310
307
 
311
308
  @Override
312
- public ConfigDiff resume(TaskSource taskSource,
313
- Schema schema, int taskCount,
314
- InputPlugin.Control control)
315
- {
309
+ public ConfigDiff resume(
310
+ final TaskSource taskSource,
311
+ final Schema schema,
312
+ final int taskCount,
313
+ final InputPlugin.Control control) {
316
314
  control.run(taskSource, schema, taskCount);
317
- return newConfigDiff();
315
+ return Exec.newConfigDiff();
318
316
  }
319
317
 
320
318
  @Override
321
- public void cleanup(TaskSource taskSource,
322
- Schema schema, int taskCount,
323
- List<TaskReport> successTaskReports)
324
- {
319
+ public void cleanup(
320
+ final TaskSource taskSource,
321
+ final Schema schema,
322
+ final int taskCount,
323
+ final List<TaskReport> successTaskReports) {
325
324
  // do nothing
326
325
  }
327
326
 
328
327
  @Override
329
- public TaskReport run(TaskSource taskSource,
330
- final Schema schema, int taskIndex,
331
- PageOutput output)
332
- {
328
+ public TaskReport run(
329
+ final TaskSource taskSource,
330
+ final Schema schema,
331
+ final int taskIndex,
332
+ final PageOutput output) {
333
333
  final PluginTask task = taskSource.loadTask(PluginTask.class);
334
334
  final BufferAllocator allocator = task.getBufferAllocator();
335
335
  final ValueWriter[] writers = newValueWriters(schema);
@@ -337,29 +337,33 @@ public class TdInputPlugin
337
337
  final boolean stopOnInvalidRecord = task.getStopOnInvalidRecord();
338
338
 
339
339
  try (final PageBuilder pageBuilder = new PageBuilder(allocator, schema, output);
340
- final TDClient client = newTDClient(task)) {
341
- client.jobResult(jobId, MESSAGE_PACK_GZ, new Function<InputStream, Void>() {
340
+ final TDClient client = newTdClient(task)) {
341
+ final TDResultFormat resultFormat = TDResultFormat.MESSAGE_PACK_GZ;
342
+ client.jobResult(jobId, resultFormat, new Function<InputStream, Void>() {
342
343
  @Override
343
- public Void apply(InputStream input)
344
- {
345
- try (MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(new GZIPInputStream(input))) {
344
+ public Void apply(InputStream input) {
345
+ try (final MessageUnpacker unpacker = MessagePack
346
+ .newDefaultUnpacker(new GZIPInputStream(input))) {
346
347
  while (unpacker.hasNext()) {
347
348
  try {
348
- Value v;
349
+ final Value v;
349
350
  try {
350
351
  v = unpacker.unpackValue();
351
- }
352
- catch (IOException e) {
352
+ } catch (IOException e) {
353
353
  throw new InvalidRecordException("Cannot unpack value", e);
354
354
  }
355
355
 
356
356
  if (!v.isArrayValue()) {
357
- throw new InvalidRecordException(String.format(ENGLISH, "Must be array value: ", v.toString()));
357
+ throw new InvalidRecordException(
358
+ String.format(Locale.ENGLISH,
359
+ "Must be array value: (%s)", v.toString()));
358
360
  }
359
361
 
360
- ArrayValue record = v.asArrayValue();
362
+ final ArrayValue record = v.asArrayValue();
361
363
  if (record.size() != schema.size()) {
362
- throw new InvalidRecordException(String.format(ENGLISH, "The size (%d) of the record is invalid", record.size()));
364
+ throw new InvalidRecordException(String.format(Locale.ENGLISH,
365
+ "The size (%d) of the record is invalid",
366
+ record.size()));
363
367
  }
364
368
 
365
369
  // write records to the page
@@ -368,16 +372,16 @@ public class TdInputPlugin
368
372
  }
369
373
 
370
374
  pageBuilder.addRecord();
371
- }
372
- catch (InvalidRecordException e) {
375
+ } catch (InvalidRecordException e) {
373
376
  if (stopOnInvalidRecord) {
374
- throw new DataException(String.format(ENGLISH, "Invalid record (%s)", e.getMessage()), e);
377
+ throw new DataException(String.format(Locale.ENGLISH,
378
+ "Invalid record (%s)", e.getMessage()), e);
375
379
  }
376
- log.warn(String.format(ENGLISH, "Skipped record (%s)", e.getMessage()));
380
+ log.warn(String.format(Locale.ENGLISH,
381
+ "Skipped record (%s)", e.getMessage()));
377
382
  }
378
383
  }
379
- }
380
- catch (IOException e) {
384
+ } catch (IOException e) {
381
385
  throw Throwables.propagate(e);
382
386
  }
383
387
 
@@ -388,58 +392,116 @@ public class TdInputPlugin
388
392
  pageBuilder.finish();
389
393
  }
390
394
 
391
- return newTaskReport();
395
+ return Exec.newTaskReport();
392
396
  }
393
397
 
394
- private ValueWriter[] newValueWriters(Schema schema)
395
- {
396
- ValueWriter[] writers = new ValueWriter[schema.size()];
398
+ private ValueWriter[] newValueWriters(final Schema schema) {
399
+ final ValueWriter[] writers = new ValueWriter[schema.size()];
397
400
  for (int i = 0; i < schema.size(); i++) {
398
401
  writers[i] = newValueWriter(schema.getColumn(i));
399
402
  }
400
403
  return writers;
401
404
  }
402
405
 
403
- private ValueWriter newValueWriter(Column column)
404
- {
405
- Type type = column.getType();
406
- if (type.equals(BOOLEAN)) {
406
+ private ValueWriter newValueWriter(final Column column) {
407
+ final Type type = column.getType();
408
+ if (type.equals(Types.BOOLEAN)) {
407
409
  return new BooleanValueWriter(column);
408
- }
409
- else if (type.equals(DOUBLE)) {
410
+ } else if (type.equals(Types.DOUBLE)) {
410
411
  return new DoubleValueWriter(column);
411
- }
412
- else if (type.equals(JSON)) {
413
- throw new ConfigException(String.format(ENGLISH, "Unsupported column type (%s:%s)", column.getName(), type)); // TODO
414
- }
415
- else if (type.equals(LONG)) {
412
+ } else if (type.equals(Types.JSON)) {
413
+ return new JsonValueWriter(column);
414
+ } else if (type.equals(Types.LONG)) {
416
415
  return new LongValueWriter(column);
417
- }
418
- else if (type.equals(STRING)) {
416
+ } else if (type.equals(Types.STRING)) {
419
417
  return new StringValueWriter(column);
420
- }
421
- else if (type.equals(TIMESTAMP)) {
422
- throw new ConfigException(String.format(ENGLISH, "Unsupported column type (%s:%s)", column.getName(), type)); // TODO
423
- }
424
- else {
425
- throw new ConfigException(String.format(ENGLISH, "Unsupported column type (%s:%s)", column.getName(), type)); // TODO
418
+ } else if (type.equals(Types.TIMESTAMP)) {
419
+ throw new ConfigException(String.format(Locale.ENGLISH,
420
+ "Unsupported column type (%s:%s)", column.getName(), type)); // TODO
421
+ } else {
422
+ throw new ConfigException(String.format(Locale.ENGLISH,
423
+ "Unsupported column type (%s:%s)", column.getName(), type)); // TODO
426
424
  }
427
425
  }
428
426
 
429
427
  @Override
430
- public ConfigDiff guess(ConfigSource config)
431
- {
432
- return newConfigDiff(); // do nothing
428
+ public ConfigDiff guess(final ConfigSource config) {
429
+ return Exec.newConfigDiff(); // do nothing
430
+ }
431
+
432
+ public interface PluginTask
433
+ extends Task {
434
+
435
+ @Config("apikey")
436
+ public String getApiKey();
437
+
438
+ @Config("endpoint")
439
+ @ConfigDefault("\"api.treasuredata.com\"")
440
+ public String getEndpoint();
441
+
442
+ @Config("use_ssl")
443
+ @ConfigDefault("true")
444
+ public boolean getUseSsl();
445
+
446
+ @Config("http_proxy")
447
+ @ConfigDefault("null")
448
+ public Optional<HttpProxyTask> getHttpProxy();
449
+
450
+ // TODO timeout
451
+ // TODO query, database
452
+
453
+ @Config("query")
454
+ @ConfigDefault("null")
455
+ public Optional<String> getQuery();
456
+
457
+ @Config("database")
458
+ @ConfigDefault("null")
459
+ public Optional<String> getDatabase();
460
+
461
+ @Config("job_id")
462
+ @ConfigDefault("null")
463
+ public Optional<String> getJobId();
464
+
465
+ @Config("stop_on_invalid_record")
466
+ @ConfigDefault("false")
467
+ public boolean getStopOnInvalidRecord();
468
+
469
+ // TODO column_options
470
+
471
+ @ConfigInject
472
+ BufferAllocator getBufferAllocator();
473
+ }
474
+
475
+ public interface HttpProxyTask
476
+ extends Task {
477
+
478
+ @Config("host")
479
+ public String getHost();
480
+
481
+ @Config("port")
482
+ public int getPort();
483
+
484
+ @Config("use_ssl")
485
+ @ConfigDefault("false")
486
+ public boolean getUseSsl();
487
+
488
+ @Config("user")
489
+ @ConfigDefault("null")
490
+ public Optional<String> getUser();
491
+
492
+ @Config("password")
493
+ @ConfigDefault("null")
494
+ public Optional<String> getPassword();
433
495
  }
434
496
 
435
497
  static class InvalidRecordException
436
- extends RuntimeException
437
- {
438
- InvalidRecordException(String cause) {
498
+ extends RuntimeException {
499
+
500
+ InvalidRecordException(final String cause) {
439
501
  super(cause);
440
502
  }
441
503
 
442
- InvalidRecordException(String cause, Throwable t) {
504
+ InvalidRecordException(final String cause, final Throwable t) {
443
505
  super(cause, t);
444
506
  }
445
507
  }