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, new Boolean(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