001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.component.xslt; 018 019import java.io.IOException; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import javax.xml.transform.ErrorListener; 024import javax.xml.transform.Source; 025import javax.xml.transform.TransformerException; 026import javax.xml.transform.TransformerFactory; 027import javax.xml.transform.URIResolver; 028import org.xml.sax.EntityResolver; 029 030import org.apache.camel.Component; 031import org.apache.camel.Exchange; 032import org.apache.camel.api.management.ManagedAttribute; 033import org.apache.camel.api.management.ManagedOperation; 034import org.apache.camel.api.management.ManagedResource; 035import org.apache.camel.builder.xml.ResultHandlerFactory; 036import org.apache.camel.builder.xml.XsltBuilder; 037import org.apache.camel.converter.jaxp.XmlConverter; 038import org.apache.camel.impl.ProcessorEndpoint; 039import org.apache.camel.spi.Metadata; 040import org.apache.camel.spi.UriEndpoint; 041import org.apache.camel.spi.UriParam; 042import org.apache.camel.spi.UriPath; 043import org.apache.camel.util.EndpointHelper; 044import org.apache.camel.util.ObjectHelper; 045import org.apache.camel.util.ServiceHelper; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049/** 050 * Transforms the message using a XSLT template. 051 */ 052@ManagedResource(description = "Managed XsltEndpoint") 053@UriEndpoint(scheme = "xslt", title = "XSLT", syntax = "xslt:resourceUri", producerOnly = true, label = "core,transformation") 054public class XsltEndpoint extends ProcessorEndpoint { 055 public static final String SAXON_TRANSFORMER_FACTORY_CLASS_NAME = "net.sf.saxon.TransformerFactoryImpl"; 056 057 private static final Logger LOG = LoggerFactory.getLogger(XsltEndpoint.class); 058 059 060 private volatile boolean cacheCleared; 061 private volatile XsltBuilder xslt; 062 private Map<String, Object> parameters; 063 064 @UriPath @Metadata(required = "true") 065 private String resourceUri; 066 @UriParam(defaultValue = "true") 067 private boolean contentCache = true; 068 @UriParam(label = "advanced") 069 private XmlConverter converter; 070 @UriParam(label = "advanced") 071 private String transformerFactoryClass; 072 @UriParam(label = "advanced") 073 private TransformerFactory transformerFactory; 074 @UriParam 075 private boolean saxon; 076 @UriParam(label = "advanced", javaType = "java.lang.String") 077 private List<Object> saxonExtensionFunctions; 078 @UriParam(label = "advanced") 079 private ResultHandlerFactory resultHandlerFactory; 080 @UriParam(defaultValue = "true") 081 private boolean failOnNullBody = true; 082 @UriParam(defaultValue = "string") 083 private XsltOutput output = XsltOutput.string; 084 @UriParam(defaultValue = "0") 085 private int transformerCacheSize; 086 @UriParam(label = "advanced") 087 private ErrorListener errorListener; 088 @UriParam(label = "advanced") 089 private URIResolver uriResolver; 090 @UriParam(defaultValue = "true") 091 private boolean allowStAX = true; 092 @UriParam 093 private boolean deleteOutputFile; 094 @UriParam(label = "advanced") 095 private EntityResolver entityResolver; 096 097 @Deprecated 098 public XsltEndpoint(String endpointUri, Component component, XsltBuilder xslt, String resourceUri, 099 boolean cacheStylesheet) throws Exception { 100 super(endpointUri, component, xslt); 101 this.xslt = xslt; 102 this.resourceUri = resourceUri; 103 this.contentCache = cacheStylesheet; 104 } 105 106 public XsltEndpoint(String endpointUri, Component component) { 107 super(endpointUri, component); 108 } 109 110 @ManagedOperation(description = "Clears the cached XSLT stylesheet, forcing to re-load the stylesheet on next request") 111 public void clearCachedStylesheet() { 112 this.cacheCleared = true; 113 } 114 115 @ManagedAttribute(description = "Whether the XSLT stylesheet is cached") 116 public boolean isCacheStylesheet() { 117 return contentCache; 118 } 119 120 public XsltEndpoint findOrCreateEndpoint(String uri, String newResourceUri) { 121 String newUri = uri.replace(resourceUri, newResourceUri); 122 LOG.trace("Getting endpoint with URI: {}", newUri); 123 return getCamelContext().getEndpoint(newUri, XsltEndpoint.class); 124 } 125 126 @Override 127 protected void onExchange(Exchange exchange) throws Exception { 128 if (!contentCache || cacheCleared) { 129 loadResource(resourceUri); 130 } 131 super.onExchange(exchange); 132 } 133 134 public boolean isCacheCleared() { 135 return cacheCleared; 136 } 137 138 public void setCacheCleared(boolean cacheCleared) { 139 this.cacheCleared = cacheCleared; 140 } 141 142 public XsltBuilder getXslt() { 143 return xslt; 144 } 145 146 public void setXslt(XsltBuilder xslt) { 147 this.xslt = xslt; 148 } 149 150 @ManagedAttribute(description = "The name of the template to load from classpath or file system") 151 public String getResourceUri() { 152 return resourceUri; 153 } 154 155 /** 156 * The name of the template to load from classpath or file system 157 */ 158 public void setResourceUri(String resourceUri) { 159 this.resourceUri = resourceUri; 160 } 161 162 public XmlConverter getConverter() { 163 return converter; 164 } 165 166 /** 167 * To use a custom implementation of {@link org.apache.camel.converter.jaxp.XmlConverter} 168 */ 169 public void setConverter(XmlConverter converter) { 170 this.converter = converter; 171 } 172 173 public String getTransformerFactoryClass() { 174 return transformerFactoryClass; 175 } 176 177 /** 178 * To use a custom XSLT transformer factory, specified as a FQN class name 179 */ 180 public void setTransformerFactoryClass(String transformerFactoryClass) { 181 this.transformerFactoryClass = transformerFactoryClass; 182 } 183 184 public TransformerFactory getTransformerFactory() { 185 return transformerFactory; 186 } 187 188 /** 189 * To use a custom XSLT transformer factory 190 */ 191 public void setTransformerFactory(TransformerFactory transformerFactory) { 192 this.transformerFactory = transformerFactory; 193 } 194 195 @ManagedAttribute(description = "Whether to use Saxon as the transformerFactoryClass") 196 public boolean isSaxon() { 197 return saxon; 198 } 199 200 /** 201 * Whether to use Saxon as the transformerFactoryClass. 202 * If enabled then the class net.sf.saxon.TransformerFactoryImpl. You would need to add Saxon to the classpath. 203 */ 204 public void setSaxon(boolean saxon) { 205 this.saxon = saxon; 206 } 207 208 public List<Object> getSaxonExtensionFunctions() { 209 return saxonExtensionFunctions; 210 } 211 212 /** 213 * Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition. 214 * You would need to add camel-saxon to the classpath. 215 * The function is looked up in the registry, where you can comma to separate multiple values to lookup. 216 */ 217 public void setSaxonExtensionFunctions(List<Object> extensionFunctions) { 218 this.saxonExtensionFunctions = extensionFunctions; 219 } 220 221 /** 222 * Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition. 223 * You would need to add camel-saxon to the classpath. 224 * The function is looked up in the registry, where you can comma to separate multiple values to lookup. 225 */ 226 public void setSaxonExtensionFunctions(String extensionFunctions) { 227 this.saxonExtensionFunctions = EndpointHelper.resolveReferenceListParameter( 228 getCamelContext(), 229 extensionFunctions, 230 Object.class 231 ); 232 } 233 234 public ResultHandlerFactory getResultHandlerFactory() { 235 return resultHandlerFactory; 236 } 237 238 /** 239 * Allows you to use a custom org.apache.camel.builder.xml.ResultHandlerFactory which is capable of 240 * using custom org.apache.camel.builder.xml.ResultHandler types. 241 */ 242 public void setResultHandlerFactory(ResultHandlerFactory resultHandlerFactory) { 243 this.resultHandlerFactory = resultHandlerFactory; 244 } 245 246 @ManagedAttribute(description = "Whether or not to throw an exception if the input body is null") 247 public boolean isFailOnNullBody() { 248 return failOnNullBody; 249 } 250 251 /** 252 * Whether or not to throw an exception if the input body is null. 253 */ 254 public void setFailOnNullBody(boolean failOnNullBody) { 255 this.failOnNullBody = failOnNullBody; 256 } 257 258 @ManagedAttribute(description = "What kind of option to use.") 259 public XsltOutput getOutput() { 260 return output; 261 } 262 263 /** 264 * Option to specify which output type to use. 265 * Possible values are: string, bytes, DOM, file. The first three options are all in memory based, where as file is streamed directly to a java.io.File. 266 * For file you must specify the filename in the IN header with the key Exchange.XSLT_FILE_NAME which is also CamelXsltFileName. 267 * Also any paths leading to the filename must be created beforehand, otherwise an exception is thrown at runtime. 268 */ 269 public void setOutput(XsltOutput output) { 270 this.output = output; 271 } 272 273 public int getTransformerCacheSize() { 274 return transformerCacheSize; 275 } 276 277 /** 278 * The number of javax.xml.transform.Transformer object that are cached for reuse to avoid calls to Template.newTransformer(). 279 */ 280 public void setTransformerCacheSize(int transformerCacheSize) { 281 this.transformerCacheSize = transformerCacheSize; 282 } 283 284 public ErrorListener getErrorListener() { 285 return errorListener; 286 } 287 288 /** 289 * Allows to configure to use a custom javax.xml.transform.ErrorListener. Beware when doing this then the default error 290 * listener which captures any errors or fatal errors and store information on the Exchange as properties is not in use. 291 * So only use this option for special use-cases. 292 */ 293 public void setErrorListener(ErrorListener errorListener) { 294 this.errorListener = errorListener; 295 } 296 297 @ManagedAttribute(description = "Cache for the resource content (the stylesheet file) when it is loaded.") 298 public boolean isContentCache() { 299 return contentCache; 300 } 301 302 /** 303 * Cache for the resource content (the stylesheet file) when it is loaded. 304 * If set to false Camel will reload the stylesheet file on each message processing. This is good for development. 305 * A cached stylesheet can be forced to reload at runtime via JMX using the clearCachedStylesheet operation. 306 */ 307 public void setContentCache(boolean contentCache) { 308 this.contentCache = contentCache; 309 } 310 311 public URIResolver getUriResolver() { 312 return uriResolver; 313 } 314 315 /** 316 * To use a custom javax.xml.transform.URIResolver 317 */ 318 public void setUriResolver(URIResolver uriResolver) { 319 this.uriResolver = uriResolver; 320 } 321 322 @ManagedAttribute(description = "Whether to allow using StAX as the javax.xml.transform.Source") 323 public boolean isAllowStAX() { 324 return allowStAX; 325 } 326 327 /** 328 * Whether to allow using StAX as the javax.xml.transform.Source. 329 */ 330 public void setAllowStAX(boolean allowStAX) { 331 this.allowStAX = allowStAX; 332 } 333 334 public boolean isDeleteOutputFile() { 335 return deleteOutputFile; 336 } 337 338 /** 339 * If you have output=file then this option dictates whether or not the output file should be deleted when the Exchange 340 * is done processing. For example suppose the output file is a temporary file, then it can be a good idea to delete it after use. 341 */ 342 public void setDeleteOutputFile(boolean deleteOutputFile) { 343 this.deleteOutputFile = deleteOutputFile; 344 } 345 346 public EntityResolver getEntityResolver() { 347 return entityResolver; 348 } 349 350 /** 351 * To use a custom org.xml.sax.EntityResolver with javax.xml.transform.sax.SAXSource. 352 */ 353 public void setEntityResolver(EntityResolver entityResolver) { 354 this.entityResolver = entityResolver; 355 } 356 357 public Map<String, Object> getParameters() { 358 return parameters; 359 } 360 361 /** 362 * Additional parameters to configure on the javax.xml.transform.Transformer. 363 */ 364 public void setParameters(Map<String, Object> parameters) { 365 this.parameters = parameters; 366 } 367 368 /** 369 * Loads the resource. 370 * 371 * @param resourceUri the resource to load 372 * @throws TransformerException is thrown if error loading resource 373 * @throws IOException is thrown if error loading resource 374 */ 375 protected void loadResource(String resourceUri) throws TransformerException, IOException { 376 LOG.trace("{} loading schema resource: {}", this, resourceUri); 377 Source source = xslt.getUriResolver().resolve(resourceUri, null); 378 if (source == null) { 379 throw new IOException("Cannot load schema resource " + resourceUri); 380 } else { 381 source.setSystemId(resourceUri); 382 xslt.setTransformerSource(source); 383 } 384 // now loaded so clear flag 385 cacheCleared = false; 386 } 387 388 @Override 389 protected void doStart() throws Exception { 390 super.doStart(); 391 392 LOG.debug("{} using schema resource: {}", this, resourceUri); 393 394 this.xslt = getCamelContext().getInjector().newInstance(XsltBuilder.class); 395 if (converter != null) { 396 xslt.setConverter(converter); 397 } 398 399 boolean useSaxon = false; 400 if (transformerFactoryClass == null && (saxon || saxonExtensionFunctions != null)) { 401 useSaxon = true; 402 transformerFactoryClass = SAXON_TRANSFORMER_FACTORY_CLASS_NAME; 403 } 404 405 TransformerFactory factory = transformerFactory; 406 if (factory == null && transformerFactoryClass != null) { 407 // provide the class loader of this component to work in OSGi environments 408 Class<?> factoryClass = getCamelContext().getClassResolver().resolveMandatoryClass(transformerFactoryClass, XsltComponent.class.getClassLoader()); 409 LOG.debug("Using TransformerFactoryClass {}", factoryClass); 410 factory = (TransformerFactory) getCamelContext().getInjector().newInstance(factoryClass); 411 412 if (useSaxon) { 413 XsltHelper.registerSaxonExtensionFunctions( 414 getCamelContext(), 415 factoryClass, 416 factory, 417 saxonExtensionFunctions 418 ); 419 } 420 } 421 422 if (factory != null) { 423 LOG.debug("Using TransformerFactory {}", factory); 424 xslt.getConverter().setTransformerFactory(factory); 425 } 426 if (resultHandlerFactory != null) { 427 xslt.setResultHandlerFactory(resultHandlerFactory); 428 } 429 if (errorListener != null) { 430 xslt.errorListener(errorListener); 431 } 432 xslt.setFailOnNullBody(failOnNullBody); 433 xslt.transformerCacheSize(transformerCacheSize); 434 xslt.setUriResolver(uriResolver); 435 xslt.setEntityResolver(entityResolver); 436 xslt.setAllowStAX(allowStAX); 437 xslt.setDeleteOutputFile(deleteOutputFile); 438 439 configureOutput(xslt, output.name()); 440 441 // any additional transformer parameters then make a copy to avoid side-effects 442 if (parameters != null) { 443 Map<String, Object> copy = new HashMap<String, Object>(parameters); 444 xslt.setParameters(copy); 445 } 446 447 // must load resource first which sets a template and do a stylesheet compilation to catch errors early 448 loadResource(resourceUri); 449 450 // the processor is the xslt builder 451 setProcessor(xslt); 452 } 453 454 protected void configureOutput(XsltBuilder xslt, String output) throws Exception { 455 if (ObjectHelper.isEmpty(output)) { 456 return; 457 } 458 459 if ("string".equalsIgnoreCase(output)) { 460 xslt.outputString(); 461 } else if ("bytes".equalsIgnoreCase(output)) { 462 xslt.outputBytes(); 463 } else if ("DOM".equalsIgnoreCase(output)) { 464 xslt.outputDOM(); 465 } else if ("file".equalsIgnoreCase(output)) { 466 xslt.outputFile(); 467 } else { 468 throw new IllegalArgumentException("Unknown output type: " + output); 469 } 470 } 471 472 @Override 473 protected void doStop() throws Exception { 474 super.doStop(); 475 ServiceHelper.stopService(xslt); 476 } 477}