Monday, February 21, 2022

Other Utilities

File checker


public interface AbcFileValidationService {
  boolean isFileExists(@NotBlank String absolutePathToFile);
}

@ValidatedService
public class AbcFileValidationServiceImpl implements AbcFileValidationService {

  @Override
  public boolean isFileExists(@NotBlank final String absolutePathToFile) {
    final FileSystemResource fileResource = new FileSystemResource(absolutePathToFile);
    return fileResource.exists();
  }
}

Thursday, February 17, 2022

JSON Mapper - Adaptor Pattern Approach to hide Jackson API

An example to hide the dependency on Jackson directly:


public interface AbcJsonMapper<K, E> {
  E getEntity(@NotBlank String json, @NotNull Class<E> entityType);

  String getJsonString(@NotNull E entity);

  List<E> getList(@NotBlank String jsonList, @NotNull Class<E> elementClass);

  Map<K, E> getMap(
      @NotBlank String jsonMap,
      @NotNull Class<? extends Map<K, E>> mapClass,
      @NotNull Class<K> keyClass,
      @NotNull Class<E> valueClass);
}

@Value
public class Car {

  private final String color;
  private final String type;

  @JsonCreator
  public Car(@JsonProperty("color") String color, @JsonProperty("type") String type) {
    this.color = color;
    this.type = type;
  }
}

@Service
@RequiredArgsConstructor
@Slf4j
public class AbcJsonMapperImpl<K, E> implements AbcJsonMapper<K, E> {

  @SuppressWarnings("squid:S3749")
  private final ObjectMapper jacksonObjectMapper;

  @Override
  public List<E> getList(@NotBlank final String jsonList, Class<E> elementClass) {
    log.info("jsonListAsString:{}", jsonList);
    List<E> result = Collections.emptyList();
    try {
      result =
          jacksonObjectMapper.readValue(
              jsonList,
              TypeFactory.defaultInstance().constructCollectionType(List.class, elementClass));
    } catch (JsonProcessingException e) {
      throw new AbcApplicationException(
          AbcAlertMessage.ABC_APP_JSON_MSG_PARSING_EXCEPTION
              + " ### ### ### Failed to transfer to a list of"
              + elementClass
              + " from:"
              + jsonList,
          e);
    }
    log.info("result:{}", result);
    return result;
  }

  @Override
  public E getEntity(@NotBlank String json, Class<E> entityType) {
    try {
      return this.jacksonObjectMapper.readValue(json, entityType);
    } catch (JsonProcessingException e) {
      throw new AbcApplicationException(
          AbcAlertMessage.ABC_APP_JSON_MSG_PARSING_EXCEPTION
              + " ### ### ### Failed to map to "
              + entityType
              + "  from:"
              + json,
          e);
    }
  }

  @Override
  public String getJsonString(@NotNull E entity) {
    try {
      return this.jacksonObjectMapper.writeValueAsString(entity);
    } catch (JsonProcessingException e) {
      throw new AbcApplicationException(
          AbcAlertMessage.ABC_APP_JSON_MSG_PARSING_EXCEPTION
              + " ### ### ### Failed to map to Json String from entity:"
              + entity,
          e);
    }
  }

  @Override
  public Map<K, E> getMap(
      @NotBlank String jsonMap,
      @NotNull Class<? extends Map<K, E>> mapClass,
      @NotNull Class<K> keyClass,
      @NotNull Class<E> valueClass) {
    log.info("jsonMap:{}", jsonMap);
    Map<K, E> result = Collections.emptyMap();
    try {
      result =
          jacksonObjectMapper.readValue(
              jsonMap,
              TypeFactory.defaultInstance().constructMapLikeType(mapClass, keyClass, valueClass));
    } catch (JsonProcessingException e) {
      throw new AbcApplicationException(
          AbcAlertMessage.ABC_APP_JSON_MSG_PARSING_EXCEPTION
              + " ### ### ### Failed to transfer to a map of key class: "
              + keyClass
              + ", value class:"
              + valueClass
              + " from:"
              + jsonMap,
          e);
    }
    log.info("result:{}", result);
    return result;
  }
}


@Slf4j
class JsonMapperImplTest {
  private final ObjectMapper objectMapper = new ObjectMapper();

  @Test
  void testStringList() {
    AbcJson<String, String> mapper = new AbcJsonImpl<>(this.objectMapper);
    List<String> list = mapper.getList("[\"A\", \"B\"]", String.class);
    log.info("list:{}", list);
    assertEquals(2, list.size());
  }

  @Test
  void testIntegerList() {
    AbcJson<String, Integer> mapper = new AbcJsonImpl<>(this.objectMapper);
    List<Integer> list = mapper.getList("[1, 2]", Integer.class);
    log.info("list:{}", list);
    assertEquals(2, list.size());
  }

  @Test
  void testCarList() {
    AbcJson<String, Car> mapper = new AbcJsonImpl<>(this.objectMapper);
    String jsonCarListAsString =
        "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]";
    List<Car> list = mapper.getList(jsonCarListAsString, Car.class);
    log.info("list:{}", list);
    assertEquals(2, list.size());
  }

  @Test
  void testCarMap() {
    AbcJson<String, String> mapper = new AbcJsonImpl<>(this.objectMapper);
    String jsonCarMapAsString = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
    @SuppressWarnings("unchecked")
Map<String, String> map =
        mapper.getMap(
            jsonCarMapAsString,
            (@NotNull Class<? extends Map<String, String>>) (new HashMap<>().getClass()),
            String.class,
            String.class);
    log.info("map:{}", map);
    assertEquals(2, map.keySet().size());
  }

  @Test
  void testGetEntity() {
    AbcJson<String, Car> mapper = new AbcJsonImpl<>(this.objectMapper);
    String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
    Car car = mapper.getEntity(json, Car.class);
    log.info("car:{}", car);
    assertEquals("Black", car.getColor());
    assertEquals("BMW", car.getType());
  }

  @Test
  void testgetJsonString() {
    AbcJson<String, Car> mapper = new AbcJsonImpl<>(this.objectMapper);
    Car car = new Car("Yellow", "BMW");
    String json = mapper.getJsonString(car);
    log.info("json:{}", json);
    assertEquals("{\"color\":\"Yellow\",\"type\":\"BMW\"}", json);
  }
}

Friday, January 21, 2022

Spring Boot Application Common Configurations

Start Spring Boot and load properties


@SpringBootApplication(scanBasePackages = {"com.idd.abc.base"})
@EnableRetry
@EnableScheduling
@ConfigurationPropertiesScan({"com.idd.abc.base"})
@PropertySource(ignoreResourceNotFound = true, value = "file:/vault/secrets")
@Slf4j
public class Application {
  @SuppressWarnings("squid:S4823")
  public static void main(String[] args) {
    log.info("### ### ### ABC Spring Boot Base Application starting...");
    SpringApplication app =
        new SpringApplication(Application.class, AbcMetricsServiceCounterConfiguration.class);
    app.setApplicationStartup(new BufferingApplicationStartup(2048));
    app.run(args);
  }
  @Bean
  public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
    propsConfig.setLocation(new ClassPathResource("git.properties"));
    propsConfig.setIgnoreResourceNotFound(true);
    propsConfig.setIgnoreUnresolvablePlaceholders(true);
    return propsConfig;
  }
}

Spring Boot Application Statuses


@Component
@RequiredArgsConstructor
@Slf4j
public class AbcApplicationGracefulShutdown
    implements ApplicationListener<ContextClosedEvent>, ExitCodeGenerator {
  @Override
  public void onApplicationEvent(ContextClosedEvent event) {
    log.info("@@@ @@@ @@@ ABC Application is closed by event:{}.", event.getSource());
  }
  @Override
  public int getExitCode() {
    return 0;
  }
}

@Component
@Order(0)
@Slf4j
class AbcApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
  @Value("${spring.profiles.active}")
  private String activeProfiles;
  @Override
  public void onApplicationEvent(ApplicationReadyEvent event) {
    log.info(
        "@@@ @@@ @@@ ABC Application is ready and started with profile:{}.", activeProfiles);
  }
}

Async Configuraiton


@Configuration
@RequiredArgsConstructor
@EnableAsync
public class AbcAsyncConfigurer implements AsyncConfigurer {
  @SuppressWarnings("squid:S3749")
  private final AbcAsyncExceptionHandler asyncExceptionHandler;
  @Value("${async.handler.concurrency:3}")
  private int asyncCorePoolSize;
  @Value("${async.queue.capacity:3500}")
  private int asyncQueueCapacity;
  @Bean
  public Executor asyncTaskExecutor() {
    final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(asyncCorePoolSize);
    executor.setMaxPoolSize(3 * asyncCorePoolSize);
    executor.setQueueCapacity(asyncQueueCapacity);
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.setThreadNamePrefix("AsyncTaskExecutore-");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return asyncExceptionHandler;
  }
}

@Service
@Slf4j
public class AbcAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
  @Override
  public void handleUncaughtException(final Throwable t, final Method m, final Object... params) {
    log.info("$$$ $$$ $$$ Method name:{}", m.getName());
    for (Object param : params) {
      log.info("$$$ $$$ $$$ Parameter value:{}", param);
    }
    if (t instanceof RetryExhaustedException) {
      log.error(
          AbcAlertMessage.ABC_APP_RETRY_EXHAUSTED
              + " *** *** *** method:"
              + m
              + " failed with recoverable exception and all retries exhausted. Message:"
              + t.getMessage(),
          t);
    } else if (t instanceof AbcApplicationException) {
      log.error(
          AbcAlertMessage.ABC_APP_UNRECOVERABLE_FAILURE
              + " *** *** ***  method:"
              + m
              + " falied with Un-recoverable Exception. Message:"
              + t.getMessage(),
          t);
    } else {
      log.error(
          AbcAlertMessage.ABC_APP_UN_CATEGORIZED_EXCEPTION
              + " *** *** ***  method:"
              + m
              + " failed with Un-categoried Exception. Message:"
              + t.getMessage(),
          t);
    }
  }
}

Cache


@Configuration
@EnableCaching
public class AbcCacheManagerConfiguration {
  @Bean
  @Primary
  public CacheManager abcDefaultCacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager("abcDefaultCache");
    cacheManager.setCaffeine(
        Caffeine.newBuilder()
            .initialCapacity(10)
            .maximumSize(200)
            .expireAfterWrite(1, TimeUnit.MINUTES)
            .weakKeys()
            .recordStats());
    return cacheManager;
  }

  @Bean
  public CacheManager abcSimpleCacheManager() {
    return new ConcurrentMapCacheManager("abcSimpleCache");
  }
}

@Configuration
@Slf4j
public class AbcBaseConfiguration {
  @Bean
  public SecureRandom random() {
    return new SecureRandom();
  }

  @Bean
  public String hostnameProperty(final Environment env) {
    String hostname = env.getProperty("HOSTNAME");
    if (null == hostname) {
      try {
        hostname = InetAddress.getLocalHost().getHostName();
      } catch (UnknownHostException e) {
        log.warn(
            AbcAlertMessage.ABC_APP_INTERNAL_EXCEPTION
                + " ### ### ### Failed to retrieve hostname.",
            e);
      }
    }
    return hostname;
  }

  @Bean
  public Clock clock() {
    return Clock.system(ZoneId.of("UTC"));
  }

  @Bean
  public InetAddress inetAddress() {
    try {
      return InetAddress.getLocalHost();
    } catch (Exception ex) {
      throw new AbcApplicationException(
          AbcAlertMessage.ABC_APP_INTERNAL_EXCEPTION
              + " ### ### ### Reading local inet address failed. Message:"
              + ex.getMessage(),
          ex);
    }
  }

  @Bean
  MeterRegistryCustomizer<MeterRegistry> configurer(
      @Value("${spring.application.name}") String applicationName) {
    return registry -> registry.config().commonTags("application", applicationName);
  }
}

@Configuration
public class AbcMetricsServiceCounterConfiguration {
  private static final String CAT = "cat";
  private static final String SERVICE = "service";
  @Bean
  public Counter userErrorCounter(MeterRegistry meterRegistry) {
    return meterRegistry.counter("user.id.error", CAT, SERVICE);
  }
  @Bean
  public Counter userAccessCounter(MeterRegistry meterRegistry) {
    return meterRegistry.counter("user.id.access", CAT, SERVICE);
  }
}

@Configuration
public class TimedAspectConfiguration {
  @Bean
  public TimedAspect timedAspect(MeterRegistry meterRegistry) {
    return new TimedAspect(meterRegistry);
  }
}


===================================================================
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
@Validated
public @interface ValidatedService {}

Condition

public class SolaceCondition extends AnyNestedCondition {

  SolaceCondition() {
    super(ConfigurationPhase.PARSE_CONFIGURATION);
  }

  @ConditionalOnProperty(
      value = "solace.jms.direct.enabled",
      havingValue = "true",
      matchIfMissing = false)
  static class JmsDirectCondition {}

  @ConditionalOnProperty(
      value = "solace.jms.spring.enabled",
      havingValue = "true",
      matchIfMissing = false)
  static class JmsSpringCondition {}
}

public class OnUnixCondition implements Condition {

  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    return SystemUtils.IS_OS_LINUX;
  }
}

