KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > components > modules > input > XMLFileModule


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.components.modules.input;
17
18 import org.apache.avalon.framework.component.ComponentException;
19 import org.apache.avalon.framework.component.ComponentManager;
20 import org.apache.avalon.framework.component.Composable;
21 import org.apache.avalon.framework.configuration.Configuration;
22 import org.apache.avalon.framework.configuration.ConfigurationException;
23 import org.apache.avalon.framework.logger.Logger;
24 import org.apache.avalon.framework.thread.ThreadSafe;
25 import org.apache.cocoon.components.source.SourceUtil;
26 import org.apache.commons.collections.map.AbstractReferenceMap;
27 import org.apache.commons.collections.map.ReferenceMap;
28 import org.apache.excalibur.source.Source;
29 import org.apache.excalibur.source.SourceResolver;
30 import org.apache.excalibur.source.SourceValidity;
31 import org.w3c.dom.Document JavaDoc;
32
33 import java.util.Collections JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Map JavaDoc;
36
37 /**
38
39  <grammar>
40     <define name="input.module.config.contents" combine="choice">
41        <optional><element name="reloadable"><data type="boolean"/></element></optional>
42        <optional><element name="cacheable"><data type="boolean"/></element></optional>
43        <optional>
44           <ref name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
45        </optional>
46     </define>
47
48     <define name="input.module.runtime.contents" combine="choice">
49        <optional>
50           <ref name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
51        </optional>
52     </define>
53
54     <define name="org.apache.cocoon.components.modules.input.XMLFileModule:file">
55        <element name="file">
56           <attribute name="src"><data type="anyURI"/></attribute>
57           <optional><attribute name="reloadable"><data type="boolean"/></attribute></optional>
58           <optional><attribute name="cacheable"><data type="boolean"/></attribute></optional>
59        </element>
60     </define>
61  </grammar>
62
63  * This module provides an Input Module interface to any XML document, by using
64  * XPath expressions as attribute keys.
65  * The XML can be obtained from any Cocoon <code>Source</code> (e.g.,
66  * <code>cocoon:/...</code>, <code>context://..</code>, and regular URLs).
67  * Sources can be held in memory for better performance and reloaded if
68  * changed.
69  *
70  * <p>Caching and reloading can be turned on / off (default: caching on,
71  * reloading off) through <code>&lt;reloadable&gt;false&lt;/reloadable&gt;</code>
72  * and <code>&lt;cacheable&gt;false&lt;/cacheable&gt;</code>. The file
73  * (source) to use is specified through <code>&lt;file
74  * SRC="protocol:path/to/file.xml" reloadable="true"
75  * cacheable="true"/&gt;</code> optionally overriding defaults for
76  * caching and/or reloading.</p>
77  *
78  * <p>In addition, xpath expressions are cached for higher performance.
79  * Thus, if an expression has been evaluated for a file, the result
80  * is cached and will be reused, the expression is not evaluated
81  * a second time. This can be turned off using the <code>cache-expressions</code>
82  * configuration option.</p>
83  *
84  * @author <a HREF="mailto:jefft@apache.org">Jeff Turner</a>
85  * @author <a HREF="mailto:haul@apache.org">Christian Haul</a>
86  * @version $Id: XMLFileModule.java 162005 2005-04-20 03:05:35Z vgritsenko $
87  */

