diff --git a/pom.xml b/pom.xml
index 339fd94..59aeb5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,7 @@
2.0.0
2.27.2
2.7.0
+ 3.3.2
${project.basedir}/src/test/java/
UTF-8
UTF-8
@@ -61,7 +62,11 @@
-
+
+ dev.failsafe
+ failsafe
+ ${failsafe.version}
+
io.cdap.cdap
cdap-api
@@ -365,12 +370,6 @@
2.0.2
test
-
- com.github.rholder
- guava-retrying
- 2.0.0
-
-
diff --git a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java
index da10995..c377536 100644
--- a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java
+++ b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnector.java
@@ -151,7 +151,7 @@ List listEntities() throws TransportException, IOException {
URL dataURL = HttpUrl.parse(config.getBaseURL()).newBuilder().build().url();
SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config);
SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient.callSuccessFactorsEntity
- (dataURL, MediaType.APPLICATION_JSON, METADATA);
+ (dataURL, MediaType.APPLICATION_JSON);
try (InputStream inputStream = responseContainer.getResponseStream()) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String result = reader.lines().collect(Collectors.joining(""));
@@ -225,7 +225,11 @@ private InputStream callEntityData(long top, String entityName)
addQueryParameter(TOP_OPTION, String.valueOf(top)).addQueryParameter(SELECT_OPTION, selectFields.toString())
.build().url();
SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config);
- SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient.callSuccessFactorsWithRetry(dataURL);
+ SuccessFactorsResponseContainer responseContainer =
+ successFactorsHttpClient.callSuccessFactorsWithRetry(
+ dataURL, MediaType.APPLICATION_JSON, SuccessFactorsPluginConfig.DEFAULT_INITIAL_RETRY_DURATION_SECONDS,
+ SuccessFactorsPluginConfig.DEFAULT_MAX_RETRY_DURATION_SECONDS,
+ SuccessFactorsPluginConfig.DEFAULT_RETRY_MULTIPLIER, SuccessFactorsPluginConfig.DEFAULT_MAX_RETRY_COUNT);
ExceptionParser.checkAndThrowException("", responseContainer);
return responseContainer.getResponseStream();
@@ -255,7 +259,7 @@ private InputStream getMetaDataStream(String entity) throws TransportException,
.addPathSegment(METADATACALL).build().url();
SuccessFactorsTransporter successFactorsHttpClient = new SuccessFactorsTransporter(config);
SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient
- .callSuccessFactorsEntity(metadataURL, MediaType.APPLICATION_XML, METADATA);
+ .callSuccessFactorsEntity(metadataURL, MediaType.APPLICATION_XML);
return responseContainer.getResponseStream();
}
}
diff --git a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java
index 02bac6c..57377f3 100644
--- a/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java
+++ b/src/main/java/io/cdap/plugin/successfactors/connector/SuccessFactorsConnectorConfig.java
@@ -20,7 +20,6 @@
import io.cdap.cdap.api.annotation.Name;
import io.cdap.cdap.api.plugin.PluginConfig;
import io.cdap.cdap.etl.api.FailureCollector;
-import io.cdap.plugin.successfactors.common.exception.SuccessFactorsServiceException;
import io.cdap.plugin.successfactors.common.exception.TransportException;
import io.cdap.plugin.successfactors.common.util.ResourceConstants;
import io.cdap.plugin.successfactors.common.util.SuccessFactorsUtil;
@@ -153,7 +152,7 @@ public void validateConnection(FailureCollector collector) {
SuccessFactorsResponseContainer responseContainer = null;
try {
responseContainer =
- successFactorsHttpClient.callSuccessFactorsEntity(testerURL, MediaType.APPLICATION_JSON, TEST);
+ successFactorsHttpClient.callSuccessFactorsEntity(testerURL, MediaType.APPLICATION_JSON);
} catch (TransportException e) {
LOG.error("Unable to fetch the response", e);
collector.addFailure("Unable to call SuccessFactorsEntity",
diff --git a/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java b/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java
index 62849fa..9aab7aa 100644
--- a/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java
+++ b/src/main/java/io/cdap/plugin/successfactors/source/config/SuccessFactorsPluginConfig.java
@@ -54,6 +54,14 @@ public class SuccessFactorsPluginConfig extends PluginConfig {
private static final String COMMON_ACTION = ResourceConstants.ERR_MISSING_PARAM_OR_MACRO_ACTION.getMsgForKey();
private static final Pattern PATTERN = Pattern.compile("\\(.*\\)");
private static final String SAP_SUCCESSFACTORS_ENTITY_NAME = "Entity Name";
+ private static final String NAME_INITIAL_RETRY_DURATION = "initialRetryDuration";
+ private static final String NAME_MAX_RETRY_DURATION = "maxRetryDuration";
+ private static final String NAME_RETRY_MULTIPLIER = "retryMultiplier";
+ private static final String NAME_MAX_RETRY_COUNT = "maxRetryCount";
+ public static final int DEFAULT_INITIAL_RETRY_DURATION_SECONDS = 2;
+ public static final int DEFAULT_RETRY_MULTIPLIER = 2;
+ public static final int DEFAULT_MAX_RETRY_COUNT = 3;
+ public static final int DEFAULT_MAX_RETRY_DURATION_SECONDS = 10;
@Macro
@Name(ENTITY_NAME)
@@ -128,6 +136,30 @@ public class SuccessFactorsPluginConfig extends PluginConfig {
@Description("The existing connection to use.")
private SuccessFactorsConnectorConfig connection;
+ @Name(NAME_INITIAL_RETRY_DURATION)
+ @Description("Time taken for the first retry. Default is 2 seconds.")
+ @Nullable
+ @Macro
+ private Integer initialRetryDuration;
+
+ @Name(NAME_MAX_RETRY_DURATION)
+ @Description("Maximum time in seconds retries can take. Default is 300 seconds.")
+ @Nullable
+ @Macro
+ private Integer maxRetryDuration;
+
+ @Name(NAME_MAX_RETRY_COUNT)
+ @Description("Maximum number of retries allowed. Default is 3.")
+ @Nullable
+ @Macro
+ private Integer maxRetryCount;
+
+ @Name(NAME_RETRY_MULTIPLIER)
+ @Description("Multiplier for exponential backoff. Default is 2.")
+ @Nullable
+ @Macro
+ private Integer retryMultiplier;
+
@VisibleForTesting
public SuccessFactorsPluginConfig(String referenceName,
String baseURL,
@@ -142,7 +174,11 @@ public SuccessFactorsPluginConfig(String referenceName,
@Nullable String selectOption,
@Nullable String expandOption,
@Nullable String additionalQueryParameters,
- String paginationType) {
+ String paginationType,
+ @Nullable Integer initialRetryDuration,
+ @Nullable Integer maxRetryDuration,
+ @Nullable Integer retryMultiplier,
+ @Nullable Integer maxRetryCount) {
this.connection = new SuccessFactorsConnectorConfig(username, password, baseURL, proxyUrl, proxyPassword,
proxyUsername);
this.referenceName = referenceName;
@@ -153,6 +189,10 @@ public SuccessFactorsPluginConfig(String referenceName,
this.expandOption = expandOption;
this.paginationType = paginationType;
this.additionalQueryParameters = additionalQueryParameters;
+ this.initialRetryDuration = initialRetryDuration;
+ this.maxRetryDuration = maxRetryDuration;
+ this.retryMultiplier = retryMultiplier;
+ this.maxRetryCount = maxRetryCount;
}
@Nullable
public SuccessFactorsConnectorConfig getConnection() {
@@ -210,6 +250,22 @@ public String getAdditionalQueryParameters() {
return this.additionalQueryParameters;
}
+ public int getInitialRetryDuration() {
+ return initialRetryDuration == null ? DEFAULT_INITIAL_RETRY_DURATION_SECONDS : initialRetryDuration;
+ }
+
+ public int getMaxRetryDuration() {
+ return maxRetryDuration == null ? DEFAULT_MAX_RETRY_DURATION_SECONDS : maxRetryDuration;
+ }
+
+ public int getRetryMultiplier() {
+ return retryMultiplier == null ? DEFAULT_RETRY_MULTIPLIER : retryMultiplier;
+ }
+
+ public int getMaxRetryCount() {
+ return maxRetryCount == null ? DEFAULT_MAX_RETRY_COUNT : maxRetryCount;
+ }
+
/**
* Checks if the call to SuccessFactors service is required for metadata creation.
* condition parameters: ['host' | 'serviceName' | 'entityName' | 'username' | 'password']
@@ -243,6 +299,7 @@ public void validatePluginParameters(FailureCollector failureCollector) {
validateMandatoryParameters(failureCollector);
validateBasicCredentials(failureCollector);
validateEntityParameter(failureCollector);
+ validateRetryConfiguration(failureCollector);
failureCollector.getOrThrowException();
}
@@ -292,6 +349,43 @@ private void validateEntityParameter(FailureCollector failureCollector) {
}
}
+ /**
+ * Validates the retry configuration.
+ *
+ * @param failureCollector {@code FailureCollector}
+ */
+ public void validateRetryConfiguration(FailureCollector failureCollector) {
+ if (containsMacro(NAME_INITIAL_RETRY_DURATION) || containsMacro(NAME_MAX_RETRY_DURATION) ||
+ containsMacro(NAME_MAX_RETRY_COUNT) || containsMacro(NAME_RETRY_MULTIPLIER)) {
+ return;
+ }
+ if (initialRetryDuration != null && initialRetryDuration <= 0) {
+ failureCollector.addFailure("Initial retry duration must be greater than 0.",
+ "Please specify a valid initial retry duration.")
+ .withConfigProperty(NAME_INITIAL_RETRY_DURATION);
+ }
+ if (maxRetryDuration != null && maxRetryDuration <= 0) {
+ failureCollector.addFailure("Max retry duration must be greater than 0.",
+ "Please specify a valid max retry duration.")
+ .withConfigProperty(NAME_MAX_RETRY_DURATION);
+ }
+ if (maxRetryCount != null && maxRetryCount <= 0) {
+ failureCollector.addFailure("Max retry count must be greater than 0.",
+ "Please specify a valid max retry count.")
+ .withConfigProperty(NAME_MAX_RETRY_COUNT);
+ }
+ if (retryMultiplier != null && retryMultiplier <= 1) {
+ failureCollector.addFailure("Retry multiplier must be strictly greater than 1.",
+ "Please specify a valid retry multiplier.")
+ .withConfigProperty(NAME_RETRY_MULTIPLIER);
+ }
+ if (maxRetryDuration != null && initialRetryDuration != null && maxRetryDuration <= initialRetryDuration) {
+ failureCollector.addFailure("Max retry duration must be greater than initial retry duration.",
+ "Please specify a valid max retry duration.")
+ .withConfigProperty(NAME_MAX_RETRY_DURATION);
+ }
+ }
+
/**
* Helper class to simplify {@link SuccessFactorsPluginConfig} class creation.
*/
@@ -310,6 +404,10 @@ public static class Builder {
private String proxyUrl;
private String proxyUsername;
private String proxyPassword;
+ private Integer initialRetryDuration;
+ private Integer maxRetryDuration;
+ private Integer retryMultiplier;
+ private Integer maxRetryCount;
public Builder referenceName(String referenceName) {
this.referenceName = referenceName;
@@ -379,11 +477,29 @@ public Builder additionalQueryParameters(@Nullable String additionalQueryParamet
return this;
}
+ public Builder setInitialRetryDuration(Integer initialRetryDuration) {
+ this.initialRetryDuration = initialRetryDuration;
+ return this;
+ }
+ public Builder setMaxRetryDuration(Integer maxRetryDuration) {
+ this.maxRetryDuration = maxRetryDuration;
+ return this;
+ }
+ public Builder setRetryMultiplier(Integer retryMultiplier) {
+ this.retryMultiplier = retryMultiplier;
+ return this;
+ }
+ public Builder setMaxRetryCount(Integer maxRetryCount) {
+ this.maxRetryCount = maxRetryCount;
+ return this;
+ }
+
public SuccessFactorsPluginConfig build() {
return new SuccessFactorsPluginConfig(referenceName, baseURL, entityName, associateEntityName, username,
password, proxyUrl, proxyUsername, proxyPassword,
filterOption, selectOption, expandOption, additionalQueryParameters,
- paginationType);
+ paginationType, initialRetryDuration, maxRetryDuration,
+ retryMultiplier, maxRetryCount);
}
}
}
diff --git a/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java b/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
index 0780dc6..0e21ed6 100644
--- a/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
+++ b/src/main/java/io/cdap/plugin/successfactors/source/service/SuccessFactorsService.java
@@ -97,7 +97,7 @@ public SuccessFactorsService(SuccessFactorsPluginConfig pluginConfig,
public void checkSuccessFactorsURL() throws TransportException, SuccessFactorsServiceException {
SuccessFactorsResponseContainer responseContainer =
- successFactorsHttpClient.callSuccessFactorsEntity(urlContainer.getTesterURL(), MediaType.APPLICATION_JSON, TEST);
+ successFactorsHttpClient.callSuccessFactorsEntity(urlContainer.getTesterURL(), MediaType.APPLICATION_JSON);
ExceptionParser.checkAndThrowException(ResourceConstants.ERR_FAILED_ENTITY_VALIDATION.getMsgForKey(),
responseContainer);
@@ -163,7 +163,9 @@ private SuccessFactorsEntityProvider fetchServiceMetadata(InputStream metadataSt
*/
private InputStream callEntityMetadata() throws TransportException {
SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient
- .callSuccessFactorsEntity(urlContainer.getMetadataURL(), MediaType.APPLICATION_XML, METADATA);
+ .callSuccessFactorsWithRetry(urlContainer.getMetadataURL(), MediaType.APPLICATION_XML, pluginConfig
+ .getInitialRetryDuration(), pluginConfig.getMaxRetryDuration(), pluginConfig.getRetryMultiplier(),
+ pluginConfig.getMaxRetryCount());
return responseContainer.getResponseStream();
}
@@ -320,7 +322,10 @@ private InputStream callEntityData(@Nullable Long skip, @Nullable Long top)
} else {
dataURL = urlContainer.getDataFetchURL(skip, top);
}
- SuccessFactorsResponseContainer responseContainer = successFactorsHttpClient.callSuccessFactorsWithRetry(dataURL);
+ SuccessFactorsResponseContainer responseContainer =
+ successFactorsHttpClient.callSuccessFactorsWithRetry(
+ dataURL, MediaType.APPLICATION_JSON, pluginConfig.getInitialRetryDuration(), pluginConfig.getMaxRetryDuration(),
+ pluginConfig.getRetryMultiplier(), pluginConfig.getMaxRetryCount());
ExceptionParser.checkAndThrowException("", responseContainer);
return responseContainer.getResponseStream();
@@ -337,7 +342,7 @@ public List getNonNavigationalProperties() throws TransportException, Su
/**
* Filter the data stream after removing the expanded entity data.
- *
+ *
* Data stream after conversion to JSON has the following format:
* "d": {
* "results": [
diff --git a/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java b/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java
index 730a50a..9b85b0c 100644
--- a/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java
+++ b/src/main/java/io/cdap/plugin/successfactors/source/transport/SuccessFactorsTransporter.java
@@ -16,11 +16,9 @@
package io.cdap.plugin.successfactors.source.transport;
-import com.github.rholder.retry.RetryException;
-import com.github.rholder.retry.Retryer;
-import com.github.rholder.retry.RetryerBuilder;
-import com.github.rholder.retry.StopStrategies;
-import com.github.rholder.retry.WaitStrategies;
+import dev.failsafe.Failsafe;
+import dev.failsafe.FailsafeException;
+import dev.failsafe.RetryPolicy;
import io.cdap.cdap.api.retry.RetryableException;
import io.cdap.plugin.successfactors.common.exception.TransportException;
import io.cdap.plugin.successfactors.common.util.ResourceConstants;
@@ -43,13 +41,10 @@
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.Base64;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import javax.ws.rs.core.MediaType;
-
/**
* This {@code SuccessFactorsTransporter} class is used to
* make a rest web service call to the SAP SuccessFactors exposed services.
@@ -58,8 +53,6 @@ public class SuccessFactorsTransporter {
static final String SERVICE_VERSION = "dataserviceversion";
private static final Logger LOG = LoggerFactory.getLogger(SuccessFactorsTransporter.class);
private static final long CONNECTION_TIMEOUT = 300;
- private static final long WAIT_TIME = 5;
- private static final long MAX_NUMBER_OF_RETRY_ATTEMPTS = 5;
private SuccessFactorsConnectorConfig config;
private Response response;
@@ -77,11 +70,10 @@ public SuccessFactorsTransporter(SuccessFactorsConnectorConfig pluginConfig) {
*
* @param endpoint type of URL
* @param mediaType mediaType for Accept header property, supported types are 'application/json' & 'application/xml'
- * @param fetchType type of call i.e. TEST / METADATA / COUNT, used for logging purpose.
* @return {@code SuccessFactorsResponseContainer}
* @throws TransportException any http client exceptions are wrapped under it
*/
- public SuccessFactorsResponseContainer callSuccessFactorsEntity(URL endpoint, String mediaType, String fetchType)
+ public SuccessFactorsResponseContainer callSuccessFactorsEntity(URL endpoint, String mediaType)
throws TransportException {
try {
@@ -100,13 +92,27 @@ public SuccessFactorsResponseContainer callSuccessFactorsEntity(URL endpoint, St
*
* @param endpoint record fetch URL
* @return {@code SuccessFactorsResponseContainer}
- * @throws IOException any http client exceptions
* @throws TransportException any error while preparing the {@code OkHttpClient}
*/
- public SuccessFactorsResponseContainer callSuccessFactorsWithRetry(URL endpoint)
- throws IOException, TransportException {
-
- Response res = retrySapTransportCall(endpoint, MediaType.APPLICATION_JSON);
+ public SuccessFactorsResponseContainer callSuccessFactorsWithRetry(URL endpoint, String mediaType,
+ int initialRetryDuration, int maxRetryDuration,
+ int retryMultiplier, int maxRetryCount)
+ throws TransportException {
+ LOG.debug(
+ "Retrying the call to SuccessFactors with initialRetryDuration: {}, maxRetryDuration: {}, retryMultiplier: {}, "
+ + "maxRetryCount: {}",
+ initialRetryDuration, maxRetryDuration, retryMultiplier, maxRetryCount);
+ LOG.debug("Endpoint: {}, MediaType: {}", endpoint, mediaType);
+ Response res;
+ try {
+ res = Failsafe.with(getRetryPolicy(initialRetryDuration, maxRetryDuration, retryMultiplier, maxRetryCount))
+ .get(() -> retrySapTransportCall(endpoint, mediaType));
+ } catch (FailsafeException e) {
+ if (e.getCause() != null) {
+ throw new RuntimeException(e.getCause());
+ }
+ throw e;
+ }
try {
return prepareResponseContainer(res);
@@ -122,33 +128,39 @@ public SuccessFactorsResponseContainer callSuccessFactorsWithRetry(URL endpoint)
* @param endpoint record fetch URL
* @param mediaType mediaType for Accept header property
* @return {@code Response}
- * @throws IOException if all retries fail
*/
- public Response retrySapTransportCall(URL endpoint, String mediaType) throws IOException {
- Callable fetchRecords = () -> {
+ public Response retrySapTransportCall(URL endpoint, String mediaType) {
+ try {
response = transport(endpoint, mediaType);
if (response != null && response.code() >= HttpURLConnection.HTTP_INTERNAL_ERROR) {
throw new RetryableException();
}
- return true;
- };
-
- Retryer retryer = RetryerBuilder.newBuilder()
- .retryIfExceptionOfType(RetryableException.class)
- .withWaitStrategy(WaitStrategies.exponentialWait(WAIT_TIME, TimeUnit.SECONDS))
- .withStopStrategy(StopStrategies.stopAfterAttempt((int) MAX_NUMBER_OF_RETRY_ATTEMPTS))
- .build();
-
- try {
- retryer.call(fetchRecords);
- } catch (RetryException | ExecutionException e) {
+ } catch (Exception e) {
LOG.error("Data Recovery failed for URL {}.", endpoint);
- throw new IOException();
+ if (e instanceof RetryableException) {
+ throw (RetryableException) e;
+ } else if (e instanceof IOException) {
+ throw new RetryableException("IOException occurred while calling SuccessFactors.");
+ } else {
+ throw new RuntimeException(e);
+ }
}
-
return response;
}
+ private RetryPolicy