Usage Demo

@ValidatedService
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
  @SuppressWarnings("squid:S3749")
  private final SecureRandom random;

  @SuppressWarnings("squid:S3749")
  @Qualifier("userAccessCounter")
  private final Counter accessCouter;

  private SoftReference<Map<Long, User>> userMapRef = new SoftReference<>(new HashMap<>());

  @Timed(
      extraTags = {"cat", "service"},
      percentiles = {0.50, 0.95, 0.99},
      histogram = false)
  @Log4jDiagnosticContextEnable
  @Override
  public String getAccount(@NotNull final User user) {
    accessCouter.increment();
    return user.getId() + "-" + random.nextInt(100000000);
  }

  /*
   * Example of asynchronous execution
   */
  @Async("asyncTaskExecutor")
  @Timed(
      extraTags = {"cat", "service"},
      percentiles = {0.50, 0.95, 0.99},
      histogram = false)
  @Log4jDiagnosticContextEnable
  @Override
  public ListenableFuture<String> getIdAndName(@NotNull final User user) {
    accessCouter.increment();
    return new AsyncResult<>(user.getId() + "-" + user.getName());
  }

  /*
   * Example of handling AsyncUncaughtExceptionHandler
   */
  @Async("asyncTaskExecutor")
  @Timed(
      extraTags = {"cat", "service"},
      percentiles = {0.50, 0.95, 0.99},
      histogram = false)
  @Log4jDiagnosticContextEnable
  @Override
  public void asyncFailure(final User user) {
    accessCouter.increment();
    throw new RuntimeException("TEST EXCEPTION");
  }

  @Timed(
      extraTags = {"cat", "service"},
      percentiles = {0.50, 0.95, 0.99},
      histogram = false)
  @Log4jDiagnosticContextEnable
  @Override
  public synchronized User getUser(long id) {
    Map<Long, User> userMap = this.getUserMap();
    User user = userMap.get(id);
    if (user == null) {
      user = createUser(id);
    }

    return user;
  }

  @Timed(
      extraTags = {"cat", "service"},
      percentiles = {0.50, 0.95, 0.99},
      histogram = false)
  @Log4jDiagnosticContextEnable
  @Override
  public synchronized User updateUser(long id) {
    Map<Long, User> userMap = this.getUserMap();
    User user = userMap.get(id);
    if (user == null) {
      user = createUser(id);
    } else {
      user.setName(String.valueOf(random.nextInt(100)));
    }

    return user;
  }

  private Map<Long, User> getUserMap() {
    Map<Long, User> userMap = this.userMapRef.get();
    if (userMap == null) {
      this.userMapRef = new SoftReference<>(new ConcurrentHashMap<>());
    }
    return userMapRef.get();
  }

  private User createUser(long id) {
    User user;
    user = new User();
    user.setId(id);
    user.setName(String.valueOf(random.nextInt(100)));
    return user;
  }
}



Log4j MDC setting with Spring Annotation and Spring AOP

Aspect definition:


@Aspect
@Configuration
@RequiredArgsConstructor
public class ApplicationMDCAspect {
  @SuppressWarnings("squid:S3749")
  private final Log4jMDCSetter log4jMdcSetter;
  @Pointcut(
      "execution(* *(..)) && @annotation(com.idd.abc.base.logging.Log4jDiagnosticContextEnable)")
  public void log4jDiagnosticContextEnabled() {
    // do nothing.
  }
  @Before("com.idd.abc.base.logging.ApplicationMDCAspect.log4jDiagnosticContextEnabled()")
  public void setMdcToResourceBean(JoinPoint joinPoint) {
    log4jMdcSetter.setHostAndAppInfoIfMissing();
  }
}

Enable Aspect:


@Configuration
@EnableAspectJAutoProxy
public class AbcApplicationMdcAspect extends ApplicationMDCAspect {
  public AbcApplicationMdcAspect(Log4jMDCSetter log4jMdcSetter) {
    super(log4jMdcSetter);
  }
}



@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log4jDiagnosticContextEnable {}

public interface CorrelationIdSupplier {
  String createCorrelationId();
}

public interface Log4jMDCSetter {
  void clear();
  void setHostAndAppInfoIfMissing();
  void addParameter(String key, String value);
  void setUniqueId(String uniqueId);
  String getUniqueId();
}


@Service
@RequiredArgsConstructor
public final class CorrelationIdSupplierImpl implements CorrelationIdSupplier {
  @SuppressWarnings("squid:S3749")
  private final Clock clock;
  @SuppressWarnings("squid:S3749")
  @Qualifier("hostnameProperty")
  private final String hostname;
  /** @return a new generated correlation id */
  @Override
  public String createCorrelationId() {
    return new StringBuilder()
        .append(hostname)
        .append("-")
        .append(LocalDateTime.now(clock).getSecond())
        .append("-")
        .append(UUID.randomUUID())
        .toString();
  }
}
@Service
@RequiredArgsConstructor
@Slf4j
public final class Log4JMDCSetterImpl implements Log4jMDCSetter {
  private static final String APPLICATION_KEY = "APPLICATION";
  private static final String CORRELATION_ID_KEY = "CORRELATION_ID";
  private static final String HOSTNAME_KEY = "HOSTNAME";
  private static final String UNIQUE_ID = "UNIQUE_ID";

  @SuppressWarnings("squid:S3749")
  private final CorrelationIdSupplier correlationIdSupplier;
  @SuppressWarnings("squid:S3749")
  private final InetAddress inetAddress;
  @Value("${spring.application.name}")
  private String applicationName;
  
  @Override
  public void clear() {
    ThreadContext.clearAll();
  }

  @Override
  public void setHostAndAppInfoIfMissing() {
    String correlationId = ThreadContext.get(CORRELATION_ID_KEY);
    if (!StringUtils.isBlank(correlationId)) {
      return;
    }
    ThreadContext.put(APPLICATION_KEY, applicationName);
    ThreadContext.put(CORRELATION_ID_KEY, createCorrelationId());
    ThreadContext.put(HOSTNAME_KEY, inetAddress.getHostName());
  }

  @Override
  public void addParameter(String key, String value) {
    ThreadContext.put(key, value);
  }

  @Override
  public void setUniqueId(String uniqueId) {
    ThreadContext.put(UNIQUE_ID, uniqueId);
  }

  @Override
  public String getUniqueId() {
    final String uniqueId = ThreadContext.get(UNIQUE_ID);
    return uniqueId != null ? uniqueId : "NO UNIQIE_ID";
  }

  private String createCorrelationId() {
    try {
      return correlationIdSupplier.createCorrelationId();
    } catch (Exception e) {
      log.warn(
          DmAlertMessage.CAS_DM_APP_INTERNAL_EXCEPTION
              + " ### ### ### Exception when generating correlation id",
          e);
      return UUID.randomUUID().toString();
    }
  }
}

Thursday, January 20, 2022

Spring Retry Configuration

Spring Retry Basic Configuration with @Retriable

public class RetryTemplateConfiguration {
  @SuppressWarnings("squid:S3749")
  private final RetryTemplateConfigProperties config;
  @SuppressWarnings("squid:S3749")
  private final RetryLoggingListener retryLoggingListener;

  @Bean
  public RetryTemplate retryTemplate() {
    //      FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
    //      fixedBackOffPolicy.setBackOffPeriod(2000l);
    //      retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
    final Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
    exceptionMap.put(RecoverableFailureException.class, true);
    final RetryTemplate retryTemplate = new RetryTemplate();
    retryTemplate.setRetryPolicy(new SimpleRetryPolicy(config.getTotalRetries(), exceptionMap));
    retryTemplate.registerListener(retryLoggingListener);
    final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(config.getIntialInterval());
    backOffPolicy.setMaxInterval(config.getMaxInterval());
    backOffPolicy.setMultiplier(config.getMaxInterval());
    retryTemplate.setBackOffPolicy(backOffPolicy);
    return retryTemplate;
  }


@ConfigurationProperties(prefix = "abc.base.retry")
@Data
public class RetryTemplateConfigProperties {

  private int intialInterval;
  private int maxInterval;
  private int multiplier;
  private int totalRetries;
}


@Service
@Slf4j
public class RetryLoggingListener extends RetryListenerSupport {

  @Override
  public <T, E extends Throwable> void close(
      RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
    if (context.getRetryCount() > 0) {
      log.info(
          "%%% %%% %%% [{}] retry closing, count:{}",
          context.getAttribute("context.name"), context.getRetryCount());
    }
    super.close(context, callback, throwable);
  }

  @Override
  public <T, E extends Throwable> void onError(
      RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
    if (context.getRetryCount() > 0) {

      log.info(
          "%%% %%% %%% ["
              + context.getAttribute("context.name")
              + "] retry onError, count: {}"
              + context.getRetryCount()
              + ", exception type:"
              + throwable.getClass(),
          throwable);
    }
    super.onError(context, callback, throwable);
  }

  @Override
  public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
    log.info("%%% %%% %%%, retry opening...");
    return super.open(context, callback);
  }
}



@ExtendWith(SpringExtension.class)
@ContextConfiguration
@ActiveProfiles("retry")
class SpringRetryTest {

  @Autowired private CallerService callerService;

  @MockBean private RetriableService retriableService;

  @BeforeAll
  public static void setUp() {
    System.setProperty("spring.profiles.active", "retry");
  }

  @Test
  void retriesAfterOneFailAndThenPass() {
    callerService.intialCall("name");
    verify(retriableService, times(3)).retriableMethod("name");
  }

  @Configuration
  @EnableRetry
  @EnableAspectJAutoProxy(proxyTargetClass = true)
  @Profile("retry")
  public static class Application {
    private final RetriableService retriableService;

    public Application(RetriableService bService) {
      this.retriableService = bService;
    }

    @SuppressWarnings("unchecked")
    @Bean
    public CallerService aService() {
      when(retriableService.retriableMethod("name")).thenThrow(RecoverableFailureException.class).thenThrow(RecoverableFailureException.class).thenReturn("full name");
      return Mockito.spy(new CallerServiceImpl(retriableService));
    }

    @Bean
    public RetryLoggingListener retryLoggingListener() {
      return new RetryLoggingListener();
    }
  }
}


public interface CallerService {
  String intialCall(String name);
}


@Service
public class CallerServiceImpl implements CallerService {

  private final RetriableService retriableService;

  public CallerServiceImpl(RetriableService retriableService) {
    this.retriableService = retriableService;
  }

  @Override
  @Retryable(
      value = {RecoverableFailureException.class},
      maxAttempts = 6,
      backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 1800000),
      listeners = {"retryLoggingListener"})
  public String intialCall(String name) {
    return this.retriableService.retriableMethod(name);
  }
}

public interface RetriableService {
  String retriableMethod(String name);
}

Wednesday, January 19, 2022

A Sample of Maven Parent POM to create Library and Spring Based Release Builds

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.idd.abc</groupId>
  <artifactId>abc-parent</artifactId>
  <version>0.0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>

  <name>Base Parent POM for ABC application</name>
  <description>See README.md</description>

  <properties>
    <java.release.version>1.8</java.release.version>
    <maven.compiler.source>${java.release.version}</maven.compiler.source>
    <maven.compiler.target>${java.release.version}</maven.compiler.target>
    <maven.build.timestamp.format>yyyy-MM-dd_HH-mm-ss</maven.build.timestamp.format>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

    <app.id>${project.artifactId}</app.id>
    <start-class>com.idd.abc.base.Application</start-class>

    <spring-boot.version>2.6.2</spring-boot.version>
    <spring-core.version>5.3.14</spring-core.version>

    <log4j2-logstash-layout.version>1.0.5</log4j2-logstash-layout.version>

    <buildnumber-maven-plugin.version>3.0.0</buildnumber-maven-plugin.version>
    <git-code-format-maven-plugin.version>2.7</git-code-format-maven-plugin.version><!-- 3.0 available, requires JDK 11 -->
    <git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
    <googleformatter-maven-plugin.version>1.7.5</googleformatter-maven-plugin.version>
    <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
    <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
    <maven-enforcer-plugin.version>3.0.0</maven-enforcer-plugin.version>
    <maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version>
    <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
    <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>

    <local-log-location>C:\\app\\spring-boot-poc\\logs</local-log-location>
    <server-log-location>/app/abc/logs</server-log-location>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Remove dependencies on logback -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>${spring-boot.version}</version>
        <exclusions>
          <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
          </exclusion>
          <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
          </exclusion>
        </exclusions>
      </dependency>

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring-boot.version}</version>
        <exclusions>
          <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
          </exclusion>
          <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
          </exclusion>
        </exclusions>
      </dependency>

      <!-- Remove dependencies on commons-logging -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring-core.version}</version>
        <exclusions>
          <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
          </exclusion>
        </exclusions>
      </dependency>

    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- re-defined -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
    </dependency>

    <!-- Web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Cache -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
      <groupId>com.github.ben-manes.caffeine</groupId>
      <artifactId>caffeine</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.retry</groupId>
      <artifactId>spring-retry</artifactId>
    </dependency>

    <!-- production ready -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
      <groupId>io.micrometer</groupId>
      <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
    <dependency>
      <groupId>io.micrometer</groupId>
      <artifactId>micrometer-registry-jmx</artifactId>
    </dependency>

    <!-- logging -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <!-- logstash layout -->
    <dependency>
      <groupId>com.vlkan.log4j2</groupId>
      <artifactId>log4j2-logstash-layout</artifactId>
      <version>${log4j2-logstash-layout.version}</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
    </dependency>

    <!-- method Level Validation -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <!-- tests -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.skyscreamer</groupId>
          <artifactId>jsonassert</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <!-- JUnit -->
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- JUnit 5 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- JUnit 4 backward compatible -->
    <dependency>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- mock -->
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-inline</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- asynchronous systems testing -->
    <dependency>
      <groupId>org.awaitility</groupId>
      <artifactId>awaitility</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- devtool -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <optional>true</optional>
    </dependency>

    <!-- Spring configuration metadata for contextual help in IDE -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
          <include>**/*.yaml</include>
          <include>**/*.yml</include>
          <include>**/*.txt</include>
          <include>**/*.cql</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/config</directory>
        <filtering>false</filtering>
        <includes>
          <include>**/*.jks</include>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>src/test/resources</directory>
        <filtering>true</filtering>
      </testResource>
    </testResources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <encoding>UTF-8</encoding>
          <source>${java.release.version}</source>
          <target>${java.release.version}</target>
          <annotationProcessors>
            <annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
          </annotationProcessors>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <mainClass>${start-class}</mainClass>
        </configuration>
        <executions>
          <execution>
            <id>spring-boot</id>
            <goals>
              <goal>build-info</goal>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <version>${maven-resources-plugin.version}</version>
        <configuration>
          <encoding>UTF-8</encoding>
          <escapeString>\</escapeString>
        </configuration>
        <executions>
          <execution>
            <id>copy-jar</id>
            <phase>package</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <outputDirectory>${basedir}/target/appBundle</outputDirectory>
              <resources>
                <resource>
                  <filtering>false</filtering>
                  <directory>${basedir}/target</directory>
                  <includes>
                    <include>*.jar</include>
                  </includes>
                </resource>
              </resources>
            </configuration>
          </execution>
          <execution>
            <id>copy-config</id>
            <phase>package</phase>
            <goals>
              <goal>copy-resources</goal>
            </goals>
            <configuration>
              <outputDirectory>${basedir}/target/appBundle/.openshift</outputDirectory>
              <resources>
                <resource>
                  <directory>.openshift/stores</directory>
                  <filtering>false</filtering>
                </resource>
                <resource>
                  <directory>.openshift/params</directory>
                  <filtering>true</filtering>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>${maven-assembly-plugin.version}</version>
        <executions>
          <execution>
            <id>dist</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <appendAssemblyId>false</appendAssemblyId>
              <descriptors>
                <descriptor>${basedir}/assembly.xml</descriptor>
              </descriptors>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>pl.project13.maven</groupId>
        <artifactId>git-commit-id-plugin</artifactId>
        <version>${git-commit-id-plugin.version}</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>revision</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <generateGitPropertiesFile>true</generateGitPropertiesFile>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
        <configuration>
          <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
          <excludes>
            <exclude>*Validation.java</exclude>
          </excludes>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>${maven-failsafe-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>com.theoryinpractise</groupId>
        <artifactId>googleformatter-maven-plugin</artifactId>
        <version>${googleformatter-maven-plugin.version}</version>
        <dependencies>
          <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-core</artifactId>
            <version>3.8.1</version>
          </dependency>
          <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-settings</artifactId>
            <version>3.8.1</version>
          </dependency>
        </dependencies>
        <executions>
          <execution>
            <id>reformat-sources</id>
            <configuration>
              <includeStale>true</includeStale>
              <style>GOOGLE</style>
              <formatMain>true</formatMain>
              <formatTest>true</formatTest>
              <filterModified>false</filterModified>
              <skip>false</skip>
              <fixImports>true</fixImports>
              <maxLineLength>100</maxLineLength>
            </configuration>
            <goals>
              <goal>format</goal>
            </goals>
            <phase>process-sources</phase>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>com.cosium.code</groupId>
        <artifactId>git-code-format-maven-plugin</artifactId>
        <version>${git-code-format-maven-plugin.version}</version>
        <executions>
          <!-- On commit, format the modified java files -->
          <execution>
            <id>install-formatter-hook</id>
            <goals>
              <goal>install-hooks</goal>
            </goals>
          </execution>
          <!-- On Maven verify phase, fail if any file (including unmodified) is badly formatted -->
          <execution>
            <id>validate-code-format</id>
            <goals>
              <goal>validate-code-format</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <googleJavaFormatOptions>
            <aosp>false</aosp>
            <fixImportsOnly>false</fixImportsOnly>
            <skipSortingImports>false</skipSortingImports>
            <skipRemovingUnusedImports>false</skipRemovingUnusedImports>
          </googleJavaFormatOptions>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>${maven-enforcer-plugin.version}</version>
        <executions>
          <execution>
            <id>enforce-rules</id>
            <goals>
              <goal>enforce</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <rules>
            <banDuplicatePomDependencyVersions />
              <requireJavaVersion>
                <version>${java.release.version}</version>
              </requireJavaVersion>
            <!-- 
              <requireMavenVersion>
                <version>(3.8.0,3.9)</version>
                <message>Invalid Maven version. It should be 3.8.x</message>
              </requireMavenVersion>
             -->
            </rules>
          </configuration>
      </plugin>
    </plugins>
  </build>

  <profiles>
    <profile>
      <id>local</id>
      <activation>
        <os>
          <family>!unix</family>
        </os>
      </activation>
      <properties>
        <parent_property>PARENT_PROPERTY_LOCAL</parent_property>
        <EPAAS_ENV>e1</EPAAS_ENV>
        <log.file.location>${local-log-location}</log.file.location>
        <springboot.profiles>${app.id},e0</springboot.profiles>
      </properties>
      <build>
        <finalName>app</finalName>
      </build>
    </profile>
    <profile>
      <id>server</id>
      <activation>
        <os>
          <family>unix</family>
        </os>
      </activation>
      <properties>
        <parent_property>PARENT_PROPERTY_SERVER</parent_property>
        <log.file.location>${server-log-location}</log.file.location>
        <springboot.profiles>${app.id}</springboot.profiles>
      </properties>
      <build>
        <finalName>app</finalName>
      </build>
    </profile>
    <profile>
      <id>server-lib</id>
      <properties>
        <parent_property>PARENT_PROPERTY_SERVER_LIB</parent_property>
        <log.file.location>${server-log-location}</log.file.location>
        <springboot.profiles>${app.id},e0</springboot.profiles>
      </properties>
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <version>${maven-resources-plugin.version}</version>
            <configuration>
              <encoding>UTF-8</encoding>
              <escapeString>\</escapeString>
            </configuration>
            <executions>
              <execution>
                <id>copy-jar</id>
                <phase>none</phase>
              </execution>
              <execution>
                <id>copy-config</id>
                <phase>none</phase>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <executions>
              <execution>
                <id>spring-boot</id>
                <phase>none</phase>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>${maven-assembly-plugin.version}</version>
            <executions>
              <execution>
                <id>dist</id>
                <phase>none</phase>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

  <scm>
    <connection>scm:git:https://stash...</connection>
    <developerConnection>scm:git:https://stash....</developerConnection>
    <tag>HEAD</tag>
  </scm>
  <distributionManagement>
    <repository>
      <id>corporate</id>
      <url>dav:https://.../</url>
    </repository>
    <snapshotRepository>
      <id>snapshots</id>
      <url>dav:https://.../</url>
    </snapshotRepository>
  </distributionManagement>
</project>

assembly.xml

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>bin</id>
    <formats>
        <format>tar.gz</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/appBundle</directory>
            <outputDirectory>.</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>



> README FILE

---

## An Spring Boot based application targets OpenShift deployment.

* The parent POM provides dependency definitions and dependencies management for most commonly used d Spring Boot features.
* This POM also provide Maven plugins and their configurations to be used by Jenkins builds that target OpenShift deployments and releases.

### Out-of-box Features:

- Spring Boot
- Spring MVC over Tomcat - REST endpoints
- Spring Actuator - production-ready features
- MicroMeter over Prometheus - metrics
- Log4j over slf4j
- Log4j MDC - mapped diagnostic contexts
- Lombok integration
- Cache with Spring/Caffeine
- Spring Bean Validation - entity and service validation
- ePaaS Vault integration - password management

- JUnit 4&5 integration
- Awaitility integration
- Mockito integration

- git-commit-id-plugin
- spotless code formatter

### Usage

#### Maven Profiles
There are 3 Maven profiles defined:

1. local: build a Spring Boot based package locally.
2. server: build an OpenShift release
3. server-lib: build a re-usable library as jar

#### Build parent POM and install it to local Maven repository

```
mvn -Pserver-lib,!local clean install
```