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.application.faces;
19  
20  import java.io.IOException;
21  import java.lang.reflect.Method;
22  import java.net.URL;
23  
24  import javax.servlet.Filter;
25  import javax.servlet.FilterChain;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletContext;
28  import javax.servlet.ServletException;
29  import javax.servlet.ServletRequest;
30  import javax.servlet.ServletResponse;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.apache.commons.chain.Catalog;
35  import org.apache.commons.chain.CatalogFactory;
36  import org.apache.commons.chain.Command;
37  import org.apache.commons.chain.config.ConfigParser;
38  import org.apache.commons.chain.impl.CatalogBase;
39  import org.apache.commons.chain.web.WebContext;
40  import org.apache.commons.chain.web.servlet.ServletWebContext;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.shale.util.Messages;
44  
45  
46  /***
47   * <p>{@link ShaleApplicationFilter} is a <code>Filter</code> implementation
48   * that invokes the required <em>Application Controller</em> functionality on
49   * every request.
50   * In addition, it performs overall application startup and shutdown
51   * operations on behalf of the framework.</p>
52   *
53   * <p>The detailed processing to be performed for each request is configured
54   * by a <code>Command</code> or <code>Chain</code> defined using the "Chain of
55   * Resposibility" design pattern, as implemented by the Commons Chain package.
56   * There must exist a <code>Catalog</code> named <code>shale</code>, which
57   * contains a <code>Command</code> named <code>standard</code>, that defines
58   * the processing to be performed.</p>
59   *
60   * <p>At any point, one of the <code>Command</code>s being executed may choose
61   * to complete the response itself (such as to perform an HTTP redirect),
62   * instead of allowing processing to continue.  To indicate this choice, the
63   * <code>Command</code> should follow the standard Commons Chain convention of
64   * returning <code>true</code>.  If you want processing to continue, return
65   * <code>false</code> instead.</p>
66   *
67   * <p>The default implementation of the standard command processing chain
68   * performs the following tasks:</p>
69   * <ul>
70   * <li>Invoke a command named <code>preprocess</code> (in the <code>shale</code>
71   *     catalog), if it exists.  This is where you should insert commands to be
72   *     executed <strong>before</strong> {@link ShaleApplicationFilter} passes the
73   *     request on to the next filter or servlet.</li>
74   * <li>Execute the remainder of the filter chain for this request.</li>
75   * <li>Invokes a command named <code>postprocess</code> (in the <code>shale</code>
76   *     catalog), if it exists.  This is where you should insert commands to be
77   *     executed <strong>after</strong> control returns from the invoked filter or
78   *     servlet.  Note that it is no longer possible, at this point, to replace
79   *     the response content produced by the filter or servlet -- that should
80   *     be done in a preprocess step.</li>
81   * </ul>
82   *
83   * <p><strong>NOTE</strong> - Configuration of the <code>shale</code> catalog,
84   * and the commands it contains, may be performed in any manner you desire.
85   * One convenient mechanism is to use the <code>ChainListener</code> class
86   * that is included in the Commons Chain package.  If you do not reconfigure it
87   * differently, the <code>standard</code> command (in the <code>shale</code>
88   * catalog) will be configured according to the embedded resource
89   * <code>org/apache/shale/faces/shale-config.xml</code> in the JAR file
90   * containing the core Shale runtime environment, which executes the default
91   * request processing described above.</p>
92   *
93   * $Id: ShaleApplicationFilter.java 464373 2006-10-16 04:21:54Z rahul $
94   */
95  
96  public class ShaleApplicationFilter implements Filter {
97  
98  
99      // -------------------------------------------------------- Static Variables
100 
101 
102     /***
103      * <p>The name of the Commons Chain <code>Catalog</code> to use.</p>
104      */
105     public static final String CATALOG_NAME = "shale";
106 
107 
108     /***
109      * <p>The name of the <code>Command</code> to execute during
110      * application shutdown.</p>
111      */
112     public static final String COMMAND_DESTROY = "destroy";
113 
114 
115     /***
116      * <p>The name of the <code>Command</code> to execute during
117      * application startup.</p>
118      */
119     public static final String COMMAND_INIT = "init";
120 
121 
122     /***
123      * <p>The name of the <code>Command</code> to execute before
124      * the application logic itself is invoked.</p>
125      */
126     public static final String COMMAND_PREPROCESS = "preprocess";
127 
128 
129     /***
130      * <p>The name of the <code>Command</code> to execute after
131      * the application logic itself is invoked.</p>
132      */
133     public static final String COMMAND_POSTPROCESS = "postprocess";
134 
135 
136     /***
137      * <p>The request scope attribute key under which the <code>Context</code>
138      * object used for this chain of command request to be stored, in addition
139      * to it being passed in to the command chains.</p>
140      */
141     public static final String CONTEXT_ATTR =
142       "org.apache.shale.CONTEXT_ATTR";
143 
144 
145     /***
146      * <p>The name of the internal resource containing our default
147      * configuration of the default command.</p>
148      */
149     public static final String RESOURCE_NAME =
150       "org/apache/shale/application/faces/shale-config.xml";
151 
152 
153     // ------------------------------------------------------ Instance Variables
154 
155 
156     /***
157      * <p>Chain of Responsibility <code>Catalog</code> we will be using.</p>
158      */
159     private Catalog catalog = null;
160 
161 
162     /***
163      * <p>The <code>ServletContext</code> instance for our web application.</p>
164      */
165     private ServletContext context = null;
166 
167 
168     /***
169      * <p>The <code>Log</code> instance for this class.</p>
170      */
171     private transient Log log = null;
172 
173 
174     /***
175      * <p>Message resources for this class.</p>
176      */
177     private static Messages messages =
178       new Messages("org.apache.shale.resources.Bundle",
179                    ShaleApplicationFilter.class.getClassLoader());
180 
181 
182     // ---------------------------------------------------------- Filter Methods
183 
184 
185     /***
186      * <p>Perform application shutdown finalization as necessary.</p>
187      */
188     public void destroy() {
189 
190         if (log().isInfoEnabled()) {
191             log().info(messages.getMessage("filter.finalizing"));
192         }
193 
194         // Execute the "destroy" command in the "shale" catalog (if any)
195         Command command = catalog.getCommand(COMMAND_DESTROY);
196         if (command != null) {
197             WebContext webContext = new ServletWebContext(context, null, null);
198             try {
199                 command.execute(webContext);
200             } catch (Exception e) {
201                 if (log().isErrorEnabled()) {
202                     log().error(messages.getMessage("filter.destroyException"), e);
203                 }
204             }
205         }
206 
207         // Clean up JavaServer Faces integration linkages
208         context = null;
209         catalog = null;
210 
211         // Clean up subordinate libraries as needed
212         CatalogFactory.clear();
213         cleanup();
214         LogFactory.release(Thread.currentThread().getContextClassLoader());
215 
216     }
217 
218 
219     /***
220      * <p>Perform per-request application controler functionality.</p>
221      *
222      * @param request The request we are processing
223      * @param response The response we are creating
224      * @param chain The filter chain for this request
225      *
226      * @exception IOException if an input/output error occurs
227      * @exception ServletException if a servlet exception is thrown
228      */
229     public void doFilter(ServletRequest request, ServletResponse response,
230       FilterChain chain) throws IOException, ServletException {
231 
232       // Define local variables we will need
233       Command command = null;
234       boolean result = false;
235 
236       // Construct and store a new Context for this request
237       ShaleWebContext context =
238         new ShaleWebContext(this.context,
239           (HttpServletRequest) request,
240           (HttpServletResponse) response);
241       request.setAttribute(CONTEXT_ATTR, context);
242 
243       // Invoke the "preprocess" command (if any is defined).  If this
244       // command returns true, the response has been completed already
245       // so we do NOT invoke the actual application.
246       command = catalog.getCommand(COMMAND_PREPROCESS);
247       if (command != null) {
248           try {
249               result = command.execute(context);
250           } catch (IOException e) {
251               throw e;
252           } catch (ServletException e) {
253               throw e;
254           } catch (Exception e) {
255               throw new ServletException(e);
256           }
257           if (result) {
258               // Clean up the stored request attribute
259               request.removeAttribute(CONTEXT_ATTR);
260               // Bypass calling the remainder of the application
261               return;
262           }
263       }
264 
265       // Invoke the remainder of the processing for this request
266       // (which will typically be the JSF controller servlet)
267       chain.doFilter(request, response);
268 
269       // Invoke the "postprocess" command (if any is defined).
270       command = catalog.getCommand(COMMAND_POSTPROCESS);
271       if (command != null) {
272           try {
273               command.execute(context);
274           } catch (IOException e) {
275               throw e;
276           } catch (ServletException e) {
277               throw e;
278           } catch (Exception e) {
279               throw new ServletException(e);
280           }
281       }
282 
283       // Clean up the stored request attribute
284       request.removeAttribute(CONTEXT_ATTR);
285 
286     }
287 
288 
289     /***
290      * <p>Perform application startup intiialization as necessary.</p>
291      *
292      * @param config <code>FilterConfig</code> for this filter
293      *
294      * @exception ServletException if a servlet exception is thrown
295      */
296     public void init(FilterConfig config) throws ServletException {
297 
298         if (log().isInfoEnabled()) {
299             log().info(messages.getMessage("filter.initializing"));
300         }
301 
302         context = config.getServletContext();
303 
304         // Look up the "shale" catalog and ensure "standard" is defined
305         try {
306             catalog = getCatalog();
307         } catch (ServletException e) {
308             throw e;
309         } catch (Exception e) {
310             throw new ServletException(e);
311         }
312 
313         // Execute the "init" command in the "shale" catalog (if any)
314         Command command = catalog.getCommand(COMMAND_INIT);
315         if (command != null) {
316             WebContext webContext = new ServletWebContext(context, null, null);
317             try {
318                 command.execute(webContext);
319             } catch (Exception e) {
320                 if (log().isErrorEnabled()) {
321                     log().error(messages.getMessage("filter.initException"), e);
322                 }
323                 throw new ServletException(e);
324             }
325         }
326 
327     }
328 
329 
330     // --------------------------------------------------------- Private Methods
331 
332 
333     /***
334      * <p>Clean up the Commons BeanUtils library if it has been loaded.</p>
335      */
336     private void cleanup() {
337 
338         try {
339             ClassLoader loader = Thread.currentThread().getContextClassLoader();
340             if (loader == null) {
341                 loader = ShaleApplicationFilter.class.getClassLoader();
342             }
343             Class clazz = loader.loadClass("org.apache.commons.beanutils.PropertyUtils");
344             Method method = clazz.getMethod("clearDescriptors", (Class[]) null);
345             method.invoke(null, (Object[]) null);
346         } catch (Exception e) {
347             ; // Swallow and ignore any exceptions
348         }
349 
350     }
351 
352 
353 
354     /***
355      * <p>Return the "shale" catalog with a "standard" command, configuring the
356      * default version of this command if necessary.</p>
357      *
358      * @exception Exception if a resource parsing exception occurs
359      */
360     private Catalog getCatalog() throws Exception {
361 
362         // Look up the "shale" catalog, returning any existing instance
363         // if it is fully configured
364         Catalog catalog = CatalogFactory.getInstance().getCatalog(CATALOG_NAME);
365         if ((catalog != null) &&
366             (catalog.getCommand(COMMAND_INIT) != null) &&
367             (catalog.getCommand(COMMAND_DESTROY) != null)) {
368             return catalog;
369         }
370 
371         // Create a new catalog (if necessary)
372         if (catalog == null) {
373             if (log().isDebugEnabled()) {
374                 log().debug(messages.getMessage("filter.creatingCatalog",
375                                                 new Object[] { CATALOG_NAME }));
376             }
377             catalog = new CatalogBase();
378             CatalogFactory.getInstance().addCatalog(CATALOG_NAME, catalog);
379         }
380 
381         // Configure this catalog based on our default resource
382         if (log().isDebugEnabled()) {
383             log().debug(messages.getMessage("filter.parsingResource",
384                                             new Object[] { RESOURCE_NAME }));
385         }
386         ConfigParser parser = new ConfigParser();
387         URL url = this.getClass().getClassLoader().getResource(RESOURCE_NAME);
388         if (url == null) {
389             throw new IllegalArgumentException(RESOURCE_NAME);
390         }
391         parser.parse(url);
392 
393         return catalog;
394 
395     }
396 
397 
398     /***
399      * <p>Return the <code>Log</code> instance to use, creating one if needed.</p>
400      */
401     private Log log() {
402 
403         if (this.log == null) {
404             log = LogFactory.getLog(ShaleApplicationFilter.class);
405         }
406         return log;
407 
408     }
409 
410 
411 }