88 public class XMLFileModule extends AbstractJXPathModule
89                            implements Composable, ThreadSafe {
90
91     /** Static (cocoon.xconf) configuration location, for error reporting */
92     String JavaDoc staticConfLocation;
93
94     /** Cached documents */
95     Map documents;
96
97     /** Default value for reloadability of sources. Defaults to false. */
98     boolean reloadAll;
99
100     /** Default value for cacheability of sources. Defaults to true. */
101     boolean cacheAll;
102
103     /** Default value for cacheability of xpath expressions. Defaults to true. */
104     boolean cacheExpressions;
105
106     /** Default src */
107     String JavaDoc src;
108
109     SourceResolver resolver;
110     ComponentManager manager;
111
112     //
113
// need two caches for Object and Object[]
114
//
115

116     /** XPath expression cache for single attribute values. */
117     private Map expressionCache;
118
119     /** XPath expression cache for multiple attribute values. */
120     private Map expressionValuesCache;
121
122
123     /**
124      * Takes care of (re-)loading and caching of sources.
125      */

126     protected static class DocumentHelper {
127         private boolean reloadable;
128         private boolean cacheable;
129
130         /** Source location */
131         private String JavaDoc uri;
132
133         /** Source validity */
134         private SourceValidity validity;
135
136         /** Source content cached as DOM Document */
137         private Document JavaDoc document;
138
139         /** Remember who created us (and who's caching us) */
140         private XMLFileModule instance;
141
142         /**
143          * Creates a new <code>DocumentHelper</code> instance.
144          *
145          * @param reload a <code>boolean</code> value, whether this source should be reloaded if changed.
146          * @param cache a <code>boolean</code> value, whether this source should be kept in memory.
147          * @param src a <code>String</code> value containing the URI
148          */

149         public DocumentHelper(boolean reload, boolean cache, String JavaDoc src, XMLFileModule instance) {
150             this.reloadable = reload;
151             this.cacheable = cache;
152             this.uri = src;
153             this.instance = instance;
154             // defer loading of the document
155
}
156
157         /**
158          * Returns the Document belonging to the configured
159          * source. Transparently handles reloading and caching.
160          *
161          * @param manager a <code>ComponentManager</code> value
162          * @param resolver a <code>SourceResolver</code> value
163          * @return a <code>Document</code> value
164          * @exception Exception if an error occurs
165          */

166         public synchronized Document JavaDoc getDocument(ComponentManager manager, SourceResolver resolver, Logger logger)
167         throws Exception JavaDoc {
168             Source src = null;
169             Document JavaDoc dom = null;
170             try {
171                 if (this.document == null) {
172                     if (logger.isDebugEnabled()) {
173                         logger.debug("Document not cached... Loading uri " + this.uri);
174                     }
175                     src = resolver.resolveURI(this.uri);
176                     this.validity = src.getValidity();
177                     this.document = SourceUtil.toDOM(src);
178                 } else if (this.reloadable) {
179                     if (logger.isDebugEnabled()) {
180                         logger.debug("Document cached... checking validity of uri " + this.uri);
181                     }
182
183                     int valid = this.validity == null? SourceValidity.INVALID: this.validity.isValid();
184                     if (valid != SourceValidity.VALID) {
185                         // Get new source and validity
186
src = resolver.resolveURI(this.uri);
187                         SourceValidity newValidity = src.getValidity();
188                         // If already invalid, or invalid after validities comparison, reload
189
if (valid == SourceValidity.INVALID || this.validity.isValid(newValidity) != SourceValidity.VALID) {
190                             if (logger.isDebugEnabled()) {
191                                 logger.debug("Reloading document... uri " + this.uri);
192                             }
193                             this.validity = newValidity;
194                             this.document = SourceUtil.toDOM(src);
195
196                             /*
197                              * Clear the cache, otherwise reloads won't do much.
198                              *
199                              * FIXME (pf): caches should be held in the DocumentHelper
200                              * instance itself, clearing global cache will
201                              * clear everything for each configured document.
202                              * (this is a quick fix, no time to do the whole)
203                              */

204                             this.instance.flushCache();
205                         }
206                     }
207                 }
208
209                 dom = this.document;
210             } finally {
211                 if (src != null) {
212                     resolver.release(src);
213                 }
214
215                 if (!this.cacheable) {
216                     if (logger.isDebugEnabled()) {
217                         logger.debug("Not caching document cached... uri " + this.uri);
218                     }
219                     this.validity = null;
220                     this.document = null;
221                 }
222             }
223
224             if (logger.isDebugEnabled()) {
225                 logger.debug("Done with document... uri " + this.uri);
226             }
227             return dom;
228         }
229     }
230
231
232     /**
233      * Set the current <code>ComponentManager</code> instance used by this
234      * <code>Composable</code>.
235      */

236     public void compose(ComponentManager manager) throws ComponentException {
237         this.manager = manager;
238         this.resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
239     }
240
241     /**
242      * Static (cocoon.xconf) configuration.
243      * Configuration is expected to be of the form:
244      * &lt;...&gt;
245      * &lt;reloadable&gt;true|<b>false</b>&lt;/reloadable&gt;
246      * &lt;cacheable&gt;<b>true</b>|false&lt;/cacheable&gt;
247      * &lt;file SRC="<i>src1</i>" reloadable="true|<b>false</b>" cacheable="<b>true</b>|false"/&gt;
248      * &lt;file SRC="<i>src2</i>" reloadable="true|<b>false</b>" cacheable="<b>true</b>|false"/&gt;
249      * ...
250      * &lt;/...&gt;
251      *
252      * Each &lt;file/&gt; element pre-loads an XML DOM for querying. Typically only one
253      * &lt;file&gt; is specified, and its <i>src</i> is used as a default if not
254      * overridden in the {@link #getContextObject(Configuration, Map)}
255      *
256      * @param config a <code>Configuration</code> value, as described above.
257      * @exception ConfigurationException if an error occurs
258      */

259     public void configure(Configuration config)
260     throws ConfigurationException {
261         super.configure(config);
262         this.staticConfLocation = config.getLocation();
263         this.reloadAll = config.getChild("reloadable").getValueAsBoolean(false);
264
265         if (config.getChild("cachable", false) != null) {
266             throw new ConfigurationException("Bzzt! Wrong spelling at " +
267                                              config.getChild("cachable").getLocation() +
268                                              ": please use 'cacheable', not 'cachable'");
269         }
270         this.cacheAll = config.getChild("cacheable").getValueAsBoolean(true);
271
272         this.documents = Collections.synchronizedMap(new HashMap JavaDoc());
273         Configuration[] files = config.getChildren("file");
274         for (int i = 0; i < files.length; i++) {
275             boolean reload = files[i].getAttributeAsBoolean("reloadable", this.reloadAll);
276             boolean cache = files[i].getAttributeAsBoolean("cacheable", this.cacheAll);
277             this.src = files[i].getAttribute("src");
278             // by assigning the source uri to this.src the last one will be the default
279
// OTOH caching / reload parameters can be specified in one central place
280
// if multiple file tags are used.
281
this.documents.put(files[i], new DocumentHelper(reload, cache, this.src, this));
282         }
283
284         // init caches
285
this.cacheExpressions = config.getChild("cache-expressions").getValueAsBoolean(true);
286         if (this.cacheExpressions) {
287             this.expressionCache = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT);
288             this.expressionValuesCache = new ReferenceMap(AbstractReferenceMap.SOFT, AbstractReferenceMap.SOFT);
289         }
290     }
291
292     /**
293      * Dispose this component
294      */

