com.amazonaws.services.simpleworkflow.flow.common.WorkflowExecutionUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.services.simpleworkflow.flow.common.WorkflowExecutionUtils.java

Source

/*
 * Copyright 2012-2016 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.common;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;

import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.model.CloseStatus;
import com.amazonaws.services.simpleworkflow.model.Decision;
import com.amazonaws.services.simpleworkflow.model.DescribeWorkflowExecutionRequest;
import com.amazonaws.services.simpleworkflow.model.EventType;
import com.amazonaws.services.simpleworkflow.model.ExecutionStatus;
import com.amazonaws.services.simpleworkflow.model.GetWorkflowExecutionHistoryRequest;
import com.amazonaws.services.simpleworkflow.model.History;
import com.amazonaws.services.simpleworkflow.model.HistoryEvent;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecution;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionCompletedEventAttributes;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionContinuedAsNewEventAttributes;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionDetail;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionInfo;

/**
 * Convenience methods to be used by unit tests and during development.
 * 
 * @author fateev
 */
public class WorkflowExecutionUtils {

    /**
     * Blocks until workflow instance completes and returns its result. Useful
     * for unit tests and during development. <strong>Never</strong> use in
     * production setting as polling for worklow instance status is an expensive
     * operation.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * @return workflow instance result.
     * @throws InterruptedException
     *             if thread is interrupted
     * @throws RuntimeException
     *             if workflow instance ended up in any state but completed
     */
    public static WorkflowExecutionCompletedEventAttributes waitForWorkflowExecutionResult(
            AmazonSimpleWorkflow service, String domain, WorkflowExecution workflowExecution)
            throws InterruptedException {
        try {
            return waitForWorkflowExecutionResult(service, domain, workflowExecution, 0);
        } catch (TimeoutException e) {
            throw new Error("should never happen", e);
        }
    }

    /**
     * Waits up to specified timeout until workflow instance completes and
     * returns its result. Useful for unit tests and during development.
     * <strong>Never</strong> use in production setting as polling for worklow
     * instance status is an expensive operation.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * @return workflow instance result.
     * @throws InterruptedException
     *             if thread is interrupted
     * @throws TimeoutException
     *             if instance is not complete after specified timeout
     * @throws RuntimeException
     *             if workflow instance ended up in any state but completed
     */
    public static WorkflowExecutionCompletedEventAttributes waitForWorkflowExecutionResult(
            AmazonSimpleWorkflow service, String domain, WorkflowExecution workflowExecution, long timeoutSeconds)
            throws InterruptedException, TimeoutException {
        if (!waitForWorkflowInstanceCompletion(service, domain, workflowExecution, timeoutSeconds)
                .equals(CloseStatus.COMPLETED.toString())) {
            String historyDump = WorkflowExecutionUtils.prettyPrintHistory(service, domain, workflowExecution);
            throw new RuntimeException("Workflow instance is not in completed state:\n" + historyDump);
        }
        return getWorkflowExecutionResult(service, domain, workflowExecution);
    }

    /**
     * Returns result of workflow instance execution. result of
     * {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * 
     * @throws IllegalStateException
     *             if workflow is still running
     * @throws RuntimeException
     *             if workflow instance ended up in any state but completed
     */
    public static WorkflowExecutionCompletedEventAttributes getWorkflowExecutionResult(AmazonSimpleWorkflow service,
            String domain, WorkflowExecution workflowExecution) {
        HistoryEvent closeEvent = getInstanceCloseEvent(service, domain, workflowExecution);
        if (closeEvent == null) {
            throw new IllegalStateException("Workflow is still running");
        }
        if (closeEvent.getEventType().equals(EventType.WorkflowExecutionCompleted.toString())) {
            return closeEvent.getWorkflowExecutionCompletedEventAttributes();
        }
        throw new RuntimeException("Workflow end state is not completed: " + prettyPrintHistoryEvent(closeEvent));
    }

    public static HistoryEvent getInstanceCloseEvent(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) {

        WorkflowExecutionInfo executionInfo = describeWorkflowInstance(service, domain, workflowExecution);
        if (executionInfo == null || executionInfo.getExecutionStatus().equals(ExecutionStatus.OPEN.toString())) {
            return null;
        }

        List<HistoryEvent> events = getHistory(service, domain, workflowExecution);
        HistoryEvent result = null;
        for (HistoryEvent event : events) {
            if (isWorkflowExecutionCompletedEvent(event)) {
                result = event;
                break;
            }
        }
        return result;
    }

    public static boolean isWorkflowExecutionCompletedEvent(HistoryEvent event) {
        return ((event != null) && (event.getEventType().equals(EventType.WorkflowExecutionCompleted.toString())
                || event.getEventType().equals(EventType.WorkflowExecutionCanceled.toString())
                || event.getEventType().equals(EventType.WorkflowExecutionFailed.toString())
                || event.getEventType().equals(EventType.WorkflowExecutionTimedOut.toString())
                || event.getEventType().equals(EventType.WorkflowExecutionContinuedAsNew.toString())
                || event.getEventType().equals(EventType.WorkflowExecutionTerminated.toString())));
    }

    public static boolean isActivityTaskClosedEvent(HistoryEvent event) {
        return ((event != null) && (event.getEventType().equals(EventType.ActivityTaskCompleted.toString())
                || event.getEventType().equals(EventType.ActivityTaskCanceled.toString())
                || event.getEventType().equals(EventType.ActivityTaskFailed.toString())
                || event.getEventType().equals(EventType.ActivityTaskTimedOut.toString())));
    }

    public static boolean isExternalWorkflowClosedEvent(HistoryEvent event) {
        return ((event != null)
                && (event.getEventType().equals(EventType.ChildWorkflowExecutionCompleted.toString())
                        || event.getEventType().equals(EventType.ChildWorkflowExecutionCanceled.toString())
                        || event.getEventType().equals(EventType.ChildWorkflowExecutionFailed.toString())
                        || event.getEventType().equals(EventType.ChildWorkflowExecutionTerminated.toString())
                        || event.getEventType().equals(EventType.ChildWorkflowExecutionTimedOut.toString())));
    }

    public static WorkflowExecution getWorkflowIdFromExternalWorkflowCompletedEvent(HistoryEvent event) {
        if (event != null) {
            if (event.getEventType().equals(EventType.ChildWorkflowExecutionCompleted.toString())) {
                return event.getChildWorkflowExecutionCompletedEventAttributes().getWorkflowExecution();
            } else if (event.getEventType().equals(EventType.ChildWorkflowExecutionCanceled.toString())) {
                return event.getChildWorkflowExecutionCanceledEventAttributes().getWorkflowExecution();
            } else if (event.getEventType().equals(EventType.ChildWorkflowExecutionFailed.toString())) {
                return event.getChildWorkflowExecutionFailedEventAttributes().getWorkflowExecution();
            } else if (event.getEventType().equals(EventType.ChildWorkflowExecutionTerminated.toString())) {
                return event.getChildWorkflowExecutionTerminatedEventAttributes().getWorkflowExecution();
            } else if (event.getEventType().equals(EventType.ChildWorkflowExecutionTimedOut.toString())) {
                return event.getChildWorkflowExecutionTimedOutEventAttributes().getWorkflowExecution();
            }
        }

        return null;
    }

    public static String getId(HistoryEvent historyEvent) {
        String id = null;
        if (historyEvent != null) {
            if (historyEvent.getEventType().equals(EventType.StartChildWorkflowExecutionFailed.toString())) {
                id = historyEvent.getStartChildWorkflowExecutionFailedEventAttributes().getWorkflowId();
            } else if (historyEvent.getEventType().equals(EventType.ScheduleActivityTaskFailed.toString())) {
                id = historyEvent.getScheduleActivityTaskFailedEventAttributes().getActivityId();
            } else if (historyEvent.getEventType().equals(EventType.StartTimerFailed.toString())) {
                id = historyEvent.getStartTimerFailedEventAttributes().getTimerId();
            }
        }

        return id;
    }

    public static String getFailureCause(HistoryEvent historyEvent) {
        String failureCause = null;
        if (historyEvent != null) {
            if (historyEvent.getEventType().equals(EventType.StartChildWorkflowExecutionFailed.toString())) {
                failureCause = historyEvent.getStartChildWorkflowExecutionFailedEventAttributes().getCause();
            } else if (historyEvent.getEventType()
                    .equals(EventType.SignalExternalWorkflowExecutionFailed.toString())) {
                failureCause = historyEvent.getSignalExternalWorkflowExecutionFailedEventAttributes().getCause();
            } else if (historyEvent.getEventType().equals(EventType.ScheduleActivityTaskFailed.toString())) {
                failureCause = historyEvent.getScheduleActivityTaskFailedEventAttributes().getCause();
            } else if (historyEvent.getEventType().equals(EventType.StartTimerFailed.toString())) {
                failureCause = historyEvent.getStartTimerFailedEventAttributes().getCause();
            } else if (historyEvent.getEventType()
                    .equals(EventType.ContinueAsNewWorkflowExecutionFailed.toString())) {
                failureCause = historyEvent.getContinueAsNewWorkflowExecutionFailedEventAttributes().getCause();
            } else if (historyEvent.getEventType().equals(EventType.RecordMarkerFailed.toString())) {
                failureCause = historyEvent.getRecordMarkerFailedEventAttributes().getCause();
            }
        }

        return failureCause;
    }

    /**
     * Blocks until workflow instance completes. <strong>Never</strong> use in
     * production setting as polling for worklow instance status is an expensive
     * operation.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * @return instance close status
     */
    public static String waitForWorkflowInstanceCompletion(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) throws InterruptedException {
        try {
            return waitForWorkflowInstanceCompletion(service, domain, workflowExecution, 0);
        } catch (TimeoutException e) {
            throw new Error("should never happen", e);
        }
    }

    /**
     * Waits up to specified timeout for workflow instance completion.
     * <strong>Never</strong> use in production setting as polling for worklow
     * instance status is an expensive operation.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * @param timeoutSeconds
     *            maximum time to wait for completion. 0 means wait forever.
     * @return instance close status
     * @throws TimeoutException
     */
    public static String waitForWorkflowInstanceCompletion(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution, long timeoutSeconds)
            throws InterruptedException, TimeoutException {
        long start = System.currentTimeMillis();
        WorkflowExecutionInfo executionInfo = null;
        do {
            if (timeoutSeconds > 0 && System.currentTimeMillis() - start >= timeoutSeconds * 1000) {
                String historyDump = WorkflowExecutionUtils.prettyPrintHistory(service, domain, workflowExecution);
                throw new TimeoutException(
                        "Workflow instance is not complete after " + timeoutSeconds + " seconds: \n" + historyDump);
            }
            if (executionInfo != null) {
                Thread.sleep(1000);
            }
            executionInfo = describeWorkflowInstance(service, domain, workflowExecution);
        } while (executionInfo.getExecutionStatus().equals(ExecutionStatus.OPEN.toString()));
        return executionInfo.getCloseStatus();
    }

    /**
     * Like
     * {@link #waitForWorkflowInstanceCompletion(AmazonSimpleWorkflow, String, WorkflowExecution, long)}
     * , except will wait for continued generations of the original workflow
     * execution too.
     * 
     * @param service
     * @param domain
     * @param workflowExecution
     * @param timeoutSeconds
     * @return
     * @throws InterruptedException
     * @throws TimeoutException
     * 
     * @see #waitForWorkflowInstanceCompletion(AmazonSimpleWorkflow, String,
     *      WorkflowExecution, long)
     */
    public static String waitForWorkflowInstanceCompletionAcrossGenerations(AmazonSimpleWorkflow service,
            String domain, WorkflowExecution workflowExecution, long timeoutSeconds)
            throws InterruptedException, TimeoutException {

        WorkflowExecution lastExecutionToRun = workflowExecution;
        long millisecondsAtFirstWait = System.currentTimeMillis();
        String lastExecutionToRunCloseStatus = waitForWorkflowInstanceCompletion(service, domain,
                lastExecutionToRun, timeoutSeconds);

        // keep waiting if the instance continued as new
        while (lastExecutionToRunCloseStatus.equals(CloseStatus.CONTINUED_AS_NEW.toString())) {
            // get the new execution's information
            HistoryEvent closeEvent = getInstanceCloseEvent(service, domain, lastExecutionToRun);
            WorkflowExecutionContinuedAsNewEventAttributes continuedAsNewAttributes = closeEvent
                    .getWorkflowExecutionContinuedAsNewEventAttributes();

            WorkflowExecution newGenerationExecution = new WorkflowExecution()
                    .withWorkflowId(lastExecutionToRun.getWorkflowId())
                    .withRunId(continuedAsNewAttributes.getNewExecutionRunId());

            // and wait for it
            long currentTime = System.currentTimeMillis();
            long millisecondsSinceFirstWait = currentTime - millisecondsAtFirstWait;
            long timeoutInSecondsForNextWait = timeoutSeconds - (millisecondsSinceFirstWait / 1000L);

            lastExecutionToRunCloseStatus = waitForWorkflowInstanceCompletion(service, domain,
                    newGenerationExecution, timeoutInSecondsForNextWait);
            lastExecutionToRun = newGenerationExecution;
        }

        return lastExecutionToRunCloseStatus;
    }

    /**
     * Like
     * {@link #waitForWorkflowInstanceCompletionAcrossGenerations(AmazonSimpleWorkflow, String, WorkflowExecution, long)}
     * , but with no timeout.
     * 
     * @param service
     * @param domain
     * @param workflowExecution
     * @return
     * @throws InterruptedException
     */
    public static String waitForWorkflowInstanceCompletionAcrossGenerations(AmazonSimpleWorkflow service,
            String domain, WorkflowExecution workflowExecution) throws InterruptedException {
        try {
            return waitForWorkflowInstanceCompletionAcrossGenerations(service, domain, workflowExecution, 0L);
        } catch (TimeoutException e) {
            throw new Error("should never happen", e);
        }
    }

    public static WorkflowExecutionInfo describeWorkflowInstance(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) {
        DescribeWorkflowExecutionRequest describeRequest = new DescribeWorkflowExecutionRequest();
        describeRequest.setDomain(domain);
        describeRequest.setExecution(workflowExecution);
        WorkflowExecutionDetail executionDetail = service.describeWorkflowExecution(describeRequest);
        WorkflowExecutionInfo instanceMetadata = executionDetail.getExecutionInfo();
        return instanceMetadata;
    }

    /**
     * Returns workflow instance history in a human readable format.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     */
    public static String prettyPrintHistory(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) {
        return prettyPrintHistory(service, domain, workflowExecution, true);
    }

    /**
     * Returns workflow instance history in a human readable format.
     * 
     * @param workflowExecution
     *            result of
     *            {@link AmazonSimpleWorkflow#startWorkflowInstance(com.amazonaws.services.simpleworkflow.model.StartWorkflowInstanceRequest)}
     * @param showWorkflowTasks
     *            when set to false workflow task events (decider events) are
     *            not included
     */
    public static String prettyPrintHistory(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution, boolean showWorkflowTasks) {
        List<HistoryEvent> events = getHistory(service, domain, workflowExecution);
        return prettyPrintHistory(events, showWorkflowTasks);
    }

    public static List<HistoryEvent> getHistory(AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) {
        List<HistoryEvent> events = new ArrayList<HistoryEvent>();
        String nextPageToken = null;
        do {
            History history = getHistoryPage(nextPageToken, service, domain, workflowExecution);
            events.addAll(history.getEvents());
            nextPageToken = history.getNextPageToken();
        } while (nextPageToken != null);
        return events;
    }

    public static History getHistoryPage(String nextPageToken, AmazonSimpleWorkflow service, String domain,
            WorkflowExecution workflowExecution) {

        GetWorkflowExecutionHistoryRequest getHistoryRequest = new GetWorkflowExecutionHistoryRequest();
        getHistoryRequest.setDomain(domain);
        getHistoryRequest.setExecution(workflowExecution);
        getHistoryRequest.setNextPageToken(nextPageToken);

        History history = service.getWorkflowExecutionHistory(getHistoryRequest);
        if (history == null) {
            throw new IllegalArgumentException("unknown workflow execution: " + workflowExecution);
        }
        return history;
    }

    /**
     * Returns workflow instance history in a human readable format.
     * 
     * @history Workflow instance history
     * @param showWorkflowTasks
     *            when set to false workflow task events (decider events) are
     *            not included
     */
    public static String prettyPrintHistory(History history, boolean showWorkflowTasks) {
        return prettyPrintHistory(history.getEvents(), showWorkflowTasks);
    }

    public static String prettyPrintHistory(Iterable<HistoryEvent> events, boolean showWorkflowTasks) {
        StringBuffer result = new StringBuffer();
        result.append("{");
        boolean first = true;
        for (HistoryEvent event : events) {
            if (!showWorkflowTasks && event.getEventType().startsWith("WorkflowTask")) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                result.append(",");
            }
            result.append("\n    ");
            result.append(prettyPrintHistoryEvent(event));
        }
        result.append("\n}");
        return result.toString();
    }

    public static String prettyPrintDecisions(Iterable<Decision> decisions) {
        StringBuffer result = new StringBuffer();
        result.append("{");
        boolean first = true;
        for (Decision decision : decisions) {
            if (first) {
                first = false;
            } else {
                result.append(",");
            }
            result.append("\n    ");
            result.append(prettyPrintDecision(decision));
        }
        result.append("\n}");
        return result.toString();
    }

    /**
     * Returns single event in a human readable format
     * 
     * @param event
     *            event to pretty print
     */
    public static String prettyPrintHistoryEvent(HistoryEvent event) {
        String eventType = event.getEventType();
        StringBuffer result = new StringBuffer();
        result.append(eventType);
        result.append(prettyPrintObject(event, "getType", true, "    ", false));
        return result.toString();
    }

    /**
     * Returns single decision in a human readable format
     * 
     * @param event
     *            event to pretty print
     */
    public static String prettyPrintDecision(Decision decision) {
        return prettyPrintObject(decision, "getType", true, "    ", true);
    }

    /**
     * Not really a generic method for printing random object graphs. But it
     * works for events and decisions.
     */
    private static String prettyPrintObject(Object object, String methodToSkip,
            boolean skipNullsAndEmptyCollections, String indentation, boolean skipLevel) {
        StringBuffer result = new StringBuffer();
        if (object == null) {
            return "null";
        }
        Class<? extends Object> clz = object.getClass();
        if (Number.class.isAssignableFrom(clz)) {
            return String.valueOf(object);
        }
        if (Boolean.class.isAssignableFrom(clz)) {
            return String.valueOf(object);
        }
        if (clz.equals(String.class)) {
            return (String) object;
        }
        if (clz.equals(Date.class)) {
            return String.valueOf(object);
        }
        if (Map.class.isAssignableFrom(clz)) {
            return String.valueOf(object);
        }
        if (Collection.class.isAssignableFrom(clz)) {
            return String.valueOf(object);
        }
        if (!skipLevel) {
            result.append(" {");
        }
        Method[] eventMethods = object.getClass().getMethods();
        boolean first = true;
        for (Method method : eventMethods) {
            String name = method.getName();
            if (!name.startsWith("get")) {
                continue;
            }
            if (name.equals(methodToSkip) || name.equals("getClass")) {
                continue;
            }
            if (Modifier.isStatic(method.getModifiers())) {
                continue;
            }
            Object value;
            try {
                value = method.invoke(object, (Object[]) null);
                if (value != null && value.getClass().equals(String.class) && name.equals("getDetails")) {
                    value = printDetails((String) value);
                }
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e.getTargetException());
            } catch (RuntimeException e) {
                throw (RuntimeException) e;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (skipNullsAndEmptyCollections) {
                if (value == null) {
                    continue;
                }
                if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
                    continue;
                }
                if (value instanceof Collection && ((Collection<?>) value).isEmpty()) {
                    continue;
                }
            }
            if (!skipLevel) {
                if (first) {
                    first = false;
                } else {
                    result.append(";");
                }
                result.append("\n");
                result.append(indentation);
                result.append("    ");
                result.append(name.substring(3));
                result.append(" = ");
                result.append(prettyPrintObject(value, methodToSkip, skipNullsAndEmptyCollections,
                        indentation + "    ", false));
            } else {
                result.append(
                        prettyPrintObject(value, methodToSkip, skipNullsAndEmptyCollections, indentation, false));
            }
        }
        if (!skipLevel) {
            result.append("\n");
            result.append(indentation);
            result.append("}");
        }
        return result.toString();
    }

    public static String printDetails(String details) {
        Throwable failure = null;
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            mapper.enableDefaultTyping(DefaultTyping.NON_FINAL);

            failure = mapper.readValue(details, Throwable.class);
        } catch (Exception e) {
            // eat up any data converter exceptions
        }

        if (failure != null) {
            StringBuilder builder = new StringBuilder();

            // Also print callstack
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            failure.printStackTrace(pw);

            builder.append(sw.toString());

            details = builder.toString();
        }

        return details;
    }

    /**
     * Simple Workflow limits length of the reason field. This method truncates
     * the passed argument to the maximum length.
     * 
     * @param reason
     *            string value to truncate
     * @return truncated value
     */
    public static String truncateReason(String reason) {
        if (reason != null && reason.length() > FlowValueConstraint.FAILURE_REASON.getMaxSize()) {
            reason = reason.substring(0, FlowValueConstraint.FAILURE_REASON.getMaxSize());
        }
        return reason;
    }

    public static String truncateDetails(String details) {
        if (details != null && details.length() > FlowValueConstraint.FAILURE_DETAILS.getMaxSize()) {
            details = details.substring(0, FlowValueConstraint.FAILURE_DETAILS.getMaxSize());
        }
        return details;
    }
}