embulk-input-marketo-through-proxy 0.6.20

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +37 -0
  4. data/.github/workflows/build.yml +38 -0
  5. data/.gitignore +14 -0
  6. data/CHANGELOG.md +178 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +231 -0
  9. data/build.gradle +105 -0
  10. data/config/checkstyle/checkstyle.xml +128 -0
  11. data/config/checkstyle/default.xml +108 -0
  12. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  13. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  14. data/gradlew +169 -0
  15. data/gradlew.bat +84 -0
  16. data/lib/embulk/input/marketo.rb +3 -0
  17. data/settings.gradle +1 -0
  18. data/src/main/java/org/embulk/input/marketo/CsvTokenizer.java +695 -0
  19. data/src/main/java/org/embulk/input/marketo/MarketoInputPlugin.java +15 -0
  20. data/src/main/java/org/embulk/input/marketo/MarketoInputPluginDelegate.java +100 -0
  21. data/src/main/java/org/embulk/input/marketo/MarketoService.java +47 -0
  22. data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +258 -0
  23. data/src/main/java/org/embulk/input/marketo/MarketoUtils.java +212 -0
  24. data/src/main/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPlugin.java +169 -0
  25. data/src/main/java/org/embulk/input/marketo/delegate/CampaignInputPlugin.java +48 -0
  26. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectInputPlugin.java +124 -0
  27. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectResponseMapperBuilder.java +81 -0
  28. data/src/main/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPlugin.java +68 -0
  29. data/src/main/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilder.java +85 -0
  30. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithListInputPlugin.java +89 -0
  31. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPlugin.java +85 -0
  32. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +448 -0
  33. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java +160 -0
  34. data/src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java +234 -0
  35. data/src/main/java/org/embulk/input/marketo/exception/MarketoAPIException.java +30 -0
  36. data/src/main/java/org/embulk/input/marketo/model/BulkExtractRangeHeader.java +26 -0
  37. data/src/main/java/org/embulk/input/marketo/model/MarketoAccessTokenResponse.java +92 -0
  38. data/src/main/java/org/embulk/input/marketo/model/MarketoBulkExtractRequest.java +68 -0
  39. data/src/main/java/org/embulk/input/marketo/model/MarketoError.java +40 -0
  40. data/src/main/java/org/embulk/input/marketo/model/MarketoField.java +126 -0
  41. data/src/main/java/org/embulk/input/marketo/model/MarketoResponse.java +82 -0
  42. data/src/main/java/org/embulk/input/marketo/model/filter/DateRangeFilter.java +40 -0
  43. data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +344 -0
  44. data/src/main/java/org/embulk/input/marketo/rest/MarketoInputStreamResponseEntityReader.java +69 -0
  45. data/src/main/java/org/embulk/input/marketo/rest/MarketoRESTEndpoint.java +47 -0
  46. data/src/main/java/org/embulk/input/marketo/rest/MarketoResponseJetty92EntityReader.java +89 -0
  47. data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +601 -0
  48. data/src/main/java/org/embulk/input/marketo/rest/RecordPagingIterable.java +180 -0
  49. data/src/test/java/org/embulk/input/marketo/MarketoServiceImplTest.java +147 -0
  50. data/src/test/java/org/embulk/input/marketo/MarketoUtilsTest.java +89 -0
  51. data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +129 -0
  52. data/src/test/java/org/embulk/input/marketo/delegate/CampaignInputPluginTest.java +73 -0
  53. data/src/test/java/org/embulk/input/marketo/delegate/CustomObjectInputPluginTest.java +175 -0
  54. data/src/test/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPluginTest.java +102 -0
  55. data/src/test/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilderTest.java +119 -0
  56. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithListInputPluginTest.java +132 -0
  57. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPluginTest.java +134 -0
  58. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +171 -0
  59. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegateTest.java +60 -0
  60. data/src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java +325 -0
  61. data/src/test/java/org/embulk/input/marketo/rest/MarketoBaseRestClientTest.java +368 -0
  62. data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +649 -0
  63. data/src/test/resources/config/activity_bulk_extract_config.yaml +7 -0
  64. data/src/test/resources/config/custom_object_config.yaml +8 -0
  65. data/src/test/resources/config/lead_bulk_extract_config.yaml +8 -0
  66. data/src/test/resources/config/rest_config.yaml +3 -0
  67. data/src/test/resources/fixtures/activity_extract1.csv +35 -0
  68. data/src/test/resources/fixtures/activity_extract2.csv +22 -0
  69. data/src/test/resources/fixtures/activity_types.json +22 -0
  70. data/src/test/resources/fixtures/all_program_full.json +53 -0
  71. data/src/test/resources/fixtures/campaign_response.json +38 -0
  72. data/src/test/resources/fixtures/campaign_response_full.json +102 -0
  73. data/src/test/resources/fixtures/custom_object_describe.json +124 -0
  74. data/src/test/resources/fixtures/custom_object_describe_marketo_fields_full.json +22 -0
  75. data/src/test/resources/fixtures/custom_object_expected.json +66 -0
  76. data/src/test/resources/fixtures/custom_object_response.json +24 -0
  77. data/src/test/resources/fixtures/custom_object_response_full.json +23 -0
  78. data/src/test/resources/fixtures/lead_by_list.json +33 -0
  79. data/src/test/resources/fixtures/lead_by_program_response.json +47 -0
  80. data/src/test/resources/fixtures/lead_describe.json +221 -0
  81. data/src/test/resources/fixtures/lead_describe_expected.json +66 -0
  82. data/src/test/resources/fixtures/lead_describe_marketo_fields_full.json +518 -0
  83. data/src/test/resources/fixtures/lead_extract1.csv +11 -0
  84. data/src/test/resources/fixtures/lead_response_full.json +2402 -0
  85. data/src/test/resources/fixtures/lead_with_program_full.json +17 -0
  86. data/src/test/resources/fixtures/leads_extract2.csv +10 -0
  87. data/src/test/resources/fixtures/list_reponse_full.json +191 -0
  88. data/src/test/resources/fixtures/lists_response.json +31 -0
  89. data/src/test/resources/fixtures/program_response.json +71 -0
  90. metadata +173 -0
