/*
* Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.services.simpleworkflow.flow.examples.cronwithretry;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.DynamicActivitiesClient;
import com.amazonaws.services.simpleworkflow.flow.DynamicActivitiesClientImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.core.TryCatchFinally;
import com.amazonaws.services.simpleworkflow.flow.interceptors.AsyncRetryingExecutor;
import com.amazonaws.services.simpleworkflow.flow.interceptors.AsyncRunnable;
import com.amazonaws.services.simpleworkflow.flow.interceptors.AsyncScheduledExecutor;
import com.amazonaws.services.simpleworkflow.flow.interceptors.ExponentialRetryPolicy;
import com.amazonaws.services.simpleworkflow.flow.spring.CronInvocationSchedule;
/**
* Demonstrates how to create workflow that executes an activity on schedule
* specified as a "cron" string. Activity name and version are passed as input
* arguments of the workflow. In case of activity failures it is retried
* according to retry options passed as arguments of the workflow.
*
* @author fateev
*/
public class CronWithRetryWorkflowImpl implements CronWithRetryWorkflow {
private static final int SECOND = 1000;
/**
* This is needed to keep the decider logic deterministic as using
* System.currentTimeMillis() in your decider logic is not.
* WorkflowClock.currentTimeMillis() should be used instead.
*/
private final WorkflowClock clock;
private final DynamicActivitiesClient activities;
/**
* Used to create new run of the Cron workflow to reset history. This allows
* "infinite" workflows.
*/
private final CronWithRetryWorkflowSelfClient selfClient;
private final StringBuilder invocationHistory = new StringBuilder();
private TimeZone tz;
public CronWithRetryWorkflowImpl() {
this(new DecisionContextProviderImpl().getDecisionContext().getWorkflowClock(), new DynamicActivitiesClientImpl(),
new CronWithRetryWorkflowSelfClientImpl());
}
/**
* Constructor used for unit testing or configuration through IOC container
*/
public CronWithRetryWorkflowImpl(WorkflowClock clock, DynamicActivitiesClient activities,
CronWithRetryWorkflowSelfClient selfClient) {
this.clock = clock;
this.activities = activities;
this.selfClient = selfClient;
}
@Override
public void startCron(final CronWithRetryWorkflowOptions options) {
Date expiration = new Date(clock.currentTimeMillis() + options.getContinueAsNewAfterSeconds() * SECOND);
tz = TimeZone.getTimeZone(options.getTimeZone());
// Activities client could be decorated directly using CronDecorator and RetryDecorator.
// But executors are used instead to enable updates to invocationHistory.
CronInvocationSchedule cronSchedule = new CronInvocationSchedule(options.getCronExpression(), expiration, tz);
AsyncScheduledExecutor scheduledExecutor = new AsyncScheduledExecutor(cronSchedule, clock);
ExponentialRetryPolicy retryPolicy = createRetryPolicyFromOptions(options);
final AsyncRetryingExecutor retryExecutor = new AsyncRetryingExecutor(retryPolicy, clock);
scheduledExecutor.execute(new AsyncRunnable() {
@Override
public void run() throws Throwable {
retryExecutor.execute(new AsyncRunnable() {
@Override
public void run() throws Throwable {
executeActivityUpdatingInvocationHistory(options);
}
});
}
});
// Start new workflow run as soon as cron decorator exits due to expiration.
// The call to self client indicates the desire to start the new run.
// It is started only after all other tasks in the given run are completed.
selfClient.startCron(options);
}
@Override
public String getInvocationHistory() {
return invocationHistory.toString();
}
private void appendToInvocationHistory(String entry) {
if (invocationHistory.length() > 0) {
invocationHistory.append('\n');
}
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setTimeZone(tz);
invocationHistory.append(dateFormat.format(new Date(clock.currentTimeMillis())));
invocationHistory.append(" ");
invocationHistory.append(entry);
}
private void executeActivityUpdatingInvocationHistory(final CronWithRetryWorkflowOptions options) {
new TryCatchFinally() {
boolean failed;
@Override
protected void doTry() throws Throwable {
appendToInvocationHistory("starting");
activities.scheduleActivity(options.getActivity(), options.getActivityArguments(), null, Void.class);
}
@Override
protected void doCatch(Throwable e) throws Throwable {
failed = true;
appendToInvocationHistory("failure:" + e.getMessage());
throw e;
}
@Override
protected void doFinally() throws Throwable {
if (!failed) {
appendToInvocationHistory("completed");
}
}
};
}
@SuppressWarnings("unchecked")
private ExponentialRetryPolicy createRetryPolicyFromOptions(CronWithRetryWorkflowOptions options) {
ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(options.getInitialRetryIntervalSeconds());
retryPolicy.setBackoffCoefficient(options.getBackoffCoefficient());
try {
List<String> exceptionsToRetryClasses = options.getExceptionsToRetry();
if (exceptionsToRetryClasses != null) {
List<Class<? extends Throwable>> exceptionsToRetry = new ArrayList<Class<? extends Throwable>>();
for (String exceptionType : exceptionsToRetryClasses) {
exceptionsToRetry.add((Class<? extends Throwable>) Class.forName(exceptionType));
}
retryPolicy.setExceptionsToRetry(exceptionsToRetry);
}
List<String> exceptionsToExcludeClasses = options.getExceptionsToExclude();
if (exceptionsToExcludeClasses != null) {
List<Class<? extends Throwable>> exceptionsToExclude = new ArrayList<Class<? extends Throwable>>();
for (String exceptionType : exceptionsToExcludeClasses) {
exceptionsToExclude.add((Class<? extends Throwable>) Class.forName(exceptionType));
}
retryPolicy.setExceptionsToExclude(exceptionsToExclude);
}
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Invalid options: " + options, e);
}
retryPolicy.setMaximumAttempts(options.getMaximumAttempts());
retryPolicy.setMaximumRetryIntervalSeconds(options.getMaximumRetryIntervalSeconds());
retryPolicy.setRetryExpirationIntervalSeconds(options.getRetryExpirationIntervalSeconds());
return retryPolicy;
}
}