1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts.plugins;
19
20 import org.apache.commons.digester.Digester;
21 import org.apache.commons.digester.RuleSet;
22 import org.apache.commons.digester.xmlrules.DigesterLoader;
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.apache.struts.action.ActionServlet;
26 import org.apache.struts.action.PlugIn;
27 import org.apache.struts.config.ModuleConfig;
28 import org.apache.struts.util.RequestUtils;
29 import org.xml.sax.SAXException;
30
31 import javax.servlet.ServletException;
32
33 import java.io.File;
34 import java.io.IOException;
35
36 import java.net.URL;
37 import java.net.URLConnection;
38
39 /***
40 * <p>An implementation of <code>PlugIn</code> which can be configured to
41 * instantiate a graph of objects using the Commons Digester and place the
42 * root object of that graph into the Application context.</p>
43 *
44 * @version $Rev: 376813 $
45 * @see org.apache.struts.action.PlugIn
46 * @since Struts 1.2
47 */
48 public class DigestingPlugIn implements PlugIn {
49 /***
50 * Commons Logging instance.
51 */
52 private static Log log = LogFactory.getLog(DigestingPlugIn.class);
53 protected static final String SOURCE_CLASSPATH = "classpath";
54 protected static final String SOURCE_FILE = "file";
55 protected static final String SOURCE_SERVLET = "servlet";
56 protected String configPath = null;
57 protected String configSource = SOURCE_SERVLET;
58 protected String digesterPath = null;
59 protected String digesterSource = SOURCE_SERVLET;
60 protected String key = null;
61 protected ModuleConfig moduleConfig = null;
62 protected String rulesets = null;
63 protected ActionServlet servlet = null;
64 protected boolean push = false;
65
66 /***
67 * Constructor for DigestingPlugIn.
68 */
69 public DigestingPlugIn() {
70 super();
71 }
72
73 /***
74 * Receive notification that our owning module is being shut down.
75 */
76 public void destroy() {
77 this.servlet = null;
78 this.moduleConfig = null;
79 }
80
81 /***
82 * <p>Initialize a <code>Digester</code> and use it to parse a
83 * configuration file, resulting in a root object which will be placed
84 * into the ServletContext.</p>
85 *
86 * @param servlet ActionServlet that is managing all the modules in this
87 * web application
88 * @param config ModuleConfig for the module with which this plug-in is
89 * associated
90 * @throws ServletException if this <code>PlugIn</code> cannot be
91 * successfully initialized
92 */
93 public void init(ActionServlet servlet, ModuleConfig config)
94 throws ServletException {
95 this.servlet = servlet;
96 this.moduleConfig = config;
97
98 Object obj = null;
99
100 Digester digester = this.initializeDigester();
101
102 if (this.push) {
103 log.debug("push == true; pushing plugin onto digester stack");
104 digester.push(this);
105 }
106
107 try {
108 log.debug("XML data file: [path: " + this.configPath + ", source: "
109 + this.configSource + "]");
110
111 URL configURL =
112 this.getConfigURL(this.configPath, this.configSource);
113
114 if (configURL == null) {
115 throw new ServletException(
116 "Unable to locate XML data file at [path: "
117 + this.configPath + ", source: " + this.configSource + "]");
118 }
119
120 URLConnection conn = configURL.openConnection();
121
122 conn.setUseCaches(false);
123 conn.connect();
124 obj = digester.parse(conn.getInputStream());
125 } catch (IOException e) {
126
127 log.error("Exception processing config", e);
128 throw new ServletException(e);
129 } catch (SAXException e) {
130
131 log.error("Exception processing config", e);
132 throw new ServletException(e);
133 }
134
135 this.storeGeneratedObject(obj);
136 }
137
138 /***
139 * Initialize the <code>Digester</code> which will be used to process the
140 * main configuration.
141 *
142 * @return a Digester, ready to use.
143 * @throws ServletException
144 */
145 protected Digester initializeDigester()
146 throws ServletException {
147 Digester digester = null;
148
149 if ((this.digesterPath != null) && (this.digesterSource != null)) {
150 try {
151 log.debug("Initialize digester from XML [path: "
152 + this.digesterPath + "; source: " + this.digesterSource
153 + "]");
154 digester =
155 this.digesterFromXml(this.digesterPath, this.digesterSource);
156 } catch (IOException e) {
157
158 log.error("Exception instantiating digester from XML ", e);
159 throw new ServletException(e);
160 }
161 } else {
162 log.debug("No XML rules for digester; call newDigesterInstance()");
163 digester = this.newDigesterInstance();
164 }
165
166 this.applyRuleSets(digester);
167
168 return digester;
169 }
170
171 /***
172 * <p>Instantiate a <code>Digester</code>.</p> <p>Subclasses may wish to
173 * override this to provide a subclass of Digester, or to configure the
174 * Digester using object methods.</p>
175 *
176 * @return a basic instance of <code>org.apache.commons.digester.Digester</code>
177 */
178 protected Digester newDigesterInstance() {
179 return new Digester();
180 }
181
182 /***
183 * <p>Instantiate a Digester from an XML input stream using the Commons
184 * <code>DigesterLoader</code>.</p>
185 *
186 * @param path the path to the digester rules XML to be found using
187 * <code>source</code>
188 * @param source a string indicating the lookup method to be used with
189 * <code>path</code>
190 * @return a configured Digester
191 * @throws FileNotFoundException
192 * @throws MalformedURLException
193 * @see #getConfigURL(String, String)
194 */
195 protected Digester digesterFromXml(String path, String source)
196 throws IOException {
197 URL configURL = this.getConfigURL(path, source);
198
199 if (configURL == null) {
200 throw new NullPointerException("No resource '" + path
201 + "' found in '" + source + "'");
202 }
203
204 return DigesterLoader.createDigester(configURL);
205 }
206
207 /***
208 * Instantiate any <code>RuleSet</code> classes defined in the
209 * <code>rulesets</code> property and use them to add rules to our
210 * <code>Digester</code>.
211 *
212 * @param digester the Digester instance to add RuleSet objects to.
213 * @throws ServletException
214 */
215 protected void applyRuleSets(Digester digester)
216 throws ServletException {
217 if ((this.rulesets == null) || (this.rulesets.trim().length() == 0)) {
218 return;
219 }
220
221 rulesets = rulesets.trim();
222
223 String ruleSet = null;
224
225 while (rulesets.length() > 0) {
226 int comma = rulesets.indexOf(",");
227
228 if (comma < 0) {
229 ruleSet = rulesets.trim();
230 rulesets = "";
231 } else {
232 ruleSet = rulesets.substring(0, comma).trim();
233 rulesets = rulesets.substring(comma + 1).trim();
234 }
235
236 if (log.isDebugEnabled()) {
237
238 log.debug("Configuring custom Digester Ruleset of type "
239 + ruleSet);
240 }
241
242 try {
243 RuleSet instance =
244 (RuleSet) RequestUtils.applicationInstance(ruleSet);
245
246 digester.addRuleSet(instance);
247 } catch (Exception e) {
248
249 log.error("Exception configuring custom Digester RuleSet", e);
250 throw new ServletException(e);
251 }
252 }
253 }
254
255 /***
256 * <p>Look up a resource path using one of a set of known path resolution
257 * mechanisms and return a URL to the resource.</p>
258 *
259 * @param path a String which is meaningful to one of the known
260 * resolution mechanisms.
261 * @param source one of the known path resolution mechanisms:
262 *
263 * <ul>
264 *
265 * <li>file - the path is a fully-qualified filesystem
266 * path.</li>
267 *
268 * <li>servlet - the path is a servlet-context relative
269 * path.</li>
270 *
271 * <li>classpath - the path is a classpath-relative
272 * path.</li>
273 *
274 * </ul>
275 * @return a URL pointing to the given path in the given mechanism.
276 * @throws java.io.FileNotFoundException
277 * @throws java.net.MalformedURLException
278 */
279 protected URL getConfigURL(String path, String source)
280 throws IOException {
281 if (SOURCE_CLASSPATH.equals(source)) {
282 return this.getClassPathURL(path);
283 }
284
285 if (SOURCE_FILE.equals(source)) {
286 return this.getFileURL(path);
287 }
288
289 if (SOURCE_SERVLET.equals(source)) {
290 return this.getServletContextURL(path);
291 }
292
293
294 throw new IllegalArgumentException("ConfigSource " + source
295 + " is not recognized");
296 }
297
298 /***
299 * Given a string, return a URL to a classpath resource of that name.
300 *
301 * @param path a Classpath-relative string identifying a resource.
302 * @return a URL identifying the resource on the classpath. TODO Do we
303 * need to be smarter about ClassLoaders?
304 */
305 protected URL getClassPathURL(String path) {
306 return getClass().getClassLoader().getResource(path);
307 }
308
309 /***
310 * Given a string, return a URL to a Servlet Context resource of that
311 * name.
312 *
313 * @param path a Classpath-relative string identifying a resource.
314 * @return a URL identifying the resource in the Servlet Context
315 * @throws MalformedURLException
316 */
317 protected URL getServletContextURL(String path)
318 throws IOException {
319 return this.servlet.getServletContext().getResource(path);
320 }
321
322 /***
323 * Given a string, return a URL to a Filesystem resource of that name.
324 *
325 * @param path a path to a file.
326 * @return a URL identifying the resource in the in the file system.
327 * @throws MalformedURLException
328 * @throws FileNotFoundException
329 */
330 protected URL getFileURL(String path)
331 throws IOException {
332 File file = new File(path);
333
334 return file.toURL();
335 }
336
337 /***
338 * @param configPath the path to configuration information for this
339 * PlugIn.
340 * @see #configSource
341 */
342 public void setConfigPath(String configPath) {
343 this.configPath = configPath;
344 }
345
346 /***
347 * @return the configPath property
348 * @see #configSource
349 */
350 public String getConfigPath() {
351 return configPath;
352 }
353
354 /***
355 * Set the source of the config file. Should be one of the following:
356 * <ul> <li> "classpath" - indicates that the configPath will be resolved
357 * by the ClassLoader. </li> <li> "file" - indicates that the configPath
358 * is a fully-qualified filesystem path. </li> <li> "servlet" - indicates
359 * that the configPath will be found by the ServletContext. </li> </ul>
360 *
361 * @param configSource the source (lookup method) for the config file.
362 * @see #configPath
363 */
364 public void setConfigSource(String configSource) {
365 this.configSource = configSource;
366 }
367
368 /***
369 * @return the string describing which access method should be used to
370 * resolve configPath.
371 * @see #configPath
372 */
373 public String getConfigSource() {
374 return configSource;
375 }
376
377 /***
378 * This method is called after the Digester runs to store the generated
379 * object somewhere. This implementation places the given object into the
380 * ServletContext under the attribute name as defined in
381 * <code>key</code>.
382 *
383 * @param obj The object to save.
384 */
385 protected void storeGeneratedObject(Object obj) {
386 log.debug("Put [" + obj + "] into application context [key:" + this.key
387 + "]");
388 this.servlet.getServletContext().setAttribute(this.getKey(), obj);
389 }
390
391 /***
392 * @param key The ServletContext attribute name to store the generated
393 * object under.
394 */
395 public void setKey(String key) {
396 this.key = key;
397 }
398
399 /***
400 * @return The ServletContext attribute name the generated object is
401 * stored under.
402 */
403 public String getKey() {
404 return key;
405 }
406
407 /***
408 * <p>A comma-delimited list of one or more classes which implement
409 * <code>org.apache.commons.digester.RuleSet</code>. (Optional)</p>
410 */
411 public void setRulesets(String ruleSets) {
412 this.rulesets = ruleSets;
413 }
414
415 /***
416 * @return The configured list of <code>RuleSet</code> classes.
417 */
418 public String getRulesets() {
419 return this.rulesets;
420 }
421
422 /***
423 * <p>The path to a Digester XML configuration file, relative to the
424 * <code>digesterSource</code> property. (Optional)</p>
425 *
426 * @see #digesterSource
427 * @see #getConfigURL(String, String)
428 */
429 public void setDigesterPath(String digesterPath) {
430 this.digesterPath = digesterPath;
431 }
432
433 /***
434 * @return the configured path to a Digester XML config file, or null.
435 * @see #digesterSource
436 * @see #getConfigURL(String, String)
437 */
438 public String getDigesterPath() {
439 return digesterPath;
440 }
441
442 /***
443 * <p>The lookup mechanism to be used to resolve <code>digesterPath</code>
444 * (optional). </p>
445 *
446 * @param digesterSource
447 * @see #getConfigURL(String, String)
448 */
449 public void setDigesterSource(String digesterSource) {
450 this.digesterSource = digesterSource;
451 }
452
453 /***
454 * @return the configured lookup mechanism for resolving
455 * <code>digesterPath</code>.
456 * @see #getConfigURL(String, String)
457 */
458 public String getDigesterSource() {
459 return this.digesterSource;
460 }
461
462 /***
463 * <p>If set to <code>true</code>, this PlugIn will be pushed onto the
464 * Digester stack before the digester <code>parse</code> method is
465 * called.</p> <p>Defaults to <code>false</code></p>
466 *
467 * @param push
468 */
469 public void setPush(boolean push) {
470 this.push = push;
471 }
472
473 /***
474 * @return Whether or not this <code>PlugIn</code> instance will be pushed
475 * onto the <code>Digester</code> stack before
476 * <code>digester.parse()</code> is called.
477 */
478 public boolean getPush() {
479 return this.push;
480 }
481 }