embulk-input-marketo 0.6.0 → 0.6.1.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +132 -12
  4. data/build.gradle +1 -1
  5. data/src/main/java/org/embulk/input/marketo/MarketoServiceImpl.java +115 -44
  6. data/src/main/java/org/embulk/input/marketo/MarketoUtils.java +83 -0
  7. data/src/main/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPlugin.java +17 -3
  8. data/src/main/java/org/embulk/input/marketo/model/BulkExtractRangeHeader.java +26 -0
  9. data/src/main/java/org/embulk/input/marketo/rest/MarketoBaseRestClient.java +8 -3
  10. data/src/main/java/org/embulk/input/marketo/rest/MarketoInputStreamResponseEntityReader.java +1 -1
  11. data/src/main/java/org/embulk/input/marketo/rest/MarketoResponseJetty92EntityReader.java +2 -4
  12. data/src/main/java/org/embulk/input/marketo/rest/MarketoRestClient.java +47 -20
  13. data/src/test/java/org/embulk/input/marketo/MarketoServiceImplTest.java +60 -57
  14. data/src/test/java/org/embulk/input/marketo/delegate/ActivityBulkExtractInputPluginTest.java +16 -15
  15. data/src/test/java/org/embulk/input/marketo/delegate/CampaignInputPluginTest.java +10 -10
  16. data/src/test/java/org/embulk/input/marketo/delegate/LeadBulkExtractInputPluginTest.java +22 -21
  17. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithListInputPluginTest.java +22 -20
  18. data/src/test/java/org/embulk/input/marketo/delegate/LeadWithProgramInputPluginTest.java +22 -20
  19. data/src/test/java/org/embulk/input/marketo/delegate/MarketoBaseBulkExtractInputPluginTest.java +27 -28
  20. data/src/test/java/org/embulk/input/marketo/rest/MarketoBaseRestClientTest.java +56 -57
  21. data/src/test/java/org/embulk/input/marketo/rest/MarketoRestClientTest.java +136 -125
  22. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64d9351af259f18fec636911fe392269ac1419be
4
- data.tar.gz: 1d0cc1b8dc270a739cac47e7e1623b7e29b1f50b
3
+ metadata.gz: ad07d4e0f62200bd2f12b4cdd7ed1dfc17542a7d
4
+ data.tar.gz: 1a52eb039bc6f9a1ed2f847681d1775e7d7f7d09
5
5
  SHA512:
6
- metadata.gz: ef07a774a24d7f1ca97c16204ee1e564dc26a7fcb70f2d1fc4f16edbb5446c1aefbc676ff3109eb1ec65eac028042fb16ffffde6455d00e93f05fa08001e4ab2
7
- data.tar.gz: a124d8beba158de3813190e26c4b9ad2db30b16a480364472cd3b9127282ba2a4a7b32b6be74676a927c417f159f5a891a4d4a675926f0b01ab1e02f3fd3bc04
6
+ metadata.gz: 8801d26caac30fab6ce0fbcb5f1e7df7dfaf58712e8273b5d11ae00c23e7cc70edf171efcd6d44e222412a54355b576b64dcdd0187dd94aee02494ada3df57df
7
+ data.tar.gz: a22b92dd76b7de41b386cf9a8a0763294a3d43d8daf5850797075da57fc95b46b4b1bb8acd3a90179314099d60d203d6cd59279450842978d598f1cde2b97ed0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.6.0 - 2017-10-10
2
+ * [major] Migrate to Java by embulk-base-restclient [#66](https://github.com/treasure-data/embulk-input-marketo/pull/66)
3
+ * [major] Migrate to REST API [#66](https://github.com/treasure-data/embulk-input-marketo/pull/66)
4
+ * [major] Add 3 more target, Campaign, Lead by list and Lead by program [#66](https://github.com/treasure-data/embulk-input-marketo/pull/66)
5
+ * [major] Support Marketo bulk extract API for lead and activity targets [#66](https://github.com/treasure-data/embulk-input-marketo/pull/66)
6
+ * [major] Support incremental ingestion for lead and activity targets [#66](https://github.com/treasure-data/embulk-input-marketo/pull/66)
7
+
8
+
9
+ ## 0.5.6 - 2016-12-14
10
+ * [maintenance] Enable tcp keepalive [#64](https://github.com/treasure-data/embulk-input-marketo/pull/64)
11
+
1
12
  ## 0.5.6 - 2016-12-14
2
13
  * [maintenance] Enable tcp keepalive [#64](https://github.com/treasure-data/embulk-input-marketo/pull/64)
3
14
 
data/README.md CHANGED
@@ -1,32 +1,152 @@
1
+ [![Build Status](https://travis-ci.org/treasure-data/embulk-input-marketo.svg?branch=master)](https://travis-ci.org/treasure-data/embulk-input-marketo)
2
+ [![Code Climate](https://codeclimate.com/github/treasure-data/embulk-input-marketo/badges/gpa.svg)](https://codeclimate.com/github/treasure-data/embulk-input-marketo)
3
+ [![Test Coverage](https://codeclimate.com/github/treasure-data/embulk-input-marketo/badges/coverage.svg)](https://codeclimate.com/github/treasure-data/embulk-input-marketo/coverage)
4
+ [![Gem Version](https://badge.fury.io/rb/embulk-input-marketo.svg)](http://badge.fury.io/rb/embulk-input-marketo)
5
+
1
6
  # Marketo input plugin for Embulk
2
7
 
3
- TODO: Write short description here and build.gradle file.
8
+ embulk-input-marketo is the gem preparing Embulk input plugins for [Marketo](http://www.marketo.com/).
9
+
10
+ - Lead(lead)
11
+ - Activity log(activity)
12
+ - Lead by list(all_lead_with_list_id)
13
+ - Lead by program(all_lead_with_program_id)
14
+ - Campaign(campaign)
15
+
16
+ This plugin uses Marketo REST API.
4
17
 
5
18
  ## Overview
6
19
 
20
+ Required Embulk version >= 0.8.33 (since 0.6.0).
21
+
7
22
  * **Plugin type**: input
8
- * **Resume supported**: yes
9
- * **Cleanup supported**: yes
23
+ * **Resume supported**: no
24
+ * **Cleanup supported**: no
10
25
  * **Guess supported**: no
11
26
 
27
+ ## Install
28
+
29
+ ```
30
+ $ embulk gem install embulk-input-marketo
31
+ ```
32
+
12
33
  ## Configuration
13
34
 
14
- - **option1**: description (integer, required)
15
- - **option2**: description (string, default: `"myvalue"`)
16
- - **option3**: description (string, default: `null`)
35
+ ### API
36
+
37
+ Below parameters are shown in "Admin" > "Web Services" page in Marketo.
38
+
39
+ ### Base configuration parameter
40
+
41
+ All target have this configuration parameters
42
+
43
+ | name | required | default value | description |
44
+ |----------------------------------|----------|---------------|----------------------------------------------------------------------------------------------------------------------------------|
45
+ | **target** | true | | Marketo targets |
46
+ | **account_id** | true | | Marketo Muchkin id |
47
+ | **client_id** | true | | Marketo REST client id |
48
+ | **client_secret** | true | | Marketo REST client secret |
49
+ | **marketo_limit_interval_milis** | false | 20 | Marketo have limitation of 100 calls per 20 second. If REST API calls are failed they will wait this amount of time before retry |
50
+ | **batch_size** | false | 300 | Token paging batch size. Some REST API support batch |
51
+ | **max_return** | false | 200 | Max return for Endpoint that use offset paging |
52
+
53
+ ### Bulk extract target configuration parameter (Lead and Activity)
54
+
55
+ All bulk extract target use this configuration parameter
56
+
57
+ | name | required | default value | description |
58
+ |-----------------------------|----------|---------------|-------------------------------------------------------------------------------------------------------------------------------|
59
+ | **from_date** | true | | Import data since this date. Example: 2017-10-11T06:43:24+00:00 |
60
+ | **fetch_days** | false | 1 | Ammount of days to fetch since from_date |
61
+ | **polling_interval_second** | false | 60 | Amount of time to wait between pooling job status in second |
62
+ | **bulk_job_timeout_second** | false | 3600 | Amount of time to wait for bulk job to complete in second |
63
+ | **incremental** | false | true | If incremental is set to true, next run will have from_date set to the previous to_date(calculated by from_date + fetch_days) |
64
+ | **incremental_column** | false | createdAt | Column use to filter from_date and to_date |
65
+
66
+ ### Lead
67
+
68
+ Lead target extract all Marketo leads, it use Marketo bulk extract feature. Configuration include bulk extract configuration.
69
+
70
+ `target: lead`
71
+
72
+ Configuration:
73
+
74
+ | name | required | default value | description |
75
+ |--------------------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
76
+ | **use_updated_at** | false | false | Lead data are not immutable so it better to do incremental ingesting with `updateAt` column, but not all Marketo Account have the feature to filter by updatedAt |
77
+
78
+ Schema type: Dynamic via describe lead endpoint.
79
+
80
+ Incremental support: yes
81
+
82
+ Range ingestion: yes
83
+
84
+ ### Activity
85
+
86
+ Activity target extract all Marketo actvity log. Configuration include all bulk extract configuraiont
87
+
88
+ `target: activity`
89
+
90
+ Schema type: Static schema
91
+
92
+ Incremental support: yes
93
+
94
+ Range ingestion: yes
95
+
96
+ ### Campaign
97
+
98
+ Campaign extract all campaign data from Marketo
99
+
100
+ `target: campaign`
101
+
102
+ Schema type: Static schema
103
+
104
+ Incremental support: no
105
+
106
+ Range ingestion: no
107
+
108
+ ### Lead by list
109
+
110
+ Extract all Lead data including lead's list id
111
+
112
+ `target: all_lead_with_list_id`
113
+
114
+ Schema type: Dynamic via describe leads. Schema will have 1 addition column name listId that contain the id of the list the lead belong to
115
+
116
+ Incremental support: no
117
+
118
+ Range ingestion: no
119
+
120
+ ### Laed by program
121
+
122
+ Extract all Lead data including lead's program id
123
+
124
+ `target: all_lead_with_program_id`
125
+
126
+ Schema type: Dynamic via describe leads. Schema will have 1 addition column name listId that contain the id of the list the lead belong to
127
+
128
+ Incremental support: no
129
+
130
+ Range ingestion: no
17
131
 
18
132
  ## Example
19
133
 
134
+ For lead, you have `partial-config.yml` like below:
135
+
20
136
  ```yaml
21
137
  in:
22
138
  type: marketo
23
- option1: example1
24
- option2: example2
139
+ target: lead
140
+ account_id: ACCOUNT_ID
141
+ client_id: CLIENT_ID
142
+ client_secret: CLIENT_SECRET
143
+ from_date: 2017-09-01
144
+ fetch_days: 1
145
+ out:
146
+ type: stdout
25
147
  ```
26
148
 
149
+ You can run `embulk guess partial-config.yml -o lead-config.yml` and got `lead-config.yml`. `lead-config.yml` includes a schema for Lead.
27
150
 
28
- ## Build
151
+ Next, you can run `embulk preview lead-config.yml` for preview and `embulk run lead-config.yml` for run.
29
152
 
30
- ```
31
- $ ./gradlew gem # -t to watch change of files and rebuild continuously
32
- ```
data/build.gradle CHANGED
@@ -16,7 +16,7 @@ repositories {
16
16
  configurations {
17
17
  provided
18
18
  }
19
- version = "0.6.0"
19
+ version = "0.6.1.alpha.1"
20
20
  sourceCompatibility = 1.7
21
21
  targetCompatibility = 1.7
22
22
 
@@ -3,7 +3,8 @@ package org.embulk.input.marketo;
3
3
  import com.fasterxml.jackson.databind.node.ObjectNode;
4
4
  import com.google.common.base.Function;
5
5
  import com.google.common.collect.Iterables;
6
- import com.google.common.io.ByteStreams;
6
+ import org.apache.commons.lang3.StringUtils;
7
+ import org.embulk.input.marketo.model.BulkExtractRangeHeader;
7
8
  import org.embulk.input.marketo.model.MarketoField;
8
9
  import org.embulk.input.marketo.rest.MarketoRestClient;
9
10
  import org.embulk.input.marketo.rest.RecordPagingIterable;
@@ -11,14 +12,11 @@ import org.embulk.spi.DataException;
11
12
  import org.embulk.spi.Exec;
12
13
  import org.slf4j.Logger;
13
14
 
14
- import javax.annotation.Nullable;
15
-
16
15
  import java.io.File;
17
16
  import java.io.FileOutputStream;
18
17
  import java.io.IOException;
19
18
  import java.io.InputStream;
20
19
  import java.io.OutputStream;
21
- import java.util.ArrayList;
22
20
  import java.util.Date;
23
21
  import java.util.List;
24
22
 
@@ -31,6 +29,10 @@ public class MarketoServiceImpl implements MarketoService
31
29
 
32
30
  private static final String DEFAULT_FILE_FORMAT = "csv";
33
31
 
32
+ private static final int BUF_SIZE = 0x1000;
33
+
34
+ private static final int MAX_RESUME_TIME = 50;
35
+
34
36
  private MarketoRestClient marketoRestClient;
35
37
 
36
38
  public MarketoServiceImpl(MarketoRestClient marketoRestClient)
@@ -39,9 +41,9 @@ public class MarketoServiceImpl implements MarketoService
39
41
  }
40
42
 
41
43
  @Override
42
- public File extractLead(Date startTime, Date endTime, List<String> extractedFields, String filterField, int pollingTimeIntervalSecond, int bulkJobTimeoutSecond)
44
+ public File extractLead(final Date startTime, Date endTime, List<String> extractedFields, String filterField, int pollingTimeIntervalSecond, final int bulkJobTimeoutSecond)
43
45
  {
44
- String exportID = marketoRestClient.createLeadBulkExtract(startTime, endTime, extractedFields, filterField);
46
+ final String exportID = marketoRestClient.createLeadBulkExtract(startTime, endTime, extractedFields, filterField);
45
47
  marketoRestClient.startLeadBulkExtract(exportID);
46
48
  try {
47
49
  marketoRestClient.waitLeadExportJobComplete(exportID, pollingTimeIntervalSecond, bulkJobTimeoutSecond);
@@ -50,27 +52,41 @@ public class MarketoServiceImpl implements MarketoService
50
52
  LOGGER.error("Exception when waiting for export job id: {}", exportID, e);
51
53
  throw new DataException("Error when wait for bulk extract");
52
54
  }
53
- InputStream leadBulkExtractResult = marketoRestClient.getLeadBulkExtractResult(exportID);
54
- return saveExtractedFile(exportID, leadBulkExtractResult);
55
+ return downloadBulkExtract(new Function<BulkExtractRangeHeader, InputStream>()
56
+ {
57
+ @Override
58
+ public InputStream apply(BulkExtractRangeHeader bulkExtractRangeHeader)
59
+ {
60
+ return marketoRestClient.getLeadBulkExtractResult(exportID, bulkExtractRangeHeader);
61
+ }
62
+ });
55
63
  }
56
64
 
57
- private File saveExtractedFile(String exportID, InputStream leadBulkExtractResult)
65
+ private long saveExtractedFile(InputStream extractResult, File tempFile) throws DownloadBulkExtractException
58
66
  {
59
- File tempFile = Exec.getTempFileSpace().createTempFile(DEFAULT_FILE_FORMAT);
60
- try (OutputStream fileOuputStream = new FileOutputStream(tempFile)) {
61
- ByteStreams.copy(leadBulkExtractResult, fileOuputStream);
67
+ long total = 0;
68
+ try (OutputStream fileOuputStream = new FileOutputStream(tempFile, true)) {
69
+ byte[] buf = new byte[BUF_SIZE];
70
+ while (true) {
71
+ int r = extractResult.read(buf);
72
+ if (r == -1) {
73
+ break;
74
+ }
75
+ fileOuputStream.write(buf, 0, r);
76
+ total += r;
77
+ }
62
78
  }
63
79
  catch (IOException e) {
64
- LOGGER.error("Encounter exception when download bulk extract file, job id [{}]", exportID, e);
65
- throw new DataException("Can't download bulk extract file");
80
+ LOGGER.error("Encounter exception when download bulk extract file", e);
81
+ throw new DownloadBulkExtractException("Encounter exception when download bulk extract file", e, total);
66
82
  }
67
- return tempFile;
83
+ return total;
68
84
  }
69
85
 
70
86
  @Override
71
87
  public File extractAllActivity(Date startTime, Date endTime, int pollingTimeIntervalSecond, int bulkJobTimeoutSecond)
72
88
  {
73
- String exportID = marketoRestClient.createActivityExtract(startTime, endTime);
89
+ final String exportID = marketoRestClient.createActivityExtract(startTime, endTime);
74
90
  marketoRestClient.startActitvityBulkExtract(exportID);
75
91
  try {
76
92
  marketoRestClient.waitActitvityExportJobComplete(exportID, pollingTimeIntervalSecond, bulkJobTimeoutSecond);
@@ -79,49 +95,82 @@ public class MarketoServiceImpl implements MarketoService
79
95
  LOGGER.error("Exception when waiting for export job id: {}", exportID, e);
80
96
  throw new DataException("Error when wait for bulk extract");
81
97
  }
82
- InputStream activitiesBulkExtractResult = marketoRestClient.getActivitiesBulkExtractResult(exportID);
83
- return saveExtractedFile(exportID, activitiesBulkExtractResult);
98
+ return downloadBulkExtract(new Function<BulkExtractRangeHeader, InputStream>()
99
+ {
100
+ @Override
101
+ public InputStream apply(BulkExtractRangeHeader bulkExtractRangeHeader)
102
+ {
103
+ return marketoRestClient.getActivitiesBulkExtractResult(exportID, bulkExtractRangeHeader);
104
+ }
105
+ });
106
+ }
107
+ private File downloadBulkExtract(Function<BulkExtractRangeHeader, InputStream> getBulkExtractfunction)
108
+ {
109
+ final File tempFile = Exec.getTempFileSpace().createTempFile(DEFAULT_FILE_FORMAT);
110
+ long startByte = 0;
111
+ int resumeTime = 0;
112
+ while (resumeTime < MAX_RESUME_TIME) {
113
+ BulkExtractRangeHeader bulkExtractRangeHeader = new BulkExtractRangeHeader(startByte);
114
+ InputStream bulkExtractResult = getBulkExtractfunction.apply(bulkExtractRangeHeader);
115
+ try {
116
+ saveExtractedFile(bulkExtractResult, tempFile);
117
+ return tempFile;
118
+ }
119
+ catch (DownloadBulkExtractException e) {
120
+ startByte = startByte + e.getByteWritten();
121
+ LOGGER.warn("will resume activity bulk extract at byte [{}]", startByte);
122
+ }
123
+ resumeTime = resumeTime + 1;
124
+ }
125
+ //Too many resume we still can't get the file
126
+ throw new DataException("Can't down load bulk extract");
84
127
  }
85
-
86
128
  @Override
87
129
  public Iterable<ObjectNode> getAllListLead(List<String> fieldNames)
88
130
  {
89
131
  RecordPagingIterable<ObjectNode> lists = marketoRestClient.getLists();
90
- List<Iterable<ObjectNode>> iterables = new ArrayList<>();
91
- for (ObjectNode node : lists) {
92
- final String id = node.get("id").asText();
93
- iterables.add(Iterables.transform(marketoRestClient.getLeadsByList(id, fieldNames), new Function<ObjectNode, ObjectNode>()
132
+ final String fieldNameString = StringUtils.join(fieldNames, ",");
133
+ return MarketoUtils.flatMap(lists, new Function<ObjectNode, Iterable<ObjectNode>>()
134
+ {
135
+ @Override
136
+ public Iterable<ObjectNode> apply(ObjectNode input)
94
137
  {
95
- @Override
96
- public ObjectNode apply(ObjectNode input)
138
+ final String id = input.get("id").asText();
139
+ return Iterables.transform(marketoRestClient.getLeadsByList(id, fieldNameString), new Function<ObjectNode, ObjectNode>()
97
140
  {
98
- input.put(MarketoUtils.LIST_ID_COLUMN_NAME, id);
99
- return input;
100
- }
101
- }));
102
- }
103
- return Iterables.concat(iterables);
141
+ @Override
142
+ public ObjectNode apply(ObjectNode input)
143
+ {
144
+ input.put(MarketoUtils.LIST_ID_COLUMN_NAME, id);
145
+ return input;
146
+ }
147
+ });
148
+ }
149
+ });
104
150
  }
105
151
 
106
152
  @Override
107
153
  public Iterable<ObjectNode> getAllProgramLead(List<String> fieldNames)
108
154
  {
109
155
  RecordPagingIterable<ObjectNode> lists = marketoRestClient.getPrograms();
110
- List<Iterable<ObjectNode>> iterables = new ArrayList<>();
111
- for (ObjectNode node : lists) {
112
- final String id = node.get("id").asText();
113
- iterables.add(Iterables.transform(marketoRestClient.getLeadsByProgram(id, fieldNames), new Function<ObjectNode, ObjectNode>()
156
+ final String fieldNameString = StringUtils.join(fieldNames, ",");
157
+ return MarketoUtils.flatMap(lists, new Function<ObjectNode, Iterable<ObjectNode>>()
158
+ {
159
+ @Override
160
+ public Iterable<ObjectNode> apply(ObjectNode input)
114
161
  {
115
- @Nullable
116
- @Override
117
- public ObjectNode apply(@Nullable ObjectNode input)
162
+ final String id = input.get("id").asText();
163
+ return Iterables.transform(marketoRestClient.getLeadsByProgram(id, fieldNameString), new Function<ObjectNode, ObjectNode>()
118
164
  {
119
- input.put(MarketoUtils.PROGRAM_ID_COLUMN_NAME, id);
120
- return input;
121
- }
122
- }));
123
- }
124
- return Iterables.concat(iterables);
165
+ @Override
166
+ public ObjectNode apply(ObjectNode input)
167
+ {
168
+ input.put(MarketoUtils.PROGRAM_ID_COLUMN_NAME, id);
169
+ return input;
170
+ }
171
+ });
172
+ }
173
+ });
125
174
  }
126
175
 
127
176
  @Override
@@ -151,4 +200,26 @@ public class MarketoServiceImpl implements MarketoService
151
200
  columns.add(new MarketoField(MarketoUtils.LIST_ID_COLUMN_NAME, MarketoField.MarketoDataType.STRING));
152
201
  return columns;
153
202
  }
203
+
204
+ private static class DownloadBulkExtractException extends Exception
205
+ {
206
+ private final long byteWritten;
207
+
208
+ public DownloadBulkExtractException(String message, Throwable cause, long byteWritten)
209
+ {
210
+ super(message, cause);
211
+ this.byteWritten = byteWritten;
212
+ }
213
+
214
+ public DownloadBulkExtractException(Throwable cause, long byteWritten)
215
+ {
216
+ super(cause);
217
+ this.byteWritten = byteWritten;
218
+ }
219
+
220
+ public long getByteWritten()
221
+ {
222
+ return byteWritten;
223
+ }
224
+ }
154
225
  }
@@ -11,12 +11,17 @@ import org.embulk.base.restclient.jackson.JacksonTopLevelValueLocator;
11
11
  import org.embulk.base.restclient.record.ServiceRecord;
12
12
  import org.embulk.base.restclient.record.ValueLocator;
13
13
  import org.embulk.input.marketo.model.MarketoField;
14
+ import org.embulk.spi.Exec;
15
+ import org.embulk.spi.util.RetryExecutor;
14
16
  import org.joda.time.DateTime;
17
+ import org.slf4j.Logger;
15
18
 
16
19
  import javax.annotation.Nullable;
17
20
 
18
21
  import java.util.ArrayList;
22
+ import java.util.Iterator;
19
23
  import java.util.List;
24
+ import java.util.NoSuchElementException;
20
25
  import java.util.Set;
21
26
 
22
27
  /**
@@ -126,4 +131,82 @@ public class MarketoUtils
126
131
  '}';
127
132
  }
128
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
+ }
129
212
  }