Es könnte so einfach sein… Man nehme eine lokale Datei und sende sie an den Amazon Cloud File Service (AWS S3). Datei gespeichert und weiter im Kontext. Denkste!

Auf dem Weg zwischen Client und Server kann so einiges schief gehen. Sei es, dass das genutzte WLAN einen kurzen Aussetzer hat, man mit seinem Smartphone durch den Tunnel fährt oder einfach mal der Cloud Service Probleme macht.

Kleines Beispiel aus der Praxis: In meinem eigenen WLAN-Netzwerk kommt es bei allen 20k bis 30k FTP-Uploads zu einer IOException, wenn ich den Standard Apache FTPClient in Java einsetze.

Abhilfe schaffen so genannte Retry-Frameworks oder Code-Snippets. Bei meiner Suche nach einem schönen Framework bin ich vor allem auf aspektorientierte Retry-Annotationen gestoßen. Wenn man AOP bereits einsetzt, bestimmt die eleganteste Wahl. Aus verschiedenen Gründen kommt dieser Ansatz bei mir allerdings nicht zum Einsatz. Andere Frameworks konnten entweder durch hohe Abhängigkeiten oder wenig Dokumentation und fehlender Support nicht punkten. Was bleibt? Richtig - selber schreiben.

Zu diesem Zwecke habe ich einen RetryHelper zum Handling der Exception und der Ausführung der Operation programmiert. Außerdem gibt es die Möglichkeit nach einer Exception ein Objekt neu zu verbinden – dazu die Schnittstelle IReconnectable. Zu guter Letzt noch die abstrakten Objekte RetryOperation (Operation ohne Rückgabewert) und RetryOperationReturn (Operation mit Rückgabewert). Im Code sieht das dann folgendermaßen aus:

RetryHelper.doWithRetry(objectImplementsIReconnectable, MAX_RETRIES, new RetryOperation<RuntimeException>(RuntimeException.class) {
   @Override
   public void exec() {
      getS3Client().putObject(new PutObjectRequest(MainConfig.S3_BUCKET_NAME, myLocalFilePath, inputFile));
   }
});

 

Hier der Helper:

/*
 * copyright @ bitschmid.com
 */
import java.util.logging.Level;
import java.util.logging.Logger;

public class RetryHelper {

    private static final Logger LOG = Logger.getLogger(RetryHelper.class.getName());

    /**
     * Use this method to execute a RetryOperation (operation without return
     * value) until it ends without Exception T or try it for a specified amount
     * of attemts.
     *
     * @param <T> The type of Exception (and it's children) to catch.
     * @param objectToReconnect An object (implements IReconnectable) to
     * reconnect after cought Exception or null, if nothing to reconnect.
     * @param maxAttempts The maximum nuber of tries.
     * @param operation The operation to execute.
     * @throws T The type of Exception (and it's children) to catch.
     */
    public static <T extends Exception> void doWithRetry(IReconnectable objectToReconnect, int maxAttempts, RetryOperation<T> operation) throws T {
        for (int count = 0; count < maxAttempts; count++) {
            try {
                operation.exec();
                return;
            } catch (Exception e) {
                if (objectToReconnect != null) {
                    sleepSeconds(5 * (count + 1));
                    objectToReconnect.reconnect();
                }
                if (e.getClass().isAssignableFrom(operation.getMyExceptionClass())) {
                    operation.handleException((T) e, maxAttempts, (count + 1));
                } else {
                    throw e;
                }
            }
        }
    }

    /**
     * Use this method to execute a RetryOperationReturn (operation with return
     * value) until it ends without Exception T or try it for a specified amount
     * of attemts.
     *
     * @param <T> The type of Exception (and it's children) to catch.
     * @param objectToReconnect An object (implements IReconnectable) to
     * reconnect after cought Exception or null, if nothing to reconnect.
     * @param maxAttempts The maximum nuber of tries.
     * @param RetryOperationReturn The operation to execute.
     * @throws T The type of Exception (and it's children) to catch.
     */
    public static <T extends Exception, ReturnValue extends Object> ReturnValue doWithRetry(IReconnectable objectToReconnect, int maxAttempts, RetryOperationReturn<T, ReturnValue> operation) throws T {
        for (int count = 0; count < maxAttempts; count++) {
            try {
                return operation.exec();
            } catch (Exception e) {
                if (objectToReconnect != null) {
                    sleepSeconds(5 * (count + 1));
                    objectToReconnect.reconnect();
                }
                if (e.getClass().isAssignableFrom(operation.getMyExceptionClass())) {
                    operation.handleException((T) e, maxAttempts, (count + 1));
                } else {
                    throw e;
                }
            }
        }
        throw new RuntimeException("This should never ever happen!"); // should never happen!
    }

    private static void sleepSeconds(int seconds) {
        try {
            Thread.sleep((1000 * seconds));
        } catch (InterruptedException ex) {
            LOG.log(Level.SEVERE, "InterruptedException in sleepSeconds() occurred!", ex);
            Thread.currentThread().interrupt();
        }
    }
}

 

Hier die Operations:

/**
 *
 * copyright @ bitschmid.com
 */
public abstract class RetryOperation<T extends Exception> {

    private final Class<T> myExceptionClass;

    public RetryOperation(Class<T> myExceptionClass) {
        this.myExceptionClass = myExceptionClass;
    }

    /**
     * Executes the code you want to retry.
     *
     * @throws T
     */
    public abstract void exec() throws T;

    /**
     * You can override this method for spezial exception handling.
     *
     * @param cause
     * @param maxRetries
     * @param currentRetry
     * @throws T
     */
    public void handleException(T cause, int maxRetries, int currentRetry) throws T {
        if (currentRetry >= maxRetries) {
            throw cause;
        }
    }

    /**
     * Returns the Class type of the exception
     *
     * @return
     */
    public Class<T> getMyExceptionClass() {
        return myExceptionClass;
    }
}


/** * * copyright @ bitschmid.com */ public abstract class RetryOperationReturn<T extends Exception, ReturnValue extends Object> { private final Class<T> myExceptionClass; public RetryOperationReturn(Class<T> myExceptionClass) { this.myExceptionClass = myExceptionClass; } /** * Executes the code you want to retry. * * @throws T */ public abstract ReturnValue exec() throws T; /** * You can override this method for spezial exception handling. * * @param cause * @param maxRetries * @param currentRetry * @throws T */ public void handleException(T cause, int maxRetries, int currentRetry) throws T { if (currentRetry >= maxRetries) { throw cause; } } /** * Returns the class type of the exception * * @return */ public Class<T> getMyExceptionClass() { return myExceptionClass; } }

 

Hier das reconnect-Interface: 

/**
 *
 * @author ManSch
 */
public interface IReconnectable {

    /**
     * Reconnects this object
     */
    public void reconnect();

}