Retry Utils in AEM using Java Streams

Problem Statement:

  1. How to retry a connection?
  2. Retry posting the data at least some time until the response is 200
  3. Retry any OAK operations, when the exception occurs

Introduction:

Usually, with respect to AEM, we don’t have Retry Utils which can retry the particular operation whenever an exception occurred.

If we are doing multiple transactions on the AEM repository, especially on a particular node like updating properties or updating references, the OAK operation would through exceptions like your operation is blocked by another operation or invalid modification.

If we are connecting to external services through REST API and the connection has failed or timeout and if we want to connect to the external system then we don’t have the option to retry out of the box

Create a Retry Utils as shown below:

Retry on Exception:

package com.test.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryOnException {
    private static final Logger log = LoggerFactory.getLogger(RetryOnException.class);

    public static interface CallToRetry {
        void process() throws Exception;
    }

    public static boolean withRetry(int maxTimes, long intervalWait, CallToRetry call) throws Exception {
        if (maxTimes <= 0) {
            throw new IllegalArgumentException("Must run at least one time");
        }
        if (intervalWait <= 0) {
            throw new IllegalArgumentException("Initial wait must be at least 1");
        }
        Exception thrown = null;
        for (int counter = 0; counter < maxTimes; counter++) {
            try {
                call.process();
                return true;
            } catch (Exception e) {
                thrown = e;
                log.info("Encountered failure on {} due to {}, attempt retry {} of {}", call.getClass().getName() , e.getMessage(), (counter + 1), maxTimes, e);
            }
            try {
                Thread.sleep(intervalWait);
            } catch (InterruptedException wakeAndAbort) {
                break;
            }
        }
        throw thrown;
    }
}

The above Util can be used in any code as shown below and the retry will happen only when the exception occurs during operations

package com.test.utils;

import java.util.concurrent.atomic.AtomicInteger;
import org.aarp.www.mcp.utils.RetryOnException;

public class ExampleOne {
	public static void main(String[] args) {
		AtomicInteger atomicCounter = new AtomicInteger(0);
		try {
			RetryOnException.withRetry(3, 500, () -> {
				if(atomicCounter.getAndIncrement() < 2) {
					System.out.println("Retring count with Exception" + atomicCounter.get());
					throw new Exception("Throwing New Exception to test");
				} else {
					System.out.println("Retring count without Exception " + atomicCounter.get());
				}

			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
Exception Result

Retry on the condition:

package com.test.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryOnCondition {
    private static final Logger log = LoggerFactory.getLogger(RetryOnCondition.class);

    public static interface CallToRetry {
        boolean process() throws Exception;
    }

    public static boolean withRetry(int maxTimes, long intervalWait, CallToRetry call) throws Exception {
        if (maxTimes <= 0) {
            throw new IllegalArgumentException("Must run at least one time");
        }
        if (intervalWait <= 0) {
            throw new IllegalArgumentException("Initial wait must be at least 1");
        }
        Exception thrown = null;
        for (int counter = 0; counter < maxTimes; counter++) {
            try {
                boolean status = call.process();
                if(status) {
                	return true;
                }                
            } catch (Exception e) {
                thrown = e;
                log.info("Encountered failure on {} due to {}, attempt retry {} of {}", call.getClass().getName() , e.getMessage(), (counter + 1), maxTimes, e);
            }
            try {
                Thread.sleep(intervalWait);
            } catch (InterruptedException wakeAndAbort) {
                break;
            }
        }
        throw thrown;
    }
}

The above Util can be used to retry based on the condition like if the connection is successful or the response code is 200

package com.test.utils;

import java.util.concurrent.atomic.AtomicInteger;
import org.aarp.www.mcp.utils.RetryOnCondition;

public class ExampleTwo {
	public static void main(String[] args) {
		AtomicInteger atomicCounter = new AtomicInteger(0);
		try {
			RetryOnCondition.withRetry(3, 500, () -> onCondition(atomicCounter));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static boolean onCondition(AtomicInteger atomicCounter) throws Exception {		
		if(atomicCounter.getAndIncrement() < 2) {
			System.out.println("Retring count with Condition false " + atomicCounter.get());
			return false;
		} else {
			System.out.println("Retring count with Condition true " + atomicCounter.get());
			return true;
		}
	}
}
On Condition result

Retry n number of times:

package org.aarp.www.mcp.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RetryNTimes {
    private static final Logger log = LoggerFactory.getLogger(RetryNTimes.class);

    public static interface CallToRetry {
        void process() throws Exception;
    }

    public static boolean withRetry(int maxTimes, long intervalWait, CallToRetry call) throws Exception {
        if (maxTimes <= 0) {
            throw new IllegalArgumentException("Must run at least one time");
        }
        if (intervalWait <= 0) {
            throw new IllegalArgumentException("Initial wait must be at least 1");
        }
        Exception thrown = null;
        for (int counter = 0; counter < maxTimes; counter++) {
            try {
                call.process();
                if(counter == (maxTimes-1)) {
                	return true;
                }                
            } catch (Exception e) {
                thrown = e;
                log.info("Encountered failure on {} due to {}, attempt retry {} of {}", call.getClass().getName() , e.getMessage(), (counter + 1), maxTimes, e);
            }
            try {
                Thread.sleep(intervalWait);
            } catch (InterruptedException wakeAndAbort) {
                break;
            }
        }
        throw thrown;
    }
}

The above Util can be used in operations like revalidating the results, for example, any query results can be validated.

package com.test.utils;

import java.util.concurrent.atomic.AtomicInteger;
import org.aarp.www.mcp.utils.RetryNTimes;

public class ExampleThree {
	public static void main(String[] args) {
		AtomicInteger atomicCounter = new AtomicInteger(0);
		try {
			RetryNTimes.withRetry(3, 500, () -> {
				System.out.println("Retrying 3 times count " + atomicCounter.getAndIncrement());
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
Retying umtil retry count reached result