All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.enhydra.xml.xmlc.deferredparsing.DocumentLoaderImpl Maven / Gradle / Ivy

The newest version!
/*
 * Enhydra Java Application Server Project
 * 
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License on
 * the Enhydra web site ( http://www.enhydra.org/ ).
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
 * the License for the specific terms governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Enhydra Application Server is Lutris
 * Technologies, Inc. The Enhydra Application Server and portions created
 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s): 
 * Richard Kunze
 * Ole Arndt
 * David Li
 * Jacob Kjome
 *
 * $Id: DocumentLoaderImpl.java,v 1.12 2005/01/26 08:29:24 jkjome Exp $
 */

package org.enhydra.xml.xmlc.deferredparsing;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

import org.enhydra.xml.io.ErrorReporter;
import org.enhydra.xml.xmlc.XMLCError;
import org.enhydra.xml.xmlc.XMLCException;
import org.enhydra.xml.xmlc.XMLCLogger;
import org.enhydra.xml.xmlc.XMLCRuntimeException;
import org.enhydra.xml.xmlc.XMLObject;
import org.enhydra.xml.xmlc.compiler.EditDOM;
import org.enhydra.xml.xmlc.compiler.Parse;
import org.enhydra.xml.xmlc.dom.XMLCDocument;
import org.enhydra.xml.xmlc.metadata.DocumentClass;
import org.enhydra.xml.xmlc.metadata.MetaData;
import org.enhydra.xml.xmlc.metadata.MetaDataDocument;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