295     public void dispose() {
296         super.dispose();
297         if (this.manager != null) {
298             this.manager.release(this.resolver);
299             this.resolver = null;
300             this.manager = null;
301         }
302
303         this.documents = null;
304         this.expressionCache = null;
305         this.expressionValuesCache = null;
306     }
307
308
309     /**
310      * Retrieve document helper
311      */

312     private DocumentHelper getDocumentHelper(Configuration modeConf)
313     throws ConfigurationException {
314         boolean hasDynamicConf = false; // whether we have a <file SRC="..."> dynamic configuration
315
Configuration fileConf = null; // the nested <file>, if any
316

317         if (modeConf != null && modeConf.getChildren().length > 0) {
318             fileConf = modeConf.getChild("file", false);
319             if (fileConf == null) {
320                 if (getLogger().isDebugEnabled()) {
321                     getLogger().debug("Missing 'file' child element at " + modeConf.getLocation());
322                 }
323             } else {
324                 hasDynamicConf = true;
325             }
326         }
327
328         String JavaDoc src = this.src;
329         if (hasDynamicConf) {
330             src = fileConf.getAttribute("src");
331         }
332
333         if (src == null) {
334             throw new ConfigurationException(
335                 "No source specified"
336                     + (modeConf != null ? ", either dynamically in " + modeConf.getLocation() + ", or " : "")
337                     + " statically in "
338                     + staticConfLocation);
339         }
340
341         if (!this.documents.containsKey(src)) {
342             boolean reload = this.reloadAll;
343             boolean cache = this.cacheAll;
344             if (hasDynamicConf) {
345                 reload = fileConf.getAttributeAsBoolean("reloadable", reload);
346                 cache = fileConf.getAttributeAsBoolean("cacheable", cache);
347                 if (fileConf.getAttribute("cachable", null) != null) {
348                     throw new ConfigurationException(
349                         "Bzzt! Wrong spelling at "
350                             + fileConf.getLocation()
351                             + ": please use 'cacheable', not 'cachable'");
352                 }
353             }
354
355             this.documents.put(src, new DocumentHelper(reload, cache, src, this));
356         }
357
358         return (DocumentHelper) this.documents.get(src);
359     }
360
361     /**
362      * Get the DOM object that JXPath will operate on when evaluating
363      * attributes. This DOM is loaded from a Source, specified in the
364      * modeConf, or (if modeConf is null) from the
365      * {@link #configure(Configuration)}.
366      * @param modeConf The dynamic configuration for the current operation. May
367      * be <code>null</code>, in which case static (cocoon.xconf) configuration
368      * is used. Configuration is expected to have a &lt;file> child node, and
369      * be of the form:
370      * &lt;...&gt;
371      * &lt;file SRC="..." reloadable="true|false"/&gt;
372      * &lt;/...&gt;
373      * @param objectModel Object Model for the current module operation.
374      */

