embulk-output-td 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/README.md +63 -0
  4. data/build.gradle +79 -0
  5. data/embulk-output-td.gemspec +18 -0
  6. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  7. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  8. data/gradlew +164 -0
  9. data/gradlew.bat +90 -0
  10. data/lib/embulk/output/td.rb +3 -0
  11. data/settings.gradle +1 -0
  12. data/src/main/java/com/treasuredata/api/TdApiClient.java +436 -0
  13. data/src/main/java/com/treasuredata/api/TdApiClientConfig.java +79 -0
  14. data/src/main/java/com/treasuredata/api/TdApiConflictException.java +10 -0
  15. data/src/main/java/com/treasuredata/api/TdApiConstants.java +6 -0
  16. data/src/main/java/com/treasuredata/api/TdApiException.java +20 -0
  17. data/src/main/java/com/treasuredata/api/TdApiExecutionException.java +10 -0
  18. data/src/main/java/com/treasuredata/api/TdApiExecutionInterruptedException.java +15 -0
  19. data/src/main/java/com/treasuredata/api/TdApiExecutionTimeoutException.java +17 -0
  20. data/src/main/java/com/treasuredata/api/TdApiNotFoundException.java +10 -0
  21. data/src/main/java/com/treasuredata/api/TdApiResponseException.java +32 -0
  22. data/src/main/java/com/treasuredata/api/model/TDArrayColumnType.java +80 -0
  23. data/src/main/java/com/treasuredata/api/model/TDBulkImportSession.java +155 -0
  24. data/src/main/java/com/treasuredata/api/model/TDColumn.java +83 -0
  25. data/src/main/java/com/treasuredata/api/model/TDColumnType.java +23 -0
  26. data/src/main/java/com/treasuredata/api/model/TDColumnTypeDeserializer.java +115 -0
  27. data/src/main/java/com/treasuredata/api/model/TDDatabase.java +48 -0
  28. data/src/main/java/com/treasuredata/api/model/TDDatabaseList.java +24 -0
  29. data/src/main/java/com/treasuredata/api/model/TDMapColumnType.java +88 -0
  30. data/src/main/java/com/treasuredata/api/model/TDPrimitiveColumnType.java +61 -0
  31. data/src/main/java/com/treasuredata/api/model/TDTable.java +64 -0
  32. data/src/main/java/com/treasuredata/api/model/TDTableList.java +33 -0
  33. data/src/main/java/com/treasuredata/api/model/TDTablePermission.java +48 -0
  34. data/src/main/java/com/treasuredata/api/model/TDTableSchema.java +44 -0
  35. data/src/main/java/com/treasuredata/api/model/TDTableType.java +36 -0
  36. data/src/main/java/org/embulk/output/FinalizableExecutorService.java +84 -0
  37. data/src/main/java/org/embulk/output/MsgpackGZFileBuilder.java +148 -0
  38. data/src/main/java/org/embulk/output/RecordWriter.java +567 -0
  39. data/src/main/java/org/embulk/output/TdOutputPlugin.java +390 -0
  40. data/src/test/java/org/embulk/output/TestTdOutputPlugin.java +5 -0
  41. metadata +119 -0
