2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.view.faces;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import javax.faces.context.ExternalContext;
26  import javax.faces.context.FacesContext;
27  import javax.faces.el.ValueBinding;
28  
29  import javax.faces.event.PhaseEvent;
30  import javax.faces.event.PhaseId;
31  import javax.faces.event.PhaseListener;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.shale.view.AbstractRequestBean;
36  import org.apache.shale.view.ApplicationException;
37  import org.apache.shale.view.Constants;
38  import org.apache.shale.view.ExceptionHandler;
39  import org.apache.shale.view.ViewController;
40  
41  /***
42   * <p>{@link ViewPhaseListener} is a JavaServer Faces <code>PhaseListener</code>
43   * that implements phase related functionality for the view controller
44   * portion of Shale.</p>
45   *
46   * $Id: ViewPhaseListener.java 490577 2006-12-27 22:19:21Z craigmcc $
47   */
48  
49  public class ViewPhaseListener implements PhaseListener {
50  
51  
52      // -------------------------------------------------------- Static Variables
53  
54  
55      /***
56       * Serial version UID.
57       */
58      private static final long serialVersionUID = 2096731130324222021L;
59  
60  
61      /***
62       * <p>The <code>Log</code> instance for this class.</p>
63       */
64      private static final Log log = LogFactory.getLog(ViewPhaseListener.class);
65  
66  
67      /***
68       * <p>HTTP status to report in the exception attributes we set up.</p>
69       */
70      private static final int HTTP_STATUS = 200;
71  
72  
73      /***
74       * <p>Request scope attribute under which the {@link PhaseId} for the
75       * current phase is exposed.</p>
76       */
77      public static final String PHASE_ID = "org.apache.shale.view.PHASE_ID";
78  
79  
80      // --------------------------------------------------- PhaseListener Methods
81  
82  
83      /***
84       * <p>Perform after-phase processing for the phase defined in the
85       * specified event.</p>
86       *
87       * @param event <code>PhaseEvent</code> for the current event
88       */
89      public void afterPhase(PhaseEvent event) {
90  
91          if (log.isTraceEnabled()) {
92              log.trace("afterPhase(" + event.getFacesContext()
93                        + "," + event.getPhaseId() + ")");
94          }
95          
96          if (afterPhaseExceptionCheck(event)) {
97              // dispatched to the target error page, stop further processing
98              return;
99          }
100 
101         PhaseId phaseId = event.getPhaseId();
102         if (PhaseId.RESTORE_VIEW.equals(phaseId)) {
103             afterRestoreView(event);
104         } else if (PhaseId.RENDER_RESPONSE.equals(phaseId)
105                    || event.getFacesContext().getResponseComplete()) {
106             afterRenderResponse(event);
107         }
108         event.getFacesContext().getExternalContext().getRequestMap().remove(PHASE_ID);
109 
110     }
111 
112 
113     /***
114      * <p>Perform before-phase processing for the phase defined in the
115      * specified event.</p>
116      *
117      * @param event <code>PhaseEvent</code> for the current event
118      */
119     public void beforePhase(PhaseEvent event) {
120 
121         if (log.isTraceEnabled()) {
122             log.trace("beforePhase(" + event.getFacesContext()
123                       + "," + event.getPhaseId() + ")");
124         }
125         PhaseId phaseId = event.getPhaseId();
126         event.getFacesContext().getExternalContext().getRequestMap().put(PHASE_ID, phaseId);
127         if (PhaseId.RENDER_RESPONSE.equals(phaseId)) {
128             beforeRenderResponse(event);
129         }
130 
131     }
132 
133 
134     /***
135      * <p>Return <code>PhaseId.ANY_PHASE</code>, indicating our interest
136      * in all phases of the request processing lifecycle.</p>
137      */
138     public PhaseId getPhaseId() {
139 
140         return PhaseId.ANY_PHASE;
141 
142     }
143 
144 
145     // --------------------------------------------------------- Private Methods
146 
147 
148     /***
149      * <p>If any exceptions have been accumulated, and the user has specified
150      * a forwarding URL, forward to that URL instead of allowing rendering
151      * to proceed.</p>
152      *
153      * @param event <code>PhaseEvent</code> for the current event
154      * @return <code>true</code> if exceptions have been handled
155      * and dispatched to the specified path
156      */
157     private boolean afterPhaseExceptionCheck(PhaseEvent event) {
158 
159         // Have we accumulated any exceptions during the current request?
160         // Have we already logged the exception?
161         FacesContext context = event.getFacesContext();
162         ExternalContext econtext = context.getExternalContext();
163         List list = (List)
164           econtext.getRequestMap().get(FacesConstants.EXCEPTIONS_LIST);
165         if (list == null
166          || econtext.getRequestMap().get("javax.servlet.error.exception") != null) {
167             return false;
168         }
169 
170         // Has the user specified a forwarding URL for handling exceptions?
171         String path =
172           econtext.getInitParameter(Constants.EXCEPTION_DISPATCH_PATH);
173         if (path == null) {
174             return false;
175         }
176 
177         // Forward control to the specified path instead of allowing
178         // rendering to complete, while simulating container error handling
179         try {
180             // Set up request attributes reflecting the error conditions,
181             // similar to what is passed to an error handler by the servlet
182             // container (see Section 9.9.1 of the Servlet Specification)
183             ApplicationException exception = new ApplicationException(list);
184             Map map = econtext.getRequestMap();
185             map.put("javax.servlet.error.status_code", new Integer(HTTP_STATUS)); // Not an HTTP error
186             map.put("javax.servlet.error.exception_type", ApplicationException.class);
187             map.put("javax.servlet.error.message", exception.getMessage());
188             map.put("javax.servlet.error.exception", exception);
189             StringBuffer sb = new StringBuffer("");
190             if (econtext.getRequestServletPath() != null) {
191                 sb.append(econtext.getRequestServletPath());
192             }
193             if (econtext.getRequestPathInfo() != null) {
194                 sb.append(econtext.getRequestPathInfo());
195             }
196             map.put("javax.servlet.error.request_uri", sb.toString());
197             map.put("javax.servlet.error.servlet_name", "javax.faces.webapp.FacesServlet"); // Best we can do ...
198             // Dispatch to the specified error handler
199             context.responseComplete();
200             // force a destroy before dispatching to the error page
201             econtext.getRequestMap().remove(FacesConstants.VIEWS_INITIALIZED);
202             econtext.dispatch(path);
203 
204         } catch (IOException e) {
205             handleException(context, e);
206         }
207 
208         return true;
209     }
210 
211 
212     /***
213      * <p>Remove all request scoped attributes that implement the
214      * <code>ViewController</code> interface or extend the
215      * <code>AbstractRequestBean</code> base class.  This will trigger
216      * a callback to the <code>destroy()</code> method of such beans,
217      * while we are still in the context of a JSF request.</p>
218      *
219      * @param event <code>PhaseEvent</code> for the current event
220      */
221     private void afterRenderResponse(PhaseEvent event) {
222 
223         // Initialize local values we will need
224         Map map = event.getFacesContext().getExternalContext().getRequestMap();
225         List list = new ArrayList();
226         Iterator entries = map.entrySet().iterator();
227 
228         // Remove our list of initialized views explicitly
229         map.remove(FacesConstants.VIEWS_INITIALIZED);
230 
231         // First select all the ViewController and AbstractRequestBean instances
232         while (entries.hasNext()) {
233             Map.Entry entry = (Map.Entry) entries.next();
234             if ((entry.getValue() instanceof ViewController)
235              || (entry.getValue() instanceof AbstractRequestBean)) {
236                 list.add(entry.getKey());
237             }
238         }
239 
240         // Second select all remaining instances, which will include annotated
241         // managed beans if Shale Tiger is present
242         entries = map.entrySet().iterator();
243         while (entries.hasNext()) {
244             Map.Entry entry = (Map.Entry) entries.next();
245             if (!list.contains(entry.getKey())) {
246                 list.add(entry.getKey());
247             }
248         }
249 
250         // Iterate through the keys in the specified order, removing the
251         // corresponding request scope attribute instances
252         Iterator keys = list.iterator();
253         while (keys.hasNext()) {
254             String key = (String) keys.next();
255             try {
256                 map.remove(key);
257             } catch (Exception e) {
258                 handleException(event.getFacesContext(), e);
259             }
260         }
261 
262     }
263 
264 
265     /***
266      * <p>Call the <code>preprocess()</code> method of the {@link ViewController}
267      * that has been restored, if this is a postback.</p>
268      *
269      * @param event <code>PhaseEvent</code> for the current event
270      */
271     private void afterRestoreView(PhaseEvent event) {
272 
273         Map map = event.getFacesContext().getExternalContext().getRequestMap();
274         List list = (List) map.get(FacesConstants.VIEWS_INITIALIZED);
275         if (list == null) {
276             return;
277         }
278         if (!event.getFacesContext().getExternalContext().getRequestMap().containsKey(FacesConstants.VIEW_POSTBACK)) {
279             return;
280         }
281         Iterator vcs = list.iterator();
282         while (vcs.hasNext()) {
283             Object vc = vcs.next();
284             try {
285                 getViewControllerCallbacks(event.getFacesContext()).preprocess(vc);
286             } catch (Exception e) {
287                 handleException(event.getFacesContext(), e);
288             }
289         }
290 
291     }
292 
293 
294 
295     /***
296      * <p>Call the <code>prerender()</code> method of the {@link ViewController}
297      * for the view about to be rendered (if any).</p>
298      *
299      * @param event <code>PhaseEvent</code> for the current event
300      */
301     private void beforeRenderResponse(PhaseEvent event) {
302 
303         Map map = event.getFacesContext().getExternalContext().getRequestMap();
304         String viewName = (String) map.get(FacesConstants.VIEW_NAME_RENDERED);
305         if (viewName == null) {
306             return;
307         }
308         Object vc = map.get(viewName);
309         if (vc == null) {
310             return;
311         }
312         try {
313             getViewControllerCallbacks(event.getFacesContext()).prerender(vc);
314         } catch (Exception e) {
315             handleException(event.getFacesContext(), e);
316         }
317         map.remove(FacesConstants.VIEW_NAME_RENDERED);
318 
319     }
320 
321 
322     /***
323      * <p>Return the {@link ViewControllerCallbacks} instance we
324      * will use.</p>
325      *
326      * @param context <code>FacesContext</code> for the current request
327      */
328     private ViewControllerCallbacks getViewControllerCallbacks(FacesContext context) {
329 
330         ValueBinding vb = context.getApplication().createValueBinding
331           ("#{" + FacesConstants.VIEW_CALLBACKS + "}");
332         return (ViewControllerCallbacks) vb.getValue(context);
333 
334     }
335 
336 
337     /***
338      * <p>Handle the specified exception according to the strategy
339      * defined by our current {@link ExceptionHandler}.</p>
340      *
341      * @param context FacesContext for the current request
342      * @param exception Exception to be handled
343      */
344     private void handleException(FacesContext context, Exception exception) {
345 
346         if (context == null) {
347             exception.printStackTrace(System.out);
348             return;
349         }
350         ExceptionHandler handler = (ExceptionHandler)
351           context.getApplication().getVariableResolver().resolveVariable
352                 (context, Constants.EXCEPTION_HANDLER);
353         handler.handleException(exception);
354 
355     }
356 
357 
358 }