001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.chain.web;
018    
019    
020    import java.io.InputStream;
021    import java.net.URL;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.Set;
025    import javax.servlet.ServletContext;
026    import javax.servlet.ServletContextEvent;
027    import javax.servlet.ServletContextListener;
028    import org.apache.commons.chain.Catalog;
029    import org.apache.commons.chain.CatalogFactory;
030    import org.apache.commons.chain.config.ConfigParser;
031    import org.apache.commons.chain.impl.CatalogBase;
032    import org.apache.commons.digester.RuleSet;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    
036    
037    /**
038     * <p><code>ServletContextListener</code> that automatically
039     * scans chain configuration files in the current web application at
040     * startup time, and exposes the result in a {@link Catalog} under a
041     * specified servlet context attribute.  The following <em>context</em> init
042     * parameters are utilized:</p>
043     * <ul>
044     * <li><strong>org.apache.commons.chain.CONFIG_CLASS_RESOURCE</strong> -
045     *     comma-delimited list of chain configuration resources to be loaded
046     *     via <code>ClassLoader.getResource()</code> calls.  If not specified,
047     *     no class loader resources will be loaded.</li>
048     * <li><strong>org.apache.commons.chain.CONFIG_WEB_RESOURCE</strong> -
049     *     comma-delimited list of chain configuration webapp resources
050     *     to be loaded.  If not specified, no web application resources
051     *     will be loaded.</li>
052     * <li><strong>org.apache.commons.chain.CONFIG_ATTR</strong> -
053     *     Name of the servlet context attribute under which the
054     *     resulting {@link Catalog} will be created or updated.
055     *     If not specified, it is expected that parsed resources will
056     *     contain <code>&lt;catalog&gt;</code> elements (which will
057     *     cause registration of the created {@link Catalog}s into
058     *     the {@link CatalogFactory} for this application, and no
059     *     servet context attribute will be created.
060     *     <strong>NOTE</strong> - This parameter is deprecated.</p>
061     * <li><strong>org.apache.commons.chain.RULE_SET</strong> -
062     *     Fully qualified class name of a Digester <code>RuleSet</code>
063     *     implementation to use for parsing configuration resources (this
064     *     class must have a public zero-args constructor).  If not defined,
065     *     the standard <code>RuleSet</code> implementation will be used.</li>
066     * </ul>
067     *
068     * <p>When a web application that has configured this listener is
069     * started, it will acquire the {@link Catalog} under the specified servlet
070     * context attribute key, creating a new one if there is none already there.
071     * This {@link Catalog} will then be populated by scanning configuration
072     * resources from the following sources (loaded in this order):</p>
073     * <ul>
074     * <li>Resources loaded from any <code>META-INF/chain-config.xml</code>
075     *     resource found in a JAR file in <code>/WEB-INF/lib</code>.</li>
076     * <li>Resources loaded from specified resource paths from the
077     *     webapp's class loader (via <code>ClassLoader.getResource()</code>).</li>
078     * <li>Resources loaded from specified resource paths in the web application
079     *     archive (via <code>ServetContext.getResource()</code>).</li>
080     * </ul>
081     *
082     * <p>If no attribute key is specified, on the other hand, parsed configuration
083     * resources are expected to contain <code>&lt;catalog&gt;</code> elements,
084     * and the catalogs will be registered with the {@link CatalogFactory}
085     * for this web application.</p>
086     *
087     * <p>This class requires Servlet 2.3 or later.  If you are running on
088     * Servlet 2.2 system, consider using {@link ChainServlet} instead.
089     * Note that {@link ChainServlet} uses parameters of the
090     * same names, but they are <em>servlet</em> init parameters instead
091     * of <em>context</em> init parameters.  Because of this, you can use
092     * both facilities in the same application, if desired.</p>
093     *
094     * @author Craig R. McClanahan
095     * @author Ted Husted
096     * @version $Revision: 658426 $ $Date: 2008-05-20 21:55:38 +0100 (Tue, 20 May 2008) $
097     */
098    
099    public class ChainListener implements ServletContextListener {
100    
101    
102        // ------------------------------------------------------ Manifest Constants
103    
104    
105        /**
106         * <p>The name of the context init parameter containing the name of the
107         * servlet context attribute under which our resulting {@link Catalog}
108         * will be stored.</p>
109         */
110        public static final String CONFIG_ATTR =
111            "org.apache.commons.chain.CONFIG_ATTR";
112    
113    
114        /**
115         * <p>The name of the context init parameter containing a comma-delimited
116         * list of class loader resources to be scanned.</p>
117         */
118        public static final String CONFIG_CLASS_RESOURCE =
119            "org.apache.commons.chain.CONFIG_CLASS_RESOURCE";
120    
121    
122        /**
123         * <p>The name of the context init parameter containing a comma-delimited
124         * list of web applicaton resources to be scanned.</p>
125         */
126        public static final String CONFIG_WEB_RESOURCE =
127            "org.apache.commons.chain.CONFIG_WEB_RESOURCE";
128    
129    
130        /**
131         * <p>The name of the context init parameter containing the fully
132         * qualified class name of the <code>RuleSet</code> implementation
133         * for configuring our {@link ConfigParser}.</p>
134         */
135        public static final String RULE_SET =
136            "org.apache.commons.chain.RULE_SET";
137    
138    
139        // ------------------------------------------ ServletContextListener Methods
140    
141    
142        /**
143         * <p>Remove the configured {@link Catalog} from the servlet context
144         * attributes for this web application.</p>
145         *
146         * @param event <code>ServletContextEvent</code> to be processed
147         */
148        public void contextDestroyed(ServletContextEvent event) {
149    
150            ServletContext context = event.getServletContext();
151            String attr = context.getInitParameter(CONFIG_ATTR);
152            if (attr != null) {
153                context.removeAttribute(attr);
154            }
155            CatalogFactory.clear();
156    
157        }
158    
159    
160        /**
161         * <p>Scan the required chain configuration resources, assemble the
162         * configured chains into a {@link Catalog}, and expose it as a
163         * servlet context attribute under the specified key.</p>
164         *
165         * @param event <code>ServletContextEvent</code> to be processed
166         */
167        public void contextInitialized(ServletContextEvent event) {
168    
169            Log log = LogFactory.getLog(ChainListener.class);
170            if (log.isInfoEnabled()) {
171                log.info("Initializing chain listener");
172            }
173            ServletContext context = event.getServletContext();
174    
175            // Retrieve context init parameters that we need
176            String attr = context.getInitParameter(CONFIG_ATTR);
177            String classResources =
178                context.getInitParameter(CONFIG_CLASS_RESOURCE);
179            String ruleSet = context.getInitParameter(RULE_SET);
180            String webResources = context.getInitParameter(CONFIG_WEB_RESOURCE);
181    
182            // Retrieve or create the Catalog instance we may be updating
183            Catalog catalog = null;
184            if (attr != null) {
185                catalog = (Catalog) context.getAttribute(attr);
186                if (catalog == null) {
187                    catalog = new CatalogBase();
188                }
189            }
190    
191            // Construct the configuration resource parser we will use
192            ConfigParser parser = new ConfigParser();
193            if (ruleSet != null) {
194                try {
195                    ClassLoader loader =
196                        Thread.currentThread().getContextClassLoader();
197                    if (loader == null) {
198                        loader = this.getClass().getClassLoader();
199                    }
200                    Class clazz = loader.loadClass(ruleSet);
201                    parser.setRuleSet((RuleSet) clazz.newInstance());
202                } catch (Exception e) {
203                    throw new RuntimeException("Exception initalizing RuleSet '"
204                                               + ruleSet + "' instance: "
205                                               + e.getMessage());
206                }
207            }
208    
209            // Parse the resources specified in our init parameters (if any)
210            if (attr == null) {
211                parseJarResources(context, parser, log);
212                ChainResources.parseClassResources
213                    (classResources, parser);
214                ChainResources.parseWebResources
215                    (context, webResources, parser);
216            } else {
217                parseJarResources(catalog, context, parser, log);
218                ChainResources.parseClassResources
219                    (catalog, classResources, parser);
220                ChainResources.parseWebResources
221                    (catalog, context, webResources, parser);
222            }
223    
224            // Expose the completed catalog (if requested)
225            if (attr != null) {
226                context.setAttribute(attr, catalog);
227            }
228    
229        }
230    
231    
232        // --------------------------------------------------------- Private Methods
233    
234    
235        /**
236         * <p>Parse resources found in JAR files in the <code>/WEB-INF/lib</code>
237         * subdirectory (if any).</p>
238         *
239         * @param context <code>ServletContext</code> for this web application
240         * @param parser {@link ConfigParser} to use for parsing
241         */
242        private void parseJarResources(ServletContext context,
243                                       ConfigParser parser, Log log) {
244    
245            Set jars = context.getResourcePaths("/WEB-INF/lib");
246            if (jars == null) {
247                jars = new HashSet();
248            }
249            String path = null;
250            Iterator paths = jars.iterator();
251            while (paths.hasNext()) {
252    
253                path = (String) paths.next();
254                if (!path.endsWith(".jar")) {
255                    continue;
256                }
257                URL resourceURL = null;
258                try {
259                    URL jarURL = context.getResource(path);
260                    resourceURL = new URL("jar:"
261                                          + translate(jarURL.toExternalForm())
262                                          + "!/META-INF/chain-config.xml");
263                    if (resourceURL == null) {
264                        continue;
265                    }
266                    InputStream is = null;
267                    try {
268                        is = resourceURL.openStream();
269                    } catch (Exception e) {
270                          // means there is no such resource
271                    }
272                    if (is == null) {
273                        if (log.isDebugEnabled()) {
274                            log.debug("Not Found: " + resourceURL);
275                        }
276                        continue;
277                    } else {
278                        is.close();
279                    }
280                    if (log.isDebugEnabled()) {
281                        log.debug("Parsing: " + resourceURL);
282                    }
283                    parser.parse(resourceURL);
284                } catch (Exception e) {
285                    throw new RuntimeException
286                        ("Exception parsing chain config resource '"
287                         + resourceURL.toExternalForm() + "': "
288                         + e.getMessage());
289                }
290            }
291    
292        }
293    
294    
295        /**
296         * <p>Parse resources found in JAR files in the <code>/WEB-INF/lib</code>
297         * subdirectory (if any).</p>
298         *
299         * @param catalog {@link Catalog} we are populating
300         * @param context <code>ServletContext</code> for this web application
301         * @param parser {@link ConfigParser} to use for parsing
302         *
303         * @deprecated Use the variant that does not take a catalog, on a
304         *  configuration resource containing "catalog" element(s)
305         */
306        private void parseJarResources(Catalog catalog, ServletContext context,
307                                       ConfigParser parser, Log log) {
308    
309            Set jars = context.getResourcePaths("/WEB-INF/lib");
310            if (jars == null) {
311                jars = new HashSet();
312            }
313            String path = null;
314            Iterator paths = jars.iterator();
315            while (paths.hasNext()) {
316    
317                path = (String) paths.next();
318                if (!path.endsWith(".jar")) {
319                    continue;
320                }
321                URL resourceURL = null;
322                try {
323                    URL jarURL = context.getResource(path);
324                    resourceURL = new URL("jar:"
325                                          + translate(jarURL.toExternalForm())
326                                          + "!/META-INF/chain-config.xml");
327                    if (resourceURL == null) {
328                        continue;
329                    }
330                    InputStream is = null;
331                    try {
332                        is = resourceURL.openStream();
333                    } catch (Exception e) {
334                          // means there is no such resource
335                    }
336                    if (is == null) {
337                        if (log.isDebugEnabled()) {
338                            log.debug("Not Found: " + resourceURL);
339                        }
340                        continue;
341                    } else {
342                        is.close();
343                    }
344                    if (log.isDebugEnabled()) {
345                        log.debug("Parsing: " + resourceURL);
346                    }
347                    parser.parse(catalog, resourceURL);
348                } catch (Exception e) {
349                    throw new RuntimeException
350                        ("Exception parsing chain config resource '"
351                         + resourceURL.toExternalForm() + "': "
352                         + e.getMessage());
353                }
354            }
355    
356        }
357    
358    
359        /**
360         * <p>Translate space character into <code>&pct;20</code> to avoid problems
361         * with paths that contain spaces on some JVMs.</p>
362         *
363         * @param value Value to translate
364         */
365        private String translate(String value) {
366    
367            while (true) {
368                int index = value.indexOf(' ');
369                if (index < 0) {
370                    break;
371                }
372                value = value.substring(0, index) + "%20" + value.substring(index + 1);
373            }
374            return (value);
375    
376        }
377    
378    
379    }