KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > webflow > executor > jsf > FlowPhaseListener


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.webflow.executor.jsf;
17
18 import java.io.IOException JavaDoc;
19 import java.util.Iterator JavaDoc;
20 import java.util.Map JavaDoc;
21
22 import javax.faces.application.ViewHandler;
23 import javax.faces.component.UIViewRoot;
24 import javax.faces.context.FacesContext;
25 import javax.faces.event.PhaseEvent;
26 import javax.faces.event.PhaseId;
27 import javax.faces.event.PhaseListener;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.springframework.webflow.context.ExternalContext;
32 import org.springframework.webflow.context.ExternalContextHolder;
33 import org.springframework.webflow.core.collection.LocalAttributeMap;
34 import org.springframework.webflow.definition.FlowDefinition;
35 import org.springframework.webflow.definition.registry.FlowDefinitionLocator;
36 import org.springframework.webflow.execution.FlowExecution;
37 import org.springframework.webflow.execution.FlowExecutionFactory;
38 import org.springframework.webflow.execution.ViewSelection;
39 import org.springframework.webflow.execution.repository.FlowExecutionKey;
40 import org.springframework.webflow.execution.repository.FlowExecutionRepository;
41 import org.springframework.webflow.execution.support.ApplicationView;
42 import org.springframework.webflow.execution.support.ExternalRedirect;
43 import org.springframework.webflow.execution.support.FlowDefinitionRedirect;
44 import org.springframework.webflow.execution.support.FlowExecutionRedirect;
45 import org.springframework.webflow.executor.support.FlowExecutorArgumentHandler;
46 import org.springframework.webflow.executor.support.RequestParameterFlowExecutorArgumentHandler;
47
48 /**
49  * JSF phase listener that is responsible for managing a {@link FlowExecution}
50  * object representing an active user conversation so that other JSF artifacts
51  * that execute in different phases of the JSF lifecycle may have access to it.
52  * <p>
53  * This phase listener implements the following algorithm:
54  * <ul>
55  * <li>On BEFORE_RESTORE_VIEW, restore the {@link FlowExecution} the user is
56  * participating in if a call to
57  * {@link FlowExecutorArgumentHandler#extractFlowExecutionKey(ExternalContext)}
58  * returns a submitted flow execution identifier. Place the restored flow
59  * execution in a holder that other JSF artifacts such as VariableResolvers,
60  * PropertyResolvers, and NavigationHandlers may access during the request
61  * lifecycle.
62  * <li>On BEFORE_RENDER_RESPONSE, if a flow execution was restored in the
63  * RESTORE_VIEW phase generate a new key for identifying the updated execution
64  * within a the selected {@link FlowExecutionRepository}. Expose managed flow
65  * execution attributes to the views before rendering.
66  * <li>On AFTER_RENDER_RESPONSE, if a flow execution was restored in the
67  * RESTORE_VIEW phase <em>save</em> the updated execution to the repository
68  * using the new key generated in the BEFORE_RENDER_RESPONSE phase.
69  * </ul>
70  *
71  * @author Colin Sampaleanu
72  * @author Keith Donald
73  */

