embulk-output-mailchimp 0.2.3 → 0.3.2

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 (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
+ }