@@ -0,0 +1,15 @@
1
+ package org.embulk.input.marketo;
2
+
3
+ import org.embulk.base.restclient.RestClientInputPluginBase;
4
+
5
+ /**
6
+ * Created by tai.khuu on 8/22/17.
7
+ */
8
+ public class MarketoInputPlugin
9
+ extends RestClientInputPluginBase<MarketoInputPluginDelegate.PluginTask>
10
+ {
11
+ public MarketoInputPlugin()
12
+ {
13
+ super(MarketoInputPluginDelegate.PluginTask.class, new MarketoInputPluginDelegate());
14
+ }
15
+ }
@@ -0,0 +1,100 @@
1
+ package org.embulk.input.marketo;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import com.fasterxml.jackson.annotation.JsonIgnore;
5
+ import com.google.common.base.Optional;
6
+
7
+ import org.embulk.base.restclient.DispatchingRestClientInputPluginDelegate;
8
+ import org.embulk.base.restclient.RestClientInputPluginDelegate;
9
+ import org.embulk.config.Config;
10
+ import org.embulk.config.ConfigDefault;
11
+ import org.embulk.config.ConfigException;
12
+ import org.embulk.input.marketo.delegate.ActivityBulkExtractInputPlugin;
13
+ import org.embulk.input.marketo.delegate.CampaignInputPlugin;
14
+ import org.embulk.input.marketo.delegate.CustomObjectInputPlugin;
15
+ import org.embulk.input.marketo.delegate.LeadBulkExtractInputPlugin;
16
+ import org.embulk.input.marketo.delegate.LeadWithListInputPlugin;
17
+ import org.embulk.input.marketo.delegate.LeadWithProgramInputPlugin;
18
+ import org.embulk.input.marketo.delegate.ProgramInputPlugin;
19
+ import org.embulk.input.marketo.rest.MarketoRestClient;
20
+
21
+ import java.util.Date;
22
+
23
+ public class MarketoInputPluginDelegate
24
+ extends DispatchingRestClientInputPluginDelegate<MarketoInputPluginDelegate.PluginTask>
25
+ {
26
+ public interface PluginTask
27
+ extends LeadWithListInputPlugin.PluginTask,
28
+ LeadBulkExtractInputPlugin.PluginTask,
29
+ LeadWithProgramInputPlugin.PluginTask,
30
+ ActivityBulkExtractInputPlugin.PluginTask,
31
+ CampaignInputPlugin.PluginTask,
32
+ ProgramInputPlugin.PluginTask,
33
+ MarketoRestClient.PluginTask,
34
+ CustomObjectInputPlugin.PluginTask
35
+ {
36
+ @Config("target")
37
+ Target getTarget();
38
+
39
+ //We don't need to let the internal plugin know that it being dispatched and force it to set require field optional
40
+ //We will hide the real from_date, and set it when validating task
41
+ @Config("hidden_from_date")
42
+ @ConfigDefault("\"1970-01-01\"")
43
+ @Override
44
+ Date getFromDate();
45
+
46
+ void setFromDate(Date fromDate);
47
+
48
+ @Config("from_date")
49
+ @ConfigDefault("null")
50
+ Optional<Date> getWrappedFromDate();
51
+ }
52
+
53
+ @SuppressWarnings("unchecked")
54
+ @Override
55
+ protected RestClientInputPluginDelegate dispatchPerTask(PluginTask task)
56
+ {
57
+ Target target = task.getTarget();
58
+ switch (target) {
59
+ case LEAD:
60
+ case ACTIVITY:
61
+ if (!task.getWrappedFromDate().isPresent()) {
62
+ throw new ConfigException("From date is required for target LEAD or ACTIVITY");
63
+ }
64
+ Date date = task.getWrappedFromDate().get();
65
+ task.setFromDate(date);
66
+ break;
67
+ }
68
+ return target.getRestClientInputPluginDelegate();
69
+ }
70
+
71
+ public enum Target
72
+ {
73
+ LEAD(new LeadBulkExtractInputPlugin()),
74
+ ACTIVITY(new ActivityBulkExtractInputPlugin()),
75
+ CAMPAIGN(new CampaignInputPlugin()),
76
+ ALL_LEAD_WITH_LIST_ID(new LeadWithListInputPlugin()),
77
+ ALL_LEAD_WITH_PROGRAM_ID(new LeadWithProgramInputPlugin()),
78
+ PROGRAM(new ProgramInputPlugin()),
79
+ CUSTOM_OBJECT(new CustomObjectInputPlugin());
80
+
81
+ private RestClientInputPluginDelegate restClientInputPluginDelegate;
82
+
83
+ Target(RestClientInputPluginDelegate restClientInputPluginDelegate)
84
+ {
85
+ this.restClientInputPluginDelegate = restClientInputPluginDelegate;
86
+ }
87
+
88
+ @JsonIgnore
89
+ public RestClientInputPluginDelegate getRestClientInputPluginDelegate()
90
+ {
91
+ return restClientInputPluginDelegate;
92
+ }
93
+
94
+ @JsonCreator
95
+ public static Target of(String value)
96
+ {
97
+ return Target.valueOf(value.toUpperCase());
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,47 @@
1
+ package org.embulk.input.marketo;
2
+
3
+ import com.fasterxml.jackson.databind.node.ObjectNode;
4
+ import org.embulk.input.marketo.model.MarketoField;
5
+
6
+ import java.io.File;
7
+ import java.util.Date;
8
+ import java.util.List;
9
+ import java.util.Set;
10
+
11
+ /**
12
+ * Created by tai.khuu on 9/6/17.
13
+ */
14
+ public interface MarketoService
15
+ {
16
+ List<MarketoField> describeLead();
17
+
18
+ File extractLead(Date startTime, Date endTime, List<String> extractedFields, String filterField, int pollingTimeIntervalSecond, int bulkJobTimeoutSecond);
19
+
20
+ File extractAllActivity(List<Integer> activityTypeIds, Date startTime, Date endTime, int pollingTimeIntervalSecond, int bulkJobTimeoutSecond);
21
+
22
+ Iterable<ObjectNode> getAllListLead(List<String> extractFields, Iterable<ObjectNode> inputListIds);
23
+
24
+ Iterable<ObjectNode> getAllProgramLead(List<String> extractFields, Iterable<ObjectNode> requestProgs);
25
+
26
+ Iterable<ObjectNode> getCampaign();
27
+
28
+ Iterable<ObjectNode> getPrograms();
29
+
30
+ Iterable<ObjectNode> getProgramsByIds(Set<String> ids);
31
+
32
+ Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue);
33
+
34
+ Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues);
35
+
36
+ Iterable<ObjectNode> getCustomObject(String customObjectApiName, String filterType, Set<String> filterValues, String returnFields);
37
+
38
+ Iterable<ObjectNode> getCustomObject(String customObjectAPIName, String customObjectFilterType, String customObjectFields, Integer fromValue, Integer toValue);
39
+
40
+ List<MarketoField> describeCustomObject(String customObjectAPIName);
41
+
42
+ Iterable<ObjectNode> getActivityTypes();
43
+
44
+ Iterable<ObjectNode> getListsByIds(Set<String> ids);
45
+
46
+ Iterable<ObjectNode> getLists();
47
+ }
@@ -0,0 +1,258 @@
1
+ package org.embulk.input.marketo;
2
+
3
+ import com.fasterxml.jackson.databind.node.ObjectNode;
4
+ import com.google.common.base.Function;
5
+ import com.google.common.collect.Iterables;
6
+ import com.google.common.collect.Lists;
7
+ import org.apache.commons.lang3.StringUtils;
8
+ import org.embulk.input.marketo.model.BulkExtractRangeHeader;
9
+ import org.embulk.input.marketo.model.MarketoField;
10
+ import org.embulk.input.marketo.rest.MarketoRestClient;
11
+ import org.embulk.input.marketo.rest.RecordPagingIterable;
12
+ import org.embulk.spi.DataException;
13
+ import org.embulk.spi.Exec;
14
+ import org.slf4j.Logger;
15
+
16
+ import java.io.File;
17
+ import java.io.FileOutputStream;
18
+ import java.io.IOException;
19
+ import java.io.InputStream;
20
+ import java.io.OutputStream;
21
+ import java.util.Date;
22
+ import java.util.List;
23
+ import java.util.Set;
24
+ import java.util.stream.Collectors;
25
+ import java.util.stream.IntStream;
26
+
27
+ /**
28
+ * Created by tai.khuu on 9/6/17.
29
+ */
30
+ public class MarketoServiceImpl implements MarketoService
31
+ {
32
+ private static final Logger LOGGER = Exec.getLogger(MarketoServiceImpl.class);
33
+
34
+ private static final String DEFAULT_FILE_FORMAT = "csv";
35
+
36
+ private static final int BUF_SIZE = 0x1000;
37
+
38
+ private static final int MAX_RESUME_TIME = 50;
39
+
40
+ private MarketoRestClient marketoRestClient;
41
+
42
+ public MarketoServiceImpl(MarketoRestClient marketoRestClient)
43
+ {
44
+ this.marketoRestClient = marketoRestClient;
45
+ }
46
+
47
+ @Override
48
+ public File extractLead(final Date startTime, Date endTime, List<String> extractedFields, String filterField, int pollingTimeIntervalSecond, final int bulkJobTimeoutSecond)
49
+ {
50
+ final String exportID = marketoRestClient.createLeadBulkExtract(startTime, endTime, extractedFields, filterField);
51
+ marketoRestClient.startLeadBulkExtract(exportID);
52
+ try {
53
+ marketoRestClient.waitLeadExportJobComplete(exportID, pollingTimeIntervalSecond, bulkJobTimeoutSecond);
54
+ }
55
+ catch (InterruptedException e) {
56
+ LOGGER.error("Exception when waiting for export job id: {}", exportID, e);
57
+ throw new DataException("Error when wait for bulk extract");
58
+ }
59
+ return downloadBulkExtract(new Function<BulkExtractRangeHeader, InputStream>()
60
+ {
61
+ @Override
62
+ public InputStream apply(BulkExtractRangeHeader bulkExtractRangeHeader)
63
+ {
64
+ return marketoRestClient.getLeadBulkExtractResult(exportID, bulkExtractRangeHeader);
65
+ }
66
+ });
67
+ }
68
+
69
+ private long saveExtractedFile(InputStream extractResult, File tempFile) throws DownloadBulkExtractException
70
+ {
71
+ long total = 0;
72
+ try (OutputStream fileOuputStream = new FileOutputStream(tempFile, true)) {
73
+ byte[] buf = new byte[BUF_SIZE];
74
+ while (true) {
75
+ int r = extractResult.read(buf);
76
+ if (r == -1) {
77
+ break;
78
+ }
79
+ fileOuputStream.write(buf, 0, r);
80
+ total += r;
81
+ }
82
+ }
83
+ catch (IOException e) {
84
+ LOGGER.error("Encounter exception when download bulk extract file", e);
85
+ throw new DownloadBulkExtractException("Encounter exception when download bulk extract file", e, total);
86
+ }
87
+ return total;
88
+ }
89
+
90
+ @Override
91
+ public File extractAllActivity(List<Integer> activityTypeIds, Date startTime, Date endTime, int pollingTimeIntervalSecond, int bulkJobTimeoutSecond)
92
+ {
93
+ final String exportID = marketoRestClient.createActivityExtract(activityTypeIds, startTime, endTime);
94
+ marketoRestClient.startActitvityBulkExtract(exportID);
95
+ try {
96
+ marketoRestClient.waitActitvityExportJobComplete(exportID, pollingTimeIntervalSecond, bulkJobTimeoutSecond);
97
+ }
98
+ catch (InterruptedException e) {
99
+ LOGGER.error("Exception when waiting for export job id: {}", exportID, e);
100
+ throw new DataException("Error when wait for bulk extract");
101
+ }
102
+ return downloadBulkExtract(new Function<BulkExtractRangeHeader, InputStream>()
103
+ {
104
+ @Override
105
+ public InputStream apply(BulkExtractRangeHeader bulkExtractRangeHeader)
106
+ {
107
+ return marketoRestClient.getActivitiesBulkExtractResult(exportID, bulkExtractRangeHeader);
108
+ }
109
+ });
110
+ }
111
+
112
+ private File downloadBulkExtract(Function<BulkExtractRangeHeader, InputStream> getBulkExtractfunction)
113
+ {
114
+ final File tempFile = Exec.getTempFileSpace().createTempFile(DEFAULT_FILE_FORMAT);
115
+ long startByte = 0;
116
+ int resumeTime = 0;
117
+ while (resumeTime < MAX_RESUME_TIME) {
118
+ BulkExtractRangeHeader bulkExtractRangeHeader = new BulkExtractRangeHeader(startByte);
119
+ InputStream bulkExtractResult = getBulkExtractfunction.apply(bulkExtractRangeHeader);
120
+ try {
121
+ saveExtractedFile(bulkExtractResult, tempFile);
122
+ return tempFile;
123
+ }
124
+ catch (DownloadBulkExtractException e) {
125
+ startByte = startByte + e.getByteWritten();
126
+ LOGGER.warn("will resume activity bulk extract at byte [{}]", startByte);
127
+ }
128
+ resumeTime = resumeTime + 1;
129
+ }
130
+ //Too many resume we still can't get the file
131
+ throw new DataException("Can't down load bulk extract");
132
+ }
133
+
134
+ @Override
135
+ public Iterable<ObjectNode> getAllListLead(List<String> fieldNames, Iterable<ObjectNode> inputListIds)
136
+ {
137
+ final String fieldNameString = StringUtils.join(fieldNames, ",");
138
+ return MarketoUtils.flatMap(inputListIds, input -> {
139
+ final String id = input.get("id").asText();
140
+ return Iterables.transform(marketoRestClient.getLeadsByList(id, fieldNameString), input1 -> input1.put(MarketoUtils.LIST_ID_COLUMN_NAME, id));
141
+ });
142
+ }
143
+
144
+ @Override
145
+ public Iterable<ObjectNode> getAllProgramLead(List<String> fieldNames, Iterable<ObjectNode> requestProgs)
146
+ {
147
+ final String fieldNameString = StringUtils.join(fieldNames, ",");
148
+ return MarketoUtils.flatMap(requestProgs, input -> {
149
+ final String id = input.get("id").asText();
150
+ return Iterables.transform(marketoRestClient.getLeadsByProgram(id, fieldNameString), input1 -> input1.put(MarketoUtils.PROGRAM_ID_COLUMN_NAME, id));
151
+ });
152
+ }
153
+
154
+ @Override
155
+ public RecordPagingIterable<ObjectNode> getCampaign()
156
+ {
157
+ return marketoRestClient.getCampaign();
158
+ }
159
+
160
+ @Override
161
+ public List<MarketoField> describeLead()
162
+ {
163
+ return marketoRestClient.describeLead();
164
+ }
165
+
166
+ private static class DownloadBulkExtractException extends Exception
167
+ {
168
+ private final long byteWritten;
169
+
170
+ public DownloadBulkExtractException(String message, Throwable cause, long byteWritten)
171
+ {
172
+ super(message, cause);
173
+ this.byteWritten = byteWritten;
174
+ }
175
+
176
+ public DownloadBulkExtractException(Throwable cause, long byteWritten)
177
+ {
178
+ super(cause);
179
+ this.byteWritten = byteWritten;
180
+ }
181
+
182
+ public long getByteWritten()
183
+ {
184
+ return byteWritten;
185
+ }
186
+ }
187
+
188
+ @Override
189
+ public Iterable<ObjectNode> getPrograms()
190
+ {
191
+ return marketoRestClient.getPrograms();
192
+ }
193
+
194
+ @Override
195
+ public Iterable<ObjectNode> getProgramsByIds(Set<String> ids)
196
+ {
197
+ return marketoRestClient.getProgramsByIds(ids);
198
+ }
199
+
200
+ @Override
201
+ public Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue)
202
+ {
203
+ return marketoRestClient.getProgramsByTag(tagType, tagValue);
204
+ }
205
+
206
+ @Override
207
+ public Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues)
208
+ {
209
+ return marketoRestClient.getProgramsByDateRange(earliestUpdatedAt, latestUpdatedAt, filterType, filterValues);
210
+ }
211
+
212
+ @Override
213
+ public List<MarketoField> describeCustomObject(String customObjectAPIName)
214
+ {
215
+ return marketoRestClient.describeCustomObject(customObjectAPIName);
216
+ }
217
+
218
+ @Override
219
+ public Iterable<ObjectNode> getCustomObject(String customObjectAPIName, String customObjectFilterType, String customObjectFields, Integer fromValue, Integer toValue)
220
+ {
221
+ if (toValue == null) {
222
+ return marketoRestClient.getCustomObject(customObjectAPIName, customObjectFilterType, customObjectFields, fromValue, toValue);
223
+ }
224
+
225
+ // make sure to import values in the whole range
226
+ Set<String> filterValues = IntStream.rangeClosed(fromValue, toValue.intValue()).mapToObj(String::valueOf).collect(Collectors.toSet());
227
+ return getCustomObject(customObjectAPIName, customObjectFilterType, filterValues, customObjectFields);
228
+ }
229
+
230
+ @Override
231
+ public Iterable<ObjectNode> getCustomObject(String customObjectApiName, String filterType, Set<String> filterValues, String returnFields)
232
+ {
233
+ // Marketo allows maximum 300 (comma separated) filterValues per request. Split the input and process by chunk.
234
+ List<List<String>> partitionedFilterValues = Lists.partition(Lists.newArrayList(filterValues), 300);
235
+ return MarketoUtils.flatMap(partitionedFilterValues, (part) -> {
236
+ String strFilterValues = StringUtils.join(part, ",");
237
+ return marketoRestClient.getCustomObject(customObjectApiName, filterType, strFilterValues, returnFields);
238
+ });
239
+ }
240
+
241
+ @Override
242
+ public Iterable<ObjectNode> getActivityTypes()
243
+ {
244
+ return marketoRestClient.getActivityTypes();
245
+ }
246
+
247
+ @Override
248
+ public Iterable<ObjectNode> getListsByIds(Set<String> ids)
249
+ {
250
+ return marketoRestClient.getListsByIds(ids);
251
+ }
252
+
253
+ @Override
254
+ public Iterable<ObjectNode> getLists()
255
+ {
256
+ return marketoRestClient.getLists();
257
+ }
258
+ }
@@ -0,0 +1,212 @@
1
+ package org.embulk.input.marketo;
2
+
3
+ import com.fasterxml.jackson.databind.ObjectMapper;
4
+ import com.fasterxml.jackson.databind.node.ObjectNode;
5
+ import com.google.common.base.Function;
6
+ import com.google.common.collect.Sets;
7
+ import org.embulk.base.restclient.ServiceResponseMapper;
8
+ import org.embulk.base.restclient.jackson.JacksonServiceRecord;
9
+ import org.embulk.base.restclient.jackson.JacksonServiceResponseMapper;
10
+ import org.embulk.base.restclient.jackson.JacksonTopLevelValueLocator;
11
+ import org.embulk.base.restclient.record.ServiceRecord;
12
+ import org.embulk.base.restclient.record.ValueLocator;
13
+ import org.embulk.input.marketo.model.MarketoField;
14
+ import org.embulk.spi.Exec;
15
+ import org.embulk.spi.util.RetryExecutor;
16
+ import org.slf4j.Logger;
17
+
18
+ import javax.annotation.Nullable;
19
+
20
+ import java.time.OffsetDateTime;
21
+ import java.util.ArrayList;
22
+ import java.util.Iterator;
23
+ import java.util.List;
24
+ import java.util.NoSuchElementException;
25
+ import java.util.Set;
26
+
27
+ /**
28
+ * Created by tai.khuu on 9/18/17.
29
+ */
30
+ public class MarketoUtils
31
+ {
32
+ public static final String MARKETO_DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z";
33
+ public static final String MARKETO_DATE_FORMAT = "%Y-%m-%d";
34
+ public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
35
+ public static final Function<ObjectNode, ServiceRecord> TRANSFORM_OBJECT_TO_JACKSON_SERVICE_RECORD_FUNCTION = new Function<ObjectNode, ServiceRecord>()
36
+ {
37
+ @Nullable
38
+ @Override
39
+ public JacksonServiceRecord apply(@Nullable ObjectNode input)
40
+ {
41
+ return new JacksonServiceRecord(input);
42
+ }
43
+ };
44
+
45
+ public static final String MARKETO_DATE_SIMPLE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
46
+
47
+ public static final String LIST_ID_COLUMN_NAME = "listId";
48
+
49
+ public static final String PROGRAM_ID_COLUMN_NAME = "programId";
50
+
51
+ private MarketoUtils()
52
+ {
53
+ }
54
+
55
+ public static ServiceResponseMapper<? extends ValueLocator> buildDynamicResponseMapper(String prefix, List<MarketoField> columns)
56
+ {
57
+ JacksonServiceResponseMapper.Builder builder = JacksonServiceResponseMapper.builder();
58
+ for (MarketoField column : columns) {
59
+ String columName = buildColumnName(prefix, column.getName());
60
+ MarketoField.MarketoDataType marketoDataType = column.getMarketoDataType();
61
+ if (marketoDataType.getFormat().isPresent()) {
62
+ builder.add(new JacksonTopLevelValueLocator(column.getName()), columName, marketoDataType.getType(), marketoDataType.getFormat().get());
63
+ }
64
+ else {
65
+ builder.add(new JacksonTopLevelValueLocator(column.getName()), columName, marketoDataType.getType());
66
+ }
67
+ }
68
+ return builder.build();
69
+ }
70
+
71
+ public static List<String> getFieldNameFromMarketoFields(List<MarketoField> columns, String... excludedFields)
72
+ {
73
+ Set<String> excludeFields = Sets.newHashSet(excludedFields);
74
+ List<String> extractedFields = new ArrayList<>();
75
+ for (MarketoField column : columns) {
76
+ if (excludeFields.contains(column.getName())) {
77
+ continue;
78
+ }
79
+ extractedFields.add(column.getName());
80
+ }
81
+ return extractedFields;
82
+ }
83
+
84
+ public static String buildColumnName(String prefix, String columnName)
85
+ {
86
+ return prefix + "_" + columnName;
87
+ }
88
+
89
+ public static final List<DateRange> sliceRange(OffsetDateTime fromDate, OffsetDateTime toDate, int rangeSize)
90
+ {
91
+ List<DateRange> ranges = new ArrayList<>();
92
+ while (fromDate.isBefore(toDate)) {
93
+ OffsetDateTime nextToDate = fromDate.plusDays(rangeSize);
94
+ if (nextToDate.isAfter(toDate)) {
95
+ ranges.add(new DateRange(fromDate, toDate));
96
+ break;
97
+ }
98
+ ranges.add(new DateRange(fromDate, nextToDate));
99
+ fromDate = nextToDate.plusSeconds(1);
100
+ }
101
+ return ranges;
102
+ }
103
+
104
+ public static String getIdentityEndPoint(String accountId)
105
+ {
106
+ return "https://" + accountId + ".mktorest.com/identity";
107
+ }
108
+
109
+ public static String getEndPoint(String accountID)
110
+ {
111
+ return "https://" + accountID + ".mktorest.com";
112
+ }
113
+
114
+ public static final class DateRange
115
+ {
116
+ public final OffsetDateTime fromDate;
117
+ public final OffsetDateTime toDate;
118
+
119
+ public DateRange(OffsetDateTime fromDate, OffsetDateTime toDate)
120
+ {
121
+ this.fromDate = fromDate;
122
+ this.toDate = toDate;
123
+ }
124
+
125
+ @Override
126
+ public String toString()
127
+ {
128
+ return "DateRange{" +
129
+ "fromDate=" + fromDate +
130
+ ", toDate=" + toDate +
131
+ '}';
132
+ }
133
+ }
134
+
135
+ public static <T> T executeWithRetry(int maximumRetries, int initialRetryIntervalMillis, int maximumRetryIntervalMillis, AlwaysRetryRetryable<T> alwaysRetryRetryable) throws RetryExecutor.RetryGiveupException, InterruptedException
136
+ {
137
+ return RetryExecutor
138
+ .retryExecutor()
139
+ .withRetryLimit(maximumRetries)
140
+ .withInitialRetryWait(initialRetryIntervalMillis)
141
+ .withMaxRetryWait(maximumRetryIntervalMillis)
142
+ .runInterruptible(alwaysRetryRetryable);
143
+ }
144
+
145
+ public abstract static class AlwaysRetryRetryable<T> implements RetryExecutor.Retryable<T>
146
+ {
147
+ private static final Logger LOGGER = Exec.getLogger(AlwaysRetryRetryable.class);
148
+
149
+ @Override
150
+ public abstract T call() throws Exception;
151
+
152
+ @Override
153
+ public boolean isRetryableException(Exception exception)
154
+ {
155
+ return true;
156
+ }
157
+
158
+ @Override
159
+ public void onRetry(Exception exception, int retryCount, int retryLimit, int retryWait) throws RetryExecutor.RetryGiveupException
160
+ {
161
+ LOGGER.info("Retry [{}]/[{}] with retryWait [{}] on exception {}", retryCount, retryLimit, retryWait, exception.getMessage());
162
+ }
163
+
164
+ @Override
165
+ public void onGiveup(Exception firstException, Exception lastException) throws RetryExecutor.RetryGiveupException
166
+ {
167
+ LOGGER.info("Giving up execution on exception", lastException);
168
+ }
169
+ }
170
+ public static <T, R> Iterable<R> flatMap(final Iterable<T> iterable, final Function<T, Iterable<R>> function)
171
+ {
172
+ final Iterator<T> iterator = iterable.iterator();
173
+ return new Iterable<R>()
174
+ {
175
+ @Override
176
+ public Iterator<R> iterator()
177
+ {
178
+ return new Iterator<R>()
179
+ {
180
+ Iterator<R> currentIterator;
181
+ @Override
182
+ public boolean hasNext()
183
+ {
184
+ if (currentIterator != null && currentIterator.hasNext()) {
185
+ return true;
186
+ }
187
+ while (iterator.hasNext()) {
188
+ currentIterator = function.apply(iterator.next()).iterator();
189
+ if (currentIterator.hasNext()) {
190
+ return true;
191
+ }
192
+ }
193
+ return false;
194
+ }
195
+ @Override
196
+ public R next()
197
+ {
198
+ if (hasNext()) {
199
+ return currentIterator.next();
200
+ }
201
+ throw new NoSuchElementException();
202
+ }
203
+ @Override
204
+ public void remove()
205
+ {
206
+ throw new UnsupportedOperationException();
207
+ }
208
+ };
209
+ }
210
+ };
211
+ }
212
+ }