embulk-output-td 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }