org.amplafi.flow.impl.BaseFlowManagement.java Source code

Java tutorial

Introduction

Here is the source code for org.amplafi.flow.impl.BaseFlowManagement.java

Source

/*
 * 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.amplafi.flow.impl;

import static com.sworddance.util.CUtilities.*;
import static org.amplafi.flow.FlowConstants.FSCONTINUE_WITH_FLOW;
import static org.amplafi.flow.FlowConstants.FSREDIRECT_URL;
import static org.amplafi.flow.FlowConstants.FSRETURN_TO_FLOW;
import static org.apache.commons.lang.StringUtils.isNotBlank;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.amplafi.flow.Flow;
import org.amplafi.flow.FlowActivity;
import org.amplafi.flow.FlowActivityImplementor;
import org.amplafi.flow.FlowActivityPhase;
import org.amplafi.flow.FlowException;
import org.amplafi.flow.FlowImplementor;
import org.amplafi.flow.FlowManagement;
import org.amplafi.flow.FlowManager;
import org.amplafi.flow.FlowPropertyDefinition;
import org.amplafi.flow.FlowState;
import org.amplafi.flow.FlowStateLifecycle;
import org.amplafi.flow.FlowStateListener;
import org.amplafi.flow.FlowStepDirection;
import org.amplafi.flow.FlowTransition;
import org.amplafi.flow.FlowTranslatorResolver;
import org.amplafi.flow.FlowTx;
import org.amplafi.flow.flowproperty.FlowPropertyDefinitionBuilder;
import org.amplafi.flow.flowproperty.FlowPropertyDefinitionImplementor;
import org.amplafi.flow.flowproperty.FlowPropertyProvider;
import org.amplafi.flow.flowproperty.FlowPropertyProviderImplementor;
import org.amplafi.flow.flowproperty.PropertyScope;
import org.amplafi.flow.flowproperty.PropertyUsage;
import org.amplafi.flow.launcher.ValueFromBindingProvider;
import org.amplafi.flow.web.PageProvider;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sworddance.beans.ClassResolver;
import com.sworddance.beans.DefaultClassResolver;
import com.sworddance.util.ApplicationIllegalArgumentException;
import com.sworddance.util.perf.LapTimer;

/**
 * A basic implementation of FlowManagement.
 */
public class BaseFlowManagement implements FlowManagement {

    protected SessionFlows sessionFlows = new SessionFlows();

    private transient FlowManager flowManager;

    private transient FlowTx flowTx;

    private transient PageProvider pageProvider;

    private transient ValueFromBindingProvider valueFromBindingProvider;

    private transient FlowTranslatorResolver flowTranslatorResolver;

    private transient Set<FlowStateListener> flowStateListeners = Collections
            .synchronizedSet(new HashSet<FlowStateListener>());

    private transient ClassResolver classResolver;

    public BaseFlowManagement() {

    }

    /**
     * @see org.amplafi.flow.FlowManagement#getFlowStates()
     */
    @Override
    public List<FlowState> getFlowStates() {
        List<FlowState> collection = new ArrayList<FlowState>();
        CollectionUtils.addAll(collection, sessionFlows.iterator());
        return collection;
    }

