KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > xml > xmlc > deferredparsing > DocumentLoaderImpl


1 /*
2  * Enhydra Java Application Server Project
3  *
4  * The contents of this file are subject to the Enhydra Public License
5  * Version 1.1 (the "License"); you may not use this file except in
6  * compliance with the License. You may obtain a copy of the License on
7  * the Enhydra web site ( http://www.enhydra.org/ ).
8  *
9  * Software distributed under the License is distributed on an "AS IS"
10  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11  * the License for the specific terms governing rights and limitations
12  * under the License.
13  *
14  * The Initial Developer of the Enhydra Application Server is Lutris
15  * Technologies, Inc. The Enhydra Application Server and portions created
16  * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17  * All Rights Reserved.
18  *
19  * Contributor(s):
20  * Richard Kunze
21  * Ole Arndt
22  * David Li
23  * Jacob Kjome
24  *
25  * $Id: DocumentLoaderImpl.java,v 1.12 2005/01/26 08:29:24 jkjome Exp $
26  */

27
28 package org.enhydra.xml.xmlc.deferredparsing;
29
30 import java.io.File JavaDoc;
31 import java.io.IOException JavaDoc;
32 import java.io.PrintWriter JavaDoc;
33 import java.lang.reflect.Field JavaDoc;
34 import java.net.MalformedURLException JavaDoc;
35 import java.net.URL JavaDoc;
36 import java.util.ArrayList JavaDoc;
37 import java.util.Date JavaDoc;
38 import java.util.Iterator JavaDoc;
39 import java.util.Map JavaDoc;
40 import java.util.WeakHashMap JavaDoc;
41
42 import org.enhydra.xml.io.ErrorReporter;
43 import org.enhydra.xml.xmlc.XMLCError;
44 import org.enhydra.xml.xmlc.XMLCException;
45 import org.enhydra.xml.xmlc.XMLCLogger;
46 import org.enhydra.xml.xmlc.XMLCRuntimeException;
47 import org.enhydra.xml.xmlc.XMLObject;
48 import org.enhydra.xml.xmlc.compiler.EditDOM;
49 import org.enhydra.xml.xmlc.compiler.Parse;
50 import org.enhydra.xml.xmlc.dom.XMLCDocument;
51 import org.enhydra.xml.xmlc.metadata.DocumentClass;
52 import org.enhydra.xml.xmlc.metadata.MetaData;
53 import org.enhydra.xml.xmlc.metadata.MetaDataDocument;
54 import org.w3c.dom.DOMImplementation JavaDoc;
55 import org.w3c.dom.Document JavaDoc;
56 import org.w3c.dom.DocumentType JavaDoc;
57 import org.w3c.dom.Node JavaDoc;
58 import org.xml.sax.InputSource JavaDoc;
59
60 /**
61  * Default implementation of the {@link DocumentLoader} interface.
62  *
63  * <p>This implementation maintains a global cache of loaded DOM
64  * objects. If a DOM object is found in cache and the cached copy is
65  * up to date with regard to the source document and XMLC metadata
66  * object, a copy of the cached object is handed out. Otherwise, the
67  * document is reparsed from the source document, the DOM edits
68  * specified in the document metadata are performed and the document
69  * is stored in cache as a template. Note that the cache may be a
70  * {@link WeakHashMap} or a map with similar semantics that frees up
71  * entries if memory gets low.</p>
72  *
73  * <p>This class can be used as the basis for specialized
74  * document loaders.</p>
75  */

76 public class DocumentLoaderImpl implements DocumentLoader {
77
78     /** The subfix of meta file names */
79     final private static String JavaDoc META_FILE_EXT = ".xmlc";
80
81     /** The associated factory */
82     private XMLCDeferredParsingFactory factory;
83
84     /** Helper class for storing cached DOMs */
85     protected static class CacheEntry {
86         volatile long timestamp = -1;
87         volatile Document document = null;
88         volatile URL JavaDoc src = null;
89         volatile boolean ready = false;
90     }
91
92     /** The map to use for caching parsed template DOMs */
93     private Map JavaDoc templateCache;
94
95     /**
96      * Default constructor. Uses a {@link Cache} as template
97      * DOM cache.
98      * @see #DocumentLoaderImpl(Map)
99      */

100     public DocumentLoaderImpl() {
101         this(null);
102     }
103
104     /**
105      * Constructor. Uses the specified map as template cache.
106      * @param cache the cache to use.
107      */

108     public DocumentLoaderImpl(Map JavaDoc cache) {
109         if (cache == null) {
110             templateCache = new Cache();
111         } else {
112             templateCache = cache;
113         }
114     }
115
116     /**
117      * Get the cache entry for a document, initialize one of not
118      * already exists
119      *
120      * Note that the cache entry does not necessarily hold a template
121      * - it may need to be initialized first. Subclasses overriding
122      * this method must make sure that new cache entries are
123      * constructed and put in the cache in a thread safe manner.
124      *
125      * @param docClass the class to get the cache entry for.
126      * @return a cache entry. The returned object is suitable as mutex
127      * for document intialization.
128      */

129     protected CacheEntry getCacheEntry(Class JavaDoc docClass) {
130         String JavaDoc name = docClass.getName();
131
132         /* XXX: This assumes that concurrent unsynchronized accesses
133          * to templateCache.get() with different keys are not
134          * disturbed by the code in the synchronized block, and that
135          * concurrent accesses with the same key return either null or
136          * the entry that has been put into the map inside the
137          * synchronized block.
138          *
139          * The synchornization is broken down to two step when the
140          * entry does not exist in the cache. First, lock the template
141          * cache and create a empty entry and template cache is
142          * unlocked. Then the new empty entry is locked and
143          * updated. Breaking down to two steps would avoid the entry
144          * creating
145          */

146         CacheEntry entry = (CacheEntry) templateCache.get(name);
147         if (entry == null) {
148             synchronized (templateCache) {
149                 entry = (CacheEntry) templateCache.get(name);
150                 if (entry == null) {
151
152                     if (getLogger().debugEnabled()) {
153                         getLogger().logDebug(
154                                              ">>>Creating cache entry for "
155                                              + docClass.getName());
156                     }
157
158                     entry = new CacheEntry();
159                     templateCache.put(name, entry);
160                 }
161             }
162         }
163
164         if (!entry.ready) {
165             synchronized (entry) {
166                 if (!entry.ready) {
167                     entry.src = findSourceUrl(docClass);
168                     entry.document = parseDocument(docClass, entry.src);
169                     entry.timestamp = getCurrentTimestamp(docClass, entry.src);
170                     entry.ready = true;
171                 }
172             }
173         }
174
175         return entry;
176     }
177
178     /**
179      * Check if the DOM instance is out of date
180      */

181     private CacheEntry checkCacheEntry(Class JavaDoc docClass, CacheEntry entry) {
182
183         // Check if the document is out of date, and reload it if yes.
184
long ts = getCurrentTimestamp(docClass, entry.src);
185
186         if (getLogger().debugEnabled()) {
187             getLogger().logDebug(">>>Checking time stamp ts=" + new Date JavaDoc(ts));
188             getLogger().logDebug(">>> entry.timestamp=" + new Date JavaDoc(entry.timestamp));
189         }
190
191         if (ts == -1 || ts > entry.timestamp) {
192             synchronized (entry) {
193                 ts = getCurrentTimestamp(docClass, entry.src);
194                 // Check if some other thread did reparse the document
195
// while this one has been waiting for the lock...
196
if (ts == -1 || ts > entry.timestamp) {
197                     entry.document = parseDocument(docClass, entry.src);
198                     entry.timestamp = getCurrentTimestamp(docClass, entry.src);
199                 } else {
200                     if (getLogger().debugEnabled()) {
201                         getLogger().logDebug(">>>DOM for "
202                                              + docClass.getName()
203                                              + " has been updated by another thread.");
204                     }
205                 }
206             }
207         }
208
209         return entry;
210     }
211
212     /**
213      * Get a DOM instance representing the specified document.
214      * @param docClass the XMLC generated class to get the DOM for.
215      * @return the DOM representing objects of <code>docClass</code>
216      */

217     public Document getDocument(Class JavaDoc docClass) throws XMLCRuntimeException {
218         if (getLogger().debugEnabled()) {
219             getLogger().logDebug(">>>DOM instance requested for class " + docClass.getName());
220         }
221
222         CacheEntry entry = getCacheEntry(docClass); // get the cached entry
223

224         entry = checkCacheEntry(docClass, entry);
225         // make sure cache entry is up to date
226

227         if (getLogger().debugEnabled()) {
228             getLogger().logDebug(">>>Document class " + entry.document.getClass().getName());
229         }
230
231         if (entry.document.getDoctype() == null) {
232             //doms such as the html dom don't store the doctype so it is
233
//safe to do a clone on the document itself
234
return (Document) entry.document.cloneNode(true);
235         } else {
236             //xml-based documents will have a doctype. As such, we will use the
237
//more correct method of creating the document.
238
DOMImplementation JavaDoc domImpl = entry.document.getImplementation();
239             //use the document name given by the doctype. A call to
240
//doc.getDocumentElement() (below) will fail if the dom
241
//implementation expects a certain name to be given to the document
242
//element and the name we provide doesn't match
243
String JavaDoc documentElement = entry.document.getDoctype().getName();
244             //Create a new doctype. Can't just pass in the old doctype
245
//because it belongs to another document, so just make a copy.
246
DocumentType JavaDoc docType = domImpl.createDocumentType(documentElement, entry.document.getDoctype().getPublicId(), entry.document.getDoctype().getSystemId());
247             Document doc = domImpl.createDocument("", documentElement, docType);
248             Node JavaDoc node = doc.importNode(entry.document.getDocumentElement(), true);
249             doc.replaceChild(node, doc.getDocumentElement());
250             return doc;
251         }
252     }
253
254     /**
255      * Get one of the compiled-in constants in a XMLC-generated class (or
256      * subclass).
257      */

258     public static Object JavaDoc getClassConstant(
259                                           Class JavaDoc xmlcBasedClass,
260                                           String JavaDoc constName) {
261         // Use generated constant to find the class.
262
try {
263             Field JavaDoc field = xmlcBasedClass.getField(constName);
264             return field.get(null);
265         } catch (NoSuchFieldException JavaDoc except) {
266             throw new XMLCRuntimeException("Couldn't find class constant \""
267                                            + constName
268                                            + "\" in class \""
269                                            + xmlcBasedClass.getName()
270                                            + "\", maybe be a XMLC generated class or descendent",
271                                            except);
272         } catch (IllegalAccessException JavaDoc except) {
273             throw new XMLCRuntimeException(except);
274         }
275     }
276
277     /**
278      * Get the name of the source file associated with <code>docClass</code>
279      */

280     protected String JavaDoc getSourceFileName(Class JavaDoc docClass)
281         throws XMLCRuntimeException {
282         String JavaDoc srcPath =
283             (String JavaDoc) getClassConstant(docClass,
284                                       XMLObject.XMLC_SOURCE_FILE_FIELD_NAME);
285         if (srcPath == null) {
286             throw new XMLCRuntimeException(
287                                            XMLObject.XMLC_SOURCE_FILE_FIELD_NAME
288                                            + " is null in class "
289                                            + docClass.getName());
290         }
291         if (srcPath.startsWith("/")) {
292             srcPath = srcPath.substring(1);
293         }
294         return srcPath;
295     }
296
297     /**
298      * Get the document URL for a document on the class class path
299      * @param loader class loader to used to located
300      */

301     private URL JavaDoc getDocURLFromClasspath(String JavaDoc path, ClassLoader JavaDoc loader) {
302         URL JavaDoc srcURL = loader.getResource(path);
303
304         if ((srcURL != null) && getLogger().debugEnabled()) {
305             getLogger().logDebug(
306                                  ">>>Get document '" + srcURL + "' from classpath");
307         }
308
309         return srcURL;
310     }
311
312     /**
313      * Get the document URL for a document on the resource path
314      *
315      * @param path relative path to the source file
316      */

317     private URL JavaDoc getDocURLFromResourceDir(String JavaDoc path) {
318         File JavaDoc sourceFile = null;
319         File JavaDoc dir = null;
320
321         for (Iterator JavaDoc iter = getFactory().getResourceDirList().iterator();
322              iter.hasNext();
323              ) {
324             dir = (File JavaDoc) iter.next();
325             sourceFile = new File JavaDoc(dir, path);
326             /* checking under dir using long path */
327             if (sourceFile.exists()) {
328                 break;
329             }
330
331             sourceFile = null;
332         }
333
334         if (sourceFile != null) {
335             try {
336                 return sourceFile.toURL();
337             } catch (MalformedURLException JavaDoc e) {
338                 throw new XMLCError("Cannot construct URL for file " + sourceFile, e);
339             }
340         } else {
341             return null;
342         }
343     }
344
345     /**
346      * Get possible file pathes by subtracting package prefix from the
347      * path prefix. The original path is included in the returned value
348      */

349     private String JavaDoc[] getPathFromPackagePrefix(String JavaDoc srcPath) {
350         ArrayList JavaDoc list = new ArrayList JavaDoc();
351         list.add(srcPath);
352
353         for (Iterator JavaDoc iter = getFactory().getPackagePrefixList().iterator();
354              iter.hasNext();
355              ) {
356             String JavaDoc prefix = (String JavaDoc) iter.next();
357             if (srcPath.startsWith(prefix)) {
358                 list.add(srcPath.substring(prefix.length() + 1));
359                 break;
360             }
361         }
362
363         return (String JavaDoc[]) list.toArray(new String JavaDoc[0]);
364     }
365
366     /**
367      * Find the URL of the source document associated with <code>docClass</code>
368      */

369     protected URL JavaDoc findSourceUrl(Class JavaDoc docClass) throws XMLCRuntimeException {
370         URL JavaDoc sourceDoc = null;
371         String JavaDoc srcPath = getSourceFileName(docClass);
372
373         String JavaDoc[] path = getPathFromPackagePrefix(srcPath);
374
375         for (int i = 0; i < path.length; i++) {
376             //search in resource dirs first so one can copy files to the classloader
377
//for fallback, but still be able to override files located in the
378
//classloader by putting files in a resoruce path. Makes for simpler
379
//development and more flexible deployment.
380
sourceDoc = getDocURLFromResourceDir(path[i]);
381             if (sourceDoc == null) {
382                 sourceDoc =
383                     getDocURLFromClasspath(path[i], docClass.getClassLoader());
384             }
385
386             if (sourceDoc != null) {
387                 break;
388             }
389         }
390
391         if (sourceDoc == null) {
392             throw new XMLCRuntimeException(
393                                            "Source file '"
394                                            + srcPath
395                                            + "' not found for "
396                                            + docClass.getName());
397         }
398
399         return sourceDoc;
400     }
401
402     /**
403      * Get the meta-data file path. This id the name the interface name for
404      * class with .xmlc extension.
405      */

406     protected String JavaDoc metaDataFileName(Class JavaDoc docClass)
407         throws XMLCRuntimeException {
408         // Convert class to file path, drop Impl and add extension.
409
String JavaDoc fileName = docClass.getName().replace('.', '/');
410
411         if (fileName.endsWith(DocumentClass.IMPLEMENTATION_SUFFIX)) {
412             fileName =
413                 fileName.substring(
414                                    0,
415                                    fileName.length()
416                                    - DocumentClass.IMPLEMENTATION_SUFFIX.length());
417         }
418
419         return fileName + META_FILE_EXT;
420     }
421
422     /**
423      * Load the XMLC meta-data file.
424      * @param docClass the class to load the metadata for
425      * @param errorReporter the error handler to use
426      */

427     protected MetaData loadMetaData(
428                                     Class JavaDoc docClass,
429                                     ErrorReporter errorReporter)
430         throws XMLCException {
431         String JavaDoc srcPath = metaDataFileName(docClass);
432         if (getLogger().debugEnabled()) {
433             getLogger().logDebug(
434                                  ">>>Loading metadata '"
435                                  + srcPath
436                                  + "' for "
437                                  + docClass.getName());
438         }
439
440         //search in resource dirs first so one can copy files to the classloader
441
//for fallback, but still be able to override files located in the
442
//classloader by putting files in a resoruce path. Makes for simpler
443
//development and more flexible deployment.
444
URL JavaDoc metaFile = getDocURLFromResourceDir(srcPath);
445         if (metaFile == null) {
446             metaFile = getDocURLFromClasspath(srcPath, docClass.getClassLoader());
447             if (metaFile == null) {
448                 String JavaDoc defaultMetaFile = factory.getDefaultMetaDataFile();
449                 if (defaultMetaFile != null) {
450                     metaFile = getDocURLFromResourceDir(defaultMetaFile);
451                     if (metaFile == null) {
452                         metaFile =
453                             getDocURLFromClasspath(
454                                                    defaultMetaFile,
455                                                    docClass.getClassLoader());
456                     }
457                 }
458             }
459         }
460
461         if (metaFile == null) {
462             throw new XMLCRuntimeException(
463                                            "Metadat file '"
464                                            + srcPath
465                                            + "' not found for "
466                                            + docClass.getName());
467         }
468
469         if (getLogger().debugEnabled()) {
470             getLogger().logDebug(
471                                  ">>>Load MetaData from " + metaFile);
472         }
473
474         MetaDataDocument metaDataDoc =
475             MetaDataDocument.parseMetaData(
476                                            new InputSource JavaDoc(metaFile.toString()),
477                                            errorReporter,
478                                            docClass.getClassLoader());
479         return metaDataDoc.getMetaData();
480     }
481
482     /**
483      * Get the current timestamp of the *ML template for docClass.
484      * @param docClass the class to get the information for
485      * @param src the source URL for this class, as returned by
486      * {@link #findSourceUrl(Class)}
487      * @return the timestamp, or -1 to force a reload.
488      */

489     protected long getCurrentTimestamp(Class JavaDoc docClass, URL JavaDoc src) {
490         try {
491             return src.openConnection().getLastModified();
492         } catch (IOException JavaDoc e) {
493             getLogger().logInfo("Cannot read last modified time for " + src, e);
494             return -1;
495         }
496     }
497
498     /** Create a new template DOM for docClass
499      * @param docClass the class to laod the DOM for
500      * @param src the source file for this class, as returned by
501      * {@link #findSourceFile}
502      */

503     protected Document parseDocument(Class JavaDoc docClass, URL JavaDoc src) {
504         ErrorReporter errorReporter = new ErrorReporter();
505         try {
506             if (getLogger().debugEnabled()) {
507                 getLogger().logDebug(
508                                      ">>>Parsing DOM for "
509                                      + docClass.getName()
510                                      + " from source URL "
511                                      + src.toString());
512             }
513
514             MetaData metaData = loadMetaData(docClass, errorReporter);
515             metaData.getInputDocument().setUrl(src.toString());
516
517             Parse parser = createParser(docClass, errorReporter, null);
518             XMLCDocument xmlcDoc = parser.parse(metaData);
519
520             EditDOM domEditor = createDOMEditor(docClass, metaData);
521             domEditor.edit(xmlcDoc);
522
523             return xmlcDoc.getDocument();
524         } catch (Exception JavaDoc e) {
525             XMLCRuntimeException except;
526             if (e instanceof XMLCRuntimeException) {
527                 except = (XMLCRuntimeException) e;
528             } else {
529                 except = new XMLCRuntimeException(e);
530             }
531             throw except;
532         }
533     }
534
535     /** Create the parser to use. Override this if you need a special
536      * version */

537     protected Parse createParser(
538                                  Class JavaDoc docClass,
539                                  ErrorReporter errorReporter,
540                                  PrintWriter JavaDoc verboseOut) {
541         return new Parse(errorReporter, verboseOut);
542     }
543
544     /** Create the DOM editor to use. Override this if you need a special
545      * version */

546     protected EditDOM createDOMEditor(Class JavaDoc docClass, MetaData metaData) {
547         return new EditDOM(metaData);
548     }
549
550     /**
551      * Bind to a factory.
552      *
553      * @param factory Factory that is creating the object.
554      */

555     public void init(XMLCDeferredParsingFactory factory) {
556         this.factory = factory;
557     }
558
559     /** Get the associated factory */
560     protected XMLCDeferredParsingFactory getFactory() {
561         return factory;
562     }
563
564     /** Get the associated logger */
565     protected XMLCLogger getLogger() {
566         return factory.getLogger();
567     }
568 }
569
Popular Tags