embulk-input-marketo_extended 0.6.18

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/.github/PULL_REQUEST_TEMPLATE.md +37 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGELOG.md +170 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +213 -0
  8. data/build.gradle +103 -0
  9. data/config/checkstyle/checkstyle.xml +128 -0
  10. data/config/checkstyle/default.xml +108 -0
  11. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  12. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  13. data/gradlew +169 -0
  14. data/gradlew.bat +84 -0
  15. data/lib/embulk/input/marketo.rb +3 -0
  16. data/settings.gradle +1 -0
  17. data/src/main/java/org/embulk/input/marketo/CsvTokenizer.java +700 -0
  18. data/src/main/java/org/embulk/input/marketo/MarketoInputPlugin.java +15 -0
  19. data/src/main/java/org/embulk/input/marketo/MarketoInputPluginDelegate.java +100 -0
  20. data/src/main/java/org/embulk/input/marketo/MarketoService.java +38 -0
  21. data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +245 -0
  22. data/src/main/java/org/embulk/input/marketo/MarketoUtils.java +212 -0
  23. data/src/main/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPlugin.java +167 -0
  24. data/src/main/java/org/embulk/input/marketo/delegate/CampaignInputPlugin.java +48 -0
  25. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectInputPlugin.java +75 -0
  26. data/src/main/java/org/embulk/input/marketo/delegate/CustomObjectResponseMapperBuilder.java +81 -0
  27. data/src/main/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPlugin.java +66 -0
  28. data/src/main/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilder.java +85 -0
  29. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithListInputPlugin.java +64 -0
  30. data/src/main/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPlugin.java +60 -0
  31. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +441 -0
  32. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseInputPluginDelegate.java +92 -0
  33. data/src/main/java/org/embulk/input/marketo/delegate/ProgramInputPlugin.java +228 -0
  34. data/src/main/java/org/embulk/input/marketo/exception/MarketoAPIException.java +30 -0
  35. data/src/main/java/org/embulk/input/marketo/model/BulkExtractRangeHeader.java +26 -0
  36. data/src/main/java/org/embulk/input/marketo/model/MarketoAccessTokenResponse.java +92 -0
  37. data/src/main/java/org/embulk/input/marketo/model/MarketoBulkExtractRequest.java +68 -0
  38. data/src/main/java/org/embulk/input/marketo/model/MarketoError.java +40 -0
  39. data/src/main/java/org/embulk/input/marketo/model/MarketoField.java +126 -0
  40. data/src/main/java/org/embulk/input/marketo/model/MarketoResponse.java +82 -0
  41. data/src/main/java/org/embulk/input/marketo/model/filter/DateRangeFilter.java +40 -0
  42. data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +306 -0
  43. data/src/main/java/org/embulk/input/marketo/rest/MarketoInputStreamResponseEntityReader.java +69 -0
  44. data/src/main/java/org/embulk/input/marketo/rest/MarketoRESTEndpoint.java +47 -0
  45. data/src/main/java/org/embulk/input/marketo/rest/MarketoResponseJetty92EntityReader.java +89 -0
  46. data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +569 -0
  47. data/src/main/java/org/embulk/input/marketo/rest/RecordPagingIterable.java +180 -0
  48. data/src/test/java/org/embulk/input/marketo/MarketoServiceImplTest.java +140 -0
  49. data/src/test/java/org/embulk/input/marketo/MarketoUtilsTest.java +87 -0
  50. data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +128 -0
  51. data/src/test/java/org/embulk/input/marketo/delegate/CampaignInputPluginTest.java +73 -0
  52. data/src/test/java/org/embulk/input/marketo/delegate/CustomObjectInputPluginTest.java +102 -0
  53. data/src/test/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPluginTest.java +99 -0
  54. data/src/test/java/org/embulk/input/marketo/delegate/LeadServiceResponseMapperBuilderTest.java +119 -0
  55. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithListInputPluginTest.java +101 -0
  56. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPluginTest.java +103 -0
  57. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +169 -0
  58. data/src/test/java/org/embulk/input/marketo/delegate/ProgramInputPluginTest.java +343 -0
  59. data/src/test/java/org/embulk/input/marketo/rest/MarketoBaseRestClientTest.java +368 -0
  60. data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +584 -0
  61. data/src/test/resources/config/activity_bulk_extract_config.yaml +7 -0
  62. data/src/test/resources/config/custom_object_config.yaml +8 -0
  63. data/src/test/resources/config/lead_bulk_extract_config.yaml +8 -0
  64. data/src/test/resources/config/rest_config.yaml +3 -0
  65. data/src/test/resources/fixtures/activity_extract1.csv +35 -0
  66. data/src/test/resources/fixtures/activity_extract2.csv +22 -0
  67. data/src/test/resources/fixtures/activity_types.json +22 -0
  68. data/src/test/resources/fixtures/all_program_full.json +53 -0
  69. data/src/test/resources/fixtures/campaign_response.json +38 -0
  70. data/src/test/resources/fixtures/campaign_response_full.json +102 -0
  71. data/src/test/resources/fixtures/custom_object_describe.json +124 -0
  72. data/src/test/resources/fixtures/custom_object_describe_marketo_fields_full.json +22 -0
  73. data/src/test/resources/fixtures/custom_object_expected.json +66 -0
  74. data/src/test/resources/fixtures/custom_object_response.json +24 -0
  75. data/src/test/resources/fixtures/custom_object_response_full.json +23 -0
  76. data/src/test/resources/fixtures/lead_by_list.json +33 -0
  77. data/src/test/resources/fixtures/lead_by_program_response.json +47 -0
  78. data/src/test/resources/fixtures/lead_describe.json +221 -0
  79. data/src/test/resources/fixtures/lead_describe_expected.json +66 -0
  80. data/src/test/resources/fixtures/lead_describe_marketo_fields_full.json +518 -0
  81. data/src/test/resources/fixtures/lead_extract1.csv +11 -0
  82. data/src/test/resources/fixtures/lead_response_full.json +2402 -0
  83. data/src/test/resources/fixtures/lead_with_program_full.json +17 -0
  84. data/src/test/resources/fixtures/leads_extract2.csv +10 -0
  85. data/src/test/resources/fixtures/list_reponse_full.json +191 -0
  86. data/src/test/resources/fixtures/lists_response.json +31 -0
  87. data/src/test/resources/fixtures/program_response.json +71 -0
  88. metadata +171 -0