74 public class FlowPhaseListener implements PhaseListener {
75
76     /**
77      * Logger, usable by subclasses.
78      */

79     protected final Log logger = LogFactory.getLog(getClass());
80
81     /**
82      * A helper for handling arguments needed by this phase listener.
83      */

84     private FlowExecutorArgumentHandler argumentHandler = new RequestParameterFlowExecutorArgumentHandler();
85
86     /**
87      * Resolves selected Web Flow view names to JSF view ids.
88      */

89     private ViewIdMapper viewIdMapper = new DefaultViewIdMapper();
90
91     /**
92      * Returns the argument handler used by this phase listener.
93      */

94     public FlowExecutorArgumentHandler getArgumentHandler() {
95         return argumentHandler;
96     }
97
98     /**
99      * Sets the argument handler to use.
100      */

101     public void setArgumentHandler(FlowExecutorArgumentHandler argumentHandler) {
102         this.argumentHandler = argumentHandler;
103     }
104
105     /**
106      * Returns the JSF view id resolver used by this phase listener.
107      */

108     public ViewIdMapper getViewIdMapper() {
109         return viewIdMapper;
110     }
111
112     /**
113      * Sets the JSF view id mapper used by this phase listener.
114      */

115     public void setViewIdMapper(ViewIdMapper viewIdMapper) {
116         this.viewIdMapper = viewIdMapper;
117     }
118
119     public PhaseId getPhaseId() {
120         return PhaseId.ANY_PHASE;
121     }
122
123     public void beforePhase(PhaseEvent event) {
124         if (event.getPhaseId() == PhaseId.RESTORE_VIEW) {
125             ExternalContextHolder.setExternalContext(new JsfExternalContext(event.getFacesContext()));
126             restoreFlowExecution(event.getFacesContext());
127         }
128         else if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
129             if (FlowExecutionHolderUtils.isFlowExecutionRestored(event.getFacesContext())) {
130                 prepareResponse(getCurrentContext(), FlowExecutionHolderUtils.getFlowExecutionHolder(event
131                         .getFacesContext()));
132             }
133         }
134     }
135
136     public void afterPhase(PhaseEvent event) {
137         if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
138             try {
139                 if (FlowExecutionHolderUtils.isFlowExecutionChanged(event.getFacesContext())) {
140                     saveFlowExecution(getCurrentContext(), FlowExecutionHolderUtils.getFlowExecutionHolder(event
141                             .getFacesContext()));
142                 }
143             }
144             finally {
145                 ExternalContextHolder.setExternalContext(null);
146             }
147         }
148     }
149
150     private JsfExternalContext getCurrentContext() {
151         return (JsfExternalContext) ExternalContextHolder.getExternalContext();
152     }
153
154     protected void restoreFlowExecution(FacesContext facesContext) {
155         JsfExternalContext context = new JsfExternalContext(facesContext);
156         if (argumentHandler.isFlowExecutionKeyPresent(context)) {
157             // restore flow execution from repository so it will be
158
// available to variable/property resolvers and the flow
159
// navigation handler (this could happen as part of a submission or
160
// flow execution redirect)
161
FlowExecutionRepository repository = getRepository(context);
162             FlowExecutionKey flowExecutionKey = repository.parseFlowExecutionKey(argumentHandler
163                     .extractFlowExecutionKey(context));
164             FlowExecution flowExecution = repository.getFlowExecution(flowExecutionKey);
165             if (logger.isDebugEnabled()) {
166                 logger.debug("Loaded existing flow execution from repository with id '" + flowExecutionKey + "'");
167             }
168             FlowExecutionHolderUtils.setFlowExecutionHolder(new FlowExecutionHolder(flowExecutionKey, flowExecution),
169                     facesContext);
170         }
171         else if (argumentHandler.isFlowIdPresent(context)) {
172             // launch a new flow execution (this could happen as part of a flow
173
// redirect)
174
String JavaDoc flowId = argumentHandler.extractFlowId(context);
175             FlowDefinition flowDefinition = getLocator(context).getFlowDefinition(flowId);
176             FlowExecution flowExecution = getFactory(context).createFlowExecution(flowDefinition);
177             FlowExecutionHolder holder = new FlowExecutionHolder(flowExecution);
178             FlowExecutionHolderUtils.setFlowExecutionHolder(holder, facesContext);
179             ViewSelection selectedView = flowExecution.start(createInput(flowExecution, context), context);
180             if (logger.isDebugEnabled()) {
181                 logger.debug("Started new flow execution");
182             }
183             holder.setViewSelection(selectedView);
184             holder.markNeedsSave();
185         }
186     }
187
188     /**
189      * Factory method that creates the input attribute map for a newly created
190      * {@link FlowExecution}. TODO - add support for input mappings here
191      * @param flowExecution the new flow execution (yet to be started)
192      * @param context the external context
193      * @return the input map
194      */

195     protected LocalAttributeMap createInput(FlowExecution flowExecution, ExternalContext context) {
196         return null;
197     }
198
199     protected void prepareResponse(JsfExternalContext context, FlowExecutionHolder holder) {
200         if (holder.needsSave()) {
201             generateKey(context, holder);
202         }
203         ViewSelection selectedView = holder.getViewSelection();
204         if (selectedView == null) {
205             selectedView = holder.getFlowExecution().refresh(context);
206             holder.setViewSelection(selectedView);
207         }
208         if (selectedView instanceof ApplicationView) {
209             prepareApplicationView(context.getFacesContext(), holder);
210         }
211         else if (selectedView instanceof FlowExecutionRedirect) {
212             String JavaDoc url = argumentHandler.createFlowExecutionUrl(holder.getFlowExecutionKey().toString(), holder
213                     .getFlowExecution(), context);
214             sendRedirect(url, context);
215         }
216         else if (selectedView instanceof ExternalRedirect) {
217             String JavaDoc flowExecutionKey = holder.getFlowExecution().isActive() ? holder.getFlowExecutionKey().toString()
218                     : null;
219             String JavaDoc url = argumentHandler.createExternalUrl((ExternalRedirect) holder.getViewSelection(),
220                     flowExecutionKey, context);
221             sendRedirect(url, context);
222         }
223         else if (selectedView instanceof FlowDefinitionRedirect) {
224             String JavaDoc url = argumentHandler.createFlowDefinitionUrl((FlowDefinitionRedirect) holder.getViewSelection(),
225                     context);
226             sendRedirect(url, context);
227         }
228     }
229
230     protected void prepareApplicationView(FacesContext facesContext, FlowExecutionHolder holder) {
231         ApplicationView forward = (ApplicationView) holder.getViewSelection();
232         if (forward != null) {
233             putInto(facesContext.getExternalContext().getRequestMap(), forward.getModel());
234             updateViewRoot(facesContext, viewIdMapper.mapViewId(forward.getViewName()));
235         }
236         Map JavaDoc requestMap = facesContext.getExternalContext().getRequestMap();
237         argumentHandler.exposeFlowExecutionContext(holder.getFlowExecutionKey().toString(), holder.getFlowExecution(),
238                 requestMap);
239     }
240
241     private void updateViewRoot(FacesContext facesContext, String JavaDoc viewId) {
242         UIViewRoot viewRoot = facesContext.getViewRoot();
243         if (viewRoot == null || hasViewChanged(viewRoot, viewId)) {
244             // create the specified view so that it can be rendered
245
if (logger.isDebugEnabled()) {
246                 logger.debug("Creating new view with id '" + viewId + "' from previous view with id '"
247                         + viewRoot.getViewId() + "'");
248             }
249             ViewHandler handler = facesContext.getApplication().getViewHandler();
250             UIViewRoot view = handler.createView(facesContext, viewId);
251             facesContext.setViewRoot(view);
252         }
253     }
254
255     private boolean hasViewChanged(UIViewRoot viewRoot, String JavaDoc viewId) {
256         return !viewRoot.getViewId().equals(viewId);
257     }
258
259     private void generateKey(JsfExternalContext context, FlowExecutionHolder holder) {
260         FlowExecution flowExecution = holder.getFlowExecution();
261         if (flowExecution.isActive()) {
262             // generate new continuation key for the flow execution
263
// before rendering the response
264
FlowExecutionKey flowExecutionKey = holder.getFlowExecutionKey();
265             FlowExecutionRepository repository = getRepository(context);
266             if (flowExecutionKey == null) {
267                 // it is an new conversation, generate a brand new key
268
flowExecutionKey = repository.generateKey(flowExecution);
269             }
270             else {
271                 // it is an existing conversaiton, use same conversation id,
272
// generate a new continuation id
273
flowExecutionKey = repository.getNextKey(flowExecution, flowExecutionKey);
274             }
275             holder.setFlowExecutionKey(flowExecutionKey);
276         }
277     }
278
279     protected void saveFlowExecution(JsfExternalContext context, FlowExecutionHolder holder) {
280         FlowExecution flowExecution = holder.getFlowExecution();
281         FlowExecutionRepository repository = getRepository(context);
282         if (flowExecution.isActive()) {
283             // save the flow execution out to the repository
284
if (logger.isDebugEnabled()) {
285                 logger.debug("Saving continuation to repository with key " + holder.getFlowExecutionKey());
286             }
287             repository.putFlowExecution(holder.getFlowExecutionKey(), flowExecution);
288         }
289         else {
290             if (holder.getFlowExecutionKey() != null) {
291                 // remove the flow execution from the repository
292
if (logger.isDebugEnabled()) {
293                     logger.debug("Removing execution in repository with key '" + holder.getFlowExecutionKey() + "'");
294                 }
295                 repository.removeFlowExecution(holder.getFlowExecutionKey());
296             }
297         }
298     }
299
300     /**
301      * Utility method needed needed only because we can not rely on JSF
302      * RequestMap supporting Map's putAll method. Tries putAll, falls back to
303      * individual adds
304      * @param targetMap the target map to add the model data to
305      * @param map the model data to add to the target map
306      */

307     private void putInto(Map JavaDoc targetMap, Map JavaDoc map) {
308         try {
309             targetMap.putAll(map);
310         }
311         catch (UnsupportedOperationException JavaDoc e) {
312             // work around nasty MyFaces bug where it's RequestMap doesn't
313
// support putAll remove after it's fixed in MyFaces
314
Iterator JavaDoc it = map.entrySet().iterator();
315             while (it.hasNext()) {
316                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
317                 targetMap.put(entry.getKey(), entry.getValue());
318             }
319         }
320     }
321
322     private void sendRedirect(String JavaDoc url, JsfExternalContext context) {
323         try {
324             context.getFacesContext().getExternalContext().redirect(url);
325             context.getFacesContext().responseComplete();
326         }
327         catch (IOException JavaDoc e) {
328             throw new IllegalArgumentException JavaDoc("Could not send redirect to " + url);
329         }
330     }
331
332     /**
333      * Standard default view id resolver which uses the web flow view name as
334      * the jsf view id
335      */

336     public static class DefaultViewIdMapper implements ViewIdMapper {
337         public String JavaDoc mapViewId(String JavaDoc viewName) {
338             return viewName;
339         }
340     }
341
342     private FlowDefinitionLocator getLocator(JsfExternalContext context) {
343         return FlowFacesUtils.getDefinitionLocator(context.getFacesContext());
344     }
345
346     private FlowExecutionFactory getFactory(JsfExternalContext context) {
347         return FlowFacesUtils.getExecutionFactory(context.getFacesContext());
348     }
349
350     private FlowExecutionRepository getRepository(JsfExternalContext context) {
351         return FlowFacesUtils.getExecutionRepository(context.getFacesContext());
352     }
353 }
Popular Tags