View Javadoc

1   /*
2    * $Id: DigestingPlugIn.java 376813 2006-02-10 19:42:56Z husted $
3    *
4    * Copyright 2003,2004 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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             // TODO Internationalize msg
127             log.error("Exception processing config", e);
128             throw new ServletException(e);
129         } catch (SAXException e) {
130             // TODO Internationalize msg
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                 // TODO Internationalize msg
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                 // TODO Internationalize msg
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                 // TODO Internationalize msg
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         // TODO Internationalize msg
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 }