@@ -0,0 +1,69 @@
1
+ package org.embulk.input.marketo.rest;
2
+
3
+ import com.fasterxml.jackson.core.type.TypeReference;
4
+ import com.fasterxml.jackson.databind.ObjectMapper;
5
+ import com.fasterxml.jackson.databind.ObjectReader;
6
+ import com.fasterxml.jackson.databind.node.ObjectNode;
7
+ import com.google.common.io.CharStreams;
8
+ import org.eclipse.jetty.client.api.Response;
9
+ import org.eclipse.jetty.client.util.InputStreamResponseListener;
10
+ import org.eclipse.jetty.http.HttpHeader;
11
+ import org.embulk.input.marketo.exception.MarketoAPIException;
12
+ import org.embulk.input.marketo.model.MarketoResponse;
13
+ import org.embulk.util.retryhelper.jetty92.Jetty92ResponseReader;
14
+
15
+ import java.io.InputStream;
16
+ import java.io.InputStreamReader;
17
+ import java.util.concurrent.TimeUnit;
18
+
19
+ /**
20
+ * Created by tai.khuu on 9/5/17.
21
+ */
22
+ public class MarketoInputStreamResponseEntityReader implements Jetty92ResponseReader<InputStream>
23
+ {
24
+ private static final ObjectReader OBJECT_READER = new ObjectMapper().readerFor(new TypeReference<MarketoResponse<ObjectNode>>(){ });
25
+
26
+ private InputStreamResponseListener listener;
27
+
28
+ private long timeout;
29
+
30
+ public MarketoInputStreamResponseEntityReader(long timeout)
31
+ {
32
+ this.timeout = timeout;
33
+ }
34
+
35
+ @Override
36
+ public Response.Listener getListener()
37
+ {
38
+ this.listener = new InputStreamResponseListener();
39
+ return this.listener;
40
+ }
41
+
42
+ @Override
43
+ public Response getResponse() throws Exception
44
+ {
45
+ return this.listener.get(timeout, TimeUnit.MILLISECONDS);
46
+ }
47
+
48
+ @Override
49
+ public InputStream readResponseContent() throws Exception
50
+ {
51
+ if (!getResponse().getHeaders().getField(HttpHeader.CONTENT_TYPE).getValue().equals("text/csv")) {
52
+ String errorString = readResponseContentInString();
53
+
54
+ MarketoResponse<ObjectNode> errorResponse = OBJECT_READER.readValue(errorString);
55
+ if (!errorResponse.isSuccess()) {
56
+ throw new MarketoAPIException(errorResponse.getErrors());
57
+ }
58
+ }
59
+ return this.listener.getInputStream();
60
+ }
61
+
62
+ @Override
63
+ public String readResponseContentInString() throws Exception
64
+ {
65
+ try (InputStreamReader inputStreamReader = new InputStreamReader(this.listener.getInputStream())) {
66
+ return CharStreams.toString(inputStreamReader);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,47 @@
1
+ package org.embulk.input.marketo.rest;
2
+
3
+ import org.apache.commons.lang3.text.StrSubstitutor;
4
+
5
+ import java.util.Map;
6
+
7
+ /**
8
+ * Created by tai.khuu on 9/5/17.
9
+ */
10
+ public enum MarketoRESTEndpoint
11
+ {
12
+ ACCESS_TOKEN("/oauth/token"),
13
+ CREATE_LEAD_EXTRACT("/bulk/v1/leads/export/create.json"),
14
+ CREATE_ACTIVITY_EXTRACT("/bulk/v1/activities/export/create.json"),
15
+ DESCRIBE_LEAD("/rest/v1/leads/describe.json"),
16
+ START_LEAD_EXPORT_JOB("/bulk/v1/leads/export/${export_id}/enqueue.json"),
17
+ START_ACTIVITY_EXPORT_JOB("/bulk/v1/activities/export/${export_id}/enqueue.json"),
18
+ GET_ACTIVITY_EXPORT_STATUS("/bulk/v1/activities/export/${export_id}/status.json"),
19
+ GET_LEAD_EXPORT_STATUS("/bulk/v1/leads/export/${export_id}/status.json"),
20
+ GET_LEAD_EXPORT_RESULT("/bulk/v1/leads/export/${export_id}/file.json"),
21
+ GET_ACTIVITY_EXPORT_RESULT("/bulk/v1/activities/export/${export_id}/file.json"),
22
+ GET_LISTS("/rest/v1/lists.json"),
23
+ GET_LEADS_BY_LIST("/rest/v1/lists/${list_id}/leads.json"),
24
+ GET_PROGRAMS("/rest/asset/v1/programs.json"),
25
+ GET_LEADS_BY_PROGRAM("/rest/v1/leads/programs/${program_id}.json"),
26
+ GET_CAMPAIGN("/rest/v1/campaigns.json"),
27
+ GET_PROGRAMS_BY_TAG("/rest/asset/v1/program/byTag.json"),
28
+ GET_CUSTOM_OBJECT("/rest/v1/customobjects/${api_name}.json"),
29
+ GET_CUSTOM_OBJECT_DESCRIBE("/rest/v1/customobjects/${api_name}/describe.json"),
30
+ GET_ACTIVITY_TYPES("/rest/v1/activities/types.json");
31
+ private String endpoint;
32
+
33
+ MarketoRESTEndpoint(String endpoint)
34
+ {
35
+ this.endpoint = endpoint;
36
+ }
37
+
38
+ public String getEndpoint()
39
+ {
40
+ return endpoint;
41
+ }
42
+
43
+ public String getEndpoint(Map<String, String> pathParams)
44
+ {
45
+ return StrSubstitutor.replace(endpoint, pathParams);
46
+ }
47
+ }
@@ -0,0 +1,89 @@
1
+ package org.embulk.input.marketo.rest;
2
+
3
+ import com.fasterxml.jackson.databind.DeserializationFeature;
4
+ import com.fasterxml.jackson.databind.JavaType;
5
+ import com.fasterxml.jackson.databind.ObjectMapper;
6
+ import com.fasterxml.jackson.databind.node.ObjectNode;
7
+ import com.google.common.io.CharStreams;
8
+ import org.eclipse.jetty.client.api.Response;
9
+ import org.eclipse.jetty.client.util.InputStreamResponseListener;
10
+ import org.embulk.input.marketo.exception.MarketoAPIException;
11
+ import org.embulk.input.marketo.model.MarketoResponse;
12
+ import org.embulk.spi.DataException;
13
+ import org.embulk.spi.Exec;
14
+ import org.embulk.util.retryhelper.jetty92.Jetty92ResponseReader;
15
+ import org.slf4j.Logger;
16
+
17
+ import java.io.IOException;
18
+ import java.io.InputStream;
19
+ import java.io.InputStreamReader;
20
+ import java.nio.charset.StandardCharsets;
21
+ import java.util.concurrent.TimeUnit;
22
+
23
+ /**
24
+ * Created by tai.khuu on 9/1/17.
25
+ */
26
+ public class MarketoResponseJetty92EntityReader<T> implements Jetty92ResponseReader<MarketoResponse<T>>
27
+ {
28
+ private InputStreamResponseListener listener;
29
+
30
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
31
+
32
+ private static final Logger LOGGER = Exec.getLogger(MarketoResponseJetty92EntityReader.class);
33
+ private Long timeout;
34
+
35
+ private JavaType javaType;
36
+
37
+ public MarketoResponseJetty92EntityReader(long timeout)
38
+ {
39
+ this.timeout = timeout;
40
+ javaType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(MarketoResponse.class, MarketoResponse.class, ObjectNode.class);
41
+ }
42
+
43
+ public MarketoResponseJetty92EntityReader(long timeout, Class<T> resultClass)
44
+ {
45
+ this.listener = new InputStreamResponseListener();
46
+ this.timeout = timeout;
47
+ this.javaType = OBJECT_MAPPER.getTypeFactory().constructParametrizedType(MarketoResponse.class, MarketoResponse.class, resultClass);
48
+ }
49
+
50
+ @Override
51
+ public Response.Listener getListener()
52
+ {
53
+ this.listener = new InputStreamResponseListener();
54
+ return this.listener;
55
+ }
56
+
57
+ @Override
58
+ public Response getResponse() throws Exception
59
+ {
60
+ return this.listener.get(timeout, TimeUnit.MILLISECONDS);
61
+ }
62
+
63
+ @Override
64
+ public MarketoResponse<T> readResponseContent() throws Exception
65
+ {
66
+ String response = readResponseContentInString();
67
+ try {
68
+ MarketoResponse<T> marketoResponse = OBJECT_MAPPER.readValue(response, javaType);
69
+ if (!marketoResponse.isSuccess()) {
70
+ throw new MarketoAPIException(marketoResponse.getErrors());
71
+ }
72
+ return marketoResponse;
73
+ }
74
+ catch (IOException ex) {
75
+ LOGGER.error("Can't parse json content", ex);
76
+ throw new DataException("Exception when parse json content");
77
+ }
78
+ }
79
+
80
+ @Override
81
+ public String readResponseContentInString() throws Exception
82
+ {
83
+ InputStream inputStream = this.listener.getInputStream();
84
+ try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
85
+ String reponseContent = CharStreams.toString(inputStreamReader);
86
+ return reponseContent;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,569 @@
1
+ package org.embulk.input.marketo.rest;
2
+
3
+ import com.fasterxml.jackson.core.JsonProcessingException;
4
+ import com.fasterxml.jackson.databind.JsonNode;
5
+ import com.fasterxml.jackson.databind.node.ObjectNode;
6
+ import com.google.common.base.Optional;
7
+ import com.google.common.collect.ArrayListMultimap;
8
+ import com.google.common.collect.ImmutableListMultimap;
9
+ import com.google.common.collect.ImmutableMap;
10
+ import com.google.common.collect.Multimap;
11
+ import org.apache.commons.lang3.StringUtils;
12
+ import org.eclipse.jetty.client.util.FormContentProvider;
13
+ import org.eclipse.jetty.util.Fields;
14
+ import org.embulk.config.Config;
15
+ import org.embulk.config.ConfigDefault;
16
+ import org.embulk.config.ConfigException;
17
+ import org.embulk.config.Task;
18
+ import org.embulk.input.marketo.MarketoUtils;
19
+ import org.embulk.input.marketo.model.BulkExtractRangeHeader;
20
+ import org.embulk.input.marketo.model.MarketoBulkExtractRequest;
21
+ import org.embulk.input.marketo.model.MarketoError;
22
+ import org.embulk.input.marketo.model.MarketoField;
23
+ import org.embulk.input.marketo.model.MarketoResponse;
24
+ import org.embulk.input.marketo.model.filter.DateRangeFilter;
25
+ import org.embulk.spi.DataException;
26
+ import org.embulk.spi.Exec;
27
+ import org.embulk.spi.type.Type;
28
+ import org.embulk.spi.type.Types;
29
+ import org.embulk.util.retryhelper.jetty92.DefaultJetty92ClientCreator;
30
+ import org.embulk.util.retryhelper.jetty92.Jetty92RetryHelper;
31
+ import org.slf4j.Logger;
32
+
33
+ import java.io.InputStream;
34
+ import java.text.SimpleDateFormat;
35
+ import java.util.ArrayList;
36
+ import java.util.Date;
37
+ import java.util.HashMap;
38
+ import java.util.List;
39
+ import java.util.Map;
40
+
41
+ /**
42
+ * Created by tai.khuu on 8/22/17.
43
+ */
44
+ public class MarketoRestClient extends MarketoBaseRestClient
45
+ {
46
+ private static final String BATCH_SIZE = "batchSize";
47
+
48
+ private static final String NEXT_PAGE_TOKEN = "nextPageToken";
49
+
50
+ private static final String OFFSET = "offset";
51
+
52
+ private static final String MAX_RETURN = "maxReturn";
53
+
54
+ private static final String MAX_BATCH_SIZE = "300";
55
+
56
+ private static final String DEFAULT_MAX_RETURN = "200";
57
+
58
+ private static final String RANGE_HEADER = "Range";
59
+
60
+ private static final String FILTER_TYPE = "filterType";
61
+
62
+ private static final String FILTER_VALUES = "filterValues";
63
+
64
+ private static final String FIELDS = "fields";
65
+
66
+ private static final int MAX_REQUEST_SIZE = 300;
67
+
68
+ private static final int CONNECT_TIMEOUT_IN_MILLIS = 30000;
69
+ private static final int IDLE_TIMEOUT_IN_MILLIS = 60000;
70
+
71
+ private String endPoint;
72
+
73
+ private Integer batchSize;
74
+
75
+ private Integer maxReturn;
76
+
77
+ private static final Logger LOGGER = Exec.getLogger(MarketoRestClient.class.getCanonicalName());
78
+
79
+ private static final Map<String, Type> TYPE_MAPPING = new ImmutableMap.Builder<String, Type>()
80
+ .put("datetime", Types.TIMESTAMP)
81
+ .put("email", Types.STRING)
82
+ .put("float", Types.DOUBLE)
83
+ .put("integer", Types.LONG)
84
+ .put("formula", Types.STRING)
85
+ .put("percent", Types.LONG)
86
+ .put("url", Types.STRING)
87
+ .put("phone", Types.STRING)
88
+ .put("textarea", Types.STRING)
89
+ .put("text", Types.STRING)
90
+ .put("string", Types.STRING)
91
+ .put("score", Types.LONG)
92
+ .put("boolean", Types.BOOLEAN)
93
+ .put("currency", Types.DOUBLE)
94
+ .put("date", Types.TIMESTAMP)
95
+ .put("reference", Types.STRING)
96
+ .build();
97
+
98
+ public interface PluginTask extends Task
99
+ {
100
+ @Config("account_id")
101
+ String getAccountId();
102
+
103
+ @Config("client_secret")
104
+ String getClientSecret();
105
+
106
+ @Config("client_id")
107
+ String getClientId();
108
+
109
+ @Config("marketo_limit_interval_milis")
110
+ @ConfigDefault("20000")
111
+ Integer getMarketoLimitIntervalMilis();
112
+
113
+ @Config("batch_size")
114
+ @ConfigDefault("300")
115
+ Integer getBatchSize();
116
+ void setBatchSize(Integer batchSize);
117
+
118
+ @Config("max_return")
119
+ @ConfigDefault("200")
120
+ Integer getMaxReturn();
121
+ void setMaxReturn(Integer maxReturn);
122
+
123
+ @Config("read_timeout_millis")
124
+ @ConfigDefault("60000")
125
+ Long getReadTimeoutMillis();
126
+
127
+ @Config("maximum_retries")
128
+ @ConfigDefault("7")
129
+ Integer getMaximumRetries();
130
+
131
+ @Config("initial_retry_interval_milis")
132
+ @ConfigDefault("20000")
133
+ Integer getInitialRetryIntervalMilis();
134
+
135
+ @Config("maximum_retries_interval_milis")
136
+ @ConfigDefault("120000")
137
+ Integer getMaximumRetriesIntervalMilis();
138
+
139
+ @Config("partner_api_key")
140
+ @ConfigDefault("null")
141
+ Optional<String> getPartnerApiKey();
142
+ }
143
+
144
+ public MarketoRestClient(PluginTask task)
145
+ {
146
+ this(MarketoUtils.getEndPoint(task.getAccountId()),
147
+ MarketoUtils.getIdentityEndPoint(task.getAccountId()),
148
+ task.getClientId(),
149
+ task.getClientSecret(),
150
+ task.getPartnerApiKey(),
151
+ task.getBatchSize(),
152
+ task.getMaxReturn(),
153
+ task.getReadTimeoutMillis(),
154
+ task.getMarketoLimitIntervalMilis(),
155
+ new Jetty92RetryHelper(task.getMaximumRetries(),
156
+ task.getInitialRetryIntervalMilis(),
157
+ task.getMaximumRetriesIntervalMilis(),
158
+ new DefaultJetty92ClientCreator(CONNECT_TIMEOUT_IN_MILLIS, IDLE_TIMEOUT_IN_MILLIS)));
159
+ }
160
+
161
+ public MarketoRestClient(String endPoint,
162
+ String identityEndPoint,
163
+ String clientId,
164
+ String clientSecret,
165
+ Optional<String> partnerApiKey,
166
+ Integer batchSize,
167
+ Integer maxReturn,
168
+ long readTimeoutMilis,
169
+ int marketoLimitIntervalMilis,
170
+ Jetty92RetryHelper retryHelper)
171
+ {
172
+ super(identityEndPoint, clientId, clientSecret, partnerApiKey, marketoLimitIntervalMilis, readTimeoutMilis, retryHelper);
173
+ this.endPoint = endPoint;
174
+ this.batchSize = batchSize;
175
+ this.maxReturn = maxReturn;
176
+ }
177
+
178
+ public List<MarketoField> describeLead()
179
+ {
180
+ MarketoResponse<ObjectNode> jsonResponse = doGet(endPoint + MarketoRESTEndpoint.DESCRIBE_LEAD.getEndpoint(), null, null, new MarketoResponseJetty92EntityReader<ObjectNode>(this.readTimeoutMillis));
181
+ List<MarketoField> marketoFields = new ArrayList<>();
182
+ List<ObjectNode> fields = jsonResponse.getResult();
183
+ for (int i = 0; i < fields.size(); i++) {
184
+ ObjectNode field = fields.get(i);
185
+ String dataType = field.get("dataType").asText();
186
+ if (field.has("rest")) {
187
+ ObjectNode restField = (ObjectNode) field.get("rest");
188
+ String name = restField.get("name").asText();
189
+ marketoFields.add(new MarketoField(name, dataType));
190
+ }
191
+ }
192
+ return marketoFields;
193
+ }
194
+
195
+ private Type getType(String dataType)
196
+ {
197
+ return TYPE_MAPPING.containsKey(dataType.toLowerCase()) ? TYPE_MAPPING.get(dataType.toLowerCase()) : Types.STRING;
198
+ }
199
+
200
+ public String createLeadBulkExtract(Date startTime, Date endTime, List<String> extractFields, String fitlerField)
201
+ {
202
+ MarketoBulkExtractRequest marketoBulkExtractRequest = getMarketoBulkExtractRequest(startTime, endTime, extractFields, fitlerField);
203
+ return sendCreateBulkExtractRequest(marketoBulkExtractRequest, MarketoRESTEndpoint.CREATE_LEAD_EXTRACT);
204
+ }
205
+
206
+ private MarketoBulkExtractRequest getMarketoBulkExtractRequest(Date startTime, Date endTime, List<String> extractFields, String rangeFilterName)
207
+ {
208
+ SimpleDateFormat timeFormat = new SimpleDateFormat(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
209
+ MarketoBulkExtractRequest marketoBulkExtractRequest = new MarketoBulkExtractRequest();
210
+ if (extractFields != null) {
211
+ marketoBulkExtractRequest.setFields(extractFields);
212
+ }
213
+ marketoBulkExtractRequest.setFormat("CSV");
214
+ Map<String, Object> filterMap = new HashMap<>();
215
+ DateRangeFilter dateRangeFilter = new DateRangeFilter();
216
+ dateRangeFilter.setStartAt(timeFormat.format(startTime));
217
+ dateRangeFilter.setEndAt(timeFormat.format(endTime));
218
+ filterMap.put(rangeFilterName, dateRangeFilter);
219
+ marketoBulkExtractRequest.setFilter(filterMap);
220
+ return marketoBulkExtractRequest;
221
+ }
222
+
223
+ public String createActivityExtract(List<Integer> activityTypeIds, Date startTime, Date endTime)
224
+ {
225
+ MarketoBulkExtractRequest marketoBulkExtractRequest = getMarketoBulkExtractRequest(startTime, endTime, null, "createdAt");
226
+ if (activityTypeIds != null && !activityTypeIds.isEmpty()) {
227
+ marketoBulkExtractRequest.getFilter().put("activityTypeIds", activityTypeIds);
228
+ }
229
+ return sendCreateBulkExtractRequest(marketoBulkExtractRequest, MarketoRESTEndpoint.CREATE_ACTIVITY_EXTRACT);
230
+ }
231
+
232
+ public String sendCreateBulkExtractRequest(MarketoBulkExtractRequest request, MarketoRESTEndpoint endpoint)
233
+ {
234
+ MarketoResponse<ObjectNode> marketoResponse = null;
235
+ try {
236
+ LOGGER.info("Send bulk extract request [{}]", request);
237
+ marketoResponse = doPost(endPoint + endpoint.getEndpoint(), null, null, OBJECT_MAPPER.writeValueAsString(request), new MarketoResponseJetty92EntityReader<ObjectNode>(readTimeoutMillis));
238
+ }
239
+ catch (JsonProcessingException e) {
240
+ LOGGER.error("Encounter exception when deserialize bulk extract request", e);
241
+ throw new DataException("Can't create bulk extract");
242
+ }
243
+ if (!marketoResponse.isSuccess()) {
244
+ MarketoError marketoError = marketoResponse.getErrors().get(0);
245
+ throw new DataException(marketoError.getCode() + ": " + marketoError.getMessage());
246
+ }
247
+ ObjectNode objectNode = marketoResponse.getResult().get(0);
248
+ return objectNode.get("exportId").asText();
249
+ }
250
+
251
+ public void startLeadBulkExtract(String exportId)
252
+ {
253
+ startBulkExtract(MarketoRESTEndpoint.START_LEAD_EXPORT_JOB, exportId);
254
+ }
255
+
256
+ public void startActitvityBulkExtract(String exportId)
257
+ {
258
+ startBulkExtract(MarketoRESTEndpoint.START_ACTIVITY_EXPORT_JOB, exportId);
259
+ }
260
+
261
+ private void startBulkExtract(MarketoRESTEndpoint marketoRESTEndpoint, String exportId)
262
+ {
263
+ MarketoResponse<ObjectNode> marketoResponse = doPost(endPoint + marketoRESTEndpoint.getEndpoint(
264
+ new ImmutableMap.Builder<String, String>().put("export_id", exportId).build()), null, null, null,
265
+ new MarketoResponseJetty92EntityReader<ObjectNode>(readTimeoutMillis));
266
+ if (!marketoResponse.isSuccess()) {
267
+ MarketoError error = marketoResponse.getErrors().get(0);
268
+ throw new DataException(String.format("Can't start job for export Job id : %s, error code: %s, error message: %s", exportId, error.getCode(), error.getMessage()));
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Wait for lead bulk extract job
274
+ * Will block and wait until job status switch to complete
275
+ * If job run logger than bulk job timeout then will stop and throw exception
276
+ * If job status is failed or cancel will also throw exception
277
+ *
278
+ * @param exportId
279
+ * @throws InterruptedException
280
+ */
281
+ public void waitLeadExportJobComplete(String exportId, int pollingInterval, int waitTimeout) throws InterruptedException
282
+ {
283
+ waitExportJobComplete(MarketoRESTEndpoint.GET_LEAD_EXPORT_STATUS, exportId, pollingInterval, waitTimeout);
284
+ }
285
+
286
+ /**
287
+ * Wait for activites bulk extract job
288
+ * Will block and wait until job status switch to complete
289
+ * If job run logger than bulk job timeout then will stop and throw exception
290
+ * If job status is failed or cancel will also throw exception
291
+ *
292
+ * @param exportId
293
+ * @throws InterruptedException
294
+ */
295
+ public void waitActitvityExportJobComplete(String exportId, int pollingInterval, int waitTimeout) throws InterruptedException
296
+ {
297
+ waitExportJobComplete(MarketoRESTEndpoint.GET_ACTIVITY_EXPORT_STATUS, exportId, pollingInterval, waitTimeout);
298
+ }
299
+
300
+ private void waitExportJobComplete(MarketoRESTEndpoint marketoRESTEndpoint, String exportId, int pollingInterval, int waitTimeout) throws InterruptedException
301
+ {
302
+ long waitTime = 0;
303
+ long waitTimeoutMs = waitTimeout * 1000;
304
+ long now = System.currentTimeMillis();
305
+ while (true) {
306
+ MarketoResponse<ObjectNode> marketoResponse = doGet(this.endPoint + marketoRESTEndpoint.getEndpoint(
307
+ new ImmutableMap.Builder<String, String>().put("export_id", exportId).build()), null, null, new MarketoResponseJetty92EntityReader<ObjectNode>(readTimeoutMillis));
308
+ if (marketoResponse.isSuccess()) {
309
+ ObjectNode objectNode = marketoResponse.getResult().get(0);
310
+ String status = objectNode.get("status").asText();
311
+ if (status == null) {
312
+ throw new DataException("Can't get bulk extract status export job id: " + exportId);
313
+ }
314
+ LOGGER.info("Jobs [{}] status is [{}]", exportId, status);
315
+ switch (status) {
316
+ case "Completed":
317
+ LOGGER.info("Total wait time ms is [{}]", waitTime);
318
+ LOGGER.info("File size is [{}] bytes", objectNode.get("fileSize"));
319
+ return;
320
+ case "Failed":
321
+ throw new DataException("Bulk extract job failed exportId: " + exportId + " errorMessage: " + objectNode.get("errorMsg").asText());
322
+ case "Cancel":
323
+ throw new DataException("Bulk extract job canceled, exportId: " + exportId);
324
+ }
325
+ }
326
+ Thread.sleep(pollingInterval * 1000);
327
+ waitTime = System.currentTimeMillis() - now;
328
+ if (waitTime >= waitTimeoutMs) {
329
+ throw new DataException("Job timeout exception, exportJob: " + exportId + ", run longer than " + waitTimeout + " seconds");
330
+ }
331
+ }
332
+ }
333
+
334
+ public InputStream getLeadBulkExtractResult(String exportId, BulkExtractRangeHeader bulkExtractRangeHeader)
335
+ {
336
+ return getBulkExtractResult(MarketoRESTEndpoint.GET_LEAD_EXPORT_RESULT, exportId, bulkExtractRangeHeader);
337
+ }
338
+
339
+ public InputStream getActivitiesBulkExtractResult(String exportId, BulkExtractRangeHeader bulkExtractRangeHeader)
340
+ {
341
+ return getBulkExtractResult(MarketoRESTEndpoint.GET_ACTIVITY_EXPORT_RESULT, exportId, bulkExtractRangeHeader);
342
+ }
343
+
344
+ private InputStream getBulkExtractResult(MarketoRESTEndpoint endpoint, String exportId, BulkExtractRangeHeader bulkExtractRangeHeader)
345
+ {
346
+ LOGGER.info("Download bulk export job [{}]", exportId);
347
+ Map<String, String> headers = new HashMap<>();
348
+ if (bulkExtractRangeHeader != null) {
349
+ headers.put(RANGE_HEADER, bulkExtractRangeHeader.toRangeHeaderValue());
350
+ LOGGER.info("Range header value [{}]", bulkExtractRangeHeader.toRangeHeaderValue());
351
+ }
352
+ return doGet(this.endPoint + endpoint.getEndpoint(new ImmutableMap.Builder().put("export_id", exportId).build()), headers, null, new MarketoInputStreamResponseEntityReader(readTimeoutMillis));
353
+ }
354
+
355
+ public RecordPagingIterable<ObjectNode> getLists()
356
+ {
357
+ return getRecordWithTokenPagination(endPoint + MarketoRESTEndpoint.GET_LISTS.getEndpoint(), new ImmutableListMultimap.Builder<String, String>().put(BATCH_SIZE, MAX_BATCH_SIZE).build(), ObjectNode.class);
358
+ }
359
+
360
+ public RecordPagingIterable<ObjectNode> getPrograms()
361
+ {
362
+ return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_PROGRAMS.getEndpoint(), new ImmutableListMultimap.Builder<String, String>().put(MAX_RETURN, DEFAULT_MAX_RETURN).build(), ObjectNode.class);
363
+ }
364
+
365
+ public RecordPagingIterable<ObjectNode> getLeadsByProgram(String programId, String fieldNames)
366
+ {
367
+ Multimap<String, String> multimap = ArrayListMultimap.create();
368
+ multimap.put("fields", fieldNames);
369
+ return getRecordWithTokenPagination(endPoint + MarketoRESTEndpoint.GET_LEADS_BY_PROGRAM.getEndpoint(new ImmutableMap.Builder().put("program_id", programId).build()), multimap, ObjectNode.class);
370
+ }
371
+
372
+ public RecordPagingIterable<ObjectNode> getLeadsByList(String listId, String fieldNames)
373
+ {
374
+ Multimap<String, String> multimap = ArrayListMultimap.create();
375
+ multimap.put("fields", fieldNames);
376
+ return getRecordWithTokenPagination(endPoint + MarketoRESTEndpoint.GET_LEADS_BY_LIST.getEndpoint(new ImmutableMap.Builder().put("list_id", listId).build()), multimap, ObjectNode.class);
377
+ }
378
+
379
+ public RecordPagingIterable<ObjectNode> getCampaign()
380
+ {
381
+ return getRecordWithTokenPagination(endPoint + MarketoRESTEndpoint.GET_CAMPAIGN.getEndpoint(), null, ObjectNode.class);
382
+ }
383
+
384
+ private <T> RecordPagingIterable<T> getRecordWithOffsetPagination(final String endPoint, final Multimap<String, String> parameters, final Class<T> recordClass)
385
+ {
386
+ return new RecordPagingIterable<>(new RecordPagingIterable.PagingFunction<RecordPagingIterable.OffsetPage<T>>()
387
+ {
388
+ @Override
389
+ public RecordPagingIterable.OffsetPage<T> getNextPage(RecordPagingIterable.OffsetPage<T> currentPage)
390
+ {
391
+ return getOffsetPage(currentPage.getNextOffSet());
392
+ }
393
+
394
+ @Override
395
+ public RecordPagingIterable.OffsetPage<T> getFirstPage()
396
+ {
397
+ return getOffsetPage(0);
398
+ }
399
+
400
+ private RecordPagingIterable.OffsetPage<T> getOffsetPage(int offset)
401
+ {
402
+ ImmutableListMultimap.Builder<String, String> params = new ImmutableListMultimap.Builder<>();
403
+ params.put(OFFSET, String.valueOf(offset));
404
+ params.put(MAX_RETURN, String.valueOf(maxReturn));
405
+ if (parameters != null) {
406
+ params.putAll(parameters);
407
+ }
408
+ MarketoResponse<T> marketoResponse = doGet(endPoint, null, params.build(), new MarketoResponseJetty92EntityReader<>(readTimeoutMillis, recordClass));
409
+ return new RecordPagingIterable.OffsetPage<>(marketoResponse.getResult(), offset + marketoResponse.getResult().size(), marketoResponse.getResult().size() == maxReturn);
410
+ }
411
+ });
412
+ }
413
+ private <T> RecordPagingIterable<T> getRecordWithTokenPagination(final String endPoint, final Multimap<String, String> parameters, final Class<T> recordClass)
414
+ {
415
+ return new RecordPagingIterable<>(new RecordPagingIterable.PagingFunction<RecordPagingIterable.TokenPage<T>>()
416
+ {
417
+ @Override
418
+ public RecordPagingIterable.TokenPage<T> getNextPage(RecordPagingIterable.TokenPage<T> currentPage)
419
+ {
420
+ return getTokenPage(currentPage);
421
+ }
422
+ @Override
423
+ public RecordPagingIterable.TokenPage<T> getFirstPage()
424
+ {
425
+ return getTokenPage(null);
426
+ }
427
+
428
+ @SuppressWarnings("unchecked")
429
+ private RecordPagingIterable.TokenPage<T> getTokenPage(RecordPagingIterable.TokenPage page)
430
+ {
431
+ ImmutableListMultimap.Builder params = new ImmutableListMultimap.Builder<>();
432
+ params.put("_method", "GET");
433
+ Fields fields = new Fields();
434
+ if (page != null) {
435
+ fields.add(NEXT_PAGE_TOKEN, page.getNextPageToken());
436
+ }
437
+ fields.add(BATCH_SIZE, String.valueOf(batchSize));
438
+ if (parameters != null) {
439
+ for (String key : parameters.keySet()) {
440
+ //params that is passed in should overwrite default
441
+ fields.remove(key);
442
+ for (String value : parameters.get(key)) {
443
+ fields.add(key, value);
444
+ }
445
+ }
446
+ }
447
+ //Let do GET Disguise in POST here to overcome Marketo URI Too long error
448
+ FormContentProvider formContentProvider = new FormContentProvider(fields);
449
+ MarketoResponse<T> marketoResponse = doPost(endPoint, null, params.build(), new MarketoResponseJetty92EntityReader<>(readTimeoutMillis, recordClass), formContentProvider);
450
+ return new RecordPagingIterable.TokenPage<>(marketoResponse.getResult(), marketoResponse.getNextPageToken(), marketoResponse.getNextPageToken() != null);
451
+ }
452
+ });
453
+ }
454
+
455
+ public Iterable<ObjectNode> getProgramsByTag(String tagType, String tagValue)
456
+ {
457
+ Multimap<String, String> multimap = ArrayListMultimap.create();
458
+ multimap.put("tagType", tagType);
459
+ multimap.put("tagValue", tagValue);
460
+ return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_PROGRAMS_BY_TAG.getEndpoint(), multimap, ObjectNode.class);
461
+ }
462
+
463
+ public Iterable<ObjectNode> getProgramsByDateRange(Date earliestUpdatedAt, Date latestUpdatedAt, String filterType, List<String> filterValues)
464
+ {
465
+ SimpleDateFormat timeFormat = new SimpleDateFormat(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
466
+ Multimap<String, String> multimap = ArrayListMultimap.create();
467
+ multimap.put("earliestUpdatedAt", timeFormat.format(earliestUpdatedAt));
468
+ multimap.put("latestUpdatedAt", timeFormat.format(latestUpdatedAt));
469
+ // put filter params if exist.
470
+ if (filterType != null) {
471
+ multimap.put("filterType", filterType);
472
+ multimap.put("filterValues", StringUtils.join(filterValues, ","));
473
+ }
474
+ return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_PROGRAMS.getEndpoint(), multimap, ObjectNode.class);
475
+ }
476
+
477
+ public List<MarketoField> describeCustomObject(String apiName)
478
+ {
479
+ MarketoResponse<ObjectNode> jsonResponse = doGet(endPoint + MarketoRESTEndpoint.GET_CUSTOM_OBJECT_DESCRIBE.getEndpoint(new ImmutableMap.Builder().put("api_name", apiName).build()), null, null, new MarketoResponseJetty92EntityReader<ObjectNode>(this.readTimeoutMillis));
480
+ if (jsonResponse.getResult().size() == 0) {
481
+ throw new ConfigException(String.format("Custom Object %s is not exits.", apiName));
482
+ }
483
+ List<MarketoField> marketoFields = new ArrayList<>();
484
+ JsonNode fieldNodes = jsonResponse.getResult().get(0).path("fields");
485
+ for (JsonNode node : fieldNodes) {
486
+ String dataType = node.get("dataType").asText();
487
+ String name = node.get("name").asText();
488
+ marketoFields.add(new MarketoField(name, dataType));
489
+ }
490
+ if (marketoFields.size() == 0) {
491
+ throw new ConfigException(String.format("Custom Object %s don't have any field data.", apiName));
492
+ }
493
+ return marketoFields;
494
+ }
495
+ private <T> RecordPagingIterable<T> getCustomObjectRecordWithPagination(final String endPoint, final String customObjectFilterType, final String customObjectFields, final Integer fromValue, final Integer toValue, final Class<T> recordClass)
496
+ {
497
+ return new RecordPagingIterable<>(new RecordPagingIterable.PagingFunction<RecordPagingIterable.OffsetWithTokenPage<T>>()
498
+ {
499
+ @Override
500
+ public RecordPagingIterable.OffsetWithTokenPage<T> getNextPage(RecordPagingIterable.OffsetWithTokenPage<T> currentPage)
501
+ {
502
+ return getOffsetPage(currentPage.getNextOffSet(), currentPage.getNextPageToken());
503
+ }
504
+
505
+ @Override
506
+ public RecordPagingIterable.OffsetWithTokenPage<T> getFirstPage()
507
+ {
508
+ return getOffsetPage(fromValue, "");
509
+ }
510
+
511
+ private RecordPagingIterable.OffsetWithTokenPage<T> getOffsetPage(int offset, String nextPageToken)
512
+ {
513
+ boolean isMoreResult = true;
514
+ boolean isEndOffset = false;
515
+ int nextOffset = offset + MAX_REQUEST_SIZE;
516
+
517
+ if (toValue != null) {
518
+ if (toValue <= nextOffset) {
519
+ nextOffset = toValue + 1;
520
+ isEndOffset = true;
521
+ }
522
+ }
523
+ StringBuilder filterValues = new StringBuilder();
524
+ for (int i = offset; i < (nextOffset - 1); i++) {
525
+ filterValues.append(String.valueOf(i)).append(",");
526
+ }
527
+ filterValues.append(String.valueOf(nextOffset - 1));
528
+
529
+ ImmutableListMultimap.Builder<String, String> params = new ImmutableListMultimap.Builder<>();
530
+ params.put(FILTER_TYPE, customObjectFilterType);
531
+ params.put(FILTER_VALUES, filterValues.toString());
532
+ if (StringUtils.isNotBlank(nextPageToken)) {
533
+ params.put(NEXT_PAGE_TOKEN, nextPageToken);
534
+ }
535
+ if (customObjectFields != null) {
536
+ params.put(FIELDS, customObjectFields);
537
+ }
538
+ MarketoResponse<T> marketoResponse = doGet(endPoint, null, params.build(), new MarketoResponseJetty92EntityReader<>(readTimeoutMillis, recordClass));
539
+ String nextToken = "";
540
+ if (StringUtils.isNotBlank(marketoResponse.getNextPageToken())) {
541
+ nextToken = marketoResponse.getNextPageToken();
542
+ //skip offset when nextPageToken is exits.
543
+ nextOffset = offset;
544
+ }
545
+
546
+ if (toValue == null) {
547
+ if (marketoResponse.getResult().isEmpty()) {
548
+ isMoreResult = false;
549
+ }
550
+ }
551
+ else {
552
+ if (isEndOffset && StringUtils.isBlank(nextToken)) {
553
+ isMoreResult = false;
554
+ }
555
+ }
556
+ return new RecordPagingIterable.OffsetWithTokenPage<>(marketoResponse.getResult(), nextOffset, nextToken, isMoreResult);
557
+ }
558
+ });
559
+ }
560
+ public Iterable<ObjectNode> getCustomObject(String customObjectAPIName, String customObjectFilterType, String customObjectFields, Integer fromValue, Integer toValue)
561
+ {
562
+ return getCustomObjectRecordWithPagination(endPoint + MarketoRESTEndpoint.GET_CUSTOM_OBJECT.getEndpoint(new ImmutableMap.Builder().put("api_name", customObjectAPIName).build()), customObjectFilterType, customObjectFields, fromValue, toValue, ObjectNode.class);
563
+ }
564
+
565
+ public Iterable<ObjectNode> getActivityTypes()
566
+ {
567
+ return getRecordWithOffsetPagination(endPoint + MarketoRESTEndpoint.GET_ACTIVITY_TYPES.getEndpoint(), new ImmutableListMultimap.Builder<String, String>().put(MAX_RETURN, DEFAULT_MAX_RETURN).build(), ObjectNode.class);
568
+ }
569
+ }