001: /*
002: * Copyright 2004-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.springframework.webflow.executor.jsf;
017:
018: import javax.faces.application.NavigationHandler;
019: import javax.faces.context.FacesContext;
020:
021: import org.apache.commons.logging.Log;
022: import org.apache.commons.logging.LogFactory;
023: import org.springframework.binding.mapping.AttributeMapper;
024: import org.springframework.web.jsf.DecoratingNavigationHandler;
025: import org.springframework.webflow.context.ExternalContext;
026: import org.springframework.webflow.context.ExternalContextHolder;
027: import org.springframework.webflow.core.collection.LocalAttributeMap;
028: import org.springframework.webflow.core.collection.MutableAttributeMap;
029: import org.springframework.webflow.definition.FlowDefinition;
030: import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
031: import org.springframework.webflow.engine.NoMatchingTransitionException;
032: import org.springframework.webflow.execution.FlowExecution;
033: import org.springframework.webflow.execution.FlowExecutionFactory;
034: import org.springframework.webflow.execution.ViewSelection;
035: import org.springframework.webflow.executor.RequestParameterInputMapper;
036: import org.springframework.webflow.executor.support.FlowExecutorArgumentExtractor;
037:
038: /**
039: * An implementation of a JSF <code>NavigationHandler</code> that provides integration with Spring Web Flow.
040: * Responsible for delegating to Spring Web Flow to launch and resume flow executions, treating JSF action outcomes
041: * (like a command button click) as web flow events.
042: *
043: * This class delegates to the standard NavigationHandler implementation when a navigation request does not pertain to a
044: * flow execution.
045: * <p>
046: * The following navigation handler algorithm is implemented by default:
047: * </p>
048: * <p>
049: * If a flow execution has been restored in the current request:
050: * <ul>
051: * <li>Resume the flow execution by signaling the JSF action outcome as an event against the current state.
052: * <li>Once event processing completes expose the selected view as the "current" {@link ViewSelection}.
053: * </ul>
054: * </p>
055: * <p>
056: * If a flow execution has not been restored in the current request:
057: * <ul>
058: * <li>If the specified logical outcome is of the form <em>flowId:xxx</em> look up the corresponding
059: * {@link FlowDefinition} with that id and launch a new flow execution in the starting state. Expose the new execution
060: * as the "current" flow execution for this request. Expose the first selected view as the "current" view selection.
061: * <li>If the specified logical outcome is not of the form <em>flowId:xxx</em>, simply delegate to the standard
062: * <code>NavigationHandler</code> implementation and return.
063: * </ul>
064: * </p>
065: * How the flowId and eventId arguments are extracted can be customized by setting a custom
066: * {@link #setArgumentExtractor(FlowExecutorArgumentExtractor) argument extractor}.
067: *
068: * Note about customization: since NavigationHandlers managed directly by the JSF provider cannot be benefit from
069: * DependencyInjection, See Spring's {@link org.springframework.web.jsf.DelegatingNavigationHandlerProxy} when you need
070: * to customize a FlowNavigationHandler instance.
071: *
072: * @author Craig McClanahan
073: * @author Colin Sampaleanu
074: * @author Keith Donald
075: */
076: public class FlowNavigationHandler extends DecoratingNavigationHandler {
077:
078: /**
079: * Logger, usable by subclasses.
080: */
081: protected final Log logger = LogFactory.getLog(getClass());
082:
083: /**
084: * A helper for extracting parameters needed by this flow navigation handler.
085: */
086: private FlowExecutorArgumentExtractor argumentExtractor = new FlowNavigationHandlerArgumentExtractor();
087:
088: /**
089: * The service responsible for mapping attributes of an {@link ExternalContext} to a new {@link FlowExecution}
090: * during the {@link #launch(String, ExternalContext) launch flow} operation.
091: * <p>
092: * This allows developers to control what attributes are made available in the <code>inputMap</code> to new
093: * top-level flow executions. The starting execution may then choose to map that available input into its own local
094: * scope.
095: * <p>
096: * The default implementation simply exposes all request parameters as flow execution input attributes. May be null.
097: */
098: private AttributeMapper inputMapper = new RequestParameterInputMapper();
099:
100: /**
101: * Create a new {@link FlowNavigationHandler} using the default constructor.
102: */
103: public FlowNavigationHandler() {
104: super ();
105: }
106:
107: /**
108: * Create a new {@link FlowNavigationHandler}, wrapping the specified standard navigation handler implementation.
109: * @param originalNavigationHandler Standard <code>NavigationHandler</code> we are wrapping
110: */
111: public FlowNavigationHandler(
112: NavigationHandler originalNavigationHandler) {
113: super (originalNavigationHandler);
114: }
115:
116: /**
117: * Returns the argument extractor used by this navigation handler.
118: */
119: public FlowExecutorArgumentExtractor getArgumentExtractor() {
120: return argumentExtractor;
121: }
122:
123: /**
124: * Sets the argument extractor to use by this navigation handler. Call to customize how flow id and event id
125: * arguments are extracted.
126: */
127: public void setArgumentExtractor(
128: FlowExecutorArgumentExtractor argumentExtractor) {
129: this .argumentExtractor = argumentExtractor;
130: }
131:
132: /**
133: * Returns the configured flow execution input mapper.
134: */
135: public AttributeMapper getInputMapper() {
136: return inputMapper;
137: }
138:
139: /**
140: * Sets the service responsible for mapping attributes of an {@link ExternalContext} to a new {@link FlowExecution}
141: * during a launch flow operation.
142: * <p>
143: * The default implementation simply exposes all request parameters as flow execution input attributes. May be null.
144: * @see RequestParameterInputMapper
145: */
146: public void setInputMapper(AttributeMapper inputMapper) {
147: this .inputMapper = inputMapper;
148: }
149:
150: public void handleNavigation(FacesContext facesContext,
151: String fromAction, String outcome,
152: NavigationHandler originalNavigationHandler) {
153: try {
154: JsfExternalContext context = getCurrentContext();
155: // record the navigation handler context
156: context.handleNavigationCalled(fromAction, outcome);
157: // first see if we need to launch a new flow execution if the flow id is present
158: if (argumentExtractor.isFlowIdPresent(context)) {
159: // a flow execution launch has been requested - create the new execution
160: String flowId = argumentExtractor
161: .extractFlowId(context);
162: FlowDefinition flowDefinition = getLocator(context)
163: .getFlowDefinition(flowId);
164: FlowExecution flowExecution = getFactory(context)
165: .createFlowExecution(flowDefinition);
166: // check to see if this execution was created while another was running
167: if (FlowExecutionHolderUtils
168: .isFlowExecutionRestored(facesContext)) {
169: // replace the current flow execution with the new one
170: FlowExecutionHolderUtils.getFlowExecutionHolder(
171: facesContext).replaceWith(flowExecution);
172: } else {
173: // bind the new execution as the 'current execution'
174: FlowExecutionHolderUtils.setFlowExecutionHolder(
175: new FlowExecutionHolder(flowExecution),
176: facesContext);
177: }
178: // start the new execution
179: ViewSelection selectedView = flowExecution.start(
180: createInput(context), context);
181: // set the starting view to render
182: FlowExecutionHolderUtils.getFlowExecutionHolder(
183: facesContext).setViewSelection(selectedView);
184: } else {
185: // not a launch request - see if this is a resume request to continue an existing execution
186: if (FlowExecutionHolderUtils
187: .isFlowExecutionRestored(facesContext)) {
188: // a flow execution has been restored - see if we need to signal an event against it
189: if (argumentExtractor.isEventIdPresent(context)) {
190: // signal the event against the current flow execution
191: String eventId = argumentExtractor
192: .extractEventId(context);
193: try {
194: FlowExecutionHolder holder = FlowExecutionHolderUtils
195: .getFlowExecutionHolder(facesContext);
196: ViewSelection selectedView = holder
197: .getFlowExecution().signalEvent(
198: eventId, context);
199: // set the next view to render
200: holder.setViewSelection(selectedView);
201: } catch (NoMatchingTransitionException e) {
202: if (logger.isDebugEnabled()) {
203: logger
204: .debug("No flow state transition found for event '"
205: + eventId
206: + "'; falling back to standard navigation handler.");
207: }
208: // not a valid event in the current state: proceed with standard navigation
209: originalNavigationHandler.handleNavigation(
210: facesContext, fromAction, outcome);
211: }
212: }
213: } else {
214: // neither a flow launch or resume request: proceed with standard navigation
215: originalNavigationHandler.handleNavigation(
216: facesContext, fromAction, outcome);
217: }
218: }
219: } catch (RuntimeException e) {
220: cleanupResources(facesContext);
221: throw e;
222: } catch (Error e) {
223: cleanupResources(facesContext);
224: throw e;
225: }
226: }
227:
228: /**
229: * Factory method that creates the input attribute map for a newly created {@link FlowExecution}. This
230: * implementation uses the registered input mapper, if any.
231: * @param context the external context
232: * @return the input map, or null if no input
233: */
234: protected MutableAttributeMap createInput(ExternalContext context) {
235: if (inputMapper != null) {
236: MutableAttributeMap inputMap = new LocalAttributeMap();
237: inputMapper.map(context, inputMap, null);
238: return inputMap;
239: } else {
240: return null;
241: }
242: }
243:
244: // helpers
245:
246: private JsfExternalContext getCurrentContext() {
247: return (JsfExternalContext) ExternalContextHolder
248: .getExternalContext();
249: }
250:
251: private FlowDefinitionLocator getLocator(JsfExternalContext context) {
252: return FlowFacesUtils.getDefinitionLocator(context
253: .getFacesContext());
254: }
255:
256: private FlowExecutionFactory getFactory(JsfExternalContext context) {
257: return FlowFacesUtils.getExecutionFactory(context
258: .getFacesContext());
259: }
260:
261: private void cleanupResources(FacesContext context) {
262: if (logger.isDebugEnabled()) {
263: logger.debug("Cleaning up allocated flow system resources");
264: }
265: FlowExecutionHolderUtils.cleanupCurrentFlowExecution(context);
266: ExternalContextHolder.setExternalContext(null);
267: }
268: }
|