Issue Details (XML | Word | Printable)

Key: SWF-76
Type: New Feature New Feature
Status: Resolved Resolved
Resolution: Won't Fix
Priority: Minor Minor
Assignee: Unassigned
Reporter: Aaron Dressin
Votes: 1
Watchers: 2
Operations

If you were logged in you would be able to see more operations.
Spring Web Flow

Support for WebWork

Created: 09/Mar/06 06:54 PM   Updated: 13/Aug/07 11:46 AM
Component/s: None
Affects Version/s: None
Fix Version/s: None

Time Tracking:
Not Specified

Issue Links:
Depends
 


 Description  « Hide
I built a hook for WebWork users into web flow (based on the Struts FlowAction integration). Pre-defined view states are returned as "known" global results in the xwork.xml file of the type "redirect" that parse the location variable using the keys specified in this class:

package org.springframework.webflow.executor.webwork;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.webflow.ExternalContext;
import org.springframework.webflow.FlowArtifactException;
import org.springframework.webflow.action.FormObjectAccessor;
import org.springframework.webflow.execution.FlowLocator;
import org.springframework.webflow.executor.FlowExecutor;
import org.springframework.webflow.executor.FlowExecutorImpl;
import org.springframework.webflow.executor.ResponseInstruction;
import org.springframework.webflow.executor.support.FlowExecutorArgumentExtractor;
import org.springframework.webflow.executor.support.FlowRequestHandler;
import org.springframework.webflow.support.ApplicationView;
import org.springframework.webflow.support.ExternalRedirect;
import org.springframework.webflow.support.FlowRedirect;

import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.ActionSupport;

/**
 * Point of integration between WebWork and Spring Web Flow (based on the Struts /
 * Spring Web Flow integration support by Keith Donald and Erwin Vervaet (see
 * {@link org.springframework.webflow.executor.struts.FlowAction}): a WebWork
 * Action that acts a front controller entry point into the web flow system. A
 * single FlowAction may launch any new FlowExecution. In addition, a single
 * Flow Action may signal events in any existing/restored FlowExecutions.
 * <p>
 * Requests are managed by and delegated to a {@link FlowExecutor}, which this
 * class delegates to using a {@link FlowRequestHandler} (allowing reuse of
 * common front flow controller logic in other environments). Consult the
 * JavaDoc of those classes for more information on how requests are processed.
 * <p>
 * <li>By default, to have this controller launch a new flow execution
 * (conversation), have the client send a
 * {@link FlowExecutorArgumentExtractor#getFlowIdParameterName()} request
 * parameter indicating the flow definition to launch.
 * <li>To have this controller participate in an existing flow execution
 * (conversation), have the client send a
 * {@link FlowExecutorArgumentExtractor#getFlowExecutionKeyParameterName()}
 * request parameter identifying the conversation to participate in.
 * <p>
 * On each request received by this action, a {@link WebWorkExternalContext}
 * object is created as input to the web flow system. This external source event
 * provides access to the action invocation.
 * <p>
 * Below is an example <code>xwork.xml</code> configuration for a FlowAction:
 *
 * <pre>
 * &lt;action name=&quot;flowAction&quot;
 * class=&quot;org.springframework.webflow.executor.webwork.FlowAction&quot;
 * &lt;/action&gt;
 * </pre>
 *
 * This example associates the logical request URL
 * <code>/flowAction.action</code> as a Flow controller. It is expected that
 * flows to launch be provided in a dynamic fashion by the views (allowing this
 * single <code>FlowAction</code> to manage any number of flow executions).
 * <p>
 * Other notes regarding WebWork/Spring Web Flow integration:
 * <p>
 * <ul>
 * <li>This controller can (and should) be used as a singleton to avoid
 * unnecessary initialization for each request.
 * <li>Logical view names returned when <code>ViewStates</code> and
 * <code>EndStates</code> are entered are mapped to physical view templates
 * using standard WebWork results (typically global results).
 * <li>This class depends on a {@link FlowExecutor} instance to be configured.
 * <li> If relying on Spring's object factory , a FlowExecutor reference can
 * simply be injected using standard Spring DependencyInjection techniques. If
 * you are not using this approach, this class will attempt a root context
 * lookup on initialization, first querying for a bean of instance
 * {@link FlowExecutor} named {@link #FLOW_EXECUTOR_BEAN_NAME}, then, if not
 * found, querying for a bean of instance {@link FlowLocator} named
 * {@link #FLOW_LOCATOR_BEAN_NAME}. If the FlowLocator dependency is resolved,
 * this class will automatically configure a default flow executor
 * implementation suitable for a WebWork environment (see
 * {@link #setFlowLocator(FlowLocator)}). In addition, you may choose to simply
 * inject a FlowLocator directly if the FlowExecutor defaults meet your
 * requirements.
 * </ul>
 *
 * @author Aaron Dressin
 * @author Chris Sheddy
 */
