blob: da358eceee68e335a5b86e4ecd1aae8ac76c21de [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package com.sun.star.comp.xsltfilter;
//Standard Java classes
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
// Imported TraX classes
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
//StarOffice Interfaces and UNO
import com.sun.star.beans.NamedValue;
import com.sun.star.comp.loader.FactoryHelper;
import com.sun.star.io.XActiveDataControl;
import com.sun.star.io.XActiveDataSink;
import com.sun.star.io.XActiveDataSource;
import com.sun.star.io.XInputStream;
import com.sun.star.io.XOutputStream;
import com.sun.star.io.XSeekable;
import com.sun.star.io.XStreamListener;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.XServiceName;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XTypeProvider;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.Type;
import com.sun.star.uno.UnoRuntime;
//Uno to java Adaptor
import com.sun.star.lib.uno.adapter.XInputStreamToInputStreamAdapter;
import com.sun.star.lib.uno.adapter.XOutputStreamToOutputStreamAdapter;
import javax.xml.transform.Templates;
import net.sf.saxon.FeatureKeys;
/** This outer class provides an inner class to implement the service
* description and a method to instantiate the
* component on demand (__getServiceFactory()).
*/
public class XSLTransformer
implements XTypeProvider, XServiceName, XServiceInfo, XActiveDataSink,
XActiveDataSource, XActiveDataControl, XInitialization, URIResolver, EntityResolver {
/**
* This component provides java based XSL transformations
* A SAX based interface is not feasible when crossing language bordes
* since too much time would be wasted by bridging the events between environments
* example: 190 pages document, 82000 events 8seconds transform 40(!) sec. bridging
*
*/
private XInputStream m_xis;
private XOutputStream m_xos; // private static HashMap templatecache;
private static final String STATSPROP = "XSLTransformer.statsfile";
private static PrintStream statsp;
private String stylesheeturl;
private String targeturl;
private String targetbaseurl;
private String sourceurl;
private String sourcebaseurl;
private String pubtype = new String();
private String systype = new String(); // processing thread
private Thread t; // listeners
private Vector listeners = new Vector(); //
private XMultiServiceFactory svcfactory; // cache for transformations by stylesheet
private static Hashtable xsltReferences = new Hashtable();
// struct for cached stylesheets
private static class Transformation {
public Templates cachedXSLT;
public long lastmod;
}
// Resolve URIs to an empty source
public Source resolve(String href, String base) {
return new StreamSource(new StringReader(""));
}
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, java.io.IOException {
return new InputSource(new StringReader(""));
}
// --- Initialization ---
public XSLTransformer(XMultiServiceFactory msf) {
svcfactory = msf;
}
public void initialize(Object[] values) throws com.sun.star.uno.Exception {
// some configurable debugging
String statsfilepath = null;
if ((statsfilepath = System.getProperty(STATSPROP)) != null) {
try {
File statsfile = new File(statsfilepath);
statsp = new PrintStream(new FileOutputStream(statsfile.getPath(), false));
} catch (java.lang.Exception e) {
System.err.println("XSLTransformer: could not open statsfile'" + statsfilepath + "'");
System.err.println(" " + e.getClass().getName() + ": " + e.getMessage());
System.err.println(" output disabled");
}
}
// reading the values
NamedValue nv = null;
debug("The transformation's parameters as 'name = value' pairs:\n");
for (int i = 0; i < values.length; i++) {
nv = (NamedValue) AnyConverter.toObject(new Type(NamedValue.class), values[i]);
if (nv.Name != null && !nv.Name.equals("")) {
debug(nv.Name + " = " + nv.Value);
}
if (nv.Name.equals("StylesheetURL")) {
stylesheeturl = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("SourceURL")) {
sourceurl = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("TargetURL")) {
targeturl = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("SourceBaseURL")) {
sourcebaseurl = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("TargetBaseURL")) {
targetbaseurl = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("SystemType")) {
systype = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
} else if (nv.Name.equals("PublicType")) {
pubtype = (String) AnyConverter.toObject(
new Type(String.class), nv.Value);
}
}
}
// --- XActiveDataSink xistream = aStream;
public void setInputStream(XInputStream aStream) {
m_xis = aStream;
}
public com.sun.star.io.XInputStream getInputStream() {
return m_xis;
}
// --- XActiveDataSource
public void setOutputStream(XOutputStream aStream) {
m_xos = aStream;
}
public com.sun.star.io.XOutputStream getOutputStream() {
return m_xos;
}
// --- XActiveDataControl
public void addListener(XStreamListener aListener) {
if (aListener != null && !listeners.contains(aListener)) {
listeners.add(aListener);
}
}
public void removeListener(XStreamListener aListener) {
if (aListener != null) {
listeners.removeElement(aListener);
}
}
public void start() {
// notify listeners
t = new Thread() {
@Override
public void run() {
// Local variabes used outside try block in finally block
InputStream is = null;
Source source = null;
BufferedOutputStream os = null;
PrintStream origOut = System.out;
PrintStream origErr = System.err;
if (statsp != null) {
System.setErr(statsp);
System.setOut(statsp);
}
try {
debug("\n\nStarting transformation...");
// Set up context class loader for SAXParserFactory and
// TransformerFactory calls below:
setContextClassLoader(this.getClass().getClassLoader());
for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
XStreamListener l = (XStreamListener) e.nextElement();
l.started();
}
XSeekable xseek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, m_xis);
if (xseek != null) {
xseek.seek(0);
}
is = new BufferedInputStream(
new XInputStreamToInputStreamAdapter(m_xis));
//Source xmlsource = new StreamSource(xmlinput);
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(false);
spf.setNamespaceAware(true);
XMLReader xmlReader = spf.newSAXParser().getXMLReader();
xmlReader.setEntityResolver(XSLTransformer.this);
source = new SAXSource(xmlReader, new InputSource(is));
// in order to help performance and to remedy a a possible memory
// leak in xalan, where it seems, that Transformer instances cannot
// be reclaimed though they are no longer referenced here, we use
// a cache of weak references (ie. xsltReferences) created for specific
// style sheet URLs see also #i48384#
Templates xsltTemplate = null;
Transformer transformer = null;
Transformation transformation = null;
// File stylefile = new File(new URI(stylesheeturl));
long lastmod = 0;
try {
URL uStyle = new URL(stylesheeturl);
URLConnection c = uStyle.openConnection();
lastmod = c.getLastModified();
} catch (java.lang.Exception ex) {
// lastmod will remain at 0;
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
synchronized (xsltReferences) {
java.lang.ref.WeakReference ref = null;
// try to get the xsltTemplate reference from the cache
if ((ref = (java.lang.ref.WeakReference) xsltReferences.get(stylesheeturl)) == null ||
(transformation = ((Transformation) ref.get())) == null ||
((Transformation) ref.get()).lastmod < lastmod) {
// we cannot find a valid reference for this stylesheet
// or the stylsheet was updated
if (ref != null) {
xsltReferences.remove(stylesheeturl);
}
// create new xsltTemplate for this stylesheet
TransformerFactory tfactory = TransformerFactory.newInstance();
debug("TransformerFactory is '" + tfactory.getClass().getName() + "'");
// some external saxons (Debian, Ubuntu, ...) have this disabled
// per default
tfactory.setAttribute(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, new Boolean(true));
xsltTemplate = tfactory.newTemplates(new StreamSource(stylesheeturl));
// store the transformation into the cache
transformation = new Transformation();
transformation.lastmod = lastmod;
transformation.cachedXSLT = xsltTemplate;
ref = new java.lang.ref.WeakReference(transformation);
xsltReferences.put(stylesheeturl, ref);
}
}
xsltTemplate = transformation.cachedXSLT;
transformer = xsltTemplate.newTransformer();
transformer.setOutputProperty("encoding", "UTF-8");
// transformer.setURIResolver(XSLTransformer.this);
// invalid to set 'null' as parameter as 'null' is not a valid Java object
if (sourceurl != null) {
transformer.setParameter("sourceURL", sourceurl);
}
if (sourcebaseurl != null) {
transformer.setParameter("sourceBaseURL", sourcebaseurl);
}
if (targeturl != null) {
transformer.setParameter("targetURL", targeturl);
}
if (targetbaseurl != null) {
transformer.setParameter("targetBaseURL", targetbaseurl);
}
if (pubtype != null) {
transformer.setParameter("publicType", pubtype);
}
if (systype != null) {
transformer.setParameter("systemType", systype);
}
if (svcfactory != null) {
transformer.setParameter("XMultiServiceFactory", svcfactory);
}
os = new BufferedOutputStream(
new XOutputStreamToOutputStreamAdapter(m_xos));
StreamResult sr = new StreamResult(os);
long tstart = System.currentTimeMillis();
transformer.transform(source, sr);
debug("finished transformation in " + (System.currentTimeMillis() - tstart) + "ms");
} catch (java.lang.Throwable ex) {
// notify any listeners about close
for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
XStreamListener l = (XStreamListener) e.nextElement();
l.error(new com.sun.star.uno.Exception(ex.getClass().getName() + ": " + ex.getMessage()));
}
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
} finally {
// dereference input buffer
source = null;
try {
if (is != null) {
is.close();
}
} catch (java.lang.Throwable ex) {
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
try {
if (os != null) {
os.close();
}
} catch (java.lang.Throwable ex) {
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
try {
if (m_xis != null) {
m_xis.closeInput();
}
} catch (java.lang.Throwable ex) {
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
try {
if (m_xos != null) {
m_xos.closeOutput();
}
} catch (java.lang.Throwable ex) {
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
// resetting standard input/error streams from logfile to default
if (statsp != null) {
System.setErr(origErr);
System.setOut(origOut);
}
// try to release references asap...
m_xos = null;
m_xis = null;
is = null;
os = null;
// notify any listeners about close
if (listeners != null) {
for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
XStreamListener l = (XStreamListener) e.nextElement();
l.closed();
}
}
}
}
};
t.start();
} /* a statsfile have to be created as precondition to use this function */
private static final void debug(String s) {
if (statsp != null) {
statsp.println(s);
}
}
public void terminate() {
try {
debug("terminate called");
if (t.isAlive()) {
t.interrupt();
for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
XStreamListener l = (XStreamListener) e.nextElement();
l.terminated();
}
}
} catch (java.lang.Exception ex) {
if (statsp != null) {
statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
ex.printStackTrace(statsp);
}
}
} // --- component management interfaces... ---
private final static String _serviceName = "com.sun.star.comp.JAXTHelper";
// Implement methods from interface XTypeProvider
public byte[] getImplementationId() {
byte[] byteReturn = {};
byteReturn = new String("" + this.hashCode()).getBytes();
return (byteReturn);
}
public com.sun.star.uno.Type[] getTypes() {
Type[] typeReturn = {};
try {
typeReturn = new Type[]{
new Type(XTypeProvider.class),
new Type(XServiceName.class),
new Type(XServiceInfo.class),
new Type(XActiveDataSource.class),
new Type(XActiveDataSink.class),
new Type(XActiveDataControl.class),
new Type(XInitialization.class)
};
} catch (java.lang.Exception exception) {
}
return (typeReturn);
}
// --- Implement method from interface XServiceName ---
public String getServiceName() {
return (_serviceName);
}
// --- Implement methods from interface XServiceInfo ---
public boolean supportsService(String stringServiceName) {
return (stringServiceName.equals(_serviceName));
}
public String getImplementationName() {
return (XSLTransformer.class.getName());
}
public String[] getSupportedServiceNames() {
String[] stringSupportedServiceNames = {_serviceName};
return stringSupportedServiceNames;
}
// --- component registration methods ---
public static XSingleServiceFactory __getServiceFactory(
String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey) {
XSingleServiceFactory xSingleServiceFactory = null;
if (implName.indexOf("XSLTransformer") != -1) {
xSingleServiceFactory = FactoryHelper.getServiceFactory(XSLTransformer.class,
_serviceName, multiFactory, regKey);
}
return xSingleServiceFactory;
}
}