package org.cdlib.xtf.servletBase;
/**
* Copyright (c) 2004, Regents of the University of California
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the University of California nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXSource;
import net.sf.saxon.Configuration;
import net.sf.saxon.FeatureKeys;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.trace.TraceListener;
import org.xml.sax.InputSource;
import org.cdlib.xtf.cache.FileDependency;
import org.cdlib.xtf.cache.GeneratingCache;
import org.cdlib.xtf.util.*;
/**
* This class is used to cache stylesheets so they don't have to be
* reloaded each time they're used.
*/
public class StylesheetCache extends GeneratingCache
{
private boolean dependencyChecking = false;
private GeneratingCache dependencyReceiver = null;
private TraceListenerFactory traceListenerFactory = null;
private TransformerFactory factory;
public interface TraceListenerFactory {
TraceListener createListener();
}
/**
* Constructor.
*
* @param maxEntries Max # of entries before old ones are flushed
* @param maxTime Max age (in seconds) before an entry is flushed.
* @param dependencyChecking Whether to keep track of dependencies and
* invalidate cache entries when dependents
* are updated.
*/
public StylesheetCache(int maxEntries, int maxTime, boolean dependencyChecking) {
super(maxEntries, maxTime);
this.dependencyChecking = dependencyChecking;
// Create a Saxon Configuration, and make it use the default name pool
// (it defaults to making a new name pool).
//
Configuration config = new Configuration();
config.setNamePool(NamePool.getDefaultNamePool());
// Make a factory that will compile stylesheets for us. Force it to use
// our centralized Configuration.
//
factory = new net.sf.saxon.TransformerFactoryImpl(config);
// We want to report errors in a nice, servlet kind of way.
if (!(factory.getErrorListener() instanceof XTFSaxonErrorListener))
factory.setErrorListener(new XTFSaxonErrorListener());
// Avoid loading external DTDs if possible. This not only speeds
// things up, but allows our service to work without depending on
// external servers being up and running at every moment.
//
factory.setAttribute(FeatureKeys.SOURCE_PARSER_CLASS,
DTDSuppressingXMLReader.class.getName());
// Set a URI resolver for dependency checking, if enabled.
if (dependencyChecking)
factory.setURIResolver(new DepResolver(this, factory.getURIResolver()));
}
/**
* Locate the stylesheet for the given filesystem path. If not cached,
* then load it.
*
* @param path Filesystem path of the stylesheet to load
* @return The parsed stylesheet
* @throws Exception If the stylesheet could not be loaded.
*/
public Templates find(String path)
throws Exception
{
return (Templates)super.find(path);
}
/**
* Enable or disable profiling (only affects stylesheets that are
* not already cached). If the factory is null, profiling is
* disabled.
*/
public void enableProfiling(TraceListenerFactory tlf) {
traceListenerFactory = tlf;
}
/**
* Load and parse a stylesheet from the filesystem.
*
* @param key (String)Filesystem path of the stylesheet to load
* @return The parsed stylesheet
* @throws Exception If the stylesheet could not be loaded.
*/
protected synchronized Object generate(Object key)
throws Exception
{
assert dependencyReceiver == null : "stylesheet cache should only have dependencyReceiver " +
"during external calls, not during find().";
if (dependencyChecking)
dependencyReceiver = this;
try
{
String path = (String)key;
File file = new File(path);
if (dependencyChecking)
addDependency(new FileDependency(file));
if (!path.startsWith("http:") && !file.canRead())
throw new GeneralException("Cannot read stylesheet: " + path);
// Set up the profiling listener, if profiling is enabled
if (traceListenerFactory != null) {
TraceListener listener = traceListenerFactory.createListener();
factory.setAttribute(FeatureKeys.TRACE_LISTENER, listener);
factory.setAttribute(FeatureKeys.LINE_NUMBERING, Boolean.TRUE);
}
// Load that stylesheet!
String url;
if (path.startsWith("http:"))
url = path;
else
url = file.toURL().toString();
Templates x = factory.newTemplates(new SAXSource(new InputSource(url)));
if (x == null)
throw new TransformerException("Cannot read stylesheet: " + path);
return x;
}
finally {
dependencyReceiver = null;
}
} // generate()
/** Prints out useful debugging info */
protected void logAction(String action, Object key, Object value) {
Trace.debug("StylesheetCache: " + action + ". Path=" + (String)key);
}
/**
* While loading a stylesheet, we record all the sub-stylesheets
* referenced by it, so that we can form a list of all the dependencies.
* That way, if any of them are changed, the stylesheet will be auto-
* matically reloaded.
*
* We do it by implementing a pass-through URIResolver that adds a
* dependency and then does the normal URIResolver work.
*/
private class DepResolver implements URIResolver
{
/**
* Constructor.
*
* @param cache The cache to add dependencies to
* @param realResolver The URIResolver that does the resolution
*/
DepResolver(GeneratingCache cache, URIResolver realResolver) {
this.cache = cache;
this.realResolver = realResolver;
}
/**
* Resolve a URI, and add a dependency for it to the cache.
*
* @param href Full or partial hyperlink reference
* @param base Base URI of the document
* @return A Source representing the resolved URI.
*/
public Source resolve(String href, String base)
throws TransformerException
{
if (href.indexOf(' ') >= 0)
href = href.replaceAll(" ", "%20");
if (base != null && base.indexOf(' ') >= 0)
base = base.replaceAll(" ", "%20");
// First, do the real resolution.
Source src = realResolver.resolve(href, base);
// If it's a file, add a dependency on it.
if (src != null && dependencyReceiver != null)
{
String sysId = src.getSystemId();
if (sysId != null && sysId.startsWith("file:")) {
String path = sysId.substring("file:".length());
while (path.startsWith("//"))
path = path.substring(1);
dependencyReceiver.addDependency(new FileDependency(path));
}
}
// And we're done.
return src;
} // resolve()
/** The cache to add dependencies to */
GeneratingCache cache;
/** Does the work of resolving the URI's */
URIResolver realResolver;
} // class DepResolver
} // class StylesheetCache