@@ -0,0 +1,3 @@
1
+ Embulk::JavaPlugin.register_output(
2
+ "td", "org.embulk.output.TdOutputPlugin",
3
+ File.expand_path('../../../../classpath', __FILE__))
data/settings.gradle ADDED
@@ -0,0 +1 @@
1
+ rootProject.name = 'embulk-output-td'
@@ -0,0 +1,436 @@
1
+ package com.treasuredata.api;
2
+
3
+ import com.fasterxml.jackson.databind.DeserializationFeature;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.google.common.annotations.VisibleForTesting;
6
+ import com.google.common.collect.ImmutableMap;
7
+ import com.treasuredata.api.model.TDBulkImportSession;
8
+ import com.treasuredata.api.model.TDDatabase;
9
+ import com.treasuredata.api.model.TDDatabaseList;
10
+ import com.treasuredata.api.model.TDTable;
11
+ import com.treasuredata.api.model.TDTableList;
12
+ import com.treasuredata.api.model.TDTableType;
13
+ import org.eclipse.jetty.client.HttpClient;
14
+ import org.eclipse.jetty.client.HttpProxy;
15
+ import org.eclipse.jetty.client.Origin;
16
+ import org.eclipse.jetty.client.ProxyConfiguration;
17
+ import org.eclipse.jetty.client.api.ContentResponse;
18
+ import org.eclipse.jetty.client.api.Request;
19
+ import org.eclipse.jetty.client.util.StringContentProvider;
20
+ import org.eclipse.jetty.http.HttpMethod;
21
+ import org.eclipse.jetty.util.HttpCookieStore;
22
+ import org.eclipse.jetty.util.ssl.SslContextFactory;
23
+
24
+ import javax.annotation.PostConstruct;
25
+ import javax.annotation.PreDestroy;
26
+ import java.io.ByteArrayOutputStream;
27
+ import java.io.Closeable;
28
+ import java.io.File;
29
+ import java.io.IOException;
30
+ import java.io.UnsupportedEncodingException;
31
+ import java.net.URLEncoder;
32
+ import java.security.MessageDigest;
33
+ import java.security.NoSuchAlgorithmException;
34
+ import java.text.SimpleDateFormat;
35
+ import java.util.Collections;
36
+ import java.util.Date;
37
+ import java.util.List;
38
+ import java.util.Locale;
39
+ import java.util.Map;
40
+
41
+ public class TdApiClient
42
+ implements Closeable
43
+ {
44
+ private final String apiKey;
45
+ private final TdApiClientConfig config;
46
+ private final HttpClient http;
47
+ private final ObjectMapper objectMapper;
48
+
49
+ public TdApiClient(String apiKey, TdApiClientConfig config)
50
+ {
51
+ this.apiKey = apiKey;
52
+ this.config = config;
53
+
54
+ objectMapper = new ObjectMapper();
55
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
56
+
57
+ SslContextFactory sslContextFactory = new SslContextFactory();
58
+ http = new HttpClient(sslContextFactory);
59
+ http.setConnectTimeout(10 * 1000); // TODO get from options
60
+ http.setIdleTimeout(60 * 1000); // TODO get from options
61
+ http.setMaxConnectionsPerDestination(10); // TODO get from options
62
+ http.setCookieStore(new HttpCookieStore.Empty());
63
+
64
+ // ProxyConfiguration
65
+ if (config.getHttpProxyConfig().isPresent()) {
66
+ TdApiClientConfig.HttpProxyConfig httpProxyConfig = config.getHttpProxyConfig().get();
67
+ String host = httpProxyConfig.getHost();
68
+ int port = httpProxyConfig.getPort();
69
+ boolean isSecure = httpProxyConfig.isSecure();
70
+
71
+ ProxyConfiguration proxyConfig = http.getProxyConfiguration();
72
+ proxyConfig.getProxies().add(new HttpProxy(new Origin.Address(host, port), isSecure));
73
+ }
74
+ }
75
+
76
+ private TdApiClient(TdApiClient copy, String apiKey)
77
+ {
78
+ this.apiKey = apiKey;
79
+ this.config = copy.config;
80
+ this.http = copy.http;
81
+ this.objectMapper = copy.objectMapper;
82
+ }
83
+
84
+ public TdApiClient withApikey(String apikey)
85
+ {
86
+ return new TdApiClient(this, apikey);
87
+ }
88
+
89
+ @PostConstruct
90
+ public void start() throws IOException
91
+ {
92
+ try {
93
+ http.start();
94
+ } catch (Exception e) {
95
+ throw new IOException("Failed to start http client", e);
96
+ }
97
+ }
98
+
99
+ @PreDestroy
100
+ public void close()
101
+ {
102
+ try {
103
+ http.stop();
104
+ } catch (Exception e) {
105
+ throw new RuntimeException("Failed to stop http client", e);
106
+ }
107
+ }
108
+
109
+ public List<TDDatabase> getDatabases()
110
+ {
111
+ Request request = prepareExchange(HttpMethod.GET,
112
+ buildUrl("/v3/database/list"));
113
+ ContentResponse response = executeExchange(request);
114
+ TDDatabaseList databaseList = parseResponse(response.getContent(), TDDatabaseList.class);
115
+ return databaseList.getDatabases();
116
+ }
117
+
118
+ public TDDatabase createDatabase(String databaseName)
119
+ {
120
+ Request request = prepareExchange(HttpMethod.POST,
121
+ buildUrl("/v3/database/create", databaseName));
122
+ ContentResponse response = executeExchange(request);
123
+ TDDatabase database = parseResponse(response.getContent(), TDDatabase.class);
124
+ return database;
125
+ }
126
+
127
+ public List<TDTable> getTables(String databaseName)
128
+ {
129
+ Request request = prepareExchange(HttpMethod.GET,
130
+ buildUrl("/v3/table/list/", databaseName));
131
+ ContentResponse response = executeExchange(request);
132
+ TDTableList tables = parseResponse(response.getContent(), TDTableList.class);
133
+ return tables.getTables();
134
+ }
135
+
136
+ public TDTable createTable(String databaseName, String tableName)
137
+ {
138
+ return createTable(databaseName, tableName, TDTableType.LOG);
139
+ }
140
+
141
+ public TDTable createTable(String databaseName, String tableName, TDTableType tableType) {
142
+ Request request = prepareExchange(HttpMethod.POST,
143
+ buildUrl("/v3/table/create", databaseName, tableName, tableType.name().toLowerCase()));
144
+ ContentResponse response = executeExchange(request);
145
+ TDTable table = parseResponse(response.getContent(), TDTable.class);
146
+ return table;
147
+ }
148
+
149
+ public void deleteTable(String databaseName, String tableName)
150
+ throws IOException
151
+ {
152
+ Request request = prepareExchange(HttpMethod.POST,
153
+ buildUrl("/v3/table/delete", databaseName, tableName));
154
+ ContentResponse response = executeExchange(request);
155
+ }
156
+
157
+ public void createBulkImportSession(String sessionName, String databaseName, String tableName)
158
+ {
159
+ Request request = prepareExchange(HttpMethod.POST,
160
+ buildUrl("/v3/bulk_import/create", sessionName, databaseName, tableName));
161
+ ContentResponse response = executeExchange(request);
162
+ // TODO return TDBulkImportSession
163
+ }
164
+
165
+ public TDBulkImportSession getBulkImportSession(String sessionName)
166
+ {
167
+ Request request = prepareExchange(HttpMethod.GET,
168
+ buildUrl("/v3/bulk_import/show", sessionName));
169
+ ContentResponse response = executeExchange(request);
170
+ TDBulkImportSession session = parseResponse(response.getContent(), TDBulkImportSession.class);
171
+ return session;
172
+ }
173
+
174
+ public void uploadBulkImport(String sessionName, File path)
175
+ throws IOException
176
+ {
177
+ String name = path.getName().replace(".", "_");
178
+ Request request = prepareExchange(HttpMethod.PUT,
179
+ buildUrl("/v3/bulk_import/upload_part", sessionName, name));
180
+ request.file(path.toPath());
181
+ ContentResponse response = executeExchange(request);
182
+ }
183
+
184
+ public void freezeBulkImportSession(String sessionName)
185
+ {
186
+ Request request = prepareExchange(HttpMethod.POST,
187
+ buildUrl("/v3/bulk_import/freeze", sessionName));
188
+ ContentResponse response = executeExchange(request);
189
+ }
190
+
191
+ public void performBulkImportSession(String sessionName, int priority)
192
+ {
193
+ Request request = prepareExchange(HttpMethod.POST,
194
+ buildUrl("/v3/bulk_import/perform", sessionName),
195
+ ImmutableMap.<String, String>of(),
196
+ ImmutableMap.of("priority", String.valueOf(priority)));
197
+ ContentResponse response = executeExchange(request);
198
+ }
199
+
200
+ public void commitBulkImportSession(String sessionName)
201
+ {
202
+ Request request = prepareExchange(HttpMethod.POST,
203
+ buildUrl("/v3/bulk_import/commit", sessionName));
204
+ ContentResponse response = executeExchange(request);
205
+ }
206
+
207
+ public void deleteBulkImportSession(String sessionName)
208
+ {
209
+ Request request = prepareExchange(HttpMethod.POST,
210
+ buildUrl("/v3/bulk_import/delete", sessionName));
211
+ ContentResponse response = executeExchange(request);
212
+ }
213
+
214
+ private Request prepareExchange(HttpMethod method, String url)
215
+ {
216
+ return prepareExchange(method, url, Collections.<String,String>emptyMap(),
217
+ Collections.<String,String>emptyMap());
218
+ }
219
+
220
+ private Request prepareExchange(HttpMethod method,
221
+ String url, Map<String, String> headers,
222
+ Map<String, String> query)
223
+ {
224
+ String queryString = null;
225
+ if (!query.isEmpty()) {
226
+ StringBuilder sb = new StringBuilder();
227
+ for (Map.Entry<String, String> entry : query.entrySet()) {
228
+ sb.append(encode(entry.getKey())).append("=").append(encode(entry.getValue()));
229
+ }
230
+ queryString = sb.toString();
231
+ }
232
+
233
+ if (method == HttpMethod.GET && queryString != null) {
234
+ url = url + "?" + queryString;
235
+ }
236
+ Request request = http.newRequest(url);
237
+ request.method(method);
238
+ request.agent(config.getAgentName());
239
+ request.header("Authorization", "TD1 " + apiKey);
240
+ //request.timeout(60, TimeUnit.SECONDS);
241
+ for (Map.Entry<String, String> entry: headers.entrySet()) {
242
+ request.header(entry.getKey(), entry.getValue());
243
+ }
244
+ String dateHeader = setDateHeader(request);
245
+ if (method != HttpMethod.GET && queryString != null) {
246
+ request.content(new StringContentProvider(queryString),
247
+ "application/x-www-form-urlencoded");
248
+ }
249
+
250
+ return request;
251
+ }
252
+
253
+ private static String encode(String s)
254
+ {
255
+ try {
256
+ return URLEncoder.encode(s, "UTF-8");
257
+ } catch (UnsupportedEncodingException e) {
258
+ throw new AssertionError(e);
259
+ }
260
+ }
261
+
262
+ private static final ThreadLocal<SimpleDateFormat> RFC2822_FORMAT =
263
+ new ThreadLocal<SimpleDateFormat>() {
264
+ @Override
265
+ protected SimpleDateFormat initialValue() {
266
+ return new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
267
+ }
268
+ };
269
+
270
+ private static String setDateHeader(Request request)
271
+ {
272
+ Date currentDate = new Date();
273
+ String dateHeader = RFC2822_FORMAT.get().format(currentDate);
274
+ request.header("Date", dateHeader);
275
+ return dateHeader;
276
+ }
277
+
278
+ private static final ThreadLocal<MessageDigest> SHA1 =
279
+ new ThreadLocal<MessageDigest>() {
280
+ @Override
281
+ protected MessageDigest initialValue() {
282
+ try {
283
+ return MessageDigest.getInstance("SHA-1");
284
+ } catch (NoSuchAlgorithmException e) {
285
+ throw new RuntimeException("SHA-1 digest algorithm must be available but not found", e);
286
+ }
287
+ }
288
+ };
289
+
290
+ private static final char[] hexChars = new char[16];
291
+ static {
292
+ for (int i = 0; i < 16; i++) {
293
+ hexChars[i] = Integer.toHexString(i).charAt(0);
294
+ }
295
+ }
296
+
297
+ @VisibleForTesting
298
+ static String sha1HexFromString(String string)
299
+ {
300
+ MessageDigest sha1 = SHA1.get();
301
+ sha1.reset();
302
+ sha1.update(string.getBytes());
303
+ byte[] bytes = sha1.digest();
304
+
305
+ // convert binary to hex string
306
+ char[] array = new char[bytes.length * 2];
307
+ for (int i = 0; i < bytes.length; i++) {
308
+ int b = (int) bytes[i];
309
+ array[i*2] = hexChars[(b & 0xf0) >> 4];
310
+ array[i*2+1] = hexChars[b & 0x0f];
311
+ }
312
+ return new String(array);
313
+ }
314
+
315
+ private String buildUrl(String path, String... params)
316
+ {
317
+ StringBuilder sb = new StringBuilder();
318
+ sb.append(config.getUseSsl() ? "https://" : "http://");
319
+ sb.append(config.getEndpoint());
320
+ sb.append(path);
321
+ try {
322
+ for (String param : params) {
323
+ sb.append("/");
324
+ sb.append(URLEncoder.encode(param, "UTF-8"));
325
+ }
326
+ } catch (UnsupportedEncodingException ex) {
327
+ throw new AssertionError(ex);
328
+ }
329
+ return sb.toString();
330
+ }
331
+
332
+ private ContentResponse executeExchange(Request request)
333
+ {
334
+ int retryLimit = 10;
335
+ int retryWait = 1000;
336
+
337
+ Exception firstException = null;
338
+
339
+ try {
340
+ while (true) {
341
+ Exception exception;
342
+
343
+ try {
344
+ ContentResponse response = request.send();
345
+
346
+ int status = response.getStatus();
347
+ switch(status) {
348
+ case 200:
349
+ return response;
350
+ case 404:
351
+ throw new TdApiNotFoundException(status, response.getContent());
352
+ case 409:
353
+ throw new TdApiConflictException(status, response.getContent());
354
+ }
355
+
356
+ if (status / 100 != 5) { // not 50x
357
+ throw new TdApiResponseException(status, response.getContent());
358
+ }
359
+
360
+ // retry on 50x and other errors
361
+ exception = new TdApiResponseException(status, response.getContent());
362
+
363
+ } catch (TdApiException e) {
364
+ throw e;
365
+
366
+ } catch (Exception e) {
367
+ // retry on RuntimeException
368
+ exception = e;
369
+ }
370
+
371
+ if (firstException == null) {
372
+ firstException = exception;
373
+ }
374
+
375
+ if (retryLimit <= 0) {
376
+ if (firstException instanceof TdApiException) {
377
+ throw (TdApiException) firstException;
378
+ }
379
+ throw new TdApiExecutionException(firstException);
380
+ }
381
+
382
+ retryLimit -= 1;
383
+ Thread.sleep(retryWait);
384
+ retryWait *= 2;
385
+ }
386
+ } catch (InterruptedException e) {
387
+ throw new TdApiExecutionInterruptedException(e);
388
+ }
389
+ }
390
+
391
+ private String formatRequestParameterObject(Object obj)
392
+ {
393
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
394
+ try {
395
+ objectMapper.writeValue(bo, obj);
396
+ return new String(bo.toByteArray(), "UTF-8");
397
+ } catch (UnsupportedEncodingException ex) {
398
+ throw new AssertionError(ex);
399
+ } catch (IOException ex) {
400
+ throw new RuntimeException(ex);
401
+ } finally {
402
+ try {
403
+ bo.close();
404
+ } catch (IOException ex) {
405
+ throw new RuntimeException(ex);
406
+ }
407
+ }
408
+ }
409
+
410
+ private byte[] formatPostRequestContent(String... kvs)
411
+ {
412
+ try {
413
+ StringBuilder sb = new StringBuilder();
414
+ for(int i=0; i < kvs.length; i+=2) {
415
+ if (i > 0) {
416
+ sb.append("&");
417
+ }
418
+ sb.append(encode(kvs[i]))
419
+ .append("=")
420
+ .append(encode(kvs[i + 1]));
421
+ }
422
+ return sb.toString().getBytes("UTF-8");
423
+ } catch (UnsupportedEncodingException ex) {
424
+ throw new AssertionError(ex);
425
+ }
426
+ }
427
+
428
+ private <T> T parseResponse(byte[] content, Class<T> valueType)
429
+ {
430
+ try {
431
+ return objectMapper.readValue(content, valueType);
432
+ } catch (IOException ex) {
433
+ throw new RuntimeException(ex);
434
+ }
435
+ }
436
+ }
@@ -0,0 +1,79 @@
1
+ package com.treasuredata.api;
2
+
3
+ import com.google.common.base.Optional;
4
+
5
+ public class TdApiClientConfig
6
+ implements TdApiConstants
7
+ {
8
+ public static class HttpProxyConfig
9
+ {
10
+ private String host;
11
+ private int port;
12
+ private boolean secure;
13
+
14
+ public HttpProxyConfig(String host, int port, boolean secure)
15
+ {
16
+ this.host = host;
17
+ this.port = port;
18
+ this.secure = secure;
19
+ }
20
+
21
+ public String getHost()
22
+ {
23
+ return host;
24
+ }
25
+
26
+ public int getPort()
27
+ {
28
+ return port;
29
+ }
30
+
31
+ public boolean isSecure()
32
+ {
33
+ return secure;
34
+ }
35
+ }
36
+
37
+ private String endpoint;
38
+ private boolean useSsl;
39
+ private Optional<HttpProxyConfig> httpProxyConfig;
40
+
41
+ // TODO Builder
42
+ // TODO clone
43
+
44
+ public TdApiClientConfig(String endpoint, boolean useSsl)
45
+ {
46
+ this(endpoint, useSsl, Optional.<HttpProxyConfig>absent());
47
+ }
48
+
49
+ public TdApiClientConfig(String endpoint, boolean useSsl, Optional<HttpProxyConfig> httpProxyConfig)
50
+ {
51
+ this.endpoint = endpoint;
52
+ this.useSsl = useSsl;
53
+ this.httpProxyConfig = httpProxyConfig;
54
+ }
55
+
56
+ public String getEndpoint()
57
+ {
58
+ return endpoint;
59
+ }
60
+
61
+ public void setEndpoint(String endpoint)
62
+ {
63
+ this.endpoint = endpoint;
64
+ }
65
+
66
+ public boolean getUseSsl()
67
+ {
68
+ return useSsl;
69
+ }
70
+
71
+ public Optional<HttpProxyConfig> getHttpProxyConfig()
72
+ {
73
+ return httpProxyConfig;
74
+ }
75
+
76
+ public String getAgentName() {
77
+ return AGENT_NAME;
78
+ }
79
+ }