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