/*
ESXX - The friendly ECMAscript/XML Application Server
Copyright (C) 2007-2015 Martin Blom <martin@blom.org>
This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.esxx;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.s9api.*;
import org.esxx.ESXX;
import org.esxx.js.JSURI;
import org.esxx.saxon.ESXXExpression;
import org.esxx.util.URIResolver;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import static net.sf.saxon.s9api.Serializer.Property.*;
public class Stylesheet
implements org.esxx.cache.Cached {
public Stylesheet(final URI uri, InputStream is)
throws IOException {
esxx = ESXX.getInstance();
if (is == null) {
is = esxx.openCachedURI(uri);
}
this.uri = uri;
started = new Date();
externalURIs.add(uri);
XsltCompiler compiler = esxx.getSaxonProcessor().newXsltCompiler();
URIResolver ur = new URIResolver(esxx, uri, externalURIs);
final TransformerException[] cause = { null };
try {
compiler.setURIResolver(ur);
compiler.setErrorListener(new ErrorListener() {
public void error(TransformerException ex)
throws TransformerException {
if (cause[0] == null) {
cause[0] = ex;
}
// esxx.getLogger().logp(Level.SEVERE, uri.toString(), null,
// ex.getMessageAndLocation(), ex);
throw ex;
}
public void fatalError(TransformerException ex)
throws TransformerException {
if (cause[0] == null) {
cause[0] = ex;
}
// esxx.getLogger().logp(Level.SEVERE, uri.toString(), null,
// ex.getMessage(), ex);
throw ex;
}
public void warning(TransformerException ex) {
esxx.getLogger().logp(Level.WARNING, uri.toString(), null,
ex.getMessageAndLocation());
}
});
xslt = compiler.compile(new StreamSource(is, uri.toString()));
}
catch (net.sf.saxon.s9api.SaxonApiException ex) {
String system_id = null;
int line = 0;
int column = 0;
if (cause[0] != null) {
if (cause[0].getLocator() != null) {
javax.xml.transform.SourceLocator loc = cause[0].getLocator();
system_id = loc.getSystemId();
line = loc.getLineNumber();
column = loc.getColumnNumber();
}
else if (cause[0].getCause() instanceof org.xml.sax.SAXParseException) {
org.xml.sax.SAXParseException sp = (org.xml.sax.SAXParseException) cause[0].getCause();
system_id = sp.getSystemId();
line = sp.getLineNumber();
column = sp.getColumnNumber();
}
}
if (system_id == null) {
system_id = "<no system ID>";
}
throw new ESXXException(ex.getMessage() + ": " + system_id
+ ", line " + line
+ ", column " + column + ": " + cause[0].getMessage(),
ex);
}
finally {
ur.closeAllStreams();
}
}
@Override /* Cached */ public String getFilename() {
return uri.toString();
}
public XsltExecutable getExecutable() {
return xslt;
}
@Override /* Cached */ public synchronized void logUsage(long start_time) {
++invocations;
lastAccessed = System.currentTimeMillis();
if (start_time != 0) {
executionTime += (lastAccessed - start_time);
}
}
@Override /* Cached */ public Collection<URI> getExternalURIs() {
return externalURIs;
}
@Override public String toString() {
return "[" + this.getClass().getName() + ": " + uri + "]";
}
@Override /* Cached */ public synchronized JMXBean getJMXBean() {
if (jmxBean == null) {
jmxBean = new JMXBean();
}
return jmxBean;
}
private class JMXBean
extends javax.management.StandardEmitterMBean
implements org.esxx.jmx.StylesheetMXBean {
public JMXBean() {
super(org.esxx.jmx.StylesheetMXBean.class, true,
new javax.management.NotificationBroadcasterSupport());
}
@Override public String getFilename() {
return Stylesheet.this.getFilename();
}
@Override public void unloadStylesheet() {
esxx.removeCachedStylesheet(Stylesheet.this);
}
@Override public org.esxx.jmx.ApplicationStats getStatistics() {
synchronized (Stylesheet.this) {
return new org.esxx.jmx.ApplicationStats(invocations, executionTime,
started, new Date(lastAccessed));
}
}
}
private ESXX esxx;
private JMXBean jmxBean;
private URI uri;
private XsltExecutable xslt;
private HashSet<URI> externalURIs = new HashSet<URI>();
private long invocations;
private long executionTime;
private Date started;
private long lastAccessed;
public static void transform(Context cx, Scriptable scope,
Stylesheet xslt, Map<QName, Object> params,
Node node, boolean may_adopt, String comment,
Destination dest)
throws SaxonApiException {
long start_time = System.currentTimeMillis();
Object old_scope = cx.getThreadLocal(ESXXExpression.class);
try {
XsltExecutable xe = xslt.getExecutable();
XsltTransformer tr = xe.load();
if (params != null) {
for (Map.Entry<QName, Object> param : params.entrySet()) {
tr.setParameter(param.getKey(), javaToXDM(param.getValue()));
}
}
if (!(node instanceof Document)) {
// This is sad, but Saxon can only transform the DOM Document Element node.
org.w3c.dom.DOMImplementation di = node.getOwnerDocument().getImplementation();
Document doc = di.createDocument(null, "dummy", null);
Node adopted = may_adopt ? doc.adoptNode(node) : null;
if (adopted == null) {
adopted = doc.importNode(node, true);
}
doc.replaceChild(adopted, doc.getDocumentElement());
node = doc;
}
if (comment != null) {
// User by Worker to append the debug output, and let the
// stylesheet decide if it should be output or not.
((Document) node).appendChild(((Document) node).createComment(comment));
}
tr.setSource(new DOMSource(node));
if (dest instanceof Serializer) {
Serializer s = (Serializer) dest;
// Remove this code when upgrading to Saxon 9.1 (?)
Properties op = xe.getUnderlyingCompiledStylesheet().getOutputProperties();
s.setOutputProperty(BYTE_ORDER_MARK, op.getProperty("byte-order-mark"));
s.setOutputProperty(CDATA_SECTION_ELEMENTS, op.getProperty("cdata-section-elements"));
s.setOutputProperty(DOCTYPE_PUBLIC, op.getProperty("doctype-public"));
s.setOutputProperty(DOCTYPE_SYSTEM, op.getProperty("doctype-system"));
s.setOutputProperty(ENCODING, op.getProperty("encoding"));
s.setOutputProperty(ESCAPE_URI_ATTRIBUTES, op.getProperty("escape-uri-attributes"));
s.setOutputProperty(INCLUDE_CONTENT_TYPE, op.getProperty("include-content-type"));
s.setOutputProperty(INDENT, op.getProperty("indent"));
s.setOutputProperty(MEDIA_TYPE, op.getProperty("media-type"));
s.setOutputProperty(METHOD, op.getProperty("method"));
// s.setOutputProperty(NORMALIZATION_FORM, op.getProperty("normalization-form"));
s.setOutputProperty(OMIT_XML_DECLARATION, op.getProperty("omit-xml-declaration"));
s.setOutputProperty(STANDALONE, op.getProperty("standalone"));
s.setOutputProperty(UNDECLARE_PREFIXES, op.getProperty("undeclare-prefixes"));
s.setOutputProperty(USE_CHARACTER_MAPS, op.getProperty("use-character-maps"));
s.setOutputProperty(VERSION, op.getProperty("version"));
}
tr.setDestination(dest);
// Make current scope available to ESXXExpression and begin transformation
cx.putThreadLocal(ESXXExpression.class, scope);
tr.transform();
}
finally {
cx.putThreadLocal(ESXXExpression.class, old_scope);
xslt.logUsage(start_time);
}
}
private static XdmValue javaToXDM(Object o) {
if (o instanceof JSURI) {
o = ((JSURI) o).jsGet_javaURI();
}
else if (o instanceof org.mozilla.javascript.xml.XMLObject) {
o = ESXX.e4xToDOM((Scriptable) o);
}
if (o instanceof java.math.BigDecimal) {
return new XdmAtomicValue((java.math.BigDecimal) o);
}
else if (o instanceof Boolean) {
return new XdmAtomicValue((Boolean) o);
}
else if (o instanceof Double) {
return new XdmAtomicValue((Double) o);
}
else if (o instanceof URI) {
return new XdmAtomicValue((URI) o);
}
else if (o instanceof Node) {
return ESXX.getInstance().getSaxonDocumentBuilder().wrap(o);
}
else {
return new XdmAtomicValue(Context.toString(o));
}
}
}