/**
 * Default implementation of the {@link DocumentLoader} interface.
 *
 * 

This implementation maintains a global cache of loaded DOM * objects. If a DOM object is found in cache and the cached copy is * up to date with regard to the source document and XMLC metadata * object, a copy of the cached object is handed out. Otherwise, the * document is reparsed from the source document, the DOM edits * specified in the document metadata are performed and the document * is stored in cache as a template. Note that the cache may be a * {@link WeakHashMap} or a map with similar semantics that frees up * entries if memory gets low.

* *

This class can be used as the basis for specialized * document loaders.

*/ public class DocumentLoaderImpl implements DocumentLoader { /** The subfix of meta file names */ final private static String META_FILE_EXT = ".xmlc"; /** The associated factory */ private XMLCDeferredParsingFactory factory; /** Helper class for storing cached DOMs */ protected static class CacheEntry { volatile long timestamp = -1; volatile Document document = null; volatile URL src = null; volatile boolean ready = false; } /** The map to use for caching parsed template DOMs */ private Map templateCache; /** * Default constructor. Uses a {@link Cache} as template * DOM cache. * @see #DocumentLoaderImpl(Map) */ public DocumentLoaderImpl() { this(null); } /** * Constructor. Uses the specified map as template cache. * @param cache the cache to use. */ public DocumentLoaderImpl(Map cache) { if (cache == null) { templateCache = new Cache(); } else { templateCache = cache; } } /** * Get the cache entry for a document, initialize one of not * already exists * * Note that the cache entry does not necessarily hold a template * - it may need to be initialized first. Subclasses overriding * this method must make sure that new cache entries are * constructed and put in the cache in a thread safe manner. * * @param docClass the class to get the cache entry for. * @return a cache entry. The returned object is suitable as mutex * for document intialization. */ protected CacheEntry getCacheEntry(Class docClass) { String name = docClass.getName(); /* XXX: This assumes that concurrent unsynchronized accesses * to templateCache.get() with different keys are not * disturbed by the code in the synchronized block, and that * concurrent accesses with the same key return either null or * the entry that has been put into the map inside the * synchronized block. * * The synchornization is broken down to two step when the * entry does not exist in the cache. First, lock the template * cache and create a empty entry and template cache is * unlocked. Then the new empty entry is locked and * updated. Breaking down to two steps would avoid the entry * creating */ CacheEntry entry = (CacheEntry) templateCache.get(name); if (entry == null) { synchronized (templateCache) { entry = (CacheEntry) templateCache.get(name); if (entry == null) { if (getLogger().debugEnabled()) { getLogger().logDebug( ">>>Creating cache entry for " + docClass.getName()); } entry = new CacheEntry(); templateCache.put(name, entry); } } } if (!entry.ready) { synchronized (entry) { if (!entry.ready) { entry.src = findSourceUrl(docClass); entry.document = parseDocument(docClass, entry.src); entry.timestamp = getCurrentTimestamp(docClass, entry.src); entry.ready = true; } } } return entry; } /** * Check if the DOM instance is out of date */ private CacheEntry checkCacheEntry(Class docClass, CacheEntry entry) { // Check if the document is out of date, and reload it if yes. long ts = getCurrentTimestamp(docClass, entry.src); if (getLogger().debugEnabled()) { getLogger().logDebug(">>>Checking time stamp ts=" + new Date(ts)); getLogger().logDebug(">>> entry.timestamp=" + new Date(entry.timestamp)); } if (ts == -1 || ts > entry.timestamp) { synchronized (entry) { ts = getCurrentTimestamp(docClass, entry.src); // Check if some other thread did reparse the document // while this one has been waiting for the lock... if (ts == -1 || ts > entry.timestamp) { entry.document = parseDocument(docClass, entry.src); entry.timestamp = getCurrentTimestamp(docClass, entry.src); } else { if (getLogger().debugEnabled()) { getLogger().logDebug(">>>DOM for " + docClass.getName() + " has been updated by another thread."); } } } } return entry; } /** * Get a DOM instance representing the specified document. * @param docClass the XMLC generated class to get the DOM for. * @return the DOM representing objects of docClass */ public Document getDocument(Class docClass) throws XMLCRuntimeException { if (getLogger().debugEnabled()) { getLogger().logDebug(">>>DOM instance requested for class " + docClass.getName()); } CacheEntry entry = getCacheEntry(docClass); // get the cached entry entry = checkCacheEntry(docClass, entry); // make sure cache entry is up to date if (getLogger().debugEnabled()) { getLogger().logDebug(">>>Document class " + entry.document.getClass().getName()); } if (entry.document.getDoctype() == null) { //doms such as the html dom don't store the doctype so it is //safe to do a clone on the document itself return (Document) entry.document.cloneNode(true); } else { //xml-based documents will have a doctype. As such, we will use the //more correct method of creating the document. DOMImplementation domImpl = entry.document.getImplementation(); //use the document name given by the doctype. A call to //doc.getDocumentElement() (below) will fail if the dom //implementation expects a certain name to be given to the document //element and the name we provide doesn't match String documentElement = entry.document.getDoctype().getName(); //Create a new doctype. Can't just pass in the old doctype //because it belongs to another document, so just make a copy. DocumentType docType = domImpl.createDocumentType(documentElement, entry.document.getDoctype().getPublicId(), entry.document.getDoctype().getSystemId()); Document doc = domImpl.createDocument("", documentElement, docType); Node node = doc.importNode(entry.document.getDocumentElement(), true); doc.replaceChild(node, doc.getDocumentElement()); return doc; } } /** * Get one of the compiled-in constants in a XMLC-generated class (or * subclass). */ public static Object getClassConstant( Class xmlcBasedClass, String constName) { // Use generated constant to find the class. try { Field field = xmlcBasedClass.getField(constName); return field.get(null); } catch (NoSuchFieldException except) { throw new XMLCRuntimeException("Couldn't find class constant \"" + constName + "\" in class \"" + xmlcBasedClass.getName() + "\", maybe be a XMLC generated class or descendent", except); } catch (IllegalAccessException except) { throw new XMLCRuntimeException(except); } } /** * Get the name of the source file associated with docClass */ protected String getSourceFileName(Class docClass) throws XMLCRuntimeException { String srcPath = (String) getClassConstant(docClass, XMLObject.XMLC_SOURCE_FILE_FIELD_NAME); if (srcPath == null) { throw new XMLCRuntimeException( XMLObject.XMLC_SOURCE_FILE_FIELD_NAME + " is null in class " + docClass.getName()); } if (srcPath.startsWith("/")) { srcPath = srcPath.substring(1); } return srcPath; } /** * Get the document URL for a document on the class class path * @param loader class loader to used to located */ private URL getDocURLFromClasspath(String path, ClassLoader loader) { URL srcURL = loader.getResource(path); if ((srcURL != null) && getLogger().debugEnabled()) { getLogger().logDebug( ">>>Get document '" + srcURL + "' from classpath"); } return srcURL; } /** * Get the document URL for a document on the resource path * * @param path relative path to the source file */ private URL getDocURLFromResourceDir(String path) { File sourceFile = null; File dir = null; for (Iterator iter = getFactory().getResourceDirList().iterator(); iter.hasNext(); ) { dir = (File) iter.next(); sourceFile = new File(dir, path); /* checking under dir using long path */ if (sourceFile.exists()) { break; } sourceFile = null; } if (sourceFile != null) { try { return sourceFile.toURL(); } catch (MalformedURLException e) { throw new XMLCError("Cannot construct URL for file " + sourceFile, e); } } else { return null; } } /** * Get possible file pathes by subtracting package prefix from the * path prefix. The original path is included in the returned value */ private String[] getPathFromPackagePrefix(String srcPath) { ArrayList list = new ArrayList(); list.add(srcPath); for (Iterator iter = getFactory().getPackagePrefixList().iterator(); iter.hasNext(); ) { String prefix = (String) iter.next(); if (srcPath.startsWith(prefix)) { list.add(srcPath.substring(prefix.length() + 1)); break; } } return (String[]) list.toArray(new String[0]); } /** * Find the URL of the source document associated with docClass */ protected URL findSourceUrl(Class docClass) throws XMLCRuntimeException { URL sourceDoc = null; String srcPath = getSourceFileName(docClass); String[] path = getPathFromPackagePrefix(srcPath); for (int i = 0; i < path.length; i++) { //search in resource dirs first so one can copy files to the classloader //for fallback, but still be able to override files located in the //classloader by putting files in a resoruce path. Makes for simpler //development and more flexible deployment. sourceDoc = getDocURLFromResourceDir(path[i]); if (sourceDoc == null) { sourceDoc = getDocURLFromClasspath(path[i], docClass.getClassLoader()); } if (sourceDoc != null) { break; } } if (sourceDoc == null) { throw new XMLCRuntimeException( "Source file '" + srcPath + "' not found for " + docClass.getName()); } return sourceDoc; } /** * Get the meta-data file path. This id the name the interface name for * class with .xmlc extension. */ protected String metaDataFileName(Class docClass) throws XMLCRuntimeException { // Convert class to file path, drop Impl and add extension. String fileName = docClass.getName().replace('.', '/'); if (fileName.endsWith(DocumentClass.IMPLEMENTATION_SUFFIX)) { fileName = fileName.substring( 0, fileName.length() - DocumentClass.IMPLEMENTATION_SUFFIX.length()); } return fileName + META_FILE_EXT; } /** * Load the XMLC meta-data file. * @param docClass the class to load the metadata for * @param errorReporter the error handler to use */ protected MetaData loadMetaData( Class docClass, ErrorReporter errorReporter) throws XMLCException { String srcPath = metaDataFileName(docClass); if (getLogger().debugEnabled()) { getLogger().logDebug( ">>>Loading metadata '" + srcPath + "' for " + docClass.getName()); } //search in resource dirs first so one can copy files to the classloader //for fallback, but still be able to override files located in the //classloader by putting files in a resoruce path. Makes for simpler //development and more flexible deployment. URL metaFile = getDocURLFromResourceDir(srcPath); if (metaFile == null) { metaFile = getDocURLFromClasspath(srcPath, docClass.getClassLoader()); if (metaFile == null) { String defaultMetaFile = factory.getDefaultMetaDataFile(); if (defaultMetaFile != null) { metaFile = getDocURLFromResourceDir(defaultMetaFile); if (metaFile == null) { metaFile = getDocURLFromClasspath( defaultMetaFile, docClass.getClassLoader()); } } } } if (metaFile == null) { throw new XMLCRuntimeException( "Metadat file '" + srcPath + "' not found for " + docClass.getName()); } if (getLogger().debugEnabled()) { getLogger().logDebug( ">>>Load MetaData from " + metaFile); } MetaDataDocument metaDataDoc = MetaDataDocument.parseMetaData( new InputSource(metaFile.toString()), errorReporter, docClass.getClassLoader()); return metaDataDoc.getMetaData(); } /** * Get the current timestamp of the *ML template for docClass. * @param docClass the class to get the information for * @param src the source URL for this class, as returned by * {@link #findSourceUrl(Class)} * @return the timestamp, or -1 to force a reload. */ protected long getCurrentTimestamp(Class docClass, URL src) { try { return src.openConnection().getLastModified(); } catch (IOException e) { getLogger().logInfo("Cannot read last modified time for " + src, e); return -1; } } /** Create a new template DOM for docClass * @param docClass the class to laod the DOM for * @param src the source file for this class, as returned by * {@link #findSourceFile} */ protected Document parseDocument(Class docClass, URL src) { ErrorReporter errorReporter = new ErrorReporter(); try { if (getLogger().debugEnabled()) { getLogger().logDebug( ">>>Parsing DOM for " + docClass.getName() + " from source URL " + src.toString()); } MetaData metaData = loadMetaData(docClass, errorReporter); metaData.getInputDocument().setUrl(src.toString()); Parse parser = createParser(docClass, errorReporter, null); XMLCDocument xmlcDoc = parser.parse(metaData); EditDOM domEditor = createDOMEditor(docClass, metaData); domEditor.edit(xmlcDoc); return xmlcDoc.getDocument(); } catch (Exception e) { XMLCRuntimeException except; if (e instanceof XMLCRuntimeException) { except = (XMLCRuntimeException) e; } else { except = new XMLCRuntimeException(e); } throw except; } } /** Create the parser to use. Override this if you need a special * version */ protected Parse createParser( Class docClass, ErrorReporter errorReporter, PrintWriter verboseOut) { return new Parse(errorReporter, verboseOut); } /** Create the DOM editor to use. Override this if you need a special * version */ protected EditDOM createDOMEditor(Class docClass, MetaData metaData) { return new EditDOM(metaData); } /** * Bind to a factory. * * @param factory Factory that is creating the object. */ public void init(XMLCDeferredParsingFactory factory) { this.factory = factory; } /** Get the associated factory */ protected XMLCDeferredParsingFactory getFactory() { return factory; } /** Get the associated logger */ protected XMLCLogger getLogger() { return factory.getLogger(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy