embulk-output-mailchimp 0.2.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -1
  3. data/.gitignore +10 -3
  4. data/.travis.yml +4 -7
  5. data/CHANGELOG.md +18 -0
  6. data/README.md +18 -17
  7. data/build.gradle +124 -0
  8. data/ci/travis_mailchimp.yml +8 -0
  9. data/circle.yml +16 -0
  10. data/classpath/embulk-base-restclient-0.5.0.jar +0 -0
  11. data/classpath/embulk-output-mailchimp-0.3.2.jar +0 -0
  12. data/classpath/embulk-util-retryhelper-jetty92-0.5.0.jar +0 -0
  13. data/classpath/jetty-client-9.2.14.v20151106.jar +0 -0
  14. data/classpath/jetty-http-9.2.14.v20151106.jar +0 -0
  15. data/classpath/jetty-io-9.2.14.v20151106.jar +0 -0
  16. data/classpath/jetty-util-9.2.14.v20151106.jar +0 -0
  17. data/config/checkstyle/checkstyle.xml +128 -0
  18. data/config/checkstyle/default.xml +108 -0
  19. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  20. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  21. data/gradlew +160 -0
  22. data/gradlew.bat +90 -0
  23. data/lib/embulk/output/mailchimp.rb +3 -172
  24. data/src/main/java/org/embulk/output/mailchimp/MailChimpAbstractRecordBuffer.java +320 -0
  25. data/src/main/java/org/embulk/output/mailchimp/MailChimpHttpClient.java +151 -0
  26. data/src/main/java/org/embulk/output/mailchimp/MailChimpOutputPlugin.java +18 -0
  27. data/src/main/java/org/embulk/output/mailchimp/MailChimpOutputPluginDelegate.java +164 -0
  28. data/src/main/java/org/embulk/output/mailchimp/MailChimpRecordBuffer.java +174 -0
  29. data/src/main/java/org/embulk/output/mailchimp/helper/MailChimpHelper.java +70 -0
  30. data/src/main/java/org/embulk/output/mailchimp/model/AuthMethod.java +58 -0
  31. data/src/main/java/org/embulk/output/mailchimp/model/CategoriesResponse.java +30 -0
  32. data/src/main/java/org/embulk/output/mailchimp/model/ErrorResponse.java +56 -0
  33. data/src/main/java/org/embulk/output/mailchimp/model/InterestCategoriesResponse.java +24 -0
  34. data/src/main/java/org/embulk/output/mailchimp/model/InterestResponse.java +30 -0
  35. data/src/main/java/org/embulk/output/mailchimp/model/InterestsResponse.java +24 -0
  36. data/src/main/java/org/embulk/output/mailchimp/model/MemberStatus.java +64 -0
  37. data/src/main/java/org/embulk/output/mailchimp/model/MetaDataResponse.java +36 -0
  38. data/src/main/java/org/embulk/output/mailchimp/model/ReportResponse.java +105 -0
  39. data/src/main/java/org/embulk/output/mailchimp/validation/ColumnDataValidator.java +40 -0
  40. data/src/test/java/org/embulk/output/mailchimp/CircleCICredentials.java +22 -0
  41. data/src/test/java/org/embulk/output/mailchimp/TestColumnDataValidator.java +43 -0
  42. data/src/test/java/org/embulk/output/mailchimp/TestMailChimpHelper.java +54 -0
  43. data/src/test/java/org/embulk/output/mailchimp/TestMailChimpOutputPlugin.java +158 -0
  44. data/src/test/resources/csv/email.csv +2 -0
  45. metadata +55 -122
  46. data/.ruby-version +0 -1
  47. data/Gemfile +0 -2
  48. data/Rakefile +0 -29
  49. data/embulk-output-mailchimp.gemspec +0 -27
@@ -0,0 +1,320 @@
1
+ package org.embulk.output.mailchimp;
2
+
3
+ import com.fasterxml.jackson.core.JsonParser;
4
+ import com.fasterxml.jackson.core.JsonProcessingException;
5
+ import com.fasterxml.jackson.databind.DeserializationFeature;
6
+ import com.fasterxml.jackson.databind.JsonNode;
7
+ import com.fasterxml.jackson.databind.ObjectMapper;
8
+ import com.fasterxml.jackson.databind.node.JsonNodeFactory;
9
+ import com.fasterxml.jackson.databind.node.ObjectNode;
10
+ import com.google.common.base.Function;
11
+ import com.google.common.base.Throwables;
12
+ import com.google.common.collect.FluentIterable;
13
+ import org.embulk.base.restclient.jackson.JacksonServiceRecord;
14
+ import org.embulk.base.restclient.record.RecordBuffer;
15
+ import org.embulk.base.restclient.record.ServiceRecord;
16
+ import org.embulk.config.TaskReport;
17
+ import org.embulk.output.mailchimp.model.ErrorResponse;
18
+ import org.embulk.output.mailchimp.model.InterestResponse;
19
+ import org.embulk.output.mailchimp.model.ReportResponse;
20
+ import org.embulk.spi.Column;
21
+ import org.embulk.spi.DataException;
22
+ import org.embulk.spi.Exec;
23
+ import org.embulk.spi.Schema;
24
+ import org.slf4j.Logger;
25
+
26
+ import java.io.IOException;
27
+ import java.text.MessageFormat;
28
+ import java.util.ArrayList;
29
+ import java.util.HashMap;
30
+ import java.util.List;
31
+ import java.util.Map;
32
+
33
+ import static org.embulk.output.mailchimp.helper.MailChimpHelper.containsCaseInsensitive;
34
+ import static org.embulk.output.mailchimp.model.MemberStatus.PENDING;
35
+ import static org.embulk.output.mailchimp.model.MemberStatus.SUBSCRIBED;
36
+
37
+ /**
38
+ * Created by thangnc on 4/14/17.
39
+ */
40
+ public abstract class MailChimpAbstractRecordBuffer
41
+ extends RecordBuffer
42
+ {
43
+ private static final Logger LOG = Exec.getLogger(MailChimpAbstractRecordBuffer.class);
44
+ private static final int MAX_RECORD_PER_BATCH_REQUEST = 500;
45
+ /**
46
+ * The constant mailchimpEndpoint.
47
+ */
48
+ protected static String mailchimpEndpoint = "https://{0}.api.mailchimp.com/3.0";
49
+ private final MailChimpOutputPluginDelegate.PluginTask task;
50
+ private final ObjectMapper mapper;
51
+ private final Schema schema;
52
+ private int requestCount;
53
+ private long totalCount;
54
+ private List<JsonNode> records;
55
+ private Map<String, Map<String, InterestResponse>> categories;
56
+
57
+ /**
58
+ * Instantiates a new Mail chimp abstract record buffer.
59
+ *
60
+ * @param schema the schema
61
+ * @param task the task
62
+ */
63
+ public MailChimpAbstractRecordBuffer(final Schema schema, final MailChimpOutputPluginDelegate.PluginTask task)
64
+ {
65
+ this.schema = schema;
66
+ this.task = task;
67
+ this.mapper = new ObjectMapper()
68
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
69
+ .configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false);
70
+ this.records = new ArrayList<>();
71
+ this.categories = new HashMap<>();
72
+ }
73
+
74
+ @Override
75
+ public void bufferRecord(ServiceRecord serviceRecord)
76
+ {
77
+ JacksonServiceRecord jacksonServiceRecord;
78
+
79
+ try {
80
+ jacksonServiceRecord = (JacksonServiceRecord) serviceRecord;
81
+ JsonNode record = mapper.readTree(jacksonServiceRecord.toString()).get("record");
82
+
83
+ requestCount++;
84
+ totalCount++;
85
+
86
+ records.add(record);
87
+ if (requestCount >= MAX_RECORD_PER_BATCH_REQUEST) {
88
+ ObjectNode subcribers = processSubcribers(records, task);
89
+ ReportResponse reportResponse = push(subcribers, task);
90
+
91
+ if (totalCount % 1000 == 0) {
92
+ LOG.info("Pushed {} records", totalCount);
93
+ }
94
+
95
+ LOG.info("{} records created, {} records updated, {} records failed",
96
+ reportResponse.getTotalCreated(),
97
+ reportResponse.getTotalUpdated(),
98
+ reportResponse.getErrorCount());
99
+ handleErrors(reportResponse.getErrors());
100
+
101
+ records = new ArrayList<>();
102
+ requestCount = 0;
103
+ }
104
+ }
105
+ catch (JsonProcessingException jpe) {
106
+ throw new DataException(jpe);
107
+ }
108
+ catch (ClassCastException ex) {
109
+ throw new RuntimeException(ex);
110
+ }
111
+ catch (IOException ex) {
112
+ throw Throwables.propagate(ex);
113
+ }
114
+ }
115
+
116
+ @Override
117
+ public TaskReport commitWithTaskReportUpdated(TaskReport taskReport)
118
+ {
119
+ try {
120
+ if (records.size() > 0) {
121
+ ObjectNode subcribers = processSubcribers(records, task);
122
+ ReportResponse reportResponse = push(subcribers, task);
123
+ LOG.info("Pushed {} records", records.size());
124
+ LOG.info("{} records created, {} records updated, {} records failed",
125
+ reportResponse.getTotalCreated(),
126
+ reportResponse.getTotalUpdated(),
127
+ reportResponse.getErrorCount());
128
+ handleErrors(reportResponse.getErrors());
129
+ }
130
+
131
+ cleanUp();
132
+ return Exec.newTaskReport().set("pushed", totalCount);
133
+ }
134
+ catch (JsonProcessingException jpe) {
135
+ throw new DataException(jpe);
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Receive data and build payload json that contains subscribers
141
+ *
142
+ * @param data the data
143
+ * @param task the task
144
+ * @return the object node
145
+ */
146
+ ObjectNode processSubcribers(final List<JsonNode> data, final MailChimpOutputPluginDelegate.PluginTask task)
147
+ {
148
+ LOG.info("Start to process subscriber data");
149
+ extractDataCenterBasedOnAuthMethod();
150
+ extractInterestCategories();
151
+
152
+ // Required merge fields
153
+ Map<String, String> map = new HashMap<>();
154
+ map.put("FNAME", task.getFnameColumn());
155
+ map.put("LNAME", task.getLnameColumn());
156
+
157
+ List<JsonNode> subscribersList = FluentIterable.from(data)
158
+ .transform(contactMapper(map))
159
+ .toList();
160
+
161
+ ObjectNode subscribers = JsonNodeFactory.instance.objectNode();
162
+ subscribers.putArray("members").addAll(subscribersList);
163
+ subscribers.put("update_existing", task.getUpdateExisting());
164
+ return subscribers;
165
+ }
166
+
167
+ /**
168
+ * Gets mapper.
169
+ *
170
+ * @return the mapper
171
+ */
172
+ public ObjectMapper getMapper()
173
+ {
174
+ return mapper;
175
+ }
176
+
177
+ /**
178
+ * Gets categories.
179
+ *
180
+ * @return the categories
181
+ */
182
+ public Map<String, Map<String, InterestResponse>> getCategories()
183
+ {
184
+ return categories;
185
+ }
186
+
187
+ /**
188
+ * Clean up.
189
+ */
190
+ abstract void cleanUp();
191
+
192
+ /**
193
+ * Push payload data to MailChimp API and get @{@link ReportResponse}
194
+ *
195
+ * @param node the content
196
+ * @param task the task
197
+ * @return the report response
198
+ * @throws JsonProcessingException the json processing exception
199
+ */
200
+ abstract ReportResponse push(final ObjectNode node, final MailChimpOutputPluginDelegate.PluginTask task)
201
+ throws JsonProcessingException;
202
+
203
+ /**
204
+ * Handle @{@link ErrorResponse} from MailChimp API if exists.
205
+ *
206
+ * @param errorResponses the error responses
207
+ */
208
+ abstract void handleErrors(final List<ErrorResponse> errorResponses);
209
+
210
+ /**
211
+ * Find interest category ids by pre-defined group name which user input.
212
+ *
213
+ * @param task the task
214
+ * @return the map
215
+ * @throws JsonProcessingException the json processing exception
216
+ */
217
+ abstract Map<String, Map<String, InterestResponse>> extractInterestCategoriesByGroupNames(final MailChimpOutputPluginDelegate.PluginTask task)
218
+ throws JsonProcessingException;
219
+
220
+ /**
221
+ * Extract data center from MailChimp v3 metadata.
222
+ *
223
+ * @param task the task
224
+ * @return the string
225
+ * @throws JsonProcessingException the json processing exception
226
+ */
227
+ abstract String extractDataCenter(final MailChimpOutputPluginDelegate.PluginTask task)
228
+ throws JsonProcessingException;
229
+
230
+ private void extractDataCenterBasedOnAuthMethod()
231
+ {
232
+ try {
233
+ // Extract data center from meta data URL
234
+ String dc = extractDataCenter(task);
235
+ mailchimpEndpoint = MessageFormat.format(mailchimpEndpoint, dc);
236
+ }
237
+ catch (JsonProcessingException jpe) {
238
+ throw new DataException(jpe);
239
+ }
240
+ }
241
+
242
+ private void extractInterestCategories()
243
+ {
244
+ try {
245
+ // Should loop the names and get the id of interest categories.
246
+ // The reason why we put categories validation here because we can not share data between instance.
247
+ categories = extractInterestCategoriesByGroupNames(task);
248
+ }
249
+ catch (JsonProcessingException jpe) {
250
+ throw new DataException(jpe);
251
+ }
252
+ }
253
+
254
+ private Function<JsonNode, JsonNode> contactMapper(final Map<String, String> allowColumns)
255
+ {
256
+ return new Function<JsonNode, JsonNode>()
257
+ {
258
+ @Override
259
+ public JsonNode apply(JsonNode input)
260
+ {
261
+ ObjectNode property = JsonNodeFactory.instance.objectNode();
262
+ property.put("email_address", input.findPath(task.getEmailColumn()).asText());
263
+ property.put("status", task.getDoubleOptIn() ? PENDING.getType() : SUBSCRIBED.getType());
264
+ ObjectNode mergeFields = JsonNodeFactory.instance.objectNode();
265
+ for (String allowColumn : allowColumns.keySet()) {
266
+ String value = input.findValue(allowColumns.get(allowColumn)).asText();
267
+ mergeFields.put(allowColumn, value);
268
+ }
269
+
270
+ // Update additional merge fields if exist
271
+ if (task.getMergeFields().isPresent() && !task.getMergeFields().get().isEmpty()) {
272
+ for (final Column column : schema.getColumns()) {
273
+ if (!"".equals(containsCaseInsensitive(column.getName(), task.getMergeFields().get()))) {
274
+ String value = input.findValue(column.getName()).asText();
275
+ mergeFields.put(column.getName().toUpperCase(), value);
276
+ }
277
+ }
278
+ }
279
+
280
+ property.set("merge_fields", mergeFields);
281
+
282
+ // Update interest categories if exist
283
+ if (task.getGroupingColumns().isPresent() && !task.getGroupingColumns().get().isEmpty()) {
284
+ property.set("interests", buildInterestCategories(task, input));
285
+ }
286
+
287
+ return property;
288
+ }
289
+ };
290
+ }
291
+
292
+ private ObjectNode buildInterestCategories(final MailChimpOutputPluginDelegate.PluginTask task,
293
+ final JsonNode input)
294
+ {
295
+ ObjectNode interests = JsonNodeFactory.instance.objectNode();
296
+
297
+ for (String category : task.getGroupingColumns().get()) {
298
+ String value = input.findValue(category).asText();
299
+ Map<String, InterestResponse> availableCategories = categories.get(category);
300
+
301
+ // Only update user-predefined categories if replace interests != true
302
+ // Otherwise, force update all categories include user-predefined categories
303
+ if (!task.getReplaceInterests() && availableCategories.get(value) != null) {
304
+ interests.put(availableCategories.get(value).getId(), true);
305
+ }
306
+ else if (task.getReplaceInterests()) {
307
+ for (String availableCategory : availableCategories.keySet()) {
308
+ if (availableCategory.equals(value)) {
309
+ interests.put(availableCategories.get(availableCategory).getId(), true);
310
+ }
311
+ else {
312
+ interests.put(availableCategories.get(availableCategory).getId(), false);
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ return interests;
319
+ }
320
+ }
@@ -0,0 +1,151 @@
1
+ package org.embulk.output.mailchimp;
2
+
3
+ import com.fasterxml.jackson.core.Base64Variants;
4
+ import com.fasterxml.jackson.databind.JsonNode;
5
+ import com.fasterxml.jackson.databind.ObjectMapper;
6
+ import com.fasterxml.jackson.databind.node.MissingNode;
7
+ import com.google.common.base.Throwables;
8
+ import org.eclipse.jetty.client.HttpClient;
9
+ import org.eclipse.jetty.client.api.Request;
10
+ import org.eclipse.jetty.client.api.Response;
11
+ import org.eclipse.jetty.client.util.StringContentProvider;
12
+ import org.eclipse.jetty.http.HttpMethod;
13
+ import org.eclipse.jetty.util.ssl.SslContextFactory;
14
+ import org.embulk.config.ConfigException;
15
+ import org.embulk.spi.DataException;
16
+ import org.embulk.spi.Exec;
17
+ import org.embulk.util.retryhelper.jetty92.Jetty92ClientCreator;
18
+ import org.embulk.util.retryhelper.jetty92.Jetty92RetryHelper;
19
+ import org.embulk.util.retryhelper.jetty92.Jetty92SingleRequester;
20
+ import org.embulk.util.retryhelper.jetty92.StringJetty92ResponseEntityReader;
21
+ import org.slf4j.Logger;
22
+
23
+ import java.io.IOException;
24
+
25
+ /**
26
+ * Created by thangnc on 4/14/17.
27
+ */
28
+ public class MailChimpHttpClient
29
+ {
30
+ private static final Logger LOG = Exec.getLogger(MailChimpHttpClient.class);
31
+ private final ObjectMapper jsonMapper = new ObjectMapper()
32
+ .configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false)
33
+ .configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
34
+ private Jetty92RetryHelper retryHelper;
35
+
36
+ /**
37
+ * Instantiates a new Mailchimp http client.
38
+ *
39
+ * @param task the task
40
+ */
41
+ public MailChimpHttpClient(MailChimpOutputPluginDelegate.PluginTask task)
42
+ {
43
+ retryHelper = createRetryHelper(task);
44
+ }
45
+
46
+ /**
47
+ * Close @{@link Jetty92RetryHelper} connection
48
+ */
49
+ public void close()
50
+ {
51
+ if (retryHelper != null) {
52
+ retryHelper.close();
53
+ }
54
+ }
55
+
56
+ public JsonNode sendRequest(final String endpoint, final HttpMethod method,
57
+ final MailChimpOutputPluginDelegate.PluginTask task)
58
+ {
59
+ return sendRequest(endpoint, method, "", task);
60
+ }
61
+
62
+ public JsonNode sendRequest(final String endpoint, final HttpMethod method, final String content,
63
+ final MailChimpOutputPluginDelegate.PluginTask task)
64
+ {
65
+ final String authorizationHeader = getAuthorizationHeader(task);
66
+
67
+ String responseBody = retryHelper.requestWithRetry(
68
+ new StringJetty92ResponseEntityReader(task.getTimeoutMillis()),
69
+ new Jetty92SingleRequester()
70
+ {
71
+ @Override
72
+ public void requestOnce(HttpClient client, Response.Listener responseListener)
73
+ {
74
+ Request request = client
75
+ .newRequest(endpoint)
76
+ .accept("application/json")
77
+ .method(method);
78
+ if (method == HttpMethod.POST || method == HttpMethod.PUT) {
79
+ request.content(new StringContentProvider(content), "application/json;utf-8");
80
+ }
81
+
82
+ if (!authorizationHeader.isEmpty()) {
83
+ request.header("Authorization", authorizationHeader);
84
+ }
85
+ request.send(responseListener);
86
+ }
87
+
88
+ @Override
89
+ public boolean isResponseStatusToRetry(Response response)
90
+ {
91
+ int status = response.getStatus();
92
+ return status == 429 || status / 100 != 4;
93
+ }
94
+ });
95
+
96
+ return responseBody != null && !responseBody.isEmpty() ? parseJson(responseBody) : MissingNode.getInstance();
97
+ }
98
+
99
+ private JsonNode parseJson(final String json)
100
+ throws DataException
101
+ {
102
+ try {
103
+ return this.jsonMapper.readTree(json);
104
+ }
105
+ catch (IOException ex) {
106
+ throw new DataException(ex);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * MailChimp API v3 supports non expires access_token. Then no need refresh_token
112
+ *
113
+ * @param task
114
+ * @return
115
+ */
116
+ private String getAuthorizationHeader(final MailChimpOutputPluginDelegate.PluginTask task)
117
+ {
118
+ switch (task.getAuthMethod()) {
119
+ case OAUTH:
120
+ return "OAuth " + task.getAccessToken().orNull();
121
+ case API_KEY:
122
+ return "Basic " + Base64Variants.MIME_NO_LINEFEEDS
123
+ .encode(("apikey" + ":" + task.getApikey().orNull()).getBytes());
124
+ default:
125
+ throw new ConfigException("Not supported method");
126
+ }
127
+ }
128
+
129
+ private Jetty92RetryHelper createRetryHelper(MailChimpOutputPluginDelegate.PluginTask task)
130
+ {
131
+ return new Jetty92RetryHelper(
132
+ task.getMaximumRetries(),
133
+ task.getInitialRetryIntervalMillis(),
134
+ task.getMaximumRetryIntervalMillis(),
135
+ new Jetty92ClientCreator()
136
+ {
137
+ @Override
138
+ public HttpClient createAndStart()
139
+ {
140
+ HttpClient client = new HttpClient(new SslContextFactory());
141
+ try {
142
+ client.start();
143
+ return client;
144
+ }
145
+ catch (Exception e) {
146
+ throw Throwables.propagate(e);
147
+ }
148
+ }
149
+ });
150
+ }
151
+ }
@@ -0,0 +1,18 @@
1
+ package org.embulk.output.mailchimp;
2
+
3
+ import org.embulk.base.restclient.RestClientOutputPluginBase;
4
+
5
+ /**
6
+ * Created by thangnc on 4/14/17.
7
+ */
8
+ public class MailChimpOutputPlugin
9
+ extends RestClientOutputPluginBase<MailChimpOutputPluginDelegate.PluginTask>
10
+ {
11
+ /**
12
+ * Instantiates a new @{@link MailChimpOutputPlugin}.
13
+ */
14
+ public MailChimpOutputPlugin()
15
+ {
16
+ super(MailChimpOutputPluginDelegate.PluginTask.class, new MailChimpOutputPluginDelegate());
17
+ }
18
+ }
@@ -0,0 +1,164 @@
1
+ package org.embulk.output.mailchimp;
2
+
3
+ import com.google.common.base.Optional;
4
+ import org.embulk.base.restclient.RestClientOutputPluginDelegate;
5
+ import org.embulk.base.restclient.RestClientOutputTaskBase;
6
+ import org.embulk.base.restclient.jackson.JacksonServiceRequestMapper;
7
+ import org.embulk.base.restclient.jackson.JacksonTopLevelValueLocator;
8
+ import org.embulk.base.restclient.jackson.scope.JacksonAllInObjectScope;
9
+ import org.embulk.base.restclient.record.RecordBuffer;
10
+ import org.embulk.config.Config;
11
+ import org.embulk.config.ConfigDefault;
12
+ import org.embulk.config.ConfigDiff;
13
+ import org.embulk.config.ConfigException;
14
+ import org.embulk.config.TaskReport;
15
+ import org.embulk.output.mailchimp.model.AuthMethod;
16
+ import org.embulk.spi.Exec;
17
+ import org.embulk.spi.Schema;
18
+ import org.slf4j.Logger;
19
+
20
+ import java.util.List;
21
+
22
+ import static com.google.common.base.Strings.isNullOrEmpty;
23
+ import static org.embulk.output.mailchimp.validation.ColumnDataValidator.checkExistColumns;
24
+
25
+ /**
26
+ * Created by thangnc on 4/14/17.
27
+ */
28
+ public class MailChimpOutputPluginDelegate
29
+ implements RestClientOutputPluginDelegate<MailChimpOutputPluginDelegate.PluginTask>
30
+ {
31
+ private static final Logger LOG = Exec.getLogger(MailChimpOutputPluginDelegate.class);
32
+
33
+ public MailChimpOutputPluginDelegate()
34
+ {
35
+ }
36
+
37
+ public interface PluginTask
38
+ extends RestClientOutputTaskBase
39
+ {
40
+ @Config("maximum_retries")
41
+ @ConfigDefault("6")
42
+ int getMaximumRetries();
43
+
44
+ @Config("initial_retry_interval_millis")
45
+ @ConfigDefault("1000")
46
+ int getInitialRetryIntervalMillis();
47
+
48
+ @Config("maximum_retry_interval_millis")
49
+ @ConfigDefault("32000")
50
+ int getMaximumRetryIntervalMillis();
51
+
52
+ @Config("timeout_millis")
53
+ @ConfigDefault("60000")
54
+ int getTimeoutMillis();
55
+
56
+ @Config("auth_method")
57
+ @ConfigDefault("api_key")
58
+ AuthMethod getAuthMethod();
59
+
60
+ @Config("apikey")
61
+ @ConfigDefault("null")
62
+ Optional<String> getApikey();
63
+
64
+ @Config("access_token")
65
+ @ConfigDefault("null")
66
+ Optional<String> getAccessToken();
67
+
68
+ @Config("list_id")
69
+ String getListId();
70
+
71
+ @Config("email_column")
72
+ @ConfigDefault("email")
73
+ String getEmailColumn();
74
+
75
+ @Config("fname_column")
76
+ @ConfigDefault("fname")
77
+ String getFnameColumn();
78
+
79
+ @Config("lname_column")
80
+ @ConfigDefault("lname")
81
+ String getLnameColumn();
82
+
83
+ @Config("merge_fields")
84
+ @ConfigDefault("null")
85
+ Optional<List<String>> getMergeFields();
86
+
87
+ @Config("grouping_columns")
88
+ @ConfigDefault("null")
89
+ Optional<List<String>> getGroupingColumns();
90
+
91
+ @Config("double_optin")
92
+ @ConfigDefault("true")
93
+ boolean getDoubleOptIn();
94
+
95
+ @Config("update_existing")
96
+ @ConfigDefault("false")
97
+ boolean getUpdateExisting();
98
+
99
+ @Config("replace_interests")
100
+ @ConfigDefault("true")
101
+ boolean getReplaceInterests();
102
+ }
103
+
104
+ /**
105
+ * Override @{@link RestClientOutputPluginDelegate#validateOutputTask(RestClientOutputTaskBase, Schema, int)}
106
+ * This method not only validates required configurations but also validates required columns
107
+ *
108
+ * @param task
109
+ * @param schema
110
+ * @param taskCount
111
+ */
112
+ @Override
113
+ public void validateOutputTask(final PluginTask task, final Schema schema, final int taskCount)
114
+ {
115
+ if (task.getAuthMethod() == AuthMethod.OAUTH) {
116
+ if (!task.getAccessToken().isPresent() || isNullOrEmpty(task.getAccessToken().get())) {
117
+ throw new ConfigException("'access_token' is required when auth_method is 'oauth'");
118
+ }
119
+ }
120
+ else if (task.getAuthMethod() == AuthMethod.API_KEY) {
121
+ if (!task.getApikey().isPresent() || isNullOrEmpty(task.getApikey().get())) {
122
+ throw new ConfigException("'apikey' is required when auth_method is 'api_key'");
123
+ }
124
+ }
125
+
126
+ if (isNullOrEmpty(task.getListId())) {
127
+ throw new ConfigException("'list_id' must not be null or empty string");
128
+ }
129
+
130
+ if (!checkExistColumns(schema, task.getEmailColumn(), task.getFnameColumn(), task.getLnameColumn())) {
131
+ throw new ConfigException("Columns ['email', 'fname', 'lname'] must not be null or empty string");
132
+ }
133
+ }
134
+
135
+ @Override
136
+ public RecordBuffer buildRecordBuffer(PluginTask task, Schema schema, int taskIndex)
137
+ {
138
+ return new MailChimpRecordBuffer(schema, task);
139
+ }
140
+
141
+ @Override
142
+ public JacksonServiceRequestMapper buildServiceRequestMapper(final PluginTask task)
143
+ {
144
+ return JacksonServiceRequestMapper.builder()
145
+ .add(new JacksonAllInObjectScope(), new JacksonTopLevelValueLocator("record"))
146
+ .build();
147
+ }
148
+
149
+ @Override
150
+ public ConfigDiff egestEmbulkData(final PluginTask task, final Schema schema, final int taskCount,
151
+ final List<TaskReport> taskReports)
152
+ {
153
+ long totalInserted = 0;
154
+ for (TaskReport taskReport : taskReports) {
155
+ if (taskReport.has("pushed")) {
156
+ totalInserted += taskReport.get(Long.class, "pushed");
157
+ }
158
+ }
159
+
160
+ LOG.info("Pushed completed. {} records", totalInserted);
161
+
162
+ return Exec.newConfigDiff();
163
+ }
164
+ }