public class FlowAction extends ActionSupport {

    private static final long serialVersionUID = 8807660815356492528L;

    /**
     * Key into context for the flow redirection url
     */
    public static final String FLOW_URL_KEY = "flowUrl";

    /**
     * Key into context for the external redirection url
     */
    public static final String EXTERNAL_URL_KEY = "externalUrl";

    /**
     * Key into context for the conversation redirection url
     */
    public static final String CONVERSATION_URL_KEY = "conversationUrl";

    /**
     * Result returned for a flow redirect view
     */
    public static final String FLOW_REDIRECT_RESULT = "flow_redirect";

    /**
     * Result returned for an external redirect view
     */
    public static final String EXTERNAL_REDIRECT_RESULT = "external_redirect";

    /**
     * Result returned for a conversation redirect view
     */
    public static final String CONVERSATION_REDIRECT_RESULT = "conversation_redirect";

    /**
     * The flow executor will be retreived from the application context using
     * this bean name if no executor is explicitly set.
     */
    protected static final String FLOW_EXECUTOR_BEAN_NAME = "flowExecutor";

    /**
     * The flow locator will be retreived from the application context using
     * this bean name if no executor and locator is explicitly set.
     */
    protected static final String FLOW_LOCATOR_BEAN_NAME = "flowLocator";

    /**
     * The service responsible for launching and signaling webwork-originating
     * events in flow executions.
     */
    private FlowExecutor flowExecutor;

    /**
     * Delegate for extract flow executor parameters.
     */
    private FlowExecutorArgumentExtractor parameterExtractor;

    /**
     * Set the flow locator to use for the lookup of flow definitions to
     * execute.
     */
    public void setFlowLocator(FlowLocator flowLocator) {
        flowExecutor = new FlowExecutorImpl(flowLocator);
    }

    /**
     * Returns the flow executor used by this controller.
     *
     * @return the flow executor
     */
    public FlowExecutor getFlowExecutor() {
        return flowExecutor;
    }

    /**
     * Configures the flow executor implementation to use.
     */
    public void setFlowExecutor(FlowExecutor flowExecutor) {
        this.flowExecutor = flowExecutor;
    }

    /**
     * Returns the flow executor parameter extractor used by this controller.
     *
     * @return the parameter extractor
     */
    public FlowExecutorArgumentExtractor getParameterExtractor() {
        return parameterExtractor;
    }

    /**
     * Sets the flow executor parameter extractor to use.
     *
     * @param parameterExtractor
     * the parameter extractor
     */
    public void setParameterExtractor(FlowExecutorArgumentExtractor parameterExtractor) {
        this.parameterExtractor = parameterExtractor;
    }

    public String execute() throws Exception {
        ServletContext servletContext = ServletActionContext.getServletContext();
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();

        if (getFlowExecutor() == null) {
            WebApplicationContext context = (WebApplicationContext) WebApplicationContextUtils
                .getRequiredWebApplicationContext(servletContext);
            if (context.containsBean(FLOW_EXECUTOR_BEAN_NAME)) {
                setFlowExecutor((FlowExecutor) context.getBean(FLOW_EXECUTOR_BEAN_NAME,
                    FlowExecutor.class));
            } else {
                try {
                    setFlowLocator((FlowLocator) context.getBean(FLOW_LOCATOR_BEAN_NAME,
                        FlowLocator.class));
                } catch (NoSuchBeanDefinitionException e) {
                    String message = "No '"
                            + FLOW_LOCATOR_BEAN_NAME
                            + "' or '"
                            + FLOW_EXECUTOR_BEAN_NAME
                            + "' bean definition could be found; to use Spring Web Flow with WebWork you must "
                            + "configure this FlowAction with either a FlowLocator "
                            + "(exposing a registry of flow definitions) or a custom FlowExecutor "
                            + "(allowing more configuration options)";
                    throw new FlowArtifactException(FLOW_LOCATOR_BEAN_NAME, FlowLocator.class,
                        message, e);
                }
            }
        }
        if (getParameterExtractor() == null) {
            parameterExtractor = new FlowExecutorArgumentExtractor();
        }

        ActionInvocation actionInvocation = ActionContext.getContext().getActionInvocation();
        ExternalContext context = new WebWorkExternalContext(actionInvocation, servletContext,
            request, response);
        ResponseInstruction responseInstruction = createRequestHandler().handleFlowRequest(context);

        return toResult(responseInstruction, context, actionInvocation);
    }

