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 }