embulk-output-mailchimp 0.3.20 → 0.3.21
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -0
- data/README.md +2 -1
- data/build.gradle +6 -7
- data/classpath/embulk-output-mailchimp-0.3.21.jar +0 -0
- data/src/main/java/org/embulk/output/mailchimp/MailChimpClient.java +108 -143
- data/src/main/java/org/embulk/output/mailchimp/MailChimpOutputPluginDelegate.java +8 -0
- data/src/main/java/org/embulk/output/mailchimp/MailChimpRecordBuffer.java +32 -25
- data/src/main/java/org/embulk/output/mailchimp/helper/MailChimpRetryable.java +224 -0
- metadata +4 -4
- data/classpath/embulk-output-mailchimp-0.3.20.jar +0 -0
- data/src/main/java/org/embulk/output/mailchimp/MailChimpHttpClient.java +0 -200
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 504301e8e2482b1d046fb9a880ab63a631dac2ca
|
4
|
+
data.tar.gz: 053d271506d3a0dcb9020466ed703ccfe4d17469
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c08f3cfe6e2de7f6c2c35b04dac4f50b99ab1457c285e7ba42d8c6aed854d5922e640b3e1c089039f8453048b22996bd52e0a1fb689e9b7af8cab4e5fd50954c
|
7
|
+
data.tar.gz: d5dca1a6c14f1c992fe0f3e34a987052e6aa0ee8682bfa6114c0d510fcb790e5945e9d8e46091b7184afab7279143f08fb7fb5f9690724d612d9ec803f0908ea
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
## 0.3.21 - 2018-03-02
|
2
|
+
- Refactor code to improve performance and fix NPE with merge fields that contain case sensitive [#40](https://github.com/treasure-data/embulk-output-mailchimp/pull/40)
|
3
|
+
|
1
4
|
## 0.3.20 - 2017-10-16
|
2
5
|
- Added pagination for interest categories [#39](https://github.com/treasure-data/embulk-output-mailchimp/pull/39)
|
3
6
|
- Refactor check list id to avoid confusing when get 404 error [#38](https://github.com/treasure-data/embulk-output-mailchimp/pull/38)
|
data/README.md
CHANGED
@@ -26,6 +26,7 @@ add e-mail to List in MailChimp.
|
|
26
26
|
- **language_column**: column name for language (string, optional, default: nil)
|
27
27
|
- **double_optin**: control whether to send an opt-in confirmation email (boolean, default: true)
|
28
28
|
- **max_records_per_request**: The max records per batch request. MailChimp API enables max records is 500 per batch request (int, default: 500)
|
29
|
+
- **sleep_between_requests_millis**: The time to sleep between requests to avoid flood MailChimp API (int, default: 3000)
|
29
30
|
|
30
31
|
## Example
|
31
32
|
|
@@ -51,5 +52,5 @@ out:
|
|
51
52
|
## Build
|
52
53
|
|
53
54
|
```
|
54
|
-
$
|
55
|
+
$ ./gradlew gem
|
55
56
|
```
|
data/build.gradle
CHANGED
@@ -18,22 +18,21 @@ configurations {
|
|
18
18
|
provided
|
19
19
|
}
|
20
20
|
|
21
|
-
version = "0.3.
|
21
|
+
version = "0.3.21"
|
22
22
|
|
23
23
|
sourceCompatibility = 1.7
|
24
24
|
targetCompatibility = 1.7
|
25
25
|
|
26
26
|
dependencies {
|
27
|
-
compile "org.embulk:embulk-core:0.8.
|
28
|
-
provided "org.embulk:embulk-core:0.8.
|
27
|
+
compile "org.embulk:embulk-core:0.8.25"
|
28
|
+
provided "org.embulk:embulk-core:0.8.25"
|
29
29
|
compile "org.embulk.base.restclient:embulk-base-restclient:0.5.3"
|
30
30
|
compile "org.embulk.base.restclient:embulk-util-retryhelper-jetty92:0.5.3"
|
31
|
-
// compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
|
32
31
|
|
33
32
|
testCompile "junit:junit:4.+"
|
34
|
-
testCompile "org.embulk:embulk-core:0.8.
|
35
|
-
testCompile "org.embulk:embulk-test:0.8.
|
36
|
-
testCompile "org.embulk:embulk-standards:0.8.
|
33
|
+
testCompile "org.embulk:embulk-core:0.8.25:tests"
|
34
|
+
testCompile "org.embulk:embulk-test:0.8.25"
|
35
|
+
testCompile "org.embulk:embulk-standards:0.8.25"
|
37
36
|
testCompile "org.mockito:mockito-core:2.+"
|
38
37
|
}
|
39
38
|
|
Binary file
|
@@ -5,18 +5,17 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|
5
5
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
6
6
|
import com.fasterxml.jackson.databind.JsonNode;
|
7
7
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
8
|
-
import com.fasterxml.jackson.databind.node.MissingNode;
|
9
8
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
10
9
|
import com.google.common.base.Function;
|
11
|
-
import com.google.common.base.Joiner;
|
12
10
|
import com.google.common.collect.FluentIterable;
|
13
11
|
import com.google.common.collect.ImmutableList;
|
14
12
|
import com.google.common.collect.Maps;
|
15
13
|
import org.eclipse.jetty.client.HttpResponseException;
|
16
|
-
import org.
|
14
|
+
import org.embulk.base.restclient.jackson.StringJsonParser;
|
17
15
|
import org.embulk.config.ConfigException;
|
18
16
|
import org.embulk.output.mailchimp.MailChimpOutputPluginDelegate.PluginTask;
|
19
17
|
import org.embulk.output.mailchimp.helper.MailChimpHelper;
|
18
|
+
import org.embulk.output.mailchimp.helper.MailChimpRetryable;
|
20
19
|
import org.embulk.output.mailchimp.model.CategoriesResponse;
|
21
20
|
import org.embulk.output.mailchimp.model.ErrorResponse;
|
22
21
|
import org.embulk.output.mailchimp.model.InterestCategoriesResponse;
|
@@ -24,7 +23,6 @@ import org.embulk.output.mailchimp.model.InterestResponse;
|
|
24
23
|
import org.embulk.output.mailchimp.model.InterestsResponse;
|
25
24
|
import org.embulk.output.mailchimp.model.MergeField;
|
26
25
|
import org.embulk.output.mailchimp.model.MergeFields;
|
27
|
-
import org.embulk.output.mailchimp.model.MetaDataResponse;
|
28
26
|
import org.embulk.output.mailchimp.model.ReportResponse;
|
29
27
|
import org.embulk.spi.DataException;
|
30
28
|
import org.embulk.spi.Exec;
|
@@ -38,19 +36,16 @@ import java.util.HashMap;
|
|
38
36
|
import java.util.List;
|
39
37
|
import java.util.Map;
|
40
38
|
|
41
|
-
import static org.embulk.output.mailchimp.model.AuthMethod.API_KEY;
|
42
|
-
import static org.embulk.output.mailchimp.model.AuthMethod.OAUTH;
|
43
|
-
|
44
39
|
/**
|
45
40
|
* Created by thangnc on 4/25/17.
|
46
41
|
*/
|
47
42
|
public class MailChimpClient
|
48
43
|
{
|
49
44
|
private static final Logger LOG = Exec.getLogger(MailChimpClient.class);
|
50
|
-
private
|
51
|
-
|
52
|
-
|
53
|
-
private
|
45
|
+
private final ObjectMapper mapper = new ObjectMapper()
|
46
|
+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
47
|
+
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false);
|
48
|
+
private StringJsonParser jsonParser = new StringJsonParser();
|
54
49
|
|
55
50
|
/**
|
56
51
|
* Instantiates a new Mail chimp client.
|
@@ -59,12 +54,6 @@ public class MailChimpClient
|
|
59
54
|
*/
|
60
55
|
public MailChimpClient(final PluginTask task)
|
61
56
|
{
|
62
|
-
mailchimpEndpoint = Joiner.on("/").join("https://{0}.api.mailchimp.com", API_VERSION);
|
63
|
-
this.client = new MailChimpHttpClient(task);
|
64
|
-
this.mapper = new ObjectMapper()
|
65
|
-
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
66
|
-
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false);
|
67
|
-
extractDataCenter(task);
|
68
57
|
findList(task);
|
69
58
|
}
|
70
59
|
|
@@ -75,24 +64,19 @@ public class MailChimpClient
|
|
75
64
|
* @param node the data
|
76
65
|
* @param task the task
|
77
66
|
* @return the report response
|
78
|
-
* @throws JsonProcessingException the json processing exception
|
79
67
|
*/
|
80
|
-
ReportResponse push(final ObjectNode node, PluginTask task)
|
81
|
-
throws JsonProcessingException
|
68
|
+
public ReportResponse push(final ObjectNode node, PluginTask task) throws JsonProcessingException
|
82
69
|
{
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
70
|
+
try (MailChimpRetryable retryable = new MailChimpRetryable(task)) {
|
71
|
+
String response = retryable.post(MessageFormat.format("/lists/{0}", task.getListId()),
|
72
|
+
"application/json;utf-8",
|
73
|
+
node.toString());
|
74
|
+
if (response != null && !response.isEmpty()) {
|
75
|
+
return mapper.treeToValue(jsonParser.parseJsonObject(response), ReportResponse.class);
|
76
|
+
}
|
88
77
|
|
89
|
-
|
90
|
-
ReportResponse reportResponse = new ReportResponse();
|
91
|
-
reportResponse.setErrors(new ArrayList<ErrorResponse>());
|
92
|
-
return reportResponse;
|
78
|
+
throw new DataException("The json data in response were broken.");
|
93
79
|
}
|
94
|
-
|
95
|
-
return mapper.treeToValue(response, ReportResponse.class);
|
96
80
|
}
|
97
81
|
|
98
82
|
/**
|
@@ -100,7 +84,7 @@ public class MailChimpClient
|
|
100
84
|
*
|
101
85
|
* @param errorResponses the error responses
|
102
86
|
*/
|
103
|
-
void handleErrors(List<ErrorResponse> errorResponses)
|
87
|
+
public void handleErrors(List<ErrorResponse> errorResponses)
|
104
88
|
{
|
105
89
|
if (!errorResponses.isEmpty()) {
|
106
90
|
StringBuilder errorMessage = new StringBuilder();
|
@@ -125,76 +109,77 @@ public class MailChimpClient
|
|
125
109
|
* @return the map
|
126
110
|
* @throws JsonProcessingException the json processing exception
|
127
111
|
*/
|
128
|
-
Map<String, Map<String, InterestResponse>> extractInterestCategoriesByGroupNames(final PluginTask task)
|
112
|
+
public Map<String, Map<String, InterestResponse>> extractInterestCategoriesByGroupNames(final PluginTask task)
|
129
113
|
throws JsonProcessingException
|
130
114
|
{
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
115
|
+
try (MailChimpRetryable retryable = new MailChimpRetryable(task)) {
|
116
|
+
Map<String, Map<String, InterestResponse>> categories = new HashMap<>();
|
117
|
+
if (task.getGroupingColumns().isPresent() && !task.getGroupingColumns().get().isEmpty()) {
|
118
|
+
List<String> interestCategoryNames = task.getGroupingColumns().get();
|
119
|
+
|
120
|
+
int count = 100;
|
121
|
+
int offset = 0;
|
122
|
+
int page = 1;
|
123
|
+
boolean hasMore = true;
|
124
|
+
JsonNode response;
|
125
|
+
List<CategoriesResponse> allCategoriesResponse = new ArrayList<>();
|
126
|
+
|
127
|
+
while (hasMore) {
|
128
|
+
String path = MessageFormat.format("/lists/{0}/interest-categories?count={1}&offset={2}",
|
144
129
|
task.getListId(),
|
145
130
|
count,
|
146
131
|
offset);
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
132
|
+
response = jsonParser.parseJsonObject(retryable.get(path));
|
133
|
+
InterestCategoriesResponse interestCategoriesResponse = mapper.treeToValue(response,
|
134
|
+
InterestCategoriesResponse.class);
|
135
|
+
|
136
|
+
allCategoriesResponse.addAll(interestCategoriesResponse.getCategories());
|
137
|
+
if (hasMorePage(interestCategoriesResponse.getTotalItems(), count, page)) {
|
138
|
+
offset = count;
|
139
|
+
page++;
|
140
|
+
}
|
141
|
+
else {
|
142
|
+
hasMore = false;
|
143
|
+
}
|
159
144
|
}
|
160
|
-
}
|
161
145
|
|
162
|
-
|
163
|
-
{
|
164
|
-
@Override
|
165
|
-
public String apply(CategoriesResponse input)
|
146
|
+
Function<CategoriesResponse, String> function = new Function<CategoriesResponse, String>()
|
166
147
|
{
|
167
|
-
|
148
|
+
@Override
|
149
|
+
public String apply(CategoriesResponse input)
|
150
|
+
{
|
151
|
+
return input.getTitle().toLowerCase();
|
152
|
+
}
|
153
|
+
};
|
154
|
+
|
155
|
+
// Transform to a list of available category names and validate with data that user input
|
156
|
+
ImmutableList<String> availableCategories = FluentIterable
|
157
|
+
.from(allCategoriesResponse)
|
158
|
+
.transform(function)
|
159
|
+
.toList();
|
160
|
+
|
161
|
+
for (String category : interestCategoryNames) {
|
162
|
+
if (!availableCategories.contains(category)) {
|
163
|
+
throw new ConfigException("Invalid interest category name: '" + category + "'");
|
164
|
+
}
|
168
165
|
}
|
169
|
-
};
|
170
|
-
|
171
|
-
// Transform to a list of available category names and validate with data that user input
|
172
|
-
ImmutableList<String> availableCategories = FluentIterable
|
173
|
-
.from(allCategoriesResponse)
|
174
|
-
.transform(function)
|
175
|
-
.toList();
|
176
166
|
|
177
|
-
|
178
|
-
|
179
|
-
throw new ConfigException("Invalid interest category name: '" + category + "'");
|
180
|
-
}
|
181
|
-
}
|
182
|
-
|
183
|
-
for (CategoriesResponse categoriesResponse : allCategoriesResponse) {
|
184
|
-
String detailEndpoint = MessageFormat.format(mailchimpEndpoint + "/lists/{0}/interest-categories/{1}/interests",
|
167
|
+
for (CategoriesResponse categoriesResponse : allCategoriesResponse) {
|
168
|
+
String detailPath = MessageFormat.format("/lists/{0}/interest-categories/{1}/interests",
|
185
169
|
task.getListId(),
|
186
170
|
categoriesResponse.getId());
|
187
|
-
|
171
|
+
response = jsonParser.parseJsonObject(retryable.get(detailPath));
|
188
172
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
173
|
+
// Avoid flood MailChimp API
|
174
|
+
avoidFloodAPI("Fetching next category's interests", task.getSleepBetweenRequestsMillis());
|
175
|
+
InterestsResponse interestsResponse = mapper.treeToValue(response, InterestsResponse.class);
|
176
|
+
categories.put(categoriesResponse.getTitle().toLowerCase(),
|
177
|
+
convertInterestCategoryToMap(interestsResponse.getInterests()));
|
178
|
+
}
|
194
179
|
}
|
195
|
-
}
|
196
180
|
|
197
|
-
|
181
|
+
return categories;
|
182
|
+
}
|
198
183
|
}
|
199
184
|
|
200
185
|
/**
|
@@ -205,76 +190,45 @@ public class MailChimpClient
|
|
205
190
|
* @return the map
|
206
191
|
* @throws JsonProcessingException the json processing exception
|
207
192
|
*/
|
208
|
-
Map<String, MergeField> extractMergeFieldsFromList(PluginTask task) throws JsonProcessingException
|
193
|
+
public Map<String, MergeField> extractMergeFieldsFromList(PluginTask task) throws JsonProcessingException
|
209
194
|
{
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
195
|
+
try (MailChimpRetryable retryable = new MailChimpRetryable(task)) {
|
196
|
+
int count = 100;
|
197
|
+
int offset = 0;
|
198
|
+
int page = 1;
|
199
|
+
boolean hasMore = true;
|
200
|
+
List<MergeField> allMergeFields = new ArrayList<>();
|
201
|
+
|
202
|
+
while (hasMore) {
|
203
|
+
String path = MessageFormat.format("/lists/{0}/merge-fields?count={1}&offset={2}",
|
218
204
|
task.getListId(),
|
219
205
|
count,
|
220
206
|
offset);
|
221
207
|
|
222
|
-
|
223
|
-
|
224
|
-
|
208
|
+
JsonNode response = jsonParser.parseJsonObject(retryable.get(path));
|
209
|
+
MergeFields mergeFields = mapper.treeToValue(response,
|
210
|
+
MergeFields.class);
|
225
211
|
|
226
|
-
|
212
|
+
allMergeFields.addAll(mergeFields.getMergeFields());
|
227
213
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
}
|
236
|
-
|
237
|
-
return convertMergeFieldToMap(allMergeFields);
|
238
|
-
}
|
239
|
-
|
240
|
-
private void extractDataCenter(final PluginTask task)
|
241
|
-
{
|
242
|
-
if (task.getAuthMethod() == OAUTH) {
|
243
|
-
// Extract data center from meta data URL
|
244
|
-
JsonNode response = client.sendRequest("https://login.mailchimp.com/oauth2/metadata", HttpMethod.GET, task);
|
245
|
-
MetaDataResponse metaDataResponse;
|
246
|
-
try {
|
247
|
-
metaDataResponse = mapper.treeToValue(response, MetaDataResponse.class);
|
248
|
-
mailchimpEndpoint = MessageFormat.format(mailchimpEndpoint, metaDataResponse.getDc());
|
249
|
-
}
|
250
|
-
catch (JsonProcessingException e) {
|
251
|
-
throw new DataException(e);
|
252
|
-
}
|
253
|
-
}
|
254
|
-
else if (task.getAuthMethod() == API_KEY && task.getApikey().isPresent()) {
|
255
|
-
// Authenticate and return data center
|
256
|
-
if (!task.getApikey().get().contains("-")) {
|
257
|
-
throw new ConfigException("API Key format invalid.");
|
214
|
+
if (hasMorePage(mergeFields.getTotalItems(), count, page)) {
|
215
|
+
offset = count;
|
216
|
+
page++;
|
217
|
+
}
|
218
|
+
else {
|
219
|
+
hasMore = false;
|
220
|
+
}
|
258
221
|
}
|
259
222
|
|
260
|
-
|
261
|
-
String endpoint = MessageFormat.format(mailchimpEndpoint + "/", domain);
|
262
|
-
try {
|
263
|
-
client.sendRequest(endpoint, HttpMethod.GET, task);
|
264
|
-
mailchimpEndpoint = MessageFormat.format(mailchimpEndpoint, domain);
|
265
|
-
}
|
266
|
-
catch (HttpResponseException re) {
|
267
|
-
throw new ConfigException("Your API key may be invalid, or you've attempted to access the wrong datacenter.");
|
268
|
-
}
|
223
|
+
return convertMergeFieldToMap(allMergeFields);
|
269
224
|
}
|
270
225
|
}
|
271
226
|
|
272
227
|
private void findList(final PluginTask task)
|
273
228
|
{
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
client.sendRequest(endpoint, HttpMethod.GET, task);
|
229
|
+
try (MailChimpRetryable retryable = new MailChimpRetryable(task)) {
|
230
|
+
jsonParser.parseJsonObject(retryable.get(MessageFormat.format("/lists/{0}",
|
231
|
+
task.getListId())));
|
278
232
|
}
|
279
233
|
catch (HttpResponseException hre) {
|
280
234
|
throw new ConfigException("The `list id` could not be found.");
|
@@ -319,4 +273,15 @@ public class MailChimpClient
|
|
319
273
|
int totalPage = count / pageSize + (count % pageSize > 0 ? 1 : 0);
|
320
274
|
return page < totalPage;
|
321
275
|
}
|
276
|
+
|
277
|
+
public void avoidFloodAPI(final String reason, final long millis)
|
278
|
+
{
|
279
|
+
try {
|
280
|
+
LOG.info("{} in {}ms...", reason, millis);
|
281
|
+
Thread.sleep(millis);
|
282
|
+
}
|
283
|
+
catch (InterruptedException e) {
|
284
|
+
LOG.warn("Failed to sleep: {}", e.getMessage());
|
285
|
+
}
|
286
|
+
}
|
322
287
|
}
|
@@ -107,6 +107,10 @@ public class MailChimpOutputPluginDelegate
|
|
107
107
|
@Config("max_records_per_request")
|
108
108
|
@ConfigDefault("500")
|
109
109
|
int getMaxRecordsPerRequest();
|
110
|
+
|
111
|
+
@Config("sleep_between_requests_millis")
|
112
|
+
@ConfigDefault("3000")
|
113
|
+
int getSleepBetweenRequestsMillis();
|
110
114
|
}
|
111
115
|
|
112
116
|
/**
|
@@ -129,6 +133,10 @@ public class MailChimpOutputPluginDelegate
|
|
129
133
|
if (!task.getApikey().isPresent() || isNullOrEmpty(task.getApikey().get())) {
|
130
134
|
throw new ConfigException("'apikey' is required when auth_method is 'api_key'");
|
131
135
|
}
|
136
|
+
|
137
|
+
if (!task.getApikey().get().contains("-")) {
|
138
|
+
throw new ConfigException("apikey's format invalid.");
|
139
|
+
}
|
132
140
|
}
|
133
141
|
|
134
142
|
if (isNullOrEmpty(task.getListId())) {
|
@@ -150,7 +150,7 @@ public class MailChimpRecordBuffer
|
|
150
150
|
@Override
|
151
151
|
public void close()
|
152
152
|
{
|
153
|
-
|
153
|
+
// // Do not close here
|
154
154
|
}
|
155
155
|
|
156
156
|
/**
|
@@ -165,10 +165,14 @@ public class MailChimpRecordBuffer
|
|
165
165
|
{
|
166
166
|
// Should loop the names and get the id of interest categories.
|
167
167
|
// The reason why we put categories validation here because we can not share data between instance.
|
168
|
-
categories
|
168
|
+
if (categories == null) {
|
169
|
+
categories = mailChimpClient.extractInterestCategoriesByGroupNames(task);
|
170
|
+
}
|
169
171
|
|
170
172
|
// Extract merge fields detail
|
171
|
-
availableMergeFields
|
173
|
+
if (availableMergeFields == null) {
|
174
|
+
availableMergeFields = mailChimpClient.extractMergeFieldsFromList(task);
|
175
|
+
}
|
172
176
|
|
173
177
|
// Required merge fields
|
174
178
|
Map<String, String> map = new HashMap<>();
|
@@ -208,7 +212,7 @@ public class MailChimpRecordBuffer
|
|
208
212
|
String value = input.hasNonNull(column.getName()) ? input.findValue(column.getName()).asText() : "";
|
209
213
|
|
210
214
|
// Try to convert to Json from string with the merge field's type is address
|
211
|
-
if (availableMergeFields.get(column.getName()).getType()
|
215
|
+
if (availableMergeFields.get(column.getName().toLowerCase()).getType()
|
212
216
|
.equals(MergeField.MergeFieldType.ADDRESS.getType())) {
|
213
217
|
JsonNode addressNode = toJsonNode(value);
|
214
218
|
if (addressNode instanceof NullNode) {
|
@@ -243,31 +247,32 @@ public class MailChimpRecordBuffer
|
|
243
247
|
};
|
244
248
|
}
|
245
249
|
|
246
|
-
private ObjectNode buildInterestCategories(final
|
247
|
-
final JsonNode input)
|
250
|
+
private ObjectNode buildInterestCategories(final PluginTask task, final JsonNode input)
|
248
251
|
{
|
249
252
|
ObjectNode interests = JsonNodeFactory.instance.objectNode();
|
250
253
|
|
251
|
-
|
252
|
-
String
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
} // Otherwise, force update all categories include user-predefined categories
|
264
|
-
else if (task.getReplaceInterests()) {
|
265
|
-
for (String availableCategory : availableCategories.keySet()) {
|
266
|
-
if (interestValues.contains(availableCategory)) {
|
267
|
-
interests.put(availableCategories.get(availableCategory).getId(), true);
|
254
|
+
if (task.getGroupingColumns().isPresent()) {
|
255
|
+
for (String category : task.getGroupingColumns().get()) {
|
256
|
+
String inputValue = input.findValue(category).asText();
|
257
|
+
List<String> interestValues = fromCommaSeparatedString(inputValue);
|
258
|
+
Map<String, InterestResponse> availableCategories = categories.get(category);
|
259
|
+
|
260
|
+
// Only update user-predefined categories if replace interests != true
|
261
|
+
if (!task.getReplaceInterests()) {
|
262
|
+
for (String interestValue : interestValues) {
|
263
|
+
if (availableCategories.get(interestValue) != null) {
|
264
|
+
interests.put(availableCategories.get(interestValue).getId(), true);
|
265
|
+
}
|
268
266
|
}
|
269
|
-
|
270
|
-
|
267
|
+
} // Otherwise, force update all categories include user-predefined categories
|
268
|
+
else if (task.getReplaceInterests()) {
|
269
|
+
for (String availableCategory : availableCategories.keySet()) {
|
270
|
+
if (interestValues.contains(availableCategory)) {
|
271
|
+
interests.put(availableCategories.get(availableCategory).getId(), true);
|
272
|
+
}
|
273
|
+
else {
|
274
|
+
interests.put(availableCategories.get(availableCategory).getId(), false);
|
275
|
+
}
|
271
276
|
}
|
272
277
|
}
|
273
278
|
}
|
@@ -302,6 +307,8 @@ public class MailChimpRecordBuffer
|
|
302
307
|
reportResponse.getErrorCount(), System.currentTimeMillis() - startTime);
|
303
308
|
mailChimpClient.handleErrors(reportResponse.getErrors());
|
304
309
|
|
310
|
+
mailChimpClient.avoidFloodAPI("Process next request", task.getSleepBetweenRequestsMillis());
|
311
|
+
|
305
312
|
if (duplicatedRecords.size() > 0) {
|
306
313
|
LOG.info("Start to process {} duplicated record(s)", duplicatedRecords.size());
|
307
314
|
for (JsonNode duplicatedRecord : duplicatedRecords) {
|
@@ -0,0 +1,224 @@
|
|
1
|
+
package org.embulk.output.mailchimp.helper;
|
2
|
+
|
3
|
+
import com.fasterxml.jackson.core.Base64Variants;
|
4
|
+
import com.fasterxml.jackson.databind.node.ObjectNode;
|
5
|
+
import com.google.common.base.Charsets;
|
6
|
+
import com.google.common.base.Joiner;
|
7
|
+
import org.eclipse.jetty.client.HttpClient;
|
8
|
+
import org.eclipse.jetty.client.HttpResponseException;
|
9
|
+
import org.eclipse.jetty.client.api.ContentResponse;
|
10
|
+
import org.eclipse.jetty.client.api.Request;
|
11
|
+
import org.eclipse.jetty.client.api.Response;
|
12
|
+
import org.eclipse.jetty.client.util.StringContentProvider;
|
13
|
+
import org.embulk.base.restclient.jackson.StringJsonParser;
|
14
|
+
import org.embulk.config.ConfigException;
|
15
|
+
import org.embulk.output.mailchimp.MailChimpOutputPluginDelegate.PluginTask;
|
16
|
+
import org.embulk.spi.Exec;
|
17
|
+
import org.embulk.util.retryhelper.jetty92.DefaultJetty92ClientCreator;
|
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.text.MessageFormat;
|
24
|
+
import java.util.concurrent.ExecutionException;
|
25
|
+
import java.util.concurrent.TimeoutException;
|
26
|
+
|
27
|
+
import static org.eclipse.jetty.http.HttpHeader.AUTHORIZATION;
|
28
|
+
import static org.eclipse.jetty.http.HttpMethod.GET;
|
29
|
+
import static org.eclipse.jetty.http.HttpMethod.POST;
|
30
|
+
import static org.embulk.output.mailchimp.model.AuthMethod.API_KEY;
|
31
|
+
import static org.embulk.output.mailchimp.model.AuthMethod.OAUTH;
|
32
|
+
|
33
|
+
public class MailChimpRetryable implements AutoCloseable
|
34
|
+
{
|
35
|
+
private static final Logger LOG = Exec.getLogger(MailChimpRetryable.class);
|
36
|
+
private static final int READER_TIMEOUT_MILLIS = 300000;
|
37
|
+
private static final String API_VERSION = "3.0";
|
38
|
+
private final Jetty92RetryHelper retryHelper;
|
39
|
+
private final PluginTask pluginTask;
|
40
|
+
private static TokenHolder tokenHolder;
|
41
|
+
protected StringJsonParser jsonParser = new StringJsonParser();
|
42
|
+
private String authorizationHeader;
|
43
|
+
|
44
|
+
public MailChimpRetryable(final PluginTask pluginTask)
|
45
|
+
{
|
46
|
+
this.retryHelper = new Jetty92RetryHelper(pluginTask.getMaximumRetries(),
|
47
|
+
pluginTask.getInitialRetryIntervalMillis(),
|
48
|
+
pluginTask.getMaximumRetryIntervalMillis(),
|
49
|
+
new DefaultJetty92ClientCreator(pluginTask.getTimeoutMillis(),
|
50
|
+
pluginTask.getTimeoutMillis()));
|
51
|
+
this.pluginTask = pluginTask;
|
52
|
+
authorizationHeader = buildAuthorizationHeader(pluginTask);
|
53
|
+
}
|
54
|
+
|
55
|
+
public String get(final String path)
|
56
|
+
{
|
57
|
+
return sendRequest(path, null);
|
58
|
+
}
|
59
|
+
|
60
|
+
public String post(final String path, String contentType, String body)
|
61
|
+
{
|
62
|
+
return sendRequest(path, new StringContentProvider(contentType, body, Charsets.UTF_8));
|
63
|
+
}
|
64
|
+
|
65
|
+
private String sendRequest(final String path, final StringContentProvider contentProvider)
|
66
|
+
{
|
67
|
+
return retryHelper.requestWithRetry(
|
68
|
+
new StringJetty92ResponseEntityReader(READER_TIMEOUT_MILLIS),
|
69
|
+
new Jetty92SingleRequester()
|
70
|
+
{
|
71
|
+
@Override
|
72
|
+
public void requestOnce(HttpClient client, Response.Listener responseListener)
|
73
|
+
{
|
74
|
+
createTokenHolder(client);
|
75
|
+
Request request = client.newRequest(tokenHolder.getEndpoint() + path)
|
76
|
+
.header(AUTHORIZATION, authorizationHeader)
|
77
|
+
.method(GET);
|
78
|
+
if (contentProvider != null) {
|
79
|
+
request = request.method(POST).content(contentProvider);
|
80
|
+
}
|
81
|
+
request.send(responseListener);
|
82
|
+
}
|
83
|
+
|
84
|
+
@Override
|
85
|
+
protected boolean isResponseStatusToRetry(Response response)
|
86
|
+
{
|
87
|
+
// Retry if it's a server or rate limit exceeded error
|
88
|
+
return (response.getStatus() != 500 && response.getStatus() / 100 != 4) || response.getStatus() == 429;
|
89
|
+
}
|
90
|
+
|
91
|
+
@Override
|
92
|
+
protected boolean isExceptionToRetry(Exception exception)
|
93
|
+
{
|
94
|
+
// This check is to make sure if the original exception is retryable, i.e.
|
95
|
+
// server not found, internal server error...
|
96
|
+
if (exception instanceof ConfigException || exception instanceof ExecutionException) {
|
97
|
+
return toRetry((Exception) exception.getCause());
|
98
|
+
}
|
99
|
+
return exception instanceof TimeoutException || super.isExceptionToRetry(exception);
|
100
|
+
}
|
101
|
+
});
|
102
|
+
}
|
103
|
+
|
104
|
+
@Override
|
105
|
+
public void close()
|
106
|
+
{
|
107
|
+
if (retryHelper != null) {
|
108
|
+
retryHelper.close();
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* MailChimp API v3 supports non expires access_token. Then no need refresh_token
|
114
|
+
*
|
115
|
+
* @param task
|
116
|
+
* @return
|
117
|
+
*/
|
118
|
+
private String buildAuthorizationHeader(final PluginTask task)
|
119
|
+
{
|
120
|
+
switch (task.getAuthMethod()) {
|
121
|
+
case OAUTH:
|
122
|
+
return "OAuth " + task.getAccessToken().orNull();
|
123
|
+
case API_KEY:
|
124
|
+
return "Basic " + Base64Variants.MIME_NO_LINEFEEDS
|
125
|
+
.encode(("apikey" + ":" + task.getApikey().orNull()).getBytes());
|
126
|
+
default:
|
127
|
+
throw new ConfigException("Not supported method");
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
private TokenHolder createTokenHolder(final HttpClient client)
|
132
|
+
{
|
133
|
+
if (tokenHolder != null) {
|
134
|
+
return tokenHolder;
|
135
|
+
}
|
136
|
+
|
137
|
+
LOG.info("Create new token holder and extract data center");
|
138
|
+
|
139
|
+
if (pluginTask.getAuthMethod() == OAUTH) {
|
140
|
+
try {
|
141
|
+
// Extract data center from meta data URL
|
142
|
+
ContentResponse contentResponse = client.newRequest("https://login.mailchimp.com/oauth2/metadata")
|
143
|
+
.method(GET)
|
144
|
+
.header("Authorization", authorizationHeader)
|
145
|
+
.send();
|
146
|
+
|
147
|
+
if (contentResponse.getStatus() == 200) {
|
148
|
+
ObjectNode objectNode = jsonParser.parseJsonObject(contentResponse.getContentAsString());
|
149
|
+
String endpoint = MessageFormat.format(Joiner.on("/").join("https://{0}.api.mailchimp.com", API_VERSION),
|
150
|
+
objectNode.get("dc").asText());
|
151
|
+
tokenHolder = new TokenHolder(pluginTask.getAccessToken().orNull(), null, endpoint);
|
152
|
+
return tokenHolder;
|
153
|
+
}
|
154
|
+
|
155
|
+
String message = String.format("%s %d %s",
|
156
|
+
contentResponse.getVersion(),
|
157
|
+
contentResponse.getStatus(),
|
158
|
+
contentResponse.getReason());
|
159
|
+
throw new HttpResponseException(message, contentResponse);
|
160
|
+
}
|
161
|
+
catch (Exception ex) {
|
162
|
+
throw new ConfigException("Unable to connect the data center", ex);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
else if (pluginTask.getAuthMethod() == API_KEY) {
|
166
|
+
try {
|
167
|
+
// Authenticate and return data center
|
168
|
+
String domain = pluginTask.getApikey().get().split("-")[1];
|
169
|
+
String endpoint = MessageFormat.format(Joiner.on("/").join("https://{0}.api.mailchimp.com", API_VERSION),
|
170
|
+
domain);
|
171
|
+
ContentResponse contentResponse = client.newRequest(endpoint + "/")
|
172
|
+
.method(GET)
|
173
|
+
.header("Authorization", "Basic " + Base64Variants.MIME_NO_LINEFEEDS
|
174
|
+
.encode(("apikey" + ":" + pluginTask.getApikey().get()).getBytes()))
|
175
|
+
.send();
|
176
|
+
|
177
|
+
if (contentResponse.getStatus() == 200) {
|
178
|
+
tokenHolder = new TokenHolder(null, pluginTask.getApikey().orNull(), endpoint);
|
179
|
+
return tokenHolder;
|
180
|
+
}
|
181
|
+
|
182
|
+
String message = String.format("%s %d %s",
|
183
|
+
contentResponse.getVersion(),
|
184
|
+
contentResponse.getStatus(),
|
185
|
+
contentResponse.getReason());
|
186
|
+
throw new HttpResponseException(message, contentResponse);
|
187
|
+
}
|
188
|
+
catch (Exception ex) {
|
189
|
+
throw new ConfigException("Your API key may be invalid, or you've attempted to access the wrong datacenter.");
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
throw new ConfigException("Not supported auth method");
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
class TokenHolder
|
198
|
+
{
|
199
|
+
private String accessToken;
|
200
|
+
private String apiKey;
|
201
|
+
private String endpoint;
|
202
|
+
|
203
|
+
public TokenHolder(final String accessToken, final String apiKey, final String endpoint)
|
204
|
+
{
|
205
|
+
this.accessToken = accessToken;
|
206
|
+
this.apiKey = apiKey;
|
207
|
+
this.endpoint = endpoint;
|
208
|
+
}
|
209
|
+
|
210
|
+
public String getAccessToken()
|
211
|
+
{
|
212
|
+
return accessToken;
|
213
|
+
}
|
214
|
+
|
215
|
+
public String getApiKey()
|
216
|
+
{
|
217
|
+
return apiKey;
|
218
|
+
}
|
219
|
+
|
220
|
+
public String getEndpoint()
|
221
|
+
{
|
222
|
+
return endpoint;
|
223
|
+
}
|
224
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-output-mailchimp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thang Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -62,11 +62,11 @@ files:
|
|
62
62
|
- gradlew.bat
|
63
63
|
- lib/embulk/output/mailchimp.rb
|
64
64
|
- src/main/java/org/embulk/output/mailchimp/MailChimpClient.java
|
65
|
-
- src/main/java/org/embulk/output/mailchimp/MailChimpHttpClient.java
|
66
65
|
- src/main/java/org/embulk/output/mailchimp/MailChimpOutputPlugin.java
|
67
66
|
- src/main/java/org/embulk/output/mailchimp/MailChimpOutputPluginDelegate.java
|
68
67
|
- src/main/java/org/embulk/output/mailchimp/MailChimpRecordBuffer.java
|
69
68
|
- src/main/java/org/embulk/output/mailchimp/helper/MailChimpHelper.java
|
69
|
+
- src/main/java/org/embulk/output/mailchimp/helper/MailChimpRetryable.java
|
70
70
|
- src/main/java/org/embulk/output/mailchimp/model/AddressMergeFieldAttribute.java
|
71
71
|
- src/main/java/org/embulk/output/mailchimp/model/AuthMethod.java
|
72
72
|
- src/main/java/org/embulk/output/mailchimp/model/CategoriesResponse.java
|
@@ -89,7 +89,7 @@ files:
|
|
89
89
|
- test/override_assert_raise.rb
|
90
90
|
- test/run-test.rb
|
91
91
|
- classpath/jetty-io-9.2.14.v20151106.jar
|
92
|
-
- classpath/embulk-output-mailchimp-0.3.
|
92
|
+
- classpath/embulk-output-mailchimp-0.3.21.jar
|
93
93
|
- classpath/jetty-util-9.2.14.v20151106.jar
|
94
94
|
- classpath/jetty-http-9.2.14.v20151106.jar
|
95
95
|
- classpath/jetty-client-9.2.14.v20151106.jar
|
Binary file
|
@@ -1,200 +0,0 @@
|
|
1
|
-
package org.embulk.output.mailchimp;
|
2
|
-
|
3
|
-
import com.fasterxml.jackson.core.Base64Variants;
|
4
|
-
import com.fasterxml.jackson.core.JsonParser;
|
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.MissingNode;
|
9
|
-
import com.google.common.base.Throwables;
|
10
|
-
import org.eclipse.jetty.client.HttpClient;
|
11
|
-
import org.eclipse.jetty.client.api.Request;
|
12
|
-
import org.eclipse.jetty.client.api.Response;
|
13
|
-
import org.eclipse.jetty.client.util.StringContentProvider;
|
14
|
-
import org.eclipse.jetty.http.HttpMethod;
|
15
|
-
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
16
|
-
import org.embulk.config.ConfigException;
|
17
|
-
import org.embulk.spi.DataException;
|
18
|
-
import org.embulk.spi.Exec;
|
19
|
-
import org.embulk.util.retryhelper.jetty92.Jetty92ClientCreator;
|
20
|
-
import org.embulk.util.retryhelper.jetty92.Jetty92RetryHelper;
|
21
|
-
import org.embulk.util.retryhelper.jetty92.Jetty92SingleRequester;
|
22
|
-
import org.embulk.util.retryhelper.jetty92.StringJetty92ResponseEntityReader;
|
23
|
-
import org.slf4j.Logger;
|
24
|
-
|
25
|
-
import java.io.IOException;
|
26
|
-
import java.util.concurrent.ExecutionException;
|
27
|
-
import java.util.concurrent.TimeUnit;
|
28
|
-
import java.util.concurrent.TimeoutException;
|
29
|
-
|
30
|
-
/**
|
31
|
-
* Created by thangnc on 4/14/17.
|
32
|
-
*/
|
33
|
-
public class MailChimpHttpClient
|
34
|
-
{
|
35
|
-
private static final Logger LOG = Exec.getLogger(MailChimpHttpClient.class);
|
36
|
-
private final ObjectMapper jsonMapper = new ObjectMapper()
|
37
|
-
.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, false)
|
38
|
-
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
39
|
-
|
40
|
-
/**
|
41
|
-
* Instantiates a new Mailchimp http client.
|
42
|
-
*
|
43
|
-
* @param task the task
|
44
|
-
*/
|
45
|
-
public MailChimpHttpClient(MailChimpOutputPluginDelegate.PluginTask task)
|
46
|
-
{
|
47
|
-
}
|
48
|
-
|
49
|
-
public JsonNode sendRequest(final String endpoint, final HttpMethod method,
|
50
|
-
final MailChimpOutputPluginDelegate.PluginTask task)
|
51
|
-
{
|
52
|
-
return sendRequest(endpoint, method, "", task);
|
53
|
-
}
|
54
|
-
|
55
|
-
public JsonNode sendRequest(final String endpoint, final HttpMethod method, final String content,
|
56
|
-
final MailChimpOutputPluginDelegate.PluginTask task)
|
57
|
-
{
|
58
|
-
try (final Jetty92RetryHelper retryHelper = createRetryHelper(task)) {
|
59
|
-
final String authorizationHeader = getAuthorizationHeader(task);
|
60
|
-
|
61
|
-
String responseBody = retryHelper.requestWithRetry(
|
62
|
-
new StringJetty92ResponseEntityReader(task.getTimeoutMillis()),
|
63
|
-
new Jetty92SingleRequester()
|
64
|
-
{
|
65
|
-
@Override
|
66
|
-
public void requestOnce(HttpClient client, Response.Listener responseListener)
|
67
|
-
{
|
68
|
-
Request request = client
|
69
|
-
.newRequest(endpoint)
|
70
|
-
.timeout(task.getTimeoutMillis(), TimeUnit.MILLISECONDS)
|
71
|
-
.accept("application/json")
|
72
|
-
.method(method);
|
73
|
-
if (method == HttpMethod.POST || method == HttpMethod.PUT) {
|
74
|
-
request.content(new StringContentProvider(content), "application/json;utf-8");
|
75
|
-
}
|
76
|
-
|
77
|
-
if (!authorizationHeader.isEmpty()) {
|
78
|
-
request.header("Authorization", authorizationHeader);
|
79
|
-
}
|
80
|
-
request.send(responseListener);
|
81
|
-
}
|
82
|
-
|
83
|
-
@Override
|
84
|
-
public boolean isResponseStatusToRetry(Response response)
|
85
|
-
{
|
86
|
-
int status = response.getStatus();
|
87
|
-
|
88
|
-
return status == 429 || status / 100 != 4;
|
89
|
-
}
|
90
|
-
|
91
|
-
@Override
|
92
|
-
protected boolean isExceptionToRetry(Exception exception)
|
93
|
-
{
|
94
|
-
LOG.error("Exception to retry.", exception);
|
95
|
-
// This check is to make sure the exception is retryable, e.g: server not found, internal server error...
|
96
|
-
if (exception instanceof ConfigException || exception instanceof ExecutionException) {
|
97
|
-
return toRetry((Exception) exception.getCause());
|
98
|
-
}
|
99
|
-
|
100
|
-
return exception instanceof TimeoutException || super.isExceptionToRetry(exception);
|
101
|
-
}
|
102
|
-
});
|
103
|
-
|
104
|
-
return responseBody != null && !responseBody.isEmpty() ? parseJson(responseBody) : MissingNode.getInstance();
|
105
|
-
}
|
106
|
-
catch (Exception ex) {
|
107
|
-
LOG.error("Exception occurred while sending request.", ex);
|
108
|
-
throw Throwables.propagate(ex);
|
109
|
-
}
|
110
|
-
}
|
111
|
-
|
112
|
-
private JsonNode parseJson(final String json)
|
113
|
-
throws DataException
|
114
|
-
{
|
115
|
-
try {
|
116
|
-
return this.jsonMapper.readTree(json);
|
117
|
-
}
|
118
|
-
catch (IOException ex) {
|
119
|
-
// Try to parse invalid json before throwing exception
|
120
|
-
return parseInvalidJsonString(json);
|
121
|
-
}
|
122
|
-
}
|
123
|
-
|
124
|
-
// Sometimes, the MailChimp API returns invalid JSON when we pushed a large of data. ObjectMapper can not read string json.
|
125
|
-
// So we have to use this method to parse string and build a json string as ReportResponse
|
126
|
-
// E.g invalid json response from MailChimp https://gist.github.com/thangnc/dc94026e4b13b728b7303f402b458b05
|
127
|
-
private JsonNode parseInvalidJsonString(final String json)
|
128
|
-
{
|
129
|
-
int totalCreatedIndex = json.indexOf("\"total_created\"");
|
130
|
-
int totalUpdatedIndex = json.indexOf("\"total_updated\"");
|
131
|
-
int errorCountIndex = json.indexOf("\"error_count\"");
|
132
|
-
int errorsIndex = json.indexOf("\"errors\"");
|
133
|
-
|
134
|
-
StringBuilder validJson = new StringBuilder();
|
135
|
-
validJson.append("{").append(json.substring(errorsIndex, totalCreatedIndex - 1)).append(",");
|
136
|
-
validJson.append(json.substring(totalCreatedIndex, totalCreatedIndex + "\"total_created\"".length() + 2)).append(",");
|
137
|
-
validJson.append(json.substring(totalUpdatedIndex, totalUpdatedIndex + "\"total_updated\"".length() + 2)).append(",");
|
138
|
-
validJson.append(json.substring(errorCountIndex, errorCountIndex + "\"error_count\"".length() + 2)).append("}");
|
139
|
-
|
140
|
-
try {
|
141
|
-
return this.jsonMapper.readTree(validJson.toString());
|
142
|
-
}
|
143
|
-
catch (IOException ex) {
|
144
|
-
throw new DataException(ex);
|
145
|
-
}
|
146
|
-
}
|
147
|
-
|
148
|
-
public void avoidFlushAPI(String reason)
|
149
|
-
{
|
150
|
-
try {
|
151
|
-
LOG.info("{} in 5s...", reason);
|
152
|
-
Thread.sleep(5000);
|
153
|
-
}
|
154
|
-
catch (InterruptedException e) {
|
155
|
-
LOG.warn("Failed to sleep: {}", e.getMessage());
|
156
|
-
}
|
157
|
-
}
|
158
|
-
|
159
|
-
/**
|
160
|
-
* MailChimp API v3 supports non expires access_token. Then no need refresh_token
|
161
|
-
*
|
162
|
-
* @param task
|
163
|
-
* @return
|
164
|
-
*/
|
165
|
-
private String getAuthorizationHeader(final MailChimpOutputPluginDelegate.PluginTask task)
|
166
|
-
{
|
167
|
-
switch (task.getAuthMethod()) {
|
168
|
-
case OAUTH:
|
169
|
-
return "OAuth " + task.getAccessToken().orNull();
|
170
|
-
case API_KEY:
|
171
|
-
return "Basic " + Base64Variants.MIME_NO_LINEFEEDS
|
172
|
-
.encode(("apikey" + ":" + task.getApikey().orNull()).getBytes());
|
173
|
-
default:
|
174
|
-
throw new ConfigException("Not supported method");
|
175
|
-
}
|
176
|
-
}
|
177
|
-
|
178
|
-
private Jetty92RetryHelper createRetryHelper(MailChimpOutputPluginDelegate.PluginTask task)
|
179
|
-
{
|
180
|
-
return new Jetty92RetryHelper(
|
181
|
-
task.getMaximumRetries(),
|
182
|
-
task.getInitialRetryIntervalMillis(),
|
183
|
-
task.getMaximumRetryIntervalMillis(),
|
184
|
-
new Jetty92ClientCreator()
|
185
|
-
{
|
186
|
-
@Override
|
187
|
-
public HttpClient createAndStart()
|
188
|
-
{
|
189
|
-
HttpClient client = new HttpClient(new SslContextFactory());
|
190
|
-
try {
|
191
|
-
client.start();
|
192
|
-
return client;
|
193
|
-
}
|
194
|
-
catch (Exception e) {
|
195
|
-
throw Throwables.propagate(e);
|
196
|
-
}
|
197
|
-
}
|
198
|
-
});
|
199
|
-
}
|
200
|
-
}
|