diff --git a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java index b3bac24c..579b1017 100644 --- a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java +++ b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java @@ -11,21 +11,57 @@ */ package com.adobe.aio.util.feign; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.adobe.aio.util.JacksonUtil; import feign.Feign; +import feign.Logger; import feign.Logger.Level; import feign.Request; +import feign.Retryer; import feign.form.FormEncoder; import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; import feign.optionals.OptionalDecoder; import feign.slf4j.Slf4jLogger; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; import java.util.concurrent.TimeUnit; public class FeignUtil { - public static final int DEFAULT_CONNECT_TIMEOUT_IN_SECONDS = 10; - public static final int DEFAULT_READ_TIMEOUT_IN_SECONDS = 60; + /** + * Environment variable to set the Feign logger class. + * The value should be the fully qualified class name of the logger to use. + * The name should be the result of calling {@link Class#getName()} on the logger class. + * @see feign.Logger + */ + public static final String AIO_FEIGN_LOGGER_CLASS = "AIO_FEIGN_LOGGER_CLASS"; + /** + * Environment variable to set the log level for Feign clients. + * The value should be one of the following: NONE, BASIC, HEADERS, FULL + * @see Level + */ + public static final String AIO_FEIGN_LOG_LEVEL = "AIO_FEIGN_LOG_LEVEL"; + /** + * Environment variable to set the retry period in milliseconds for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_PERIOD = "AIO_FEIGN_RETRY_PERIOD"; + /** + * Environment variable to set the max attempts for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_MAX_ATTEMPTS = "AIO_FEIGN_RETRY_MAX_ATTEMPTS"; + /** + * Environment variable to set the max period in seconds for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_MAX_PERIOD = "AIO_FEIGN_RETRY_MAX_PERIOD"; + + + private static final int DEFAULT_CONNECT_TIMEOUT_IN_SECONDS = 10; + private static final int DEFAULT_READ_TIMEOUT_IN_SECONDS = 60; + public static final long DEFAULT_RETRY_PERIOD_IN_SECONDS = 1000L; + public static final int DEFAULT_MAX_ATTEMPTS = 3; + public static final long DEFAULT_MAX_PERIOD_IN_SECONDS = 4L; private FeignUtil() { } @@ -35,15 +71,7 @@ private FeignUtil() { * our global read and time out options */ public static Feign.Builder getBaseBuilder() { - return Feign.builder() - .logger(new Slf4jLogger()) - //.logLevel(Level.BASIC) - .logLevel(Level.NONE) - //.logLevel(Level.FULL) // use this instead when debugging - .decode404() - .errorDecoder(new IOErrorDecoder()) - .options(new Request.Options(DEFAULT_CONNECT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, - DEFAULT_READ_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, true)); + return new ConfigBuilder().systemEnv().build(); } /** @@ -67,4 +95,112 @@ public static Feign.Builder getBuilderWithFormEncoder() { .encoder(new FormEncoder()); } + public static class ConfigBuilder { + private Class loggerClass; + private Level logLevel; + private long retryPeriod; + private int maxAttempts; + private long maxPeriod; + + public ConfigBuilder() { + } + + /** + * @param loggerClass the logger class to use + * @see feign.Logger + * @return the current instance of the builder for chaining + */ + public ConfigBuilder loggerClass(final Class loggerClass) { + this.loggerClass = loggerClass; + return this; + } + + public ConfigBuilder logLevel(final Level logLevel) { + this.logLevel = logLevel; + return this; + } + + public ConfigBuilder retryPeriod(final long retryPeriod) { + this.retryPeriod = retryPeriod; + return this; + } + + public ConfigBuilder maxAttempts(final int maxAttempts) { + this.maxAttempts = maxAttempts; + return this; + } + + public ConfigBuilder maxPeriod(final long maxPeriod) { + this.maxPeriod = maxPeriod; + return this; + } + + public Class getLoggerClass() { + return loggerClass; + } + + public Level getLogLevel() { + return logLevel; + } + + public long getRetryPeriod() { + return retryPeriod; + } + + public int getMaxAttempts() { + return maxAttempts; + } + + public long getMaxPeriod() { + return maxPeriod; + } + + public ConfigBuilder configMap(final Map configMap) { + try { + return this + .loggerClass((Class) Class.forName( + configMap.getOrDefault(AIO_FEIGN_LOGGER_CLASS, Slf4jLogger.class.getName()))) + .logLevel(Level.valueOf( + configMap.getOrDefault(AIO_FEIGN_LOG_LEVEL, Level.NONE.name()))) + .retryPeriod(Long.parseLong( + configMap.getOrDefault(AIO_FEIGN_RETRY_PERIOD, String.valueOf(DEFAULT_RETRY_PERIOD_IN_SECONDS)))) + .maxAttempts(Integer.parseInt( + configMap.getOrDefault(AIO_FEIGN_RETRY_MAX_ATTEMPTS, String.valueOf(DEFAULT_MAX_ATTEMPTS)))) + .maxPeriod(Long.parseLong( + configMap.getOrDefault(AIO_FEIGN_RETRY_MAX_PERIOD, String.valueOf(DEFAULT_MAX_PERIOD_IN_SECONDS)))); + } catch (Exception e) { + throw new IllegalArgumentException("Provided Feign configuration is invalid", e); + } + } + + public ConfigBuilder systemEnv() { + return configMap(System.getenv()); + } + + public Feign.Builder build() { + try { + return Feign.builder() + .logger(this.loggerClass.getConstructor().newInstance()) + .logLevel(this.logLevel) + .decode404() + .retryer( + new Retryer.Default( + this.retryPeriod, SECONDS.toMillis(this.maxPeriod), this.maxAttempts)) + .errorDecoder(new IOErrorDecoder()) + .options( + new Request.Options( + DEFAULT_CONNECT_TIMEOUT_IN_SECONDS, + TimeUnit.SECONDS, + DEFAULT_READ_TIMEOUT_IN_SECONDS, + TimeUnit.SECONDS, + true)); + } catch (NoSuchMethodException + | InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalArgumentException("Provided Feign configuration is invalid", e); + } + } + } + } diff --git a/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java b/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java new file mode 100644 index 00000000..53ff5338 --- /dev/null +++ b/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java @@ -0,0 +1,64 @@ +package com.adobe.aio.util.feign; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import feign.Logger.Level; +import feign.slf4j.Slf4jLogger; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class FeignUtilTest { + + @Test + public void testConfigBuilderConfigMapWithEmptyMap() { + Map emptyConfigMap = new HashMap<>(); + assertDoesNotThrow(() -> new FeignUtil.ConfigBuilder().configMap(emptyConfigMap).build()); + } + + @Test + public void testConfigBuilderConfigMapWithNull() { + assertThrows(IllegalArgumentException.class, () -> new FeignUtil.ConfigBuilder().configMap(null).build()); + } + + @Test + public void testConfigBuilderWithDefaults() { + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(new HashMap<>()); + assertEquals(Slf4jLogger.class, configBuilder.getLoggerClass()); + assertEquals(Level.NONE, configBuilder.getLogLevel()); + assertEquals(FeignUtil.DEFAULT_RETRY_PERIOD_IN_SECONDS, configBuilder.getRetryPeriod()); + assertEquals(FeignUtil.DEFAULT_MAX_ATTEMPTS, configBuilder.getMaxAttempts()); + assertEquals(FeignUtil.DEFAULT_MAX_PERIOD_IN_SECONDS, configBuilder.getMaxPeriod()); + } + + @ParameterizedTest + @ValueSource(strings = {"feign.Logger$JavaLogger", "feign.slf4j.Slf4jLogger", "feign.Logger$NoOpLogger", "feign.Logger$ErrorLogger"}) + public void testConfigBuilderWithLoggerNames(String loggerName) { + Map configMap = new HashMap<>(); + configMap.put(FeignUtil.AIO_FEIGN_LOGGER_CLASS, loggerName); + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(configMap); + assertEquals(loggerName, configBuilder.getLoggerClass().getName()); + assertDoesNotThrow(configBuilder::build); + } + + @ParameterizedTest + @ValueSource(strings = {"NONE", "BASIC", "HEADERS", "FULL"}) + public void testConfigBuilderWithLogLevels(String logLevel) { + Map configMap = new HashMap<>(); + configMap.put(FeignUtil.AIO_FEIGN_LOG_LEVEL, logLevel); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_PERIOD, "1"); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_MAX_ATTEMPTS, "2"); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_MAX_PERIOD, "3"); + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(configMap); + assertEquals(Level.valueOf(logLevel), configBuilder.getLogLevel()); + assertEquals(1, configBuilder.getRetryPeriod()); + assertEquals(2, configBuilder.getMaxAttempts()); + assertEquals(3, configBuilder.getMaxPeriod()); + assertDoesNotThrow(configBuilder::build); + } + +}