
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