FailureHandler.java

package de.uka.ipd.sdq.beagle.core.failurehandling;

import org.apache.commons.lang3.Validate;

import java.util.function.Supplier;

/**
 * Handler for any failures that may occur while running Beagle. Clients may obtain an
 * instance of this class to report that an exceptional situation occurred.
 *
 * <p>Failures reported through this API may be <em>recoverable</em>. This means that
 * Beagle’s execution does not have to be stopped and that there is a way to continue
 * without or to retry the failed action. Recoverable failures are handled by recover
 * functions that may optionally return a value to continue with after the failure.
 *
 * <p>The reaction to failures can be configured by setting a provider for
 * {@link FailureResolver FailureResolvers} through {@linkplain #setProvider(Supplier)}.
 * The resolvers provided by the provider will be used when a failure is handled by this
 * class.
 *
 * <p>Given that the class has a failure handler defined in the constant
 * {@code FAILURE_HANDLER}, these are some usage examples:
 *
 * <pre>
 * <code>
 * public ReturnType myMethod(ParameterType1 param1, ParameterType2 param2) {
 * 	// code
 * 	try {
 * 		// code that may fail
 * 	} catch (MyExceptionType exception) {
 * 		final FailureReport&lt;ReturnTyp&gt; failure = new FailureReport&lt;&gt;()
 * 			.message("doing something with %s and %s failed", param1, param2)
 * 			.cause(exception)
 * 			.recoverable()
 * 			.retryWith(() -&lt; myMethod(param1, param2));
 * 		return FAILURE_HANDLER.handle(failure);
 * 	}
 * 	// more code
 * }
 * </code>
 * </pre>
 *
 * <pre>
 * <code>
 * public ReturnType myMethod(ParameterType1 param1, ParameterType2 param2) {
 * 	// code
 * 	if (failed) {
 * 		final FailureReport&lt;Void&gt; failure = new FailureReport&lt;&gt;()
 * 			.message("doing something with %s and %s failed", param1, param2)
 * 			.details("We tried this and that, but doing so was not possible")
 * 			.continueWith(this::otherMethod);
 * 		FAILURE_HANDLER.handle(failure);
 * 		return null;
 * 	}
 * 	// more code
 * }
 * </code>
 * </pre>
 *
 * @author Joshua Gleitze
 */
public final class FailureHandler {

	/**
	 * The provider of handlers.
	 */
	private static Supplier<FailureResolver> provider = ExceptionThrowingFailureResolver::new;

	/**
	 * Name of the client this handle was created for.
	 */
	private final String clientName;

	/**
	 * Creates a handler for the client identifed by {@code clientName}.
	 *
	 * @param clientName The name of the client creating this handler.
	 */
	private FailureHandler(final String clientName) {
		this.clientName = clientName;
	}

	/**
	 * Creates a handler a client identified by {@code clientName} may report failures to.
	 *
	 * @param clientName A name identifying the client that may report failures to this
	 *            handler. Must not be {@code null}.
	 * @return A handler for failures of a client called {@code clientName}.
	 */
	public static FailureHandler getHandler(final String clientName) {
		Validate.notNull(clientName);
		return new FailureHandler(clientName);
	}

	/**
	 * Creates a handler instances of {@code clientType} may report failures to.
	 *
	 * @param clientType Class of the clients that may report failures to this handler.
	 *            Must not be {@code null}.
	 * @return A handler for failures of clients assignable to {@code clientType}.
	 */
	public static FailureHandler getHandler(final Class<?> clientType) {
		Validate.notNull(clientType);
		return new FailureHandler(clientType.getSimpleName());
	}

	/**
	 * Sets the provider of failure resolvers. The provider will be used to generate a
	 * resolver when a failure is reported until another provider is set through this
	 * method.
	 *
	 * @param provider A factory for resolvers. Must not be {@code null}.
	 */
	public static void setProvider(final Supplier<FailureResolver> provider) {
		Validate.notNull(provider);
		FailureHandler.provider = provider;
	}

	/**
	 * Reports a failure to this handler and makes it take action. This may include
	 * calling the recover functions potentially provided through {@code report}. This
	 * method will return the value generated by the recover functions. If the
	 * {@code report} describes a non-recoverable failure, this method will not return.
	 * This method might also not return if the handler thinks it’s not appropriate to
	 * continue.
	 *
	 * <p>A call to this method will obtain a resolver from the provider set through
	 * {@link #setProvider(Supplier)}. This resolver will be called to handle the failure.
	 *
	 * @param <RECOVER_TYPE> The recover value’s type.
	 * @param report Information about the failure.
	 * @return The value the reporter should use instead of the one that was expected to
	 *         be generated by the failed action. Is created by the recover functions in
	 *         {@code report}.
	 */
	public <RECOVER_TYPE> RECOVER_TYPE handle(final FailureReport<RECOVER_TYPE> report) {
		return provider.get().handle(report, this.clientName);
	}
}