    /**
     * Factory method that creates a new helper for processing a request into
     * this flow controller.
     *
     * @return the controller helper
     */
    protected FlowRequestHandler createRequestHandler() {
        return new FlowRequestHandler(getFlowExecutor(), getParameterExtractor());
    }

    /**
     * Return the appropriate result based on the response.
     *
     * @param response
     * @param context
     * @param actionInvocation
     * @return
     */
    @SuppressWarnings("unchecked")
    protected String toResult(ResponseInstruction response, ExternalContext context,
            ActionInvocation actionInvocation) {
        if (response.isApplicationView()) {
            // forward to a view as part of an active conversation
            ApplicationView forward = (ApplicationView) response.getViewSelection();
            Map model = new HashMap(forward.getModel());
            parameterExtractor.put(response.getFlowExecutionKey(), model);
            parameterExtractor.put(response.getFlowExecutionContext(), model);
            // expose errors
            exposeErrors(actionInvocation, model);
            actionInvocation.getInvocationContext().getContextMap().putAll(model);
            return forward.getViewName();
        } else if (response.isConversationRedirect()) {
            // redirect to active conversation URL
            Serializable conversationId = response.getFlowExecutionKey().getConversationId();
            String conversationUrl = parameterExtractor.createConversationUrl(conversationId,
                context);
            actionInvocation.getInvocationContext().getContextMap().put(CONVERSATION_URL_KEY,
                conversationUrl);
            return CONVERSATION_REDIRECT_RESULT;
        } else if (response.isExternalRedirect()) {
            // redirect to external URL
            String externalUrl = parameterExtractor.createExternalUrl((ExternalRedirect) response
                .getViewSelection(), response.getFlowExecutionKey(), context);
            actionInvocation.getInvocationContext().getContextMap().put(EXTERNAL_URL_KEY,
                externalUrl);
            return EXTERNAL_REDIRECT_RESULT;
        } else if (response.isFlowRedirect()) {
            // // restart the flow by redirecting to flow launch URL
            String flowUrl = parameterExtractor.createFlowUrl((FlowRedirect) response
                .getViewSelection(), context);
            actionInvocation.getInvocationContext().getContextMap().put(FLOW_URL_KEY, flowUrl);
            return FLOW_REDIRECT_RESULT;
        } else if (response.isNull()) {
            // no response to issue
            return null;
        }

        else {
            throw new IllegalArgumentException("Don't know how to handle response instruction "
                    + response);
        }
    }

    private void exposeErrors(ActionInvocation actionInvocation, Map model) {
        Errors errors = getCurrentErrors(model);
        if (errors instanceof BindException) {
            List allErrors = ((BindException) errors).getAllErrors();
            Iterator it = allErrors.iterator();
            while (it.hasNext()) {
                FieldError fieldError = (FieldError) it.next();

                addFieldError(fieldError.getField(), fieldError.getCode());
            }
        }
    }

    private Errors getCurrentErrors(Map model) {
        return (Errors) model.get(FormObjectAccessor.getCurrentFormErrorsName());
    }
}

------------------------------------------------------------------------------------------

package org.springframework.webflow.executor.webwork;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.webflow.context.servlet.ServletExternalContext;
import com.opensymphony.xwork.ActionInvocation;

/**
 * External context that stores the current WebWork
 * <code>ActionInvocation</code>.
 *
 * @author Aaron Dressin
 */
public class WebWorkExternalContext extends ServletExternalContext {

    private ActionInvocation actionInvocation;

    public ActionInvocation getActionInvocation() {
        return actionInvocation;
    }

    public WebWorkExternalContext(ActionInvocation actionInvocation, ServletContext servletContext,
            HttpServletRequest request, HttpServletResponse response) {
        super(servletContext, request, response);
        this.actionInvocation = actionInvocation;
    }
}

 All   Comments   Work Log   Change History   FishEye   Builds      Sort Order: Ascending order - Click to sort in descending order
Aaron Dressin added a comment - 09/Mar/06 06:55 PM
Ooops... forgot to remove the Java 5 attributes ;)

Keith Donald added a comment - 13/Aug/07 11:46 AM
Not enough interest given Struts 2.