/**
* JHOVE2 - Next-generation architecture for format-aware characterization
*
* Copyright (c) 2009 by The Regents of the University of California,
* Ithaka Harbors, Inc., and The Board of Trustees of the Leland Stanford
* Junior University.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o 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.
*
* o Neither the name of the University of California/California Digital
* Library, Ithaka Harbors/Portico, or Stanford University, 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.
*/
package org.jhove2.module.display;
import java.io.File;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.jhove2.annotation.ReportableProperty;
import org.jhove2.persist.ModuleAccessor;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.sleepycat.persist.model.Persistent;
/**
* An extension of the {@link XMLDisplayer} that directly pipelines
* the XML output to an XSLT stylesheet.
* <p>
* This displayer shall be configured (in JHOVE2 Spring configuration)
* with scope <code>prototype</code> as a new TrAX Transformer object
* shall be allocated for each new Reportable to display.</p>
*
* @author lbihanic
*/
@Persistent
public class XSLDisplayer extends XMLDisplayer {
/** The SAX transformer factory. */
private static SAXTransformerFactory stf;
/** The cache of compiled XLST stylesheets. */
private static ConcurrentMap<String,Templates> stylesheets =
new ConcurrentHashMap<String, Templates>();
private static Logger log = Logger.getLogger(XSLDisplayer.class.getName());
File styleSheetFile;
/** The XSLT stylesheet to apply, as a compile TrAX Templates object. */
transient Templates stylesheet = null;
/** The XSLT processor as a SAX ContentHandler. */
transient ContentHandler out = null;
/**
* Instantiate a new <code>XSLDisplayer</code>.
*/
public XSLDisplayer() {
this(null);
}
/**
* Instantiate a new <code>XMLDisplayer</code>.
* @param moduleAccessor
* Displayer persistence manager
*/
public XSLDisplayer(ModuleAccessor moduleAccessor) {
super(moduleAccessor);
}
/** {@inheritDoc} */
@Override
public void startDisplay(PrintStream out, int level) {
this.out = this.newTransformer(out);
this.declaration(out);
this.startTag(out, level, ELEROOT);
}
/** {@inheritDoc} */
@Override
public void endDisplay(PrintStream out, int level) {
this.endTag(out, level, ELEROOT);
try {
this.out.endPrefixMapping(XSI);
this.out.endDocument();
}
catch (SAXException e) {
throw new RuntimeException(e);
}
finally {
this.out = null;
}
}
/** {@inheritDoc} */
@Override
public void declaration(PrintStream out) {
try {
this.out.startDocument();
this.out.startPrefixMapping(XSI, XSI_URI);
}
catch (SAXException e) {
throw new RuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
public void startTag(PrintStream out, int level, String name) {
try {
// Use String interning to limit memory consumption.
name = name.intern();
if (log.isLoggable(Level.FINE)) {
log.fine("</" + name + '>');
}
this.out.startElement(this.uri, name, name, new AttributesImpl());
}
catch (SAXException e) {
throw new RuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
public void startTag(PrintStream out, int level, String name,
String... attrs) {
try {
// Use String interning to limit memory consumption.
name = name.intern();
AttributesImpl atts = new AttributesImpl();
for (int i = 0; i < attrs.length; i += 2) {
String attrName = attrs[i].intern();
atts.addAttribute("", attrName, attrName,
"CDATA", attrs[i+1].intern());
}
if (log.isLoggable(Level.FINE)) {
StringBuilder buf = new StringBuilder(256);
buf.append('<').append(name);
if (attrs.length != 0) {
for (int i = 0; i < attrs.length; i += 2) {
buf.append(' ').append(attrs[i])
.append("=\"").append(attrs[i+1]).append('"');
}
}
log.fine(buf.append('>').toString());
}
this.out.startElement(this.uri, name, name, atts);
}
catch (SAXException e) {
throw new RuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
public void endTag(PrintStream out, int level, String name) {
String iname = name.intern();
try {
// Use String interning to limit memory footprint.
// name = name.intern();
if (log.isLoggable(Level.FINE)) {
log.fine("</" + iname + '>');
}
this.out.endElement(this.uri, iname, iname);
}
catch (SAXException e) {
throw new RuntimeException("Error end tag <" + iname + ">.", e);
}
}
/** {@inheritDoc} */
@Override
public void tag(PrintStream out, int level, String name, String content) {
try {
// Use String interning to limit memory footprint.
name = name.intern();
if (log.isLoggable(Level.FINE)) {
log.fine("<" + name + '>' + content + "</" + name + '>');
}
this.out.startElement(this.uri, name, name, new AttributesImpl());
if ((content != null) && (content.length() != 0)) {
char[] ch = content.toCharArray();
this.out.characters(ch, 0, ch.length);
}
this.out.endElement(this.uri, name, name);
}
catch (SAXException e) {
throw new RuntimeException(e);
}
}
/**
* Create a new TrAX Transformer object to apply the
* {@link #setStylesheet specified XSLT stylesheet} to the XML
* output of this displayer.
* @param out the stream where to output the result of the
* transformation.
* @return the SAX ContentHandler object of the XSLT processor
* to which direct the SAX events of the XML output.
*/
protected ContentHandler newTransformer(OutputStream out) {
// Ensure the transformer factory has been initialized.
getTransformerFactory();
try {
if (this.stylesheet == null && this.styleSheetFile != null) {
loadStyleSheet();
}
// Get a new transformer, using the identity stylesheet
// if none was installed.
TransformerHandler h = (this.stylesheet != null)?
stf.newTransformerHandler(this.stylesheet):
stf.newTransformerHandler();
h.setResult(new StreamResult(out));
return h;
}
catch (TransformerException e) {
throw new RuntimeException(e);
}
}
/**
* <i>[Dependency injection]</i> Sets the XSLT stylesheet to apply
* when outputting XML.
* @param f the XSLT stylesheet file or <code>null</code> to use
* the identity transformation.
*
* @throws TransformerException if any error occurred while parsing
* the stylesheet.
*/
public void setStylesheet(File f) throws TransformerException {
if (f == null) {
throw new IllegalArgumentException("f");
}
this.styleSheetFile = f;
loadStyleSheet();
}
public void loadStyleSheet() throws TransformerException {
if (this.stylesheet != null) return;
if (this.styleSheetFile == null) return;
String styleSheetPath = this.styleSheetFile.getAbsolutePath();
Templates t = stylesheets.get(styleSheetPath);
if (t == null) {
t = getTransformerFactory().newTemplates(new StreamSource(this.styleSheetFile));
stylesheets.put(styleSheetPath, t);
}
this.stylesheet = t;
}
/**
* Returns the singleton instance of {@link SAXTransformerFactory}.
* @return the singleton instance of the SAX transformer factory
* configured at the JVM level.
*/
private static SAXTransformerFactory getTransformerFactory() {
if (stf == null) {
stf = (SAXTransformerFactory)(TransformerFactory.newInstance());
}
return stf;
}
@ReportableProperty(order = 1, value = "StyleSheet file")
public String getStylesheetFile() {
return this.styleSheetFile.getAbsolutePath() + ((this.stylesheet == null)?" null stylesheet":"stylesheet");
}
}