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