001: /*
002: * $Id: DigestingPlugIn.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.plugins;
022:
023: import org.apache.commons.digester.Digester;
024: import org.apache.commons.digester.RuleSet;
025: import org.apache.commons.digester.xmlrules.DigesterLoader;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.struts.action.ActionServlet;
029: import org.apache.struts.action.PlugIn;
030: import org.apache.struts.config.ModuleConfig;
031: import org.apache.struts.util.RequestUtils;
032: import org.xml.sax.SAXException;
033:
034: import javax.servlet.ServletException;
035:
036: import java.io.File;
037: import java.io.IOException;
038:
039: import java.net.URL;
040: import java.net.URLConnection;
041:
042: /**
043: * <p>An implementation of <code>PlugIn</code> which can be configured to
044: * instantiate a graph of objects using the Commons Digester and place the
045: * root object of that graph into the Application context.</p>
046: *
047: * @version $Rev: 471754 $
048: * @see org.apache.struts.action.PlugIn
049: * @since Struts 1.2
050: */
051: public class DigestingPlugIn implements PlugIn {
052: /**
053: * Commons Logging instance.
054: */
055: private static Log log = LogFactory.getLog(DigestingPlugIn.class);
056: protected static final String SOURCE_CLASSPATH = "classpath";
057: protected static final String SOURCE_FILE = "file";
058: protected static final String SOURCE_SERVLET = "servlet";
059: protected String configPath = null;
060: protected String configSource = SOURCE_SERVLET;
061: protected String digesterPath = null;
062: protected String digesterSource = SOURCE_SERVLET;
063: protected String key = null;
064: protected ModuleConfig moduleConfig = null;
065: protected String rulesets = null;
066: protected ActionServlet servlet = null;
067: protected boolean push = false;
068:
069: /**
070: * Constructor for DigestingPlugIn.
071: */
072: public DigestingPlugIn() {
073: super ();
074: }
075:
076: /**
077: * Receive notification that our owning module is being shut down.
078: */
079: public void destroy() {
080: this .servlet = null;
081: this .moduleConfig = null;
082: }
083:
084: /**
085: * <p>Initialize a <code>Digester</code> and use it to parse a
086: * configuration file, resulting in a root object which will be placed
087: * into the ServletContext.</p>
088: *
089: * @param servlet ActionServlet that is managing all the modules in this
090: * web application
091: * @param config ModuleConfig for the module with which this plug-in is
092: * associated
093: * @throws ServletException if this <code>PlugIn</code> cannot be
094: * successfully initialized
095: */
096: public void init(ActionServlet servlet, ModuleConfig config)
097: throws ServletException {
098: this .servlet = servlet;
099: this .moduleConfig = config;
100:
101: Object obj = null;
102:
103: Digester digester = this .initializeDigester();
104:
105: if (this .push) {
106: log
107: .debug("push == true; pushing plugin onto digester stack");
108: digester.push(this );
109: }
110:
111: try {
112: log.debug("XML data file: [path: " + this .configPath
113: + ", source: " + this .configSource + "]");
114:
115: URL configURL = this .getConfigURL(this .configPath,
116: this .configSource);
117:
118: if (configURL == null) {
119: throw new ServletException(
120: "Unable to locate XML data file at [path: "
121: + this .configPath + ", source: "
122: + this .configSource + "]");
123: }
124:
125: URLConnection conn = configURL.openConnection();
126:
127: conn.setUseCaches(false);
128: conn.connect();
129: obj = digester.parse(conn.getInputStream());
130: } catch (IOException e) {
131: // TODO Internationalize msg
132: log.error("Exception processing config", e);
133: throw new ServletException(e);
134: } catch (SAXException e) {
135: // TODO Internationalize msg
136: log.error("Exception processing config", e);
137: throw new ServletException(e);
138: }
139:
140: this .storeGeneratedObject(obj);
141: }
142:
143: /**
144: * Initialize the <code>Digester</code> which will be used to process the
145: * main configuration.
146: *
147: * @return a Digester, ready to use.
148: * @throws ServletException
149: */
150: protected Digester initializeDigester() throws ServletException {
151: Digester digester = null;
152:
153: if ((this .digesterPath != null)
154: && (this .digesterSource != null)) {
155: try {
156: log.debug("Initialize digester from XML [path: "
157: + this .digesterPath + "; source: "
158: + this .digesterSource + "]");
159: digester = this .digesterFromXml(this .digesterPath,
160: this .digesterSource);
161: } catch (IOException e) {
162: // TODO Internationalize msg
163: log.error("Exception instantiating digester from XML ",
164: e);
165: throw new ServletException(e);
166: }
167: } else {
168: log
169: .debug("No XML rules for digester; call newDigesterInstance()");
170: digester = this .newDigesterInstance();
171: }
172:
173: this .applyRuleSets(digester);
174:
175: return digester;
176: }
177:
178: /**
179: * <p>Instantiate a <code>Digester</code>.</p> <p>Subclasses may wish to
180: * override this to provide a subclass of Digester, or to configure the
181: * Digester using object methods.</p>
182: *
183: * @return a basic instance of <code>org.apache.commons.digester.Digester</code>
184: */
185: protected Digester newDigesterInstance() {
186: return new Digester();
187: }
188:
189: /**
190: * <p>Instantiate a Digester from an XML input stream using the Commons
191: * <code>DigesterLoader</code>.</p>
192: *
193: * @param path the path to the digester rules XML to be found using
194: * <code>source</code>
195: * @param source a string indicating the lookup method to be used with
196: * <code>path</code>
197: * @return a configured Digester
198: * @throws FileNotFoundException
199: * @throws MalformedURLException
200: * @see #getConfigURL(String, String)
201: */
202: protected Digester digesterFromXml(String path, String source)
203: throws IOException {
204: URL configURL = this .getConfigURL(path, source);
205:
206: if (configURL == null) {
207: throw new NullPointerException("No resource '" + path
208: + "' found in '" + source + "'");
209: }
210:
211: return DigesterLoader.createDigester(configURL);
212: }
213:
214: /**
215: * Instantiate any <code>RuleSet</code> classes defined in the
216: * <code>rulesets</code> property and use them to add rules to our
217: * <code>Digester</code>.
218: *
219: * @param digester the Digester instance to add RuleSet objects to.
220: * @throws ServletException
221: */
222: protected void applyRuleSets(Digester digester)
223: throws ServletException {
224: if ((this .rulesets == null)
225: || (this .rulesets.trim().length() == 0)) {
226: return;
227: }
228:
229: rulesets = rulesets.trim();
230:
231: String ruleSet = null;
232:
233: while (rulesets.length() > 0) {
234: int comma = rulesets.indexOf(",");
235:
236: if (comma < 0) {
237: ruleSet = rulesets.trim();
238: rulesets = "";
239: } else {
240: ruleSet = rulesets.substring(0, comma).trim();
241: rulesets = rulesets.substring(comma + 1).trim();
242: }
243:
244: if (log.isDebugEnabled()) {
245: // TODO Internationalize msg
246: log
247: .debug("Configuring custom Digester Ruleset of type "
248: + ruleSet);
249: }
250:
251: try {
252: RuleSet instance = (RuleSet) RequestUtils
253: .applicationInstance(ruleSet);
254:
255: digester.addRuleSet(instance);
256: } catch (Exception e) {
257: // TODO Internationalize msg
258: log
259: .error(
260: "Exception configuring custom Digester RuleSet",
261: e);
262: throw new ServletException(e);
263: }
264: }
265: }
266:
267: /**
268: * <p>Look up a resource path using one of a set of known path resolution
269: * mechanisms and return a URL to the resource.</p>
270: *
271: * @param path a String which is meaningful to one of the known
272: * resolution mechanisms.
273: * @param source one of the known path resolution mechanisms:
274: *
275: * <ul>
276: *
277: * <li>file - the path is a fully-qualified filesystem
278: * path.</li>
279: *
280: * <li>servlet - the path is a servlet-context relative
281: * path.</li>
282: *
283: * <li>classpath - the path is a classpath-relative
284: * path.</li>
285: *
286: * </ul>
287: * @return a URL pointing to the given path in the given mechanism.
288: * @throws java.io.FileNotFoundException
289: * @throws java.net.MalformedURLException
290: */
291: protected URL getConfigURL(String path, String source)
292: throws IOException {
293: if (SOURCE_CLASSPATH.equals(source)) {
294: return this .getClassPathURL(path);
295: }
296:
297: if (SOURCE_FILE.equals(source)) {
298: return this .getFileURL(path);
299: }
300:
301: if (SOURCE_SERVLET.equals(source)) {
302: return this .getServletContextURL(path);
303: }
304:
305: // TODO Internationalize msg
306: throw new IllegalArgumentException("ConfigSource " + source
307: + " is not recognized");
308: }
309:
310: /**
311: * Given a string, return a URL to a classpath resource of that name.
312: *
313: * @param path a Classpath-relative string identifying a resource.
314: * @return a URL identifying the resource on the classpath. TODO Do we
315: * need to be smarter about ClassLoaders?
316: */
317: protected URL getClassPathURL(String path) {
318: return getClass().getClassLoader().getResource(path);
319: }
320:
321: /**
322: * Given a string, return a URL to a Servlet Context resource of that
323: * name.
324: *
325: * @param path a Classpath-relative string identifying a resource.
326: * @return a URL identifying the resource in the Servlet Context
327: * @throws MalformedURLException
328: */
329: protected URL getServletContextURL(String path) throws IOException {
330: return this .servlet.getServletContext().getResource(path);
331: }
332:
333: /**
334: * Given a string, return a URL to a Filesystem resource of that name.
335: *
336: * @param path a path to a file.
337: * @return a URL identifying the resource in the in the file system.
338: * @throws MalformedURLException
339: * @throws FileNotFoundException
340: */
341: protected URL getFileURL(String path) throws IOException {
342: File file = new File(path);
343:
344: return file.toURL();
345: }
346:
347: /**
348: * @param configPath the path to configuration information for this
349: * PlugIn.
350: * @see #configSource
351: */
352: public void setConfigPath(String configPath) {
353: this .configPath = configPath;
354: }
355:
356: /**
357: * @return the configPath property
358: * @see #configSource
359: */
360: public String getConfigPath() {
361: return configPath;
362: }
363:
364: /**
365: * Set the source of the config file. Should be one of the following:
366: * <ul> <li> "classpath" - indicates that the configPath will be resolved
367: * by the ClassLoader. </li> <li> "file" - indicates that the configPath
368: * is a fully-qualified filesystem path. </li> <li> "servlet" - indicates
369: * that the configPath will be found by the ServletContext. </li> </ul>
370: *
371: * @param configSource the source (lookup method) for the config file.
372: * @see #configPath
373: */
374: public void setConfigSource(String configSource) {
375: this .configSource = configSource;
376: }
377:
378: /**
379: * @return the string describing which access method should be used to
380: * resolve configPath.
381: * @see #configPath
382: */
383: public String getConfigSource() {
384: return configSource;
385: }
386:
387: /**
388: * This method is called after the Digester runs to store the generated
389: * object somewhere. This implementation places the given object into the
390: * ServletContext under the attribute name as defined in
391: * <code>key</code>.
392: *
393: * @param obj The object to save.
394: */
395: protected void storeGeneratedObject(Object obj) {
396: log.debug("Put [" + obj + "] into application context [key:"
397: + this .key + "]");
398: this .servlet.getServletContext().setAttribute(this .getKey(),
399: obj);
400: }
401:
402: /**
403: * @param key The ServletContext attribute name to store the generated
404: * object under.
405: */
406: public void setKey(String key) {
407: this .key = key;
408: }
409:
410: /**
411: * @return The ServletContext attribute name the generated object is
412: * stored under.
413: */
414: public String getKey() {
415: return key;
416: }
417:
418: /**
419: * <p>A comma-delimited list of one or more classes which implement
420: * <code>org.apache.commons.digester.RuleSet</code>. (Optional)</p>
421: */
422: public void setRulesets(String ruleSets) {
423: this .rulesets = ruleSets;
424: }
425:
426: /**
427: * @return The configured list of <code>RuleSet</code> classes.
428: */
429: public String getRulesets() {
430: return this .rulesets;
431: }
432:
433: /**
434: * <p>The path to a Digester XML configuration file, relative to the
435: * <code>digesterSource</code> property. (Optional)</p>
436: *
437: * @see #digesterSource
438: * @see #getConfigURL(String, String)
439: */
440: public void setDigesterPath(String digesterPath) {
441: this .digesterPath = digesterPath;
442: }
443:
444: /**
445: * @return the configured path to a Digester XML config file, or null.
446: * @see #digesterSource
447: * @see #getConfigURL(String, String)
448: */
449: public String getDigesterPath() {
450: return digesterPath;
451: }
452:
453: /**
454: * <p>The lookup mechanism to be used to resolve <code>digesterPath</code>
455: * (optional). </p>
456: *
457: * @param digesterSource
458: * @see #getConfigURL(String, String)
459: */
460: public void setDigesterSource(String digesterSource) {
461: this .digesterSource = digesterSource;
462: }
463:
464: /**
465: * @return the configured lookup mechanism for resolving
466: * <code>digesterPath</code>.
467: * @see #getConfigURL(String, String)
468: */
469: public String getDigesterSource() {
470: return this .digesterSource;
471: }
472:
473: /**
474: * <p>If set to <code>true</code>, this PlugIn will be pushed onto the
475: * Digester stack before the digester <code>parse</code> method is
476: * called.</p> <p>Defaults to <code>false</code></p>
477: *
478: * @param push
479: */
480: public void setPush(boolean push) {
481: this .push = push;
482: }
483:
484: /**
485: * @return Whether or not this <code>PlugIn</code> instance will be pushed
486: * onto the <code>Digester</code> stack before
487: * <code>digester.parse()</code> is called.
488: */
489: public boolean getPush() {
490: return this.push;
491: }
492: }
|