/* * Copyright (c) 2010-2014. Axon Framework * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.axonframework.commandhandling.gateway; import org.axonframework.commandhandling.CommandBus; import org.axonframework.commandhandling.CommandCallback; import org.axonframework.commandhandling.CommandMessage; import org.axonframework.common.lock.DeadlockException; import org.axonframework.messaging.unitofwork.CurrentUnitOfWork; import java.util.ArrayList; import java.util.List; /** * Callback implementation that will invoke a retry scheduler if a command results in a runtime exception. * <p/> * Generally, it is not necessary to use this class directly. It is used by CommandGateway implementations to support * retrying of commands. * * @param <R> The type of return value expected by the callback * @param <C> The type of payload of the dispatched command * @author Allard Buijze * @see DefaultCommandGateway * @since 2.0 */ public class RetryingCallback<C, R> implements CommandCallback<C, R> { private final CommandCallback<C, R> delegate; private final RetryScheduler retryScheduler; private final CommandBus commandBus; private final List<Class<? extends Throwable>[]> history; /** * Initialize the RetryingCallback with the given {@code delegate}, representing the actual callback passed as * a parameter to dispatch, the given {@code commandMessage}, {@code retryScheduler} and * {@code commandBus}. * * @param delegate The callback to invoke when the command succeeds, or when retries are rejected. * @param retryScheduler The scheduler that decides if and when a retry should be scheduled * @param commandBus The commandBus on which the command must be dispatched */ public RetryingCallback(CommandCallback<C, R> delegate, RetryScheduler retryScheduler, CommandBus commandBus) { this.delegate = delegate; this.retryScheduler = retryScheduler; this.commandBus = commandBus; this.history = new ArrayList<>(); } @Override public void onSuccess(CommandMessage<? extends C> commandMessage, R result) { delegate.onSuccess(commandMessage, result); } @Override public void onFailure(CommandMessage<? extends C> commandMessage, Throwable cause) { history.add(simplify(cause)); try { // we fail immediately when the exception is checked, // or when it is a Deadlock Exception and we have an active unit of work if (!(cause instanceof RuntimeException) || (isCausedBy(cause, DeadlockException.class) && CurrentUnitOfWork.isStarted()) || !retryScheduler.scheduleRetry(commandMessage, (RuntimeException) cause, new ArrayList<>(history), new RetryDispatch(commandMessage))) { delegate.onFailure(commandMessage, cause); } } catch (Exception e) { delegate.onFailure(commandMessage, e); } } private boolean isCausedBy(Throwable exception, Class<? extends Throwable> causeType) { return causeType.isInstance(exception) || (exception.getCause() != null && isCausedBy(exception.getCause(), causeType)); } @SuppressWarnings("unchecked") private Class<? extends Throwable>[] simplify(Throwable cause) { List<Class<? extends Throwable>> types = new ArrayList<>(); types.add(cause.getClass()); Throwable rootCause = cause; while (rootCause.getCause() != null) { rootCause = rootCause.getCause(); types.add(rootCause.getClass()); } return types.toArray(new Class[types.size()]); } private class RetryDispatch implements Runnable { private final CommandMessage<? extends C> commandMessage; private RetryDispatch(CommandMessage<? extends C> commandMessage) { this.commandMessage = commandMessage; } @Override public void run() { try { commandBus.dispatch(commandMessage, RetryingCallback.this); } catch (Exception e) { RetryingCallback.this.onFailure(commandMessage, e); } } } }