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 package org.apache.commons.chain.web.javax;
18
19 import java.io.InputStream;
20 import java.net.URL;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.Set;
24
25 import javax.servlet.ServletContext;
26 import javax.servlet.ServletException;
27
28 import org.apache.commons.chain.Catalog;
29 import org.apache.commons.chain.CatalogFactory;
30 import org.apache.commons.chain.config.ConfigParser;
31 import org.apache.commons.chain.impl.CatalogBase;
32 import org.apache.commons.chain.web.CheckedConsumer;
33 import org.apache.commons.digester.RuleSet;
34 import org.slf4j.Logger;
35
36 /**
37 * Context-initializer that automatically scans chain configuration files
38 * in the current web application at startup time, and exposes the result
39 * in a {@link Catalog} under a specified servlet context attribute. The
40 * following <em>context</em> init parameters are utilized:
41 * <ul>
42 * <li><strong>org.apache.commons.chain.CONFIG_CLASS_RESOURCE</strong> -
43 * comma-delimited list of chain configuration resources to be loaded
44 * via {@code ClassLoader.getResource()} calls. If not specified,
45 * no class loader resources will be loaded.</li>
46 * <li><strong>org.apache.commons.chain.CONFIG_WEB_RESOURCE</strong> -
47 * comma-delimited list of chain configuration webapp resources
48 * to be loaded. If not specified, no web application resources
49 * will be loaded.</li>
50 * <li><strong>org.apache.commons.chain.CONFIG_ATTR</strong> -
51 * Name of the servlet context attribute under which the
52 * resulting {@link Catalog} will be created or updated.
53 * If not specified, it is expected that parsed resources will
54 * contain {@code <catalog>} elements (which will
55 * cause registration of the created {@link Catalog}s into
56 * the {@link CatalogFactory} for this application, and no
57 * servet context attribute will be created.
58 * <strong>NOTE</strong> - This parameter is deprecated.</li>
59 * <li><strong>org.apache.commons.chain.RULE_SET</strong> -
60 * Fully qualified class name of a Digester {@code RuleSet}
61 * implementation to use for parsing configuration resources (this
62 * class must have a public zero-args constructor). If not defined,
63 * the standard {@code RuleSet} implementation will be used.</li>
64 * </ul>
65 *
66 * <p>When a web application that has configured this listener is
67 * started, it will acquire the {@link Catalog} under the specified servlet
68 * context attribute key, creating a new one if there is none already there.
69 * This {@link Catalog} will then be populated by scanning configuration
70 * resources from the following sources (loaded in this order):</p>
71 * <ul>
72 * <li>Optional: Resources loaded from any {@code META-INF/chain-config.xml}
73 * resource found in a JAR file in {@code /WEB-INF/lib}.</li>
74 * <li>Resources loaded from specified resource paths from the
75 * webapp's class loader (via {@code ClassLoader.getResource()}).</li>
76 * <li>Resources loaded from specified resource paths in the web application
77 * archive (via {@code ServetContext.getResource()}).</li>
78 * </ul>
79 *
80 * <p>If no attribute key is specified, on the other hand, parsed configuration
81 * resources are expected to contain {@code <catalog>} elements,
82 * and the catalogs will be registered with the {@link CatalogFactory}
83 * for this web application.</p>
84 *
85 * @author Craig R. McClanahan
86 * @author Ted Husted
87 */
88 final class ChainInit {
89
90 /**
91 * The name of the context init parameter containing the name of the
92 * servlet context attribute under which our resulting {@link Catalog}
93 * will be stored.
94 */
95 static final String CONFIG_ATTR =
96 "org.apache.commons.chain.CONFIG_ATTR";
97
98 /**
99 * The name of the context init parameter containing a comma-delimited
100 * list of class loader resources to be scanned.
101 */
102 static final String CONFIG_CLASS_RESOURCE =
103 "org.apache.commons.chain.CONFIG_CLASS_RESOURCE";
104
105 /**
106 * The name of the context init parameter containing a comma-delimited
107 * list of web application resources to be scanned.
108 */
109 static final String CONFIG_WEB_RESOURCE =
110 "org.apache.commons.chain.CONFIG_WEB_RESOURCE";
111
112 /**
113 * The name of the context init parameter containing the fully
114 * qualified class name of the {@code RuleSet} implementation
115 * for configuring our {@link ConfigParser}.
116 */
117 static final String RULE_SET =
118 "org.apache.commons.chain.RULE_SET";
119
120 /**
121 * Remove the configured {@link Catalog} from the servlet context
122 * attributes for this web application.
123 *
124 * @param context the servlet-context
125 * @param attr the value of the {@code CONFIG_ATTR}
126 */
127 static void destroy(ServletContext context, String attr) {
128 if (attr != null) {
129 context.removeAttribute(attr);
130 }
131 CatalogFactory.clear();
132 }
133
134 /**
135 * Private constructor.
136 */
137 private ChainInit() {
138 }
139
140 /**
141 * Scan the required chain configuration resources, assemble the
142 * configured chains into a {@link Catalog}, and expose it as a
143 * servlet context attribute under the specified key.
144 *
145 * @param context the servlet-context
146 * @param attr the value of the {@code CONFIG_ATTR}
147 * @param logger to use for logging
148 * @param parseJarResources {@code true} to parse resources in jar-files
149 */
150 @SuppressWarnings("deprecation")
151 static void initialize(ServletContext context, String attr, Logger logger, boolean parseJarResources) throws ServletException {
152 String classResources = context.getInitParameter(CONFIG_CLASS_RESOURCE);
153 String ruleSet = context.getInitParameter(RULE_SET);
154 String webResources = context.getInitParameter(CONFIG_WEB_RESOURCE);
155
156 // Retrieve or create the Catalog instance we may be updating
157 Catalog<?> catalog = null;
158 if (attr != null) {
159 catalog = (Catalog<?>) context.getAttribute(attr);
160 if (catalog == null) {
161 catalog = new CatalogBase<>();
162 }
163 }
164
165 // Construct the configuration resource parser we will use
166 ConfigParser parser = new ConfigParser();
167 if (ruleSet != null) {
168 try {
169 ClassLoader loader =
170 Thread.currentThread().getContextClassLoader();
171 if (loader == null) {
172 loader = ChainInit.class.getClassLoader();
173 }
174 Class<? extends RuleSet> clazz = loader
175 .loadClass(ruleSet)
176 .asSubclass(RuleSet.class);
177 parser.setRuleSet(clazz.getDeclaredConstructor().newInstance());
178 } catch (Exception e) {
179 throw new ServletException("Exception initalizing RuleSet '"
180 + ruleSet + "' instance ", e);
181 }
182 }
183
184 // Parse the resources specified in our init parameters (if any)
185 final CheckedConsumer<URL, Exception> parse;
186 if (attr == null) {
187 parse = parser::parse;
188 } else {
189 final Catalog<?> cat = catalog;
190 parse = url -> parser.parse(cat, url);
191 }
192 if (parseJarResources) {
193 parseJarResources(context, parse, logger);
194 }
195 ChainResources.parseClassResources(classResources, parse);
196 ChainResources.parseWebResources(context, webResources, parse);
197
198 // Expose the completed catalog (if requested)
199 if (attr != null) {
200 context.setAttribute(attr, catalog);
201 }
202 }
203
204 // --------------------------------------------------------- Private Methods
205
206 /**
207 * Parse resources found in JAR files in the {@code /WEB-INF/lib}
208 * subdirectory (if any).
209 *
210 * @param <E> the type of the exception from parse-function
211 * @param context {@code ServletContext} for this web application
212 * @param parse parse-function to parse the XML document
213 * @param logger to use for logging
214 */
215 private static <E extends Exception> void parseJarResources(ServletContext context,
216 CheckedConsumer<URL, E> parse, Logger logger) {
217
218 Set<String> jars = context.getResourcePaths("/WEB-INF/lib");
219 if (jars == null) {
220 jars = Collections.emptySet();
221 }
222 String path = null;
223 Iterator<String> paths = jars.iterator();
224 while (paths.hasNext()) {
225
226 path = paths.next();
227 if (!path.endsWith(".jar")) {
228 continue;
229 }
230 URL resourceURL = null;
231 try {
232 URL jarURL = context.getResource(path);
233 path = jarURL.toExternalForm();
234
235 resourceURL = new URL("jar:"
236 + translate(path)
237 + "!/META-INF/chain-config.xml");
238 path = resourceURL.toExternalForm();
239
240 InputStream is = null;
241 try {
242 is = resourceURL.openStream();
243 } catch (Exception e) {
244 // means there is no such resource
245 logger.atTrace().setMessage("OpenStream: {}").addArgument(resourceURL).setCause(e).log();
246 }
247 if (is == null) {
248 logger.debug("Not Found: {}", resourceURL);
249 continue;
250 } else {
251 is.close();
252 }
253 logger.debug("Parsing: {}", resourceURL);
254 parse.accept(resourceURL);
255 } catch (Exception e) {
256 throw new RuntimeException("Exception parsing chain config resource '"
257 + path + "': " + e.getMessage());
258 }
259 }
260 }
261
262 /**
263 * Translate space character into {@code %20} to avoid problems
264 * with paths that contain spaces on some JVMs.
265 *
266 * @param value Value to translate
267 *
268 * @return the translated value
269 */
270 private static String translate(String value) {
271 while (true) {
272 int index = value.indexOf(' ');
273 if (index < 0) {
274 break;
275 }
276 value = value.substring(0, index) + "%20" + value.substring(index + 1);
277 }
278 return value;
279 }
280 }