375     protected Object JavaDoc getContextObject(Configuration modeConf, Map objectModel)
376     throws ConfigurationException {
377         DocumentHelper helper = getDocumentHelper(modeConf);
378
379         try {
380             return helper.getDocument(this.manager, this.resolver, getLogger());
381         } catch (Exception JavaDoc e) {
382             if (getLogger().isDebugEnabled()) {
383                 getLogger().debug("Error using source " + src + "\n" + e.getMessage(), e);
384             }
385             throw new ConfigurationException("Error using source " + src, e);
386         }
387     }
388
389     public Object JavaDoc getAttribute(String JavaDoc name, Configuration modeConf, Map objectModel)
390     throws ConfigurationException {
391         return getAttribute(name, modeConf, objectModel, false);
392     }
393
394     public Object JavaDoc[] getAttributeValues(String JavaDoc name, Configuration modeConf, Map objectModel)
395     throws ConfigurationException {
396         Object JavaDoc result = getAttribute(name, modeConf, objectModel, true);
397         return (result != null ? (Object JavaDoc[]) result : null);
398     }
399
400     private Object JavaDoc getAttribute(String JavaDoc name, Configuration modeConf, Map objectModel, boolean getValues)
401     throws ConfigurationException {
402         Object JavaDoc contextObj = getContextObject(modeConf, objectModel);
403         if (modeConf != null) {
404             name = modeConf.getChild("parameter").getValue(this.parameter != null ? this.parameter : name);
405         }
406
407         Object JavaDoc result = null;
408         Map cache = null;
409         boolean hasBeenCached = false;
410         if (this.cacheExpressions) {
411             cache = getExpressionCache(getValues? this.expressionValuesCache: this.expressionCache, contextObj);
412             hasBeenCached = cache.containsKey(name);
413             if (hasBeenCached) {
414                 result = cache.get(name);
415             }
416         }
417
418         if (!hasBeenCached) {
419             if (getValues){
420                 result = JXPathHelper.getAttributeValues(name, modeConf, this.configuration, contextObj);
421             } else {
422                 result = JXPathHelper.getAttribute(name, modeConf, this.configuration, contextObj);
423             }
424             if (this.cacheExpressions) {
425                 cache.put(name, result);
426                 if (this.getLogger().isDebugEnabled()) {
427                     this.getLogger().debug("for " + name + " newly caching result " + result);
428                 }
429             } else {
430                 if (this.getLogger().isDebugEnabled()) {
431                     this.getLogger().debug("for " + name + " result is " + result);
432                 }
433             }
434         } else {
435             if (this.getLogger().isDebugEnabled()) {
436                 this.getLogger().debug("for " + name + " using cached result " + result);
437             }
438         }
439
440         return result;
441     }
442
443     protected void flushCache() {
444         if (this.cacheExpressions) {
445             synchronized(this.expressionCache) {
446                 this.expressionCache.clear();
447             }
448             synchronized(this.expressionValuesCache) {
449                 this.expressionValuesCache.clear();
450             }
451         }
452     }
453
454     private Map getExpressionCache(Map cache, Object JavaDoc key) {
455         synchronized (cache) {
456             Map map = (Map) cache.get(key);
457             if (map == null) {
458                 map = Collections.synchronizedMap(new HashMap JavaDoc());
459                 cache.put(key, map);
460             }
461             return map;
462         }
463     }
464 }
465
Popular Tags