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 497775 2007-01-19 11:09:07Z matzew $
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         // Remove our list of initialized views explicitly
226         map.remove(FacesConstants.VIEWS_INITIALIZED);
227 	
228         List list = new ArrayList();
229         Iterator entries = map.entrySet().iterator();
230 
231 
232         // First select all the ViewController and AbstractRequestBean instances
233         while (entries.hasNext()) {
234             Map.Entry entry = (Map.Entry) entries.next();
235             if ((entry.getValue() instanceof ViewController)
236              || (entry.getValue() instanceof AbstractRequestBean)) {
237                 list.add(entry.getKey());
238             }
239         }
240 
241         // Second select all remaining instances, which will include annotated
242         // managed beans if Shale Tiger is present
243         entries = map.entrySet().iterator();
244         while (entries.hasNext()) {
245             Map.Entry entry = (Map.Entry) entries.next();
246             if (!list.contains(entry.getKey())) {
247                 list.add(entry.getKey());
248             }
249         }
250 
251         // Iterate through the keys in the specified order, removing the
252         // corresponding request scope attribute instances
253         Iterator keys = list.iterator();
254         while (keys.hasNext()) {
255             String key = (String) keys.next();
256             try {
257                 map.remove(key);
258             } catch (Exception e) {
259                 handleException(event.getFacesContext(), e);
260             }
261         }
262 
263     }
264 
265 
266     /***
267      * <p>Call the <code>preprocess()</code> method of the {@link ViewController}
268      * that has been restored, if this is a postback.</p>
269      *
270      * @param event <code>PhaseEvent</code> for the current event
271      */
272     private void afterRestoreView(PhaseEvent event) {
273 
274         Map map = event.getFacesContext().getExternalContext().getRequestMap();
275         List list = (List) map.get(FacesConstants.VIEWS_INITIALIZED);
276         if (list == null) {
277             return;
278         }
279         if (!event.getFacesContext().getExternalContext().getRequestMap().containsKey(FacesConstants.VIEW_POSTBACK)) {
280             return;
281         }
282         Iterator vcs = list.iterator();
283         while (vcs.hasNext()) {
284             Object vc = vcs.next();
285             try {
286                 getViewControllerCallbacks(event.getFacesContext()).preprocess(vc);
287             } catch (Exception e) {
288                 handleException(event.getFacesContext(), e);
289             }
290         }
291 
292     }
293 
294 
295 
296     /***
297      * <p>Call the <code>prerender()</code> method of the {@link ViewController}
298      * for the view about to be rendered (if any).</p>
299      *
300      * @param event <code>PhaseEvent</code> for the current event
301      */
302     private void beforeRenderResponse(PhaseEvent event) {
303 
304         Map map = event.getFacesContext().getExternalContext().getRequestMap();
305         String viewName = (String) map.get(FacesConstants.VIEW_NAME_RENDERED);
306         if (viewName == null) {
307             return;
308         }
309         Object vc = map.get(viewName);
310         if (vc == null) {
311             return;
312         }
313         try {
314             getViewControllerCallbacks(event.getFacesContext()).prerender(vc);
315         } catch (Exception e) {
316             handleException(event.getFacesContext(), e);
317         }
318         map.remove(FacesConstants.VIEW_NAME_RENDERED);
319 
320     }
321 
322 
323     /***
324      * <p>Return the {@link ViewControllerCallbacks} instance we
325      * will use.</p>
326      *
327      * @param context <code>FacesContext</code> for the current request
328      */
329     private ViewControllerCallbacks getViewControllerCallbacks(FacesContext context) {
330 
331         ValueBinding vb = context.getApplication().createValueBinding
332           ("#{" + FacesConstants.VIEW_CALLBACKS + "}");
333         return (ViewControllerCallbacks) vb.getValue(context);
334 
335     }
336 
337 
338     /***
339      * <p>Handle the specified exception according to the strategy
340      * defined by our current {@link ExceptionHandler}.</p>
341      *
342      * @param context FacesContext for the current request
343      * @param exception Exception to be handled
344      */
345     private void handleException(FacesContext context, Exception exception) {
346 
347         if (context == null) {
348             exception.printStackTrace(System.out);
349             return;
350         }
351         ExceptionHandler handler = (ExceptionHandler)
352           context.getApplication().getVariableResolver().resolveVariable
353                 (context, Constants.EXCEPTION_HANDLER);
354         handler.handleException(exception);
355 
356     }
357 
358 
359 }