001 package org.apache.fulcrum.xslt; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.io.Reader; 023 import java.io.StringWriter; 024 import java.io.Writer; 025 import java.net.MalformedURLException; 026 import java.net.URL; 027 import java.util.Hashtable; 028 import java.util.Iterator; 029 import java.util.Map; 030 031 import javax.xml.transform.Result; 032 import javax.xml.transform.Source; 033 import javax.xml.transform.Templates; 034 import javax.xml.transform.Transformer; 035 import javax.xml.transform.TransformerFactory; 036 import javax.xml.transform.dom.DOMSource; 037 import javax.xml.transform.stream.StreamResult; 038 import javax.xml.transform.stream.StreamSource; 039 import javax.xml.parsers.DocumentBuilder; 040 import javax.xml.parsers.DocumentBuilderFactory; 041 042 import org.apache.avalon.framework.activity.Initializable; 043 import org.apache.avalon.framework.configuration.Configurable; 044 import org.apache.avalon.framework.configuration.Configuration; 045 import org.apache.avalon.framework.configuration.ConfigurationException; 046 import org.apache.avalon.framework.context.Context; 047 import org.apache.avalon.framework.context.ContextException; 048 import org.apache.avalon.framework.context.Contextualizable; 049 import org.apache.avalon.framework.logger.AbstractLogEnabled; 050 import org.apache.avalon.framework.service.ServiceManager; 051 import org.apache.avalon.framework.service.Serviceable; 052 import org.w3c.dom.Node; 053 import org.w3c.dom.Document; 054 055 /** 056 * Implementation of the Turbine XSLT Service. It transforms xml with a given 057 * xsl file. XSL stylesheets are compiled and cached (if the service property is 058 * set) to improve speeds. 059 * 060 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a> 061 * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a> 062 * @author <a href="mailto:epugh@opensourceconnections.com">Eric Pugh</a> 063 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a> 064 */ 065 public class DefaultXSLTService extends AbstractLogEnabled implements 066 XSLTService, Initializable, Configurable, Contextualizable, Serviceable 067 { 068 /** 069 * The application root 070 */ 071 private String applicationRoot; 072 073 /** 074 * Property to control the caching of Templates. 075 */ 076 protected boolean caching = false; 077 078 /** 079 * Path to style sheets used for tranforming well-formed XML documents. The 080 * path is relative to the webapp context. 081 */ 082 protected String path; 083 084 /** 085 * Cache of compiled Templates. 086 */ 087 protected Hashtable cache = new Hashtable(); 088 089 protected final static String STYLESHEET_PATH = "path"; 090 091 protected final static String STYLESHEET_CACHING = "cache"; 092 093 /** 094 * Factory for producing templates and null transformers 095 */ 096 private static TransformerFactory tfactory; 097 098 /** 099 * Try to create a valid url object from the style parameter. 100 * 101 * @param style 102 * the xsl-Style 103 * @return a <code>URL</code> object or null if the style sheet could not 104 * be found 105 */ 106 private URL getStyleURL(String style) 107 { 108 StringBuffer sb = new StringBuffer(); 109 110 sb.append(path); 111 112 // we chop off the existing extension 113 int colon = style.lastIndexOf("."); 114 115 if (colon > 0) 116 { 117 sb.append(style.substring(0, colon)); 118 } 119 else 120 { 121 sb.append(style); 122 } 123 124 sb.append(".xslt"); 125 126 URL url = null; 127 128 try 129 { 130 url = new URL(sb.toString()); 131 } 132 catch (MalformedURLException e) 133 { 134 getLogger().error("Malformed URL: " + sb, e); 135 } 136 137 return url; 138 } 139 140 /** 141 * Compile Templates from an input file. 142 * 143 * @param source the source URL 144 * @return the compiled template 145 * @throws Exception the compilation failed 146 */ 147 protected Templates compileTemplates(URL source) throws Exception 148 { 149 StreamSource xslin = new StreamSource(source.openStream()); 150 return tfactory.newTemplates(xslin); 151 } 152 153 /** 154 * Retrieves Templates. If caching is switched on the first attempt is to 155 * load Templates from the cache. If caching is switched of or if the 156 * Stylesheet is not found in the cache new Templates are compiled from an 157 * input file. 158 * <p> 159 * This method is synchronized on the xsl cache so that a thread does not 160 * attempt to load Templates from the cache while it is still being 161 * compiled. 162 * 163 * @param xslName the name of the XSL file 164 * @return the correspondint template or null if the XSL was not found 165 * @throws Exception getting the template failed 166 */ 167 protected Templates getTemplates(String xslName) throws Exception 168 { 169 synchronized (cache) 170 { 171 URL fn = getStyleURL(xslName); 172 if (fn == null) 173 return null; 174 175 if (caching && cache.containsKey(fn)) 176 { 177 return (Templates) cache.get(fn); 178 } 179 180 Templates sr = compileTemplates(fn); 181 182 if (caching) 183 { 184 cache.put(fn, sr); 185 } 186 187 return sr; 188 } 189 } 190 191 protected void transform(String xslName, Source xmlin, Result xmlout, Map params) 192 throws Exception 193 { 194 try 195 { 196 long startTime = System.currentTimeMillis(); 197 Transformer transformer = getTransformer(xslName); 198 199 if (params != null) 200 { 201 for (Iterator it = params.entrySet().iterator(); it.hasNext(); ) 202 { 203 Map.Entry entry = (Map.Entry) it.next(); 204 transformer.setParameter(String.valueOf(entry.getKey()), entry.getValue()); 205 } 206 } 207 208 transformer.transform(xmlin, xmlout); 209 210 if(getLogger().isDebugEnabled()) 211 { 212 long duration = System.currentTimeMillis() - startTime; 213 getLogger().debug("The transforamtion '" + xslName + "' took " + duration + " ms"); 214 } 215 } 216 catch(Exception e) 217 { 218 getLogger().debug("The transformation '" + xslName + "' failed due to : " + e.getMessage()); 219 throw e; 220 } 221 } 222 223 /** 224 * Uses an xsl file to transform xml input from a reader and writes the 225 * output to a writer. 226 * 227 * @param xslName 228 * The name of the file that contains the xsl stylesheet. 229 * @param in 230 * The reader that passes the xml to be transformed 231 * @param out 232 * The writer for the transformed output 233 */ 234 public void transform(String xslName, Reader in, Writer out) 235 throws Exception 236 { 237 Source xmlin = new StreamSource(in); 238 Result xmlout = new StreamResult(out); 239 transform(xslName, xmlin, xmlout, null); 240 } 241 242 /** 243 * Uses an xsl file to transform xml input from a reader and returns a 244 * string containing the transformed output. 245 * 246 * @param xslName 247 * The name of the file that contains the xsl stylesheet. 248 * @param in 249 * The reader that passes the xml to be transformed 250 */ 251 public String transform(String xslName, Reader in) throws Exception 252 { 253 StringWriter sw = new StringWriter(); 254 transform(xslName, in, sw, null); 255 return sw.toString(); 256 } 257 258 /** 259 * Uses an xsl file to transform xml input from a DOM note and writes the 260 * output to a writer. 261 * 262 * @param xslName 263 * The name of the file that contains the xsl stylesheet. 264 * @param in 265 * The DOM Node to be transformed 266 * @param out 267 * The writer for the transformed output 268 */ 269 public void transform(String xslName, org.w3c.dom.Node in, Writer out) 270 throws Exception 271 { 272 Source xmlin = new DOMSource(in); 273 Result xmlout = new StreamResult(out); 274 transform(xslName, xmlin, xmlout, null); 275 } 276 277 /** 278 * Uses an xsl file to transform xml input from a DOM note and returns a 279 * string containing the transformed output. 280 * 281 * @param xslName 282 * The name of the file that contains the xsl stylesheet. 283 * @param in 284 * The DOM Node to be transformed 285 */ 286 public String transform(String xslName, org.w3c.dom.Node in) 287 throws Exception 288 { 289 StringWriter sw = new StringWriter(); 290 transform(xslName, in, sw); 291 return sw.toString(); 292 } 293 294 /** 295 * Uses an xsl file to transform xml input from a reader and writes the 296 * output to a writer. 297 * 298 * @param xslName 299 * The name of the file that contains the xsl stylesheet. 300 * @param in 301 * The reader that passes the xml to be transformed 302 * @param out 303 * The writer for the transformed output 304 * @param params 305 * A set of parameters that will be forwarded to the XSLT 306 */ 307 public void transform(String xslName, Reader in, Writer out, Map params) 308 throws Exception 309 { 310 Source xmlin = new StreamSource(in); 311 Result xmlout = new StreamResult(out); 312 transform(xslName, xmlin, xmlout, params); 313 } 314 315 /** 316 * Uses an xsl file to transform xml input from a reader and returns a 317 * string containing the transformed output. 318 * 319 * @param xslName 320 * The name of the file that contains the xsl stylesheet. 321 * @param in 322 * The reader that passes the xml to be transformed 323 * @param params 324 * A set of parameters that will be forwarded to the XSLT 325 */ 326 public String transform(String xslName, Reader in, Map params) 327 throws Exception 328 { 329 StringWriter sw = new StringWriter(); 330 transform(xslName, in, sw, params); 331 return sw.toString(); 332 } 333 334 /** 335 * Uses an xsl file to transform xml input from a DOM note and writes the 336 * output to a writer. 337 * 338 * @param xslName 339 * The name of the file that contains the xsl stylesheet. 340 * @param in 341 * The DOM Node to be transformed 342 * @param out 343 * The writer for the transformed output 344 * @param params 345 * A set of parameters that will be forwarded to the XSLT 346 */ 347 public void transform(String xslName, Node in, Writer out, Map params) 348 throws Exception 349 { 350 Source xmlin = new DOMSource(in); 351 Result xmlout = new StreamResult(out); 352 transform(xslName, xmlin, xmlout, params); 353 } 354 355 /** 356 * Uses an xsl file to transform xml input from a DOM note and returns a 357 * string containing the transformed output. 358 * 359 * @param xslName 360 * The name of the file that contains the xsl stylesheet. 361 * @param in 362 * The DOM Node to be transformed 363 * @param params 364 * A set of parameters that will be forwarded to the XSLT 365 */ 366 public String transform(String xslName, Node in, Map params) 367 throws Exception 368 { 369 StringWriter sw = new StringWriter(); 370 transform(xslName, in, sw, params); 371 return sw.toString(); 372 } 373 374 /** 375 * Uses an xsl file without any input. 376 * 377 * @param xslName The name of the file that contains the xsl stylesheet. 378 * @param params A set of parameters that will be forwarded to the XSLT 379 * @return the transformed output 380 * @throws Exception the transformation failed 381 */ 382 public String transform(String xslName, Map params) throws Exception { 383 384 StringWriter sw = new StringWriter(); 385 transform(xslName, sw, params); 386 return sw.toString(); 387 } 388 389 /** 390 * Uses an xsl file without any xml input (simplified stylesheet) 391 * 392 * @param xslName The name of the file that contains the xsl stylesheet 393 * @param out The writer for the transformed output. 394 * @param params A set of parameters that will be forwarded to the XSLT 395 * @throws Exception the transformation failed 396 */ 397 public void transform(String xslName, Writer out, Map params) throws Exception { 398 399 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 400 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); 401 Document document = documentBuilder.newDocument(); 402 403 transform(xslName, document.getDocumentElement(), out, params); 404 } 405 406 /** 407 * Retrieve a transformer for the given stylesheet name. If no stylesheet is 408 * available for the provided name, an identity transformer will be 409 * returned. This allows clients of this service to perform more complex 410 * transformations (for example, where parameters must be set). When 411 * possible prefer using one of the forms of {@link #transform}. 412 * 413 * @param xslName 414 * Identifies stylesheet to get transformer for 415 * @return A transformer for that stylesheet 416 * @throws Exception retrieving the transformer failed 417 */ 418 public Transformer getTransformer(String xslName) throws Exception 419 { 420 Templates sr = getTemplates(xslName); 421 422 if (sr == null) 423 { 424 return tfactory.newTransformer(); 425 } 426 else 427 { 428 return sr.newTransformer(); 429 } 430 } 431 432 // ---------------- Avalon Lifecycle Methods --------------------- 433 /** 434 * Avalon component lifecycle method 435 * 436 * This method processes the repository path, to make it relative to the web 437 * application root, if neccessary. It supports URL-style repositories. 438 */ 439 public void configure(Configuration conf) throws ConfigurationException 440 { 441 StringBuffer sb = new StringBuffer(conf.getAttribute(STYLESHEET_PATH, 442 "/")); 443 444 // is URL? 445 if (!sb.toString().matches("[a-zA-Z]{3,}://.*")) 446 { 447 // No 448 if (sb.charAt(0) != '/') 449 { 450 sb.insert(0, '/'); 451 } 452 sb.insert(0, applicationRoot); 453 sb.insert(0, "file:"); 454 } 455 456 if (sb.charAt(sb.length() - 1) != '/') 457 { 458 sb.append('/'); 459 } 460 461 path = sb.toString(); 462 caching = conf.getAttributeAsBoolean(STYLESHEET_CACHING, false); 463 } 464 465 /** 466 * Initializes the service. 467 */ 468 public void initialize() throws Exception 469 { 470 tfactory = TransformerFactory.newInstance(); 471 } 472 473 public void contextualize(Context context) throws ContextException 474 { 475 this.applicationRoot = context.get("urn:avalon:home").toString(); 476 } 477 478 /** 479 * Avalon component lifecycle method 480 */ 481 public void service(ServiceManager manager) 482 { 483 XSLTServiceFacade.setService(this); 484 } 485 }