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