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,85 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.google.common.base.Optional;
4
+ import org.embulk.base.restclient.ServiceResponseMapper;
5
+ import org.embulk.base.restclient.ServiceResponseMapperBuildable;
6
+ import org.embulk.base.restclient.record.ValueLocator;
7
+ import org.embulk.config.Config;
8
+ import org.embulk.config.ConfigDefault;
9
+ import org.embulk.input.marketo.MarketoService;
10
+ import org.embulk.input.marketo.MarketoUtils;
11
+ import org.embulk.input.marketo.model.MarketoField;
12
+ import org.embulk.spi.Exec;
13
+ import org.slf4j.Logger;
14
+
15
+ import java.util.ArrayList;
16
+ import java.util.List;
17
+
18
+ /**
19
+ * Created by tai.khuu on 5/21/18.
20
+ */
21
+ public class LeadServiceResponseMapperBuilder<T extends LeadServiceResponseMapperBuilder.PluginTask> implements ServiceResponseMapperBuildable<T>
22
+ {
23
+ private static final Logger LOGGER = Exec.getLogger(LeadServiceResponseMapperBuilder.class);
24
+ private MarketoService marketoService;
25
+
26
+ private T pluginTask;
27
+
28
+ public interface PluginTask extends MarketoBaseInputPluginDelegate.PluginTask
29
+ {
30
+ @Config("included_fields")
31
+ @ConfigDefault("null")
32
+ Optional<List<String>> getIncludedLeadFields();
33
+
34
+ @Config("extracted_fields")
35
+ @ConfigDefault("[]")
36
+ List<String> getExtractedFields();
37
+
38
+ void setExtractedFields(List<String> extractedFields);
39
+ }
40
+
41
+ public LeadServiceResponseMapperBuilder(T task, MarketoService marketoService)
42
+ {
43
+ this.pluginTask = task;
44
+ this.marketoService = marketoService;
45
+ }
46
+
47
+ protected List<MarketoField> getLeadColumns()
48
+ {
49
+ List<MarketoField> columns = marketoService.describeLead();
50
+ if (pluginTask.getIncludedLeadFields().isPresent() && !pluginTask.getIncludedLeadFields().get().isEmpty()) {
51
+ List<MarketoField> filteredColumns = new ArrayList<>();
52
+ List<String> includedFields = pluginTask.getIncludedLeadFields().get();
53
+ for (String fieldName : includedFields) {
54
+ Optional<MarketoField> includedField = lookupFieldIgnoreCase(columns, fieldName);
55
+ if (includedField.isPresent()) {
56
+ filteredColumns.add(includedField.get());
57
+ }
58
+ else {
59
+ LOGGER.warn("Included field [{}] not found in Marketo lead field", fieldName);
60
+ }
61
+ }
62
+ columns = filteredColumns;
63
+ LOGGER.info("Included Fields option is set, included columns: [{}]", columns);
64
+ }
65
+ return columns;
66
+ }
67
+
68
+ private static Optional<MarketoField> lookupFieldIgnoreCase(List<MarketoField> inputList, String lookupFieldName)
69
+ {
70
+ for (MarketoField marketoField : inputList) {
71
+ if (marketoField.getName().equalsIgnoreCase(lookupFieldName)) {
72
+ return Optional.of(marketoField);
73
+ }
74
+ }
75
+ return Optional.absent();
76
+ }
77
+
78
+ @Override
79
+ public ServiceResponseMapper<? extends ValueLocator> buildServiceResponseMapper(T task)
80
+ {
81
+ List<MarketoField> leadColumns = getLeadColumns();
82
+ pluginTask.setExtractedFields(MarketoUtils.getFieldNameFromMarketoFields(leadColumns));
83
+ return MarketoUtils.buildDynamicResponseMapper(pluginTask.getSchemaColumnPrefix(), leadColumns);
84
+ }
85
+ }
@@ -0,0 +1,89 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.fasterxml.jackson.databind.node.ObjectNode;
4
+ import com.google.common.base.Optional;
5
+ import com.google.common.collect.FluentIterable;
6
+ import org.apache.commons.lang3.StringUtils;
7
+ import org.embulk.base.restclient.ServiceResponseMapper;
8
+ import org.embulk.base.restclient.record.ServiceRecord;
9
+ import org.embulk.base.restclient.record.ValueLocator;
10
+ import org.embulk.config.Config;
11
+ import org.embulk.config.ConfigDefault;
12
+ import org.embulk.input.marketo.MarketoService;
13
+ import org.embulk.input.marketo.MarketoServiceImpl;
14
+ import org.embulk.input.marketo.MarketoUtils;
15
+ import org.embulk.input.marketo.model.MarketoField;
16
+ import org.embulk.input.marketo.rest.MarketoRestClient;
17
+
18
+ import java.util.Iterator;
19
+ import java.util.List;
20
+ import java.util.Set;
21
+ import java.util.function.Function;
22
+
23
+ /**
24
+ * Created by tai.khuu on 9/18/17.
25
+ */
26
+ public class LeadWithListInputPlugin extends MarketoBaseInputPluginDelegate<LeadWithListInputPlugin.PluginTask>
27
+ {
28
+ public interface PluginTask extends MarketoBaseInputPluginDelegate.PluginTask, LeadServiceResponseMapperBuilder.PluginTask
29
+ {
30
+ @Config("list_ids")
31
+ @ConfigDefault("null")
32
+ Optional<String> getListIds();
33
+ }
34
+
35
+ public LeadWithListInputPlugin()
36
+ {
37
+ }
38
+
39
+ @Override
40
+ protected Iterator<ServiceRecord> getServiceRecords(MarketoService marketoService, PluginTask task)
41
+ {
42
+ List<String> extractedFields = task.getExtractedFields();
43
+
44
+ Iterable<ObjectNode> listsToRequest;
45
+ if (isUserInputLists(task)) {
46
+ final String[] idsStr = StringUtils.split(task.getListIds().get(), ID_LIST_SEPARATOR_CHAR);
47
+ Function<Set<String>, Iterable<ObjectNode>> getListIds = (ids) -> marketoService.getListsByIds(ids);
48
+ listsToRequest = super.getObjectsByIds(idsStr, getListIds);
49
+ }
50
+ else {
51
+ listsToRequest = marketoService.getLists();
52
+ }
53
+
54
+ // Remove LIST_ID_COLUMN_NAME when sent fields to Marketo since LIST_ID_COLUMN_NAME are added by plugin code
55
+ extractedFields.remove(MarketoUtils.LIST_ID_COLUMN_NAME);
56
+ return FluentIterable.from(marketoService.getAllListLead(extractedFields, listsToRequest)).transform(MarketoUtils.TRANSFORM_OBJECT_TO_JACKSON_SERVICE_RECORD_FUNCTION).iterator();
57
+ }
58
+
59
+ private boolean isUserInputLists(PluginTask task)
60
+ {
61
+ return task.getListIds().isPresent() && StringUtils.isNotBlank(task.getListIds().get());
62
+ }
63
+
64
+ @Override
65
+ public ServiceResponseMapper<? extends ValueLocator> buildServiceResponseMapper(PluginTask task)
66
+ {
67
+ try (MarketoRestClient marketoRestClient = createMarketoRestClient(task)) {
68
+ MarketoService marketoService = new MarketoServiceImpl(marketoRestClient);
69
+ LeadWithListServiceResponseMapper serviceResponseMapper = new LeadWithListServiceResponseMapper(task, marketoService);
70
+ return serviceResponseMapper.buildServiceResponseMapper(task);
71
+ }
72
+ }
73
+
74
+ private static class LeadWithListServiceResponseMapper extends LeadServiceResponseMapperBuilder<PluginTask>
75
+ {
76
+ public LeadWithListServiceResponseMapper(LeadWithListInputPlugin.PluginTask task, MarketoService marketoService)
77
+ {
78
+ super(task, marketoService);
79
+ }
80
+
81
+ @Override
82
+ protected List<MarketoField> getLeadColumns()
83
+ {
84
+ List<MarketoField> leadColumns = super.getLeadColumns();
85
+ leadColumns.add(new MarketoField(MarketoUtils.LIST_ID_COLUMN_NAME, MarketoField.MarketoDataType.STRING));
86
+ return leadColumns;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,85 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.fasterxml.jackson.databind.node.ObjectNode;
4
+ import com.google.common.base.Optional;
5
+ import com.google.common.collect.FluentIterable;
6
+ import org.apache.commons.lang3.StringUtils;
7
+ import org.embulk.base.restclient.ServiceResponseMapper;
8
+ import org.embulk.base.restclient.record.ServiceRecord;
9
+ import org.embulk.base.restclient.record.ValueLocator;
10
+ import org.embulk.config.Config;
11
+ import org.embulk.config.ConfigDefault;
12
+ import org.embulk.input.marketo.MarketoService;
13
+ import org.embulk.input.marketo.MarketoServiceImpl;
14
+ import org.embulk.input.marketo.MarketoUtils;
15
+ import org.embulk.input.marketo.model.MarketoField;
16
+ import org.embulk.input.marketo.rest.MarketoRestClient;
17
+
18
+ import java.util.Iterator;
19
+ import java.util.List;
20
+ import java.util.Set;
21
+ import java.util.function.Function;
22
+
23
+ /**
24
+ * Created by tai.khuu on 9/18/17.
25
+ */
26
+ public class LeadWithProgramInputPlugin extends MarketoBaseInputPluginDelegate<LeadWithProgramInputPlugin.PluginTask>
27
+ {
28
+ public interface PluginTask extends MarketoBaseInputPluginDelegate.PluginTask, LeadServiceResponseMapperBuilder.PluginTask
29
+ {
30
+ @Config("program_ids")
31
+ @ConfigDefault("null")
32
+ Optional<String> getProgramIds();
33
+ }
34
+
35
+ @Override
36
+ protected Iterator<ServiceRecord> getServiceRecords(MarketoService marketoService, PluginTask task)
37
+ {
38
+ List<String> fieldNames = task.getExtractedFields();
39
+
40
+ Iterable<ObjectNode> programsToRequest;
41
+ if (isUserInputProgs(task)) {
42
+ final String[] idsStr = StringUtils.split(task.getProgramIds().get(), ID_LIST_SEPARATOR_CHAR);
43
+ Function<Set<String>, Iterable<ObjectNode>> getListIds = (ids) -> marketoService.getProgramsByIds(ids);
44
+ programsToRequest = super.getObjectsByIds(idsStr, getListIds);
45
+ }
46
+ else {
47
+ programsToRequest = marketoService.getPrograms();
48
+ }
49
+
50
+ // Remove PROGRAM_ID_COLUMN_NAME when sent fields to Marketo since PROGRAM_ID_COLUMN_NAME are added by plugin code
51
+ fieldNames.remove(MarketoUtils.PROGRAM_ID_COLUMN_NAME);
52
+ return FluentIterable.from(marketoService.getAllProgramLead(fieldNames, programsToRequest)).transform(MarketoUtils.TRANSFORM_OBJECT_TO_JACKSON_SERVICE_RECORD_FUNCTION).iterator();
53
+ }
54
+
55
+ private boolean isUserInputProgs(LeadWithProgramInputPlugin.PluginTask task)
56
+ {
57
+ return task.getProgramIds().isPresent() && StringUtils.isNotBlank(task.getProgramIds().get());
58
+ }
59
+
60
+ @Override
61
+ public ServiceResponseMapper<? extends ValueLocator> buildServiceResponseMapper(PluginTask task)
62
+ {
63
+ try (MarketoRestClient marketoRestClient = createMarketoRestClient(task)) {
64
+ MarketoService marketoService = new MarketoServiceImpl(marketoRestClient);
65
+ LeadWithProgramServiceResponseMapper serviceResponseMapper = new LeadWithProgramServiceResponseMapper(task, marketoService);
66
+ return serviceResponseMapper.buildServiceResponseMapper(task);
67
+ }
68
+ }
69
+
70
+ private static class LeadWithProgramServiceResponseMapper extends LeadServiceResponseMapperBuilder<PluginTask>
71
+ {
72
+ public LeadWithProgramServiceResponseMapper(LeadWithProgramInputPlugin.PluginTask task, MarketoService marketoService)
73
+ {
74
+ super(task, marketoService);
75
+ }
76
+
77
+ @Override
78
+ protected List<MarketoField> getLeadColumns()
79
+ {
80
+ List<MarketoField> leadColumns = super.getLeadColumns();
81
+ leadColumns.add(new MarketoField(MarketoUtils.PROGRAM_ID_COLUMN_NAME, MarketoField.MarketoDataType.STRING));
82
+ return leadColumns;
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,448 @@
1
+ package org.embulk.input.marketo.delegate;
2
+
3
+ import com.fasterxml.jackson.databind.node.ObjectNode;
4
+ import com.google.common.base.Function;
5
+ import com.google.common.base.Optional;
6
+ import com.google.common.collect.Iterators;
7
+ import org.embulk.base.restclient.jackson.JacksonServiceRecord;
8
+ import org.embulk.base.restclient.jackson.JacksonServiceValue;
9
+ import org.embulk.base.restclient.record.RecordImporter;
10
+ import org.embulk.base.restclient.record.ServiceRecord;
11
+ import org.embulk.base.restclient.record.ValueLocator;
12
+ import org.embulk.config.Config;
13
+ import org.embulk.config.ConfigDefault;
14
+ import org.embulk.config.ConfigDiff;
15
+ import org.embulk.config.ConfigException;
16
+ import org.embulk.config.ConfigInject;
17
+ import org.embulk.config.TaskReport;
18
+ import org.embulk.input.marketo.CsvTokenizer;
19
+ import org.embulk.input.marketo.MarketoService;
20
+ import org.embulk.input.marketo.MarketoServiceImpl;
21
+ import org.embulk.input.marketo.MarketoUtils;
22
+ import org.embulk.input.marketo.rest.MarketoRestClient;
23
+ import org.embulk.spi.BufferAllocator;
24
+ import org.embulk.spi.Column;
25
+ import org.embulk.spi.ColumnVisitor;
26
+ import org.embulk.spi.DataException;
27
+ import org.embulk.spi.Exec;
28
+ import org.embulk.spi.PageBuilder;
29
+ import org.embulk.spi.Schema;
30
+ import org.embulk.spi.json.JsonParser;
31
+ import org.embulk.spi.time.Timestamp;
32
+ import org.embulk.spi.time.TimestampParser;
33
+ import org.embulk.spi.util.InputStreamFileInput;
34
+ import org.embulk.spi.util.LineDecoder;
35
+ import org.msgpack.value.Value;
36
+
37
+ import java.io.InputStream;
38
+ import java.text.DateFormat;
39
+ import java.text.SimpleDateFormat;
40
+ import java.time.OffsetDateTime;
41
+ import java.time.ZoneOffset;
42
+ import java.time.format.DateTimeFormatter;
43
+ import java.util.ArrayList;
44
+ import java.util.Date;
45
+ import java.util.HashMap;
46
+ import java.util.Iterator;
47
+ import java.util.List;
48
+ import java.util.Map;
49
+ import java.util.NoSuchElementException;
50
+
51
+ /**
52
+ * Created by tai.khuu on 9/18/17.
53
+ */
54
+ public abstract class MarketoBaseBulkExtractInputPlugin<T extends MarketoBaseBulkExtractInputPlugin.PluginTask> extends MarketoBaseInputPluginDelegate<T>
55
+ {
56
+ private static final String FROM_DATE = "from_date";
57
+
58
+ private static final int MARKETO_MAX_RANGE_EXTRACT = 30;
59
+
60
+ public interface PluginTask extends MarketoBaseInputPluginDelegate.PluginTask, CsvTokenizer.PluginTask
61
+ {
62
+ @Config("from_date")
63
+ Date getFromDate();
64
+
65
+ @Config("fetch_days")
66
+ @ConfigDefault("1")
67
+ Integer getFetchDays();
68
+
69
+ @Config("latest_fetch_time")
70
+ @ConfigDefault("null")
71
+ Optional<Long> getLatestFetchTime();
72
+
73
+ @ConfigInject
74
+ BufferAllocator getBufferAllocator();
75
+
76
+ @Config("polling_interval_second")
77
+ @ConfigDefault("60")
78
+ Integer getPollingIntervalSecond();
79
+
80
+ @Config("bulk_job_timeout_second")
81
+ @ConfigDefault("3600")
82
+ Integer getBulkJobTimeoutSecond();
83
+
84
+ @Config("to_date")
85
+ @ConfigDefault("null")
86
+ Optional<Date> getToDate();
87
+
88
+ void setToDate(Optional<Date> toDate);
89
+
90
+ @Config("incremental_column")
91
+ @ConfigDefault("\"createdAt\"")
92
+ //Incremental column are only keep here since we don't want to introduce too much change to plugin
93
+ //Consider remove it in next release
94
+ Optional<String> getIncrementalColumn();
95
+
96
+ void setIncrementalColumn(Optional<String> incrementalColumn);
97
+
98
+ @Config("uid_column")
99
+ @ConfigDefault("null")
100
+ Optional<String> getUidColumn();
101
+ void setUidColumn(Optional<String> uidColumn);
102
+ }
103
+
104
+ @Override
105
+ public void validateInputTask(T task)
106
+ {
107
+ super.validateInputTask(task);
108
+ if (task.getFromDate() == null) {
109
+ throw new ConfigException("From date is required for Bulk Extract");
110
+ }
111
+ if (task.getFromDate().getTime() >= OffsetDateTime.parse(task.getJobStartTime(), DateTimeFormatter.ISO_OFFSET_DATE_TIME).toInstant().toEpochMilli()) {
112
+ throw new ConfigException("From date can't not be in future");
113
+ }
114
+ if (task.getIncremental()
115
+ && task.getIncrementalColumn().isPresent()
116
+ && task.getIncrementalColumn().get().equals("updatedAt")) {
117
+ throw new ConfigException("Column 'updatedAt' cannot be incremental imported");
118
+ }
119
+ //Calculate to date
120
+ OffsetDateTime toDate = getToDate(task);
121
+ task.setToDate(Optional.of(Date.from(toDate.toInstant())));
122
+ }
123
+
124
+ public OffsetDateTime getToDate(T task)
125
+ {
126
+ Date fromDate = task.getFromDate();
127
+ final OffsetDateTime jobStartTime = OffsetDateTime.parse(task.getJobStartTime(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
128
+ OffsetDateTime dateTime = OffsetDateTime.ofInstant(fromDate.toInstant(), ZoneOffset.UTC);
129
+ OffsetDateTime toDate = dateTime.plusDays(task.getFetchDays());
130
+ if (toDate.isAfter(jobStartTime)) {
131
+ //Lock down to date
132
+ toDate = jobStartTime;
133
+ }
134
+ return toDate;
135
+ }
136
+
137
+ @Override
138
+ public ConfigDiff buildConfigDiff(T task, Schema schema, int taskCount, List<TaskReport> taskReports)
139
+ {
140
+ ConfigDiff configDiff = super.buildConfigDiff(task, schema, taskCount, taskReports);
141
+ String incrementalColumn = task.getIncrementalColumn().orNull();
142
+ if (incrementalColumn != null && task.getIncremental()) {
143
+ DateFormat df = new SimpleDateFormat(MarketoUtils.MARKETO_DATE_SIMPLE_DATE_FORMAT);
144
+ // We will always move the range forward.
145
+ Date toDate = task.getToDate().orNull();
146
+ configDiff.set(FROM_DATE, df.format(toDate));
147
+ }
148
+ return configDiff;
149
+ }
150
+
151
+ @Override
152
+ public TaskReport ingestServiceData(final T task, RecordImporter recordImporter, int taskIndex, PageBuilder pageBuilder)
153
+ {
154
+ TaskReport taskReport = Exec.newTaskReport();
155
+ if (Exec.isPreview()) {
156
+ return importMockPreviewData(pageBuilder);
157
+ }
158
+ else {
159
+ try (LineDecoderIterator decoderIterator = getLineDecoderIterator(task)) {
160
+ Iterator<Map<String, String>> csvRecords = Iterators.concat(Iterators.transform(decoderIterator, new Function<LineDecoder, Iterator<Map<String, String>>>()
161
+ {
162
+ @Override
163
+ public Iterator<Map<String, String>> apply(LineDecoder input)
164
+ {
165
+ return new CsvRecordIterator(input, task);
166
+ }
167
+ }));
168
+ //Keep the preview code here when we can enable real preview
169
+ if (Exec.isPreview()) {
170
+ csvRecords = Iterators.limit(csvRecords, PREVIEW_RECORD_LIMIT);
171
+ }
172
+ int imported = 0;
173
+ while (csvRecords.hasNext()) {
174
+ Map<String, String> csvRecord = csvRecords.next();
175
+ ObjectNode objectNode = MarketoUtils.OBJECT_MAPPER.valueToTree(csvRecord);
176
+ recordImporter.importRecord(new AllStringJacksonServiceRecord(objectNode), pageBuilder);
177
+ imported = imported + 1;
178
+ }
179
+ return taskReport;
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * This method should be removed when we allow skip preview phase
186
+ * @param pageBuilder
187
+ * @return TaskReport
188
+ */
189
+ private TaskReport importMockPreviewData(final PageBuilder pageBuilder)
190
+ {
191
+ final JsonParser jsonParser = new JsonParser();
192
+ Schema schema = pageBuilder.getSchema();
193
+ for (int i = 1; i <= PREVIEW_RECORD_LIMIT; i++) {
194
+ final int rowNum = i;
195
+ schema.visitColumns(new ColumnVisitor()
196
+ {
197
+ @Override
198
+ public void booleanColumn(Column column)
199
+ {
200
+ pageBuilder.setBoolean(column, false);
201
+ }
202
+
203
+ @Override
204
+ public void longColumn(Column column)
205
+ {
206
+ pageBuilder.setLong(column, 12345L);
207
+ }
208
+
209
+ @Override
210
+ public void doubleColumn(Column column)
211
+ {
212
+ pageBuilder.setDouble(column, 12345.123);
213
+ }
214
+
215
+ @Override
216
+ public void stringColumn(Column column)
217
+ {
218
+ pageBuilder.setString(column, column.getName() + "_" + rowNum);
219
+ }
220
+
221
+ @Override
222
+ public void timestampColumn(Column column)
223
+ {
224
+ pageBuilder.setTimestamp(column, Timestamp.ofEpochMilli(System.currentTimeMillis()));
225
+ }
226
+
227
+ @Override
228
+ public void jsonColumn(Column column)
229
+ {
230
+ pageBuilder.setJson(column, jsonParser.parse("{\"mockKey\":\"mockValue\"}"));
231
+ }
232
+ });
233
+ pageBuilder.addRecord();
234
+ }
235
+ return Exec.newTaskReport();
236
+ }
237
+
238
+ private LineDecoderIterator getLineDecoderIterator(T task)
239
+ {
240
+ final OffsetDateTime fromDate = OffsetDateTime.ofInstant(task.getFromDate().toInstant(), ZoneOffset.UTC);
241
+ final OffsetDateTime toDate = task.getToDate().isPresent() ?
242
+ OffsetDateTime.ofInstant(task.getToDate().get().toInstant(), ZoneOffset.UTC) :
243
+ OffsetDateTime.now(ZoneOffset.UTC);
244
+ List<MarketoUtils.DateRange> dateRanges = MarketoUtils.sliceRange(fromDate, toDate, MARKETO_MAX_RANGE_EXTRACT);
245
+ final Iterator<MarketoUtils.DateRange> iterator = dateRanges.iterator();
246
+ return new LineDecoderIterator(iterator, task);
247
+ }
248
+
249
+ @Override
250
+ protected final Iterator<ServiceRecord> getServiceRecords(MarketoService marketoService, T task)
251
+ {
252
+ throw new UnsupportedOperationException();
253
+ }
254
+
255
+ protected abstract InputStream getExtractedStream(MarketoService service, T task, OffsetDateTime fromDate, OffsetDateTime toDate);
256
+
257
+ private static class AllStringJacksonServiceRecord extends JacksonServiceRecord
258
+ {
259
+ public AllStringJacksonServiceRecord(ObjectNode record)
260
+ {
261
+ super(record);
262
+ }
263
+
264
+ @Override
265
+ public JacksonServiceValue getValue(ValueLocator locator)
266
+ {
267
+ // We know that this thing only contain text.
268
+ JacksonServiceValue value = super.getValue(locator);
269
+ return new StringConverterJacksonServiceRecord(value.stringValue());
270
+ }
271
+ }
272
+
273
+ private static class StringConverterJacksonServiceRecord extends JacksonServiceValue
274
+ {
275
+ private String textValue;
276
+
277
+ public StringConverterJacksonServiceRecord(String textValue)
278
+ {
279
+ super(null);
280
+ this.textValue = textValue;
281
+ }
282
+
283
+ @Override
284
+ public boolean isNull()
285
+ {
286
+ return textValue == null || textValue.equals("null");
287
+ }
288
+
289
+ @Override
290
+ public boolean booleanValue()
291
+ {
292
+ return Boolean.parseBoolean(textValue);
293
+ }
294
+
295
+ @Override
296
+ public double doubleValue()
297
+ {
298
+ return Double.parseDouble(textValue);
299
+ }
300
+
301
+ @Override
302
+ public Value jsonValue(JsonParser jsonParser)
303
+ {
304
+ return jsonParser.parse(textValue);
305
+ }
306
+
307
+ @Override
308
+ public long longValue()
309
+ {
310
+ return Long.parseLong(textValue);
311
+ }
312
+
313
+ @Override
314
+ public String stringValue()
315
+ {
316
+ return textValue;
317
+ }
318
+
319
+ @Override
320
+ public Timestamp timestampValue(TimestampParser timestampParser)
321
+ {
322
+ return timestampParser.parse(textValue);
323
+ }
324
+ }
325
+
326
+ private final class LineDecoderIterator implements Iterator<LineDecoder>, AutoCloseable
327
+ {
328
+ private LineDecoder currentLineDecoder;
329
+
330
+ private Iterator<MarketoUtils.DateRange> dateRangeIterator;
331
+
332
+ private MarketoService marketoService;
333
+
334
+ private MarketoRestClient marketoRestClient;
335
+ private T task;
336
+ public LineDecoderIterator(Iterator<MarketoUtils.DateRange> dateRangeIterator, T task)
337
+ {
338
+ marketoRestClient = createMarketoRestClient(task);
339
+ marketoService = new MarketoServiceImpl(marketoRestClient);
340
+ this.dateRangeIterator = dateRangeIterator;
341
+ this.task = task;
342
+ }
343
+
344
+ @Override
345
+ public void close()
346
+ {
347
+ if (currentLineDecoder != null) {
348
+ currentLineDecoder.close();
349
+ }
350
+ if (marketoRestClient != null) {
351
+ marketoRestClient.close();
352
+ }
353
+ }
354
+
355
+ @Override
356
+ public boolean hasNext()
357
+ {
358
+ return dateRangeIterator.hasNext();
359
+ }
360
+
361
+ @Override
362
+ public LineDecoder next()
363
+ {
364
+ if (hasNext()) {
365
+ MarketoUtils.DateRange next = dateRangeIterator.next();
366
+ InputStream extractedStream = getExtractedStream(marketoService, task, next.fromDate, next.toDate);
367
+ currentLineDecoder = new LineDecoder(new InputStreamFileInput(task.getBufferAllocator(), extractedStream), task);
368
+ return currentLineDecoder;
369
+ }
370
+ throw new NoSuchElementException();
371
+ }
372
+
373
+ @Override
374
+ public void remove()
375
+ {
376
+ throw new UnsupportedOperationException("Removed are not supported");
377
+ }
378
+ }
379
+
380
+ private class CsvRecordIterator implements Iterator<Map<String, String>>
381
+ {
382
+ private CsvTokenizer tokenizer;
383
+
384
+ private List<String> headers;
385
+
386
+ private Map<String, String> currentCsvRecord;
387
+ public CsvRecordIterator(LineDecoder lineDecoder, T task)
388
+ {
389
+ tokenizer = new CsvTokenizer(lineDecoder, task);
390
+ if (!tokenizer.nextFile()) {
391
+ throw new DataException("Can't read extract input stream");
392
+ }
393
+ headers = new ArrayList<>();
394
+ tokenizer.nextRecord();
395
+ while (tokenizer.hasNextColumn()) {
396
+ headers.add(tokenizer.nextColumn());
397
+ }
398
+ }
399
+
400
+ @Override
401
+ public boolean hasNext()
402
+ {
403
+ if (currentCsvRecord == null) {
404
+ currentCsvRecord = getNextCSVRecord();
405
+ }
406
+ return currentCsvRecord != null;
407
+ }
408
+
409
+ @Override
410
+ public Map<String, String> next()
411
+ {
412
+ try {
413
+ if (hasNext()) {
414
+ return currentCsvRecord;
415
+ }
416
+ }
417
+ finally {
418
+ currentCsvRecord = null;
419
+ }
420
+ throw new NoSuchElementException();
421
+ }
422
+
423
+ @Override
424
+ public void remove()
425
+ {
426
+ throw new UnsupportedOperationException();
427
+ }
428
+ private Map<String, String> getNextCSVRecord()
429
+ {
430
+ if (!tokenizer.nextRecord()) {
431
+ return null;
432
+ }
433
+ Map<String, String> kvMap = new HashMap<>();
434
+ try {
435
+ int i = 0;
436
+ while (tokenizer.hasNextColumn()) {
437
+ kvMap.put(headers.get(i), tokenizer.nextColumnOrNull());
438
+ i++;
439
+ }
440
+ }
441
+ catch (CsvTokenizer.InvalidValueException ex) {
442
+ throw new DataException("Encounter exception when parse csv file. Please check to see if you are using the correct" +
443
+ "quote or escape character.", ex);
444
+ }
445
+ return kvMap;
446
+ }
447
+ }
448
+ }