View Javadoc

1   /*
2    * Copyright 1999-2004 The Apache Software Foundation
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.apache.commons.chain.web;
17  
18  
19  import java.io.InputStream;
20  import java.net.URL;
21  import java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.Set;
24  import javax.servlet.ServletContext;
25  import javax.servlet.ServletContextEvent;
26  import javax.servlet.ServletContextListener;
27  import org.apache.commons.chain.Catalog;
28  import org.apache.commons.chain.CatalogFactory;
29  import org.apache.commons.chain.config.ConfigParser;
30  import org.apache.commons.chain.impl.CatalogBase;
31  import org.apache.commons.digester.RuleSet;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  
35  
36  /***
37   * <p><code>ServletContextListener</code> that automatically
38   * scans chain configuration files in the current web application at
39   * startup time, and exposes the result in a {@link Catalog} under a
40   * specified servlet context attribute.  The following <em>context</em> init
41   * parameters are utilized:</p>
42   * <ul>
43   * <li><strong>org.apache.commons.chain.CONFIG_CLASS_RESOURCE</strong> -
44   *     comma-delimited list of chain configuration resources to be loaded
45   *     via <code>ClassLoader.getResource()</code> calls.  If not specified,
46   *     no class loader resources will be loaded.</li>
47   * <li><strong>org.apache.commons.chain.CONFIG_WEB_RESOURCE</strong> -
48   *     comma-delimited list of chain configuration webapp resources
49   *     to be loaded.  If not specified, no web application resources
50   *     will be loaded.</li>
51   * <li><strong>org.apache.commons.chain.CONFIG_ATTR</strong> -
52   *     Name of the servlet context attribute under which the
53   *     resulting {@link Catalog} will be created or updated.
54   *     If not specified, it is expected that parsed resources will
55   *     contain <code>&lt;catalog&gt;</code> elements (which will
56   *     cause registration of the created {@link Catalog}s into
57   *     the {@link CatalogFactory} for this application, and no
58   *     servet context attribute will be created.
59   *     <strong>NOTE</strong> - This parameter is deprecated.</p>
60   * <li><strong>org.apache.commons.chain.RULE_SET</strong> -
61   *     Fully qualified class name of a Digester <code>RuleSet</code>
62   *     implementation to use for parsing configuration resources (this
63   *     class must have a public zero-args constructor).  If not defined,
64   *     the standard <code>RuleSet</code> implementation will be used.</li>
65   * </ul>
66   *
67   * <p>When a web application that has configured this listener is
68   * started, it will acquire the {@link Catalog} under the specified servlet
69   * context attribute key, creating a new one if there is none already there.
70   * This {@link Catalog} will then be populated by scanning configuration
71   * resources from the following sources (loaded in this order):</p>
72   * <ul>
73   * <li>Resources loaded from any <code>META-INF/chain-config.xml</code>
74   *     resource found in a JAR file in <code>/WEB-INF/lib</code>.</li>
75   * <li>Resources loaded from specified resource paths from the
76   *     webapp's class loader (via <code>ClassLoader.getResource()</code>).</li>
77   * <li>Resources loaded from specified resource paths in the web application
78   *     archive (via <code>ServetContext.getResource()</code>).</li>
79   * </ul>
80   *
81   * <p>If no attribute key is specified, on the other hand, parsed configuration
82   * resources are expected to contain <code>&lt;catalog&gt;</code> elements,
83   * and the catalogs will be registered with the {@link CatalogFactory}
84   * for this web application.</p>
85   *
86   * <p>This class requires Servlet 2.3 or later.  If you are running on
87   * Servlet 2.2 system, consider using {@link ChainServlet} instead.
88   * Note that {@link ChainServlet} uses parameters of the
89   * same names, but they are <em>servlet</em> init parameters instead
90   * of <em>context</em> init parameters.  Because of this, you can use
91   * both facilities in the same application, if desired.</p>
92   *
93   * @author Craig R. McClanahan
94   * @author Ted Husted
95   * @version $Revision: 411893 $ $Date: 2006-06-05 19:59:05 +0100 (Mon, 05 Jun 2006) $
96   */
97  
98  public class ChainListener implements ServletContextListener {
99  
100 
101     // ------------------------------------------------------ Manifest Constants
102 
103 
104     /***
105      * <p>The name of the context init parameter containing the name of the
106      * servlet context attribute under which our resulting {@link Catalog}
107      * will be stored.</p>
108      */
109     public static final String CONFIG_ATTR =
110         "org.apache.commons.chain.CONFIG_ATTR";
111 
112 
113     /***
114      * <p>The name of the context init parameter containing a comma-delimited
115      * list of class loader resources to be scanned.</p>
116      */
117     public static final String CONFIG_CLASS_RESOURCE =
118         "org.apache.commons.chain.CONFIG_CLASS_RESOURCE";
119 
120 
121     /***
122      * <p>The name of the context init parameter containing a comma-delimited
123      * list of web applicaton resources to be scanned.</p>
124      */
125     public static final String CONFIG_WEB_RESOURCE =
126         "org.apache.commons.chain.CONFIG_WEB_RESOURCE";
127 
128 
129     /***
130      * <p>The name of the context init parameter containing the fully
131      * qualified class name of the <code>RuleSet</code> implementation
132      * for configuring our {@link ConfigParser}.</p>
133      */
134     public static final String RULE_SET =
135         "org.apache.commons.chain.RULE_SET";
136 
137 
138     // ------------------------------------------ ServletContextListener Methods
139 
140 
141     /***
142      * <p>Remove the configured {@link Catalog} from the servlet context
143      * attributes for this web application.</p>
144      *
145      * @param event <code>ServletContextEvent</code> to be processed
146      */
147     public void contextDestroyed(ServletContextEvent event) {
148 
149         ServletContext context = event.getServletContext();
150         String attr = context.getInitParameter(CONFIG_ATTR);
151         if (attr != null) {
152             context.removeAttribute(attr);
153         }
154         CatalogFactory.clear();
155 
156     }
157 
158 
159     /***
160      * <p>Scan the required chain configuration resources, assemble the
161      * configured chains into a {@link Catalog}, and expose it as a
162      * servlet context attribute under the specified key.</p>
163      *
164      * @param event <code>ServletContextEvent</code> to be processed
165      */
166     public void contextInitialized(ServletContextEvent event) {
167 
168         Log log = LogFactory.getLog(ChainListener.class);
169         if (log.isInfoEnabled()) {
170             log.info("Initializing chain listener");
171         }
172         ServletContext context = event.getServletContext();
173 
174         // Retrieve context init parameters that we need
175         String attr = context.getInitParameter(CONFIG_ATTR);
176         String classResources =
177             context.getInitParameter(CONFIG_CLASS_RESOURCE);
178         String ruleSet = context.getInitParameter(RULE_SET);
179         String webResources = context.getInitParameter(CONFIG_WEB_RESOURCE);
180 
181         // Retrieve or create the Catalog instance we may be updating
182         Catalog catalog = null;
183         if (attr != null) {
184             catalog = (Catalog) context.getAttribute(attr);
185             if (catalog == null) {
186                 catalog = new CatalogBase();
187             }
188         }
189 
190         // Construct the configuration resource parser we will use
191         ConfigParser parser = new ConfigParser();
192         if (ruleSet != null) {
193             try {
194                 ClassLoader loader =
195                     Thread.currentThread().getContextClassLoader();
196                 if (loader == null) {
197                     loader = this.getClass().getClassLoader();
198                 }
199                 Class clazz = loader.loadClass(ruleSet);
200                 parser.setRuleSet((RuleSet) clazz.newInstance());
201             } catch (Exception e) {
202                 throw new RuntimeException("Exception initalizing RuleSet '"
203                                            + ruleSet + "' instance: "
204                                            + e.getMessage());
205             }
206         }
207 
208         // Parse the resources specified in our init parameters (if any)
209         if (attr == null) {
210             parseJarResources(context, parser);
211             ChainResources.parseClassResources
212                 (classResources, parser);
213             ChainResources.parseWebResources
214                 (context, webResources, parser);
215         } else {
216             parseJarResources(catalog, context, parser);
217             ChainResources.parseClassResources
218                 (catalog, classResources, parser);
219             ChainResources.parseWebResources
220                 (catalog, context, webResources, parser);
221         }
222 
223         // Expose the completed catalog (if requested)
224         if (attr != null) {
225             context.setAttribute(attr, catalog);
226         }
227 
228     }
229 
230 
231     // --------------------------------------------------------- Private Methods
232 
233 
234     /***
235      * <p>Parse resources found in JAR files in the <code>/WEB-INF/lib</code>
236      * subdirectory (if any).</p>
237      *
238      * @param context <code>ServletContext</code> for this web application
239      * @param parser {@link ConfigParser} to use for parsing
240      */
241     private void parseJarResources(ServletContext context,
242                                    ConfigParser parser) {
243 
244         Set jars = context.getResourcePaths("/WEB-INF/lib");
245         if (jars == null) {
246             jars = new HashSet();
247         }
248         String path = null;
249         Iterator paths = jars.iterator();
250         while (paths.hasNext()) {
251 
252             path = (String) paths.next();
253             if (!path.endsWith(".jar")) {
254                 continue;
255             }
256             URL resourceURL = null;
257             try {
258                 URL jarURL = context.getResource(path);
259                 resourceURL = new URL("jar:"
260                                       + translate(jarURL.toExternalForm())
261                                       + "!/META-INF/chain-config.xml");
262                 if (resourceURL == null) {
263                     continue;
264                 }
265                 InputStream is = null;
266                 try {
267                     is = resourceURL.openStream();
268                 } catch (Exception e) {
269                       // means there is no such resource
270                 }
271                 if (is == null) {
272                     continue;
273                 } else {
274                     is.close();
275                 }
276                 parser.parse(resourceURL);
277             } catch (Exception e) {
278                 throw new RuntimeException
279                     ("Exception parsing chain config resource '"
280                      + resourceURL.toExternalForm() + "': "
281                      + e.getMessage());
282             }
283         }
284 
285     }
286 
287 
288     /***
289      * <p>Parse resources found in JAR files in the <code>/WEB-INF/lib</code>
290      * subdirectory (if any).</p>
291      *
292      * @param catalog {@link Catalog} we are populating
293      * @param context <code>ServletContext</code> for this web application
294      * @param parser {@link ConfigParser} to use for parsing
295      *
296      * @deprecated Use the variant that does not take a catalog, on a
297      *  configuration resource containing "catalog" element(s)
298      */
299     private void parseJarResources(Catalog catalog, ServletContext context,
300                                    ConfigParser parser) {
301 
302         Set jars = context.getResourcePaths("/WEB-INF/lib");
303         if (jars == null) {
304             jars = new HashSet();
305         }
306         String path = null;
307         Iterator paths = jars.iterator();
308         while (paths.hasNext()) {
309 
310             path = (String) paths.next();
311             if (!path.endsWith(".jar")) {
312                 continue;
313             }
314             URL resourceURL = null;
315             try {
316                 URL jarURL = context.getResource(path);
317                 resourceURL = new URL("jar:"
318                                       + translate(jarURL.toExternalForm())
319                                       + "!/META-INF/chain-config.xml");
320                 if (resourceURL == null) {
321                     continue;
322                 }
323                 InputStream is = null;
324                 try {
325                     is = resourceURL.openStream();
326                 } catch (Exception e) {
327                       // means there is no such resource
328                 }
329                 if (is == null) {
330                     continue;
331                 } else {
332                     is.close();
333                 }
334                 parser.parse(catalog, resourceURL);
335             } catch (Exception e) {
336                 throw new RuntimeException
337                     ("Exception parsing chain config resource '"
338                      + resourceURL.toExternalForm() + "': "
339                      + e.getMessage());
340             }
341         }
342 
343     }
344 
345 
346     /***
347      * <p>Translate space character into <code>&pct;20</code> to avoid problems
348      * with paths that contain spaces on some JVMs.</p>
349      *
350      * @param value Value to translate
351      */
352     private String translate(String value) {
353 
354         while (true) {
355             int index = value.indexOf(' ');
356             if (index < 0) {
357                 break;
358             }
359             value = value.substring(0, index) + value.substring(index + 1);
360         }
361         return (value);
362 
363     }
364 
365 
366 }