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><catalog></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><catalog></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 }