xref: /trunk/main/filter/source/xsltfilter/com/sun/star/comp/xsltfilter/XSLTransformer.java (revision e901e6e41c1fd2ceb1d01c5bbfa2b102c167f93f)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 package com.sun.star.comp.xsltfilter;
23 
24 // Standard Java classes
25 import java.io.BufferedInputStream;
26 import java.io.BufferedOutputStream;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.InputStream;
30 import java.io.PrintStream;
31 import java.io.StringReader;
32 import java.net.URL;
33 import java.net.URLConnection;
34 import java.util.Enumeration;
35 import java.util.Hashtable;
36 import java.util.Vector;
37 
38 // Imported TraX classes
39 import javax.xml.parsers.SAXParserFactory;
40 import javax.xml.transform.Source;
41 import javax.xml.transform.Transformer;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.URIResolver;
44 import javax.xml.transform.sax.SAXSource;
45 import javax.xml.transform.stream.StreamResult;
46 import javax.xml.transform.stream.StreamSource;
47 import org.xml.sax.EntityResolver;
48 import org.xml.sax.InputSource;
49 import org.xml.sax.SAXException;
50 import org.xml.sax.XMLReader;
51 
52 // OpenOffice Interfaces and UNO
53 import com.sun.star.beans.NamedValue;
54 import com.sun.star.comp.loader.FactoryHelper;
55 import com.sun.star.io.XActiveDataControl;
56 import com.sun.star.io.XActiveDataSink;
57 import com.sun.star.io.XActiveDataSource;
58 import com.sun.star.io.XInputStream;
59 import com.sun.star.io.XOutputStream;
60 import com.sun.star.io.XSeekable;
61 import com.sun.star.io.XStreamListener;
62 import com.sun.star.lang.XInitialization;
63 import com.sun.star.lang.XMultiServiceFactory;
64 import com.sun.star.lang.XServiceInfo;
65 import com.sun.star.lang.XServiceName;
66 import com.sun.star.lang.XSingleServiceFactory;
67 import com.sun.star.lang.XTypeProvider;
68 import com.sun.star.registry.XRegistryKey;
69 import com.sun.star.uno.AnyConverter;
70 import com.sun.star.uno.Type;
71 import com.sun.star.uno.UnoRuntime;
72 
73 // Uno to java Adaptor
74 import com.sun.star.lib.uno.adapter.XInputStreamToInputStreamAdapter;
75 import com.sun.star.lib.uno.adapter.XOutputStreamToOutputStreamAdapter;
76 import javax.xml.transform.Templates;
77 
78 import net.sf.saxon.FeatureKeys;
79 
80 /** This outer class provides an inner class to implement the service
81  * description and a method to instantiate the
82  * component on demand (__getServiceFactory()).
83  */
84 public class XSLTransformer
85         implements XTypeProvider, XServiceName, XServiceInfo, XActiveDataSink,
86         XActiveDataSource, XActiveDataControl, XInitialization, URIResolver, EntityResolver {
87 
88     /**
89      * This component provides java based XSL transformations
90      * A SAX based interface is not feasible when crossing language borders
91      * since too much time would be wasted by bridging the events between environments
92      * example: 190 pages document, 82000 events 8seconds transform 40(!) sec. bridging
93      *
94      */
95     private XInputStream m_xis;
96     private XOutputStream m_xos;    // private static HashMap templatecache;
97     private static final String STATSPROP = "XSLTransformer.statsfile";
98     private static PrintStream statsp;
99     private String stylesheeturl;
100     private String targeturl;
101     private String targetbaseurl;
102     private String sourceurl;
103     private String sourcebaseurl;
104     private String pubtype = new String();
105     private String systype = new String();    // processing thread
106     private Thread t;    // listeners
107     private Vector listeners = new Vector();    //
108     private XMultiServiceFactory svcfactory;    // cache for transformations by stylesheet
109     private static Hashtable xsltReferences = new Hashtable();
110     // struct for cached stylesheets
111     private static class Transformation {
112 
113         public Templates cachedXSLT;
114         public long lastmod;
115     }
116     // Resolve URIs to an empty source
resolve(String href, String base)117     public Source resolve(String href, String base) {
118         return new StreamSource(new StringReader(""));
119     }
120 
resolveEntity(String publicId, String systemId)121     public InputSource resolveEntity(String publicId, String systemId) throws SAXException, java.io.IOException {
122         return new InputSource(new StringReader(""));
123     }
124     // --- Initialization ---
XSLTransformer(XMultiServiceFactory msf)125     public XSLTransformer(XMultiServiceFactory msf) {
126         svcfactory = msf;
127     }
128 
initialize(Object[] values)129     public void initialize(Object[] values) throws com.sun.star.uno.Exception {
130         // some configurable debugging
131         String statsfilepath = null;
132         if ((statsfilepath = System.getProperty(STATSPROP)) != null) {
133             try {
134                 File statsfile = new File(statsfilepath);
135                 statsp = new PrintStream(new FileOutputStream(statsfile.getPath(), false));
136             } catch (java.lang.Exception e) {
137                 System.err.println("XSLTransformer: could not open statsfile'" + statsfilepath + "'");
138                 System.err.println("   " + e.getClass().getName() + ": " + e.getMessage());
139                 System.err.println("   output disabled");
140             }
141         }
142 
143         // reading the values
144         NamedValue nv = null;
145         debug("The transformation's parameters as 'name = value' pairs:\n");
146 
147         for (int i = 0; i < values.length; i++) {
148             nv = (NamedValue) AnyConverter.toObject(new Type(NamedValue.class), values[i]);
149 
150             if (nv.Name != null && !nv.Name.equals("")) {
151                 debug(nv.Name + " = " + nv.Value);
152             }
153 
154             if (nv.Name.equals("StylesheetURL")) {
155                 stylesheeturl = (String) AnyConverter.toObject(
156                         new Type(String.class), nv.Value);
157             } else if (nv.Name.equals("SourceURL")) {
158                 sourceurl = (String) AnyConverter.toObject(
159                         new Type(String.class), nv.Value);
160             } else if (nv.Name.equals("TargetURL")) {
161                 targeturl = (String) AnyConverter.toObject(
162                         new Type(String.class), nv.Value);
163             } else if (nv.Name.equals("SourceBaseURL")) {
164                 sourcebaseurl = (String) AnyConverter.toObject(
165                         new Type(String.class), nv.Value);
166             } else if (nv.Name.equals("TargetBaseURL")) {
167                 targetbaseurl = (String) AnyConverter.toObject(
168                         new Type(String.class), nv.Value);
169             } else if (nv.Name.equals("SystemType")) {
170                 systype = (String) AnyConverter.toObject(
171                         new Type(String.class), nv.Value);
172             } else if (nv.Name.equals("PublicType")) {
173                 pubtype = (String) AnyConverter.toObject(
174                         new Type(String.class), nv.Value);
175             }
176         }
177     }
178 
179     // --- XActiveDataSink        xistream = aStream;
setInputStream(XInputStream aStream)180     public void setInputStream(XInputStream aStream) {
181         m_xis = aStream;
182     }
183 
getInputStream()184     public com.sun.star.io.XInputStream getInputStream() {
185         return m_xis;
186     }
187 
188     // --- XActiveDataSource
setOutputStream(XOutputStream aStream)189     public void setOutputStream(XOutputStream aStream) {
190         m_xos = aStream;
191     }
192 
getOutputStream()193     public com.sun.star.io.XOutputStream getOutputStream() {
194         return m_xos;
195     }
196 
197     // --- XActiveDataControl
addListener(XStreamListener aListener)198     public void addListener(XStreamListener aListener) {
199         if (aListener != null && !listeners.contains(aListener)) {
200             listeners.add(aListener);
201         }
202     }
203 
removeListener(XStreamListener aListener)204     public void removeListener(XStreamListener aListener) {
205         if (aListener != null) {
206             listeners.removeElement(aListener);
207         }
208 
209     }
210 
start()211     public void start() {
212         // notify listeners
213         t = new Thread() {
214 
215             @Override
216             public void run() {
217 
218                 // Local variables used outside try block in finally block
219                 InputStream is = null;
220                 Source source = null;
221                 BufferedOutputStream os = null;
222                 PrintStream origOut = System.out;
223                 PrintStream origErr = System.err;
224                 if (statsp != null) {
225                     System.setErr(statsp);
226                     System.setOut(statsp);
227                 }
228                 try {
229                     debug("\n\nStarting transformation...");
230 
231                     // Set up context class loader for SAXParserFactory and
232                     // TransformerFactory calls below:
233                     setContextClassLoader(this.getClass().getClassLoader());
234 
235                     for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
236                         XStreamListener l = (XStreamListener) e.nextElement();
237                         l.started();
238                     }
239 
240                     XSeekable xseek = (XSeekable) UnoRuntime.queryInterface(XSeekable.class, m_xis);
241                     if (xseek != null) {
242                         xseek.seek(0);
243                     }
244 
245                     is = new BufferedInputStream(
246                             new XInputStreamToInputStreamAdapter(m_xis));
247                     //Source xmlsource = new StreamSource(xmlinput);
248                     SAXParserFactory spf = SAXParserFactory.newInstance();
249                     spf.setValidating(false);
250                     spf.setNamespaceAware(true);
251                     XMLReader xmlReader = spf.newSAXParser().getXMLReader();
252                     xmlReader.setEntityResolver(XSLTransformer.this);
253                     source = new SAXSource(xmlReader, new InputSource(is));
254 
255                     // in order to help performance and to remedy a a possible memory
256                     // leak in xalan, where it seems, that Transformer instances cannot
257                     // be reclaimed though they are no longer referenced here, we use
258                     // a cache of weak references (ie. xsltReferences) created for specific
259                     // style sheet URLs see also #i48384#
260 
261                     Templates xsltTemplate = null;
262                     Transformer transformer = null;
263                     Transformation transformation = null;
264                     // File stylefile = new File(new URI(stylesheeturl));
265                     long lastmod = 0;
266                     try {
267                         URL uStyle = new URL(stylesheeturl);
268                         URLConnection c = uStyle.openConnection();
269                         lastmod = c.getLastModified();
270                     } catch (java.lang.Exception ex) {
271                         // lastmod will remain at 0;
272                         if (statsp != null) {
273                             statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
274                             ex.printStackTrace(statsp);
275                         }
276                     }
277 
278                     synchronized (xsltReferences) {
279                         java.lang.ref.WeakReference ref = null;
280                         // try to get the xsltTemplate reference from the cache
281                         if ((ref = (java.lang.ref.WeakReference) xsltReferences.get(stylesheeturl)) == null ||
282                                 (transformation = ((Transformation) ref.get())) == null ||
283                                 ((Transformation) ref.get()).lastmod < lastmod) {
284                             // we cannot find a valid reference for this stylesheet
285                             // or the stylesheet was updated
286                             if (ref != null) {
287                                 xsltReferences.remove(stylesheeturl);
288                             }
289                             // create new xsltTemplate for this stylesheet
290                             TransformerFactory tfactory = TransformerFactory.newInstance();
291                             debug("TransformerFactory is '" + tfactory.getClass().getName() + "'");
292                 // some external saxons (Debian, Ubuntu, ...) have this disabled
293                 // per default
294                 tfactory.setAttribute(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, true);
295                             xsltTemplate = tfactory.newTemplates(new StreamSource(stylesheeturl));
296 
297                             // store the transformation into the cache
298                             transformation = new Transformation();
299                             transformation.lastmod = lastmod;
300                             transformation.cachedXSLT = xsltTemplate;
301                             ref = new java.lang.ref.WeakReference(transformation);
302                             xsltReferences.put(stylesheeturl, ref);
303                         }
304                     }
305                     xsltTemplate = transformation.cachedXSLT;
306                             transformer = xsltTemplate.newTransformer();
307                             transformer.setOutputProperty("encoding", "UTF-8");
308                             // transformer.setURIResolver(XSLTransformer.this);
309 
310                     // invalid to set 'null' as parameter as 'null' is not a valid Java object
311                     if (sourceurl != null) {
312                         transformer.setParameter("sourceURL", sourceurl);
313                     }
314                     if (sourcebaseurl != null) {
315                         transformer.setParameter("sourceBaseURL", sourcebaseurl);
316                     }
317                     if (targeturl != null) {
318                         transformer.setParameter("targetURL", targeturl);
319                     }
320                     if (targetbaseurl != null) {
321                         transformer.setParameter("targetBaseURL", targetbaseurl);
322                     }
323                     if (pubtype != null) {
324                         transformer.setParameter("publicType", pubtype);
325                     }
326                     if (systype != null) {
327                         transformer.setParameter("systemType", systype);
328                     }
329                     if (svcfactory != null) {
330                         transformer.setParameter("XMultiServiceFactory", svcfactory);
331                     }
332                     os = new BufferedOutputStream(
333                             new XOutputStreamToOutputStreamAdapter(m_xos));
334                     StreamResult sr = new StreamResult(os);
335                     long tstart = System.currentTimeMillis();
336                     transformer.transform(source, sr);
337                     debug("finished transformation in " + (System.currentTimeMillis() - tstart) + "ms");
338 
339                 } catch (java.lang.Throwable ex) {
340                     // notify any listeners about close
341                     for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
342 
343                         XStreamListener l = (XStreamListener) e.nextElement();
344                         l.error(new com.sun.star.uno.Exception(ex.getClass().getName() + ": " + ex.getMessage()));
345                     }
346                     if (statsp != null) {
347                         statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
348                         ex.printStackTrace(statsp);
349                     }
350                 } finally {
351                     // dereference input buffer
352                     source = null;
353                     try {
354                         if (is != null) {
355                             is.close();
356                         }
357                     } catch (java.lang.Throwable ex) {
358                         if (statsp != null) {
359                             statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
360                             ex.printStackTrace(statsp);
361                         }
362                     }
363                     try {
364                         if (os != null) {
365                             os.close();
366                         }
367                     } catch (java.lang.Throwable ex) {
368                         if (statsp != null) {
369                             statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
370                             ex.printStackTrace(statsp);
371                         }
372                     }
373                     try {
374                         if (m_xis != null) {
375                             m_xis.closeInput();
376                         }
377                     } catch (java.lang.Throwable ex) {
378                         if (statsp != null) {
379                             statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
380                             ex.printStackTrace(statsp);
381                         }
382                     }
383                     try {
384                         if (m_xos != null) {
385                             m_xos.closeOutput();
386                         }
387                     } catch (java.lang.Throwable ex) {
388                         if (statsp != null) {
389                             statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
390                             ex.printStackTrace(statsp);
391                         }
392                     }
393 
394                     // resetting standard input/error streams from logfile to default
395                     if (statsp != null) {
396                         System.setErr(origErr);
397                         System.setOut(origOut);
398                     }
399                     // try to release references asap...
400                     m_xos = null;
401                     m_xis = null;
402                     is = null;
403                     os = null;
404                     // notify any listeners about close
405                     if (listeners != null) {
406                         for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
407                             XStreamListener l = (XStreamListener) e.nextElement();
408                             l.closed();
409                         }
410                     }
411                 }
412             }
413         };
414         t.start();
415     } /* a statsfile have to be created as precondition to use this function */
416 
417 
debug(String s)418     private static final void debug(String s) {
419         if (statsp != null) {
420             statsp.println(s);
421         }
422     }
423 
terminate()424     public void terminate() {
425         try {
426             debug("terminate called");
427             if (t.isAlive()) {
428                 t.interrupt();
429                 for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
430                     XStreamListener l = (XStreamListener) e.nextElement();
431                     l.terminated();
432                 }
433             }
434         } catch (java.lang.Exception ex) {
435             if (statsp != null) {
436                 statsp.println(ex.getClass().getName() + ": " + ex.getMessage());
437                 ex.printStackTrace(statsp);
438             }
439         }
440     }    // --- component management interfaces... ---
441     private final static String _serviceName = "com.sun.star.comp.JAXTHelper";
442 
443     // Implement methods from interface XTypeProvider
getImplementationId()444     public byte[] getImplementationId() {
445         byte[] byteReturn = {};
446         byteReturn = new String("" + this.hashCode()).getBytes();
447         return (byteReturn);
448     }
449 
getTypes()450     public com.sun.star.uno.Type[] getTypes() {
451         Type[] typeReturn = {};
452         try {
453             typeReturn = new Type[]{
454                         new Type(XTypeProvider.class),
455                         new Type(XServiceName.class),
456                         new Type(XServiceInfo.class),
457                         new Type(XActiveDataSource.class),
458                         new Type(XActiveDataSink.class),
459                         new Type(XActiveDataControl.class),
460                         new Type(XInitialization.class)
461                     };
462         } catch (java.lang.Exception exception) {
463         }
464 
465         return (typeReturn);
466     }
467 
468     // --- Implement method from interface XServiceName ---
getServiceName()469     public String getServiceName() {
470         return (_serviceName);
471     }
472 
473     // --- Implement methods from interface XServiceInfo ---
supportsService(String stringServiceName)474     public boolean supportsService(String stringServiceName) {
475         return (stringServiceName.equals(_serviceName));
476     }
477 
getImplementationName()478     public String getImplementationName() {
479         return (XSLTransformer.class.getName());
480     }
481 
getSupportedServiceNames()482     public String[] getSupportedServiceNames() {
483         String[] stringSupportedServiceNames = {_serviceName};
484         return stringSupportedServiceNames;
485     }
486 
487     // --- component registration methods ---
__getServiceFactory( String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey)488     public static XSingleServiceFactory __getServiceFactory(
489             String implName, XMultiServiceFactory multiFactory, XRegistryKey regKey) {
490         XSingleServiceFactory xSingleServiceFactory = null;
491         if (implName.indexOf("XSLTransformer") != -1) {
492             xSingleServiceFactory = FactoryHelper.getServiceFactory(XSLTransformer.class,
493                     _serviceName, multiFactory, regKey);
494         }
495         return xSingleServiceFactory;
496     }
497 }
498