    @Override
    public String getCurrentPage() {
        FlowState flow = getCurrentFlowState();
        if (flow != null) {
            return flow.getCurrentPage();
        } else {
            return null;
        }
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getCurrentFlowState()
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FS extends FlowState> FS getCurrentFlowState() {
        return (FS) sessionFlows.getFirst();
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getActiveFlowStatesByType(java.lang.String...)
     */
    @Override
    public synchronized List<FlowState> getActiveFlowStatesByType(String... flowTypes) {
        List<String> types = Arrays.asList(flowTypes);
        ArrayList<FlowState> result = new ArrayList<FlowState>();
        for (FlowState flowState : sessionFlows) {
            if (types.contains(flowState.getFlowTypeName())) {
                result.add(flowState);
            }
        }
        return result;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getFirstFlowStateByType(java.lang.String...)
     */
    @Override
    @SuppressWarnings("unchecked")
    public synchronized <FS extends FlowState> FS getFirstFlowStateByType(String... flowTypes) {
        if (flowTypes == null) {
            return null;
        }
        List<String> types = Arrays.asList(flowTypes);
        return (FS) getFirstFlowStateByType(types);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <FS extends FlowState> FS getFirstFlowStateByType(Collection<String> types) {
        for (FlowState flowState : sessionFlows) {
            if (types.contains(flowState.getFlowTypeName())) {
                return (FS) flowState;
            }
        }
        return null;
    }

    @Override
    public Flow getFlowDefinition(String flowTypeName) {
        return getFlowManager().getFlowDefinition(flowTypeName);
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getFlowState(java.lang.String)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FS extends FlowState> FS getFlowState(String lookupKey) {
        FS flowState;
        if (isNotBlank(lookupKey)) {
            flowState = (FS) sessionFlows.get(lookupKey);
        } else {
            flowState = null;
        }
        return flowState;
    }

    /**
     * Override this method to create a custom {@link FlowState} object.
     *
     * @param flowTypeName
     * @param initialFlowState
     * @return the newly created FlowsState
     */
    @SuppressWarnings("unchecked")
    protected <FS extends FlowState> FS makeFlowState(String flowTypeName, Map<String, String> initialFlowState) {
        return (FS) new FlowStateImpl(flowTypeName, this, initialFlowState);
    }

    /**
     * @see org.amplafi.flow.FlowManagement#createFlowState(java.lang.String, java.util.Map,
     *      boolean) TODO : sees like method should be renamed.
     */
    @Override
    @SuppressWarnings("unchecked")
    public synchronized <FS extends FlowState> FS createFlowState(String flowTypeName,
            Map<String, String> initialFlowState, boolean makeNewStateCurrent) {
        LapTimer.sLap("Begin createFlowState ", flowTypeName);
        FS flowState = (FS) makeFlowState(flowTypeName, initialFlowState);
        initializeFlowState(flowState);
        if (makeNewStateCurrent || this.sessionFlows.isEmpty()) {
            makeCurrent(flowState);
        } else {
            makeLast(flowState);
        }
        LapTimer.sLap("End createFlowState ", flowTypeName);
        return flowState;
    }

    protected <FS extends FlowState> void initializeFlowState(FS flowState) {
        flowState.initializeFlow();
    }

    /**
     * @see org.amplafi.flow.FlowManagement#transitionToFlowState(FlowState, String)
     */
    @SuppressWarnings("unchecked")
    @Override
    public FlowState transitionToFlowState(FlowState flowState, String key) {
        FlowStateImplementor nextFlowState = null;
        Map<String, FlowTransition> transitions = flowState.getProperty(key, Map.class);
        String finishKey = flowState.getFinishKey();
        if (isNotEmpty(transitions) && isNotBlank(finishKey)) {
            FlowTransition flowTransition = transitions.get(finishKey);
            if (flowTransition != null) {
                FlowActivityImplementor currentActivity = flowState.getCurrentActivity();
                String flowType = currentActivity.resolveIndirectReference(flowTransition.getNextFlowType());
                if (isNotBlank(flowType)) {
                    Map<String, String> exportedValuesMap = flowState.getExportedValuesMap();
                    nextFlowState = this.createFlowState(flowType, exportedValuesMap, false);
                    nextFlowState.copyTrustedValuesMapToFlowState(flowTransition.getInitialValues());
                }
            }
        }
        return nextFlowState;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#startFlowState(java.lang.String, boolean, java.util.Map,
     *      Object)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FS extends FlowState> FS startFlowState(String flowTypeName, boolean makeNewStateCurrent,
            Map<String, String> initialFlowState, Object returnToFlow) {
        initialFlowState = initReturnToFlow(initialFlowState, returnToFlow);
        return (FS) startFlowState(flowTypeName, makeNewStateCurrent, initialFlowState);
    }

    @Override
    public <FS extends FlowState> FS startFlowState(String flowTypeName, boolean makeNewStateCurrent,
            Map<String, String> initialFlowState) {
        FS flowState = (FS) createFlowState(flowTypeName, initialFlowState, makeNewStateCurrent);
        return (FS) beginFlowState(flowState);
    }

    /**
     * @param initialFlowState
     * @param returnToFlow
     * @return initialFlowState if existed otherwise a new map if returnToFlow was legal.
     */
    protected Map<String, String> initReturnToFlow(Map<String, String> initialFlowState, Object returnToFlow) {
        if (returnToFlow != null) {
            String returnToFlowLookupKey = null;
            if (returnToFlow instanceof Boolean) {
                if (((Boolean) returnToFlow).booleanValue()) {
                    FlowState currentFlowState = getCurrentFlowState();
                    if (currentFlowState != null) {
                        returnToFlowLookupKey = currentFlowState.getLookupKey();
                    }
                }
            } else if (returnToFlow instanceof FlowState) {
                returnToFlowLookupKey = ((FlowState) returnToFlow).getLookupKey();
            } else {
                returnToFlowLookupKey = returnToFlow.toString();
            }
            if (isNotBlank(returnToFlowLookupKey)) {
                if (initialFlowState == null) {
                    initialFlowState = new HashMap<String, String>();
                }
                initialFlowState.put(FSRETURN_TO_FLOW, returnToFlowLookupKey);
            }
        }
        return initialFlowState;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#continueFlowState(java.lang.String, boolean,
     *      java.util.Map)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <FS extends FlowState> FS continueFlowState(String lookupKey, boolean makeStateCurrent,
            Map<String, String> initialFlowState) {
        FlowStateImplementor flowState = getFlowState(lookupKey);
        ApplicationIllegalArgumentException.notNull(lookupKey, ": no flow with this lookupKey found");
        flowState.copyTrustedValuesMapToFlowState(initialFlowState);
        if (makeStateCurrent) {
            makeCurrent(flowState);
        }
        return (FS) flowState;
    }

    /**
     * call flowState's {@link FlowState#begin()}. If flowState is now completed then see if the
     * flow has transitioned to a new flow.
     *
     * @param flowState
     * @return flowState if flowState has not completed, otherwise the continue flow or the return
     *         flow.
     */
    @SuppressWarnings("unchecked")
    protected <FS extends FlowState> FS beginFlowState(FlowState flowState) {
        boolean success = false;
        getLog().debug("Starting " + flowState);
        LapTimer.sLap(flowState, "beginning");
        try {
            flowState.begin();
            success = true;
            if (flowState.isCompleted()) {
                FS state = (FS) getNextFlowState(flowState);
                if (state != null) {
                    return state;
                }
            }
            return (FS) flowState;
        } catch (FlowException flowException) {
            if (!flowException.isFlowStateSet()) {
                flowException.setFlowState(flowState);
            }
            throw flowException;
        } finally {
            if (!success) {
                this.dropFlowState(flowState);
            } else {
                LapTimer.sLap(flowState, "begun");
            }
            getLog().debug("Started " + flowState);
        }
    }

    /**
     * @param flowState
     * @return
     */
    @SuppressWarnings("unchecked")
    private <FS extends FlowState> FS getNextFlowState(FlowState flowState) {
        String id = flowState.getProperty(FSCONTINUE_WITH_FLOW);
        FS next = (FS) this.getFlowState(id);
        if (next == null) {
            id = flowState.getProperty(FSRETURN_TO_FLOW);
            next = (FS) this.getFlowState(id);
        }
        return next;
    }

    @Override
    public void wireDependencies(Object object) {
        if (object instanceof FlowPropertyProvider) {
            getFlowTranslatorResolver().resolve((FlowPropertyProvider) object);
        }
        if (object instanceof FlowPropertyDefinition) {
            // HACK : really should be handling the wiring issue without special casing.
            FlowPropertyDefinition flowPropertyDefinition = (FlowPropertyDefinition) object;
            for (Object objectNeedingToBeWired : flowPropertyDefinition.getObjectsNeedingToBeWired()) {
                wireDependencies(objectNeedingToBeWired);
            }
        }
    }

    /**
     * @see org.amplafi.flow.FlowManagement#dropFlowState(org.amplafi.flow.FlowState)
     */
    @Override
    public synchronized String dropFlowState(FlowState flow) {
        String lookupKey = flow.getLookupKey();
        getLog().debug("Dropping flow " + lookupKey);
        boolean successful = false;
        try {
            if (!sessionFlows.isEmpty()) {
                FlowStateImplementor fs = sessionFlows.getFirst();
                boolean first = fs.hasLookupKey(lookupKey);
                fs = (FlowStateImplementor) sessionFlows.removeByLookupKey(lookupKey);
                if (fs != null) {
                    successful = true;
                    if (!fs.getFlowStateLifecycle().isTerminalState()) {
                        fs.setFlowLifecycleState(FlowStateLifecycle.canceled);
                    }

                    // look for redirect before clearing the flow state
                    // why before cache clearing?
                    URI redirect = fs.getProperty(FSREDIRECT_URL, URI.class);
                    String returnToFlowId = fs.getProperty(FSRETURN_TO_FLOW);
                    FlowState returnToFlow = getFlowState(returnToFlowId);
                    fs.clearCache();

                    if (!first) {
                        // dropped flow was not the current flow
                        // so we return the current flow's page.
                        return sessionFlows.getFirst().getCurrentPage();
                    } else if (redirect != null) {
                        return redirect.toString();
                    } else if (returnToFlow != null) {
                        makeCurrent(returnToFlow);
                        return returnToFlow.getCurrentPage();
                    } else if (returnToFlowId != null) {
                        getLog().warn("FlowState (" + fs.getLookupKey() + ") trying to return to a flowState ("
                                + returnToFlowId + ") that could not be found.");
                    }
                    if (!sessionFlows.isEmpty()) {
                        FlowState currentFlow = sessionFlows.getFirst();
                        makeCurrent(currentFlow);
                        return currentFlow.getCurrentPage();
                    } else {
                        // no other flows...
                        return fs.getAfterPage();
                    }
                }
            }
            return null;
        } finally {
            if (!successful) {
                getLog().info("Did not find flow to drop. key=" + lookupKey);
            }
        }
    }

    /**
     * Called by the {@link FlowState#finishFlow()}
     *
     * @param newFlowActive pass false if you already have flow to run.
     */
    @Override
    public String completeFlowState(FlowState flowState, boolean newFlowActive,
            FlowStateLifecycle flowStateLifecycle) {
        return dropFlowState(flowState);
    }

    /**
     * @see org.amplafi.flow.FlowManagement#makeCurrent(org.amplafi.flow.FlowState)
     */
    @Override
    public synchronized void makeCurrent(FlowState state) {
        if (!this.sessionFlows.isEmpty()) {
            FlowStateImplementor oldFirst = this.sessionFlows.getFirst();
            if (oldFirst != state) {
                // state was NOT already the first state.
                sessionFlows.remove((FlowStateImplementor) state);
                if (!oldFirst.isNotCurrentAllowed()) {
                    // the formerly first state is only supposed to be active if it is the first state.
                    // see if it this state is referenced as a return state -- otherwise the oldFirst will need to be dropped.
                    boolean notReferenced = state.isReferencing(oldFirst);
                    if (!notReferenced) {
                        for (FlowState flowState : this.sessionFlows) {
                            if (flowState.isReferencing(oldFirst)) {
                                notReferenced = false;
                                break;
                            }
                        }
                    }
                    if (!notReferenced) {
                        dropFlowState(oldFirst);
                    }
                } else {
                    oldFirst.clearCache();
                }
            }
        }
        this.sessionFlows.makeFirst((FlowStateImplementor) state);
    }

    protected void makeLast(FlowState flowState) {
        this.sessionFlows.addLast((FlowStateImplementor) flowState);
    }

    /**
     * @see org.amplafi.flow.FlowManagement#makeAfter(org.amplafi.flow.FlowState,
     *      org.amplafi.flow.FlowState)
     */
    @Override
    public boolean makeAfter(FlowState flowState, FlowState nextFlowState) {
        boolean wasFirst = this.sessionFlows.makeAfter((FlowStateImplementor) flowState,
                (FlowStateImplementor) nextFlowState) == 0;
        if (wasFirst) {
            makeCurrent(this.sessionFlows.getFirst());
        }
        return wasFirst;
    }

    @Override
    public <T> FlowPropertyDefinitionImplementor createFlowPropertyDefinition(
            FlowPropertyProviderImplementor flowPropertyProvider, String key, Class<T> expected, T sampleValue) {
        Class<? extends T> expectedClass;
        if (expected == null) {
            if (this.getClassResolver() != null) {
                expectedClass = getClassResolver().getRealClass(sampleValue);
            } else {
                expectedClass = DefaultClassResolver.INSTANCE.getRealClass(sampleValue);
            }
        } else {
            expectedClass = expected;
        }
        // something to be said for making it requestFlowLocal - because this would give flash persistence for free.
        // but using global allows a property to be set that is really for the next flow to be run.
        // Note: cannot use flowLocal scope because this definition may not be preserved in the flow and then the export would not properly happen.
        // Note: because of read then write possibility then we need to assume that property will be set even if it is not now.
        FlowPropertyDefinitionBuilder flowPropertyDefinitionBuilder = getFactoryFlowPropertyDefinitionBuilder(key,
                expectedClass);
        if (flowPropertyDefinitionBuilder == null) {
            // dynamically created properties should never be outputed ( probably internal )
            // this also has the nice benefit of isolating the FlowActivity
            // from accidentally accessing a property that exists in the flowstate as a residual from
            // keyvaluemap but was not declared as flowLocal ( the property would not have been copied to the flowstate's namespace)
            // Kostya: setting property usage to consume to allow flow read properties which name is not known at the flow initialization step.
            // Needed for example when handling callbacks from external services.
            flowPropertyDefinitionBuilder = new FlowPropertyDefinitionBuilder(key, expectedClass)
                    .initAccess(PropertyScope.requestFlowLocal, PropertyUsage.consume);
        }
        FlowPropertyDefinitionImplementor propertyDefinition = flowPropertyDefinitionBuilder
                .toFlowPropertyDefinition();
        if (sampleValue != null) {
            // actually going to be setting this property
            getLog().warn("FlowState: Creating a dynamic FlowDefinition for key=" + key + "(expected class="
                    + expectedClass + ") might want to check situation. FlowState=" + flowPropertyProvider);
            flowPropertyProvider.addPropertyDefinitions(propertyDefinition);
        }
        // HACK : don't think this should be a 'toString()' maybe flowProvidername ?
        getFlowTranslatorResolver().resolve(flowPropertyProvider.toString(), propertyDefinition);
        return propertyDefinition;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getInstanceFromDefinition(java.lang.String)
     */
    @Override
    public FlowImplementor getInstanceFromDefinition(String flowTypeName) {
        return getFlowManager().getInstanceFromDefinition(flowTypeName);
    }

    /**
     * @see org.amplafi.flow.FlowManagement#registerForCacheClearing()
     */
    @Override
    public void registerForCacheClearing() {

    }

    public void setFlowTx(FlowTx flowTx) {
        this.flowTx = flowTx;
    }

    @Override
    public FlowTx getFlowTx() {
        return flowTx;
    }

    @Override
    public Log getLog() {
        return LogFactory.getLog(this.getClass());
    }

    public void setFlowManager(FlowManager flowManager) {
        this.flowManager = flowManager;
    }

    public FlowManager getFlowManager() {
        return flowManager;
    }

    /**
     * @param flowTranslatorResolver the flowTranslatorResolver to set
     */
    public void setFlowTranslatorResolver(FlowTranslatorResolver flowTranslatorResolver) {
        this.flowTranslatorResolver = flowTranslatorResolver;
    }

    /**
     * @return the flowTranslatorResolver
     */
    @Override
    public FlowTranslatorResolver getFlowTranslatorResolver() {
        return flowTranslatorResolver;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#getFactoryFlowPropertyDefinitionBuilder(java.lang.String,
     *      Class)
     */
    @Override
    public FlowPropertyDefinitionBuilder getFactoryFlowPropertyDefinitionBuilder(String propertyName,
            Class<?> dataClass) {
        FlowPropertyDefinitionBuilder flowPropertyDefinitionBuilder = this.getFlowManager()
                .getFactoryFlowPropertyDefinitionBuilder(propertyName, dataClass);
        return flowPropertyDefinitionBuilder;
    }

    /**
     * @see org.amplafi.flow.FlowManagement#addFlowStateListener(org.amplafi.flow.FlowStateListener)
     */
    @Override
    public void addFlowStateListener(FlowStateListener flowStateListener) {
        if (flowStateListener != null) {
            this.getFlowStateListeners().add(flowStateListener);
        }
    }

    @Override
    public void lifecycleChange(FlowStateImplementor flowState, FlowStateLifecycle previousFlowStateLifecycle) {
        //TODO synchronization issues if new listeners being added.
        // TODO: allow FlowState specific listeners ( for example ExternalServiceConfigurationFlowActivity.finishFlow() )
        // this would make the need to extend FA disappear even more.
        for (FlowStateListener flowStateListener : this.getFlowStateListeners()) {
            flowStateListener.lifecycleChange(flowState, previousFlowStateLifecycle);
        }
    }

    @Override
    public void activityChange(FlowStateImplementor flowState, FlowActivity flowActivity,
            FlowStepDirection flowStepDirection, FlowActivityPhase flowActivityPhase) {
        //TODO synchronization issues if new listeners being added.
        for (FlowStateListener flowStateListener : this.getFlowStateListeners()) {
            flowStateListener.activityChange(flowState, flowActivity, flowStepDirection, flowActivityPhase);
        }
    }

    /**
     * @param pageProvider the pageProvider to set
     */
    public void setPageProvider(PageProvider pageProvider) {
        this.pageProvider = pageProvider;
    }

    /**
     * @return the pageProvider
     */
    public PageProvider getPageProvider() {
        return pageProvider;
    }

    /**
     * @param flowStateListeners the flowStateListeners to set
     */
    public void setFlowStateListeners(Set<FlowStateListener> flowStateListeners) {
        this.flowStateListeners.clear();
        if (isNotEmpty(flowStateListeners)) {
            this.flowStateListeners.addAll(flowStateListeners);
        }
    }

    /**
     * @return the flowStateListeners
     */
    public Set<FlowStateListener> getFlowStateListeners() {
        return flowStateListeners;
    }

    /**
     * @param valueFromBindingProvider the valueFromBindingProvider to set
     */
    public void setValueFromBindingProvider(ValueFromBindingProvider valueFromBindingProvider) {
        this.valueFromBindingProvider = valueFromBindingProvider;
    }

    /**
     * @return the valueFromBindingProvider
     */
    @Override
    public ValueFromBindingProvider getValueFromBindingProvider() {
        return valueFromBindingProvider;
    }

    /**
     * @param classResolver the classResolver to set
     */
    public void setClassResolver(ClassResolver classResolver) {
        this.classResolver = classResolver;
    }

    /**
     * @return the classResolver
     */
    @Override
    public ClassResolver getClassResolver() {
        return classResolver;
    }

    @Override
    public Collection<String> listAvailableFlows() {
        return flowManager.listAvailableFlows();
    }

}