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 = "The name of the template to load from classpath or file system") 158 public String getResourceUri() { 159 return resourceUri; 160 } 161 162 /** 163 * The name of the template to load from classpath or file system 164 */ 165 public void setResourceUri(String resourceUri) { 166 this.resourceUri = resourceUri; 167 } 168 169 public XmlConverter getConverter() { 170 return converter; 171 } 172 173 /** 174 * To use a custom implementation of {@link org.apache.camel.converter.jaxp.XmlConverter} 175 */ 176 public void setConverter(XmlConverter converter) { 177 this.converter = converter; 178 } 179 180 public String getTransformerFactoryClass() { 181 return transformerFactoryClass; 182 } 183 184 /** 185 * To use a custom XSLT transformer factory, specified as a FQN class name 186 */ 187 public void setTransformerFactoryClass(String transformerFactoryClass) { 188 this.transformerFactoryClass = transformerFactoryClass; 189 } 190 191 public TransformerFactory getTransformerFactory() { 192 return transformerFactory; 193 } 194 195 /** 196 * To use a custom XSLT transformer factory 197 */ 198 public void setTransformerFactory(TransformerFactory transformerFactory) { 199 this.transformerFactory = transformerFactory; 200 } 201 202 @ManagedAttribute(description = "Whether to use Saxon as the transformerFactoryClass") 203 public boolean isSaxon() { 204 return saxon; 205 } 206 207 /** 208 * Whether to use Saxon as the transformerFactoryClass. 209 * If enabled then the class net.sf.saxon.TransformerFactoryImpl. You would need to add Saxon to the classpath. 210 */ 211 public void setSaxon(boolean saxon) { 212 this.saxon = saxon; 213 } 214 215 public List<Object> getSaxonExtensionFunctions() { 216 return saxonExtensionFunctions; 217 } 218 219 /** 220 * Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition. 221 * You would need to add camel-saxon to the classpath. 222 * The function is looked up in the registry, where you can comma to separate multiple values to lookup. 223 */ 224 public void setSaxonExtensionFunctions(List<Object> extensionFunctions) { 225 this.saxonExtensionFunctions = extensionFunctions; 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(String extensionFunctions) { 234 this.saxonExtensionFunctions = EndpointHelper.resolveReferenceListParameter( 235 getCamelContext(), 236 extensionFunctions, 237 Object.class 238 ); 239 } 240 241 public Object getSaxonConfiguration() { 242 return saxonConfiguration; 243 } 244 245 /** 246 * To use a custom Saxon configuration 247 */ 248 public void setSaxonConfiguration(Object saxonConfiguration) { 249 this.saxonConfiguration = saxonConfiguration; 250 } 251 252 public Map<String, Object> getSaxonConfigurationProperties() { 253 return saxonConfigurationProperties; 254 } 255 256 /** 257 * To set custom Saxon configuration properties 258 */ 259 public void setSaxonConfigurationProperties(Map<String, Object> configurationProperties) { 260 this.saxonConfigurationProperties = configurationProperties; 261 } 262 263 public ResultHandlerFactory getResultHandlerFactory() { 264 return resultHandlerFactory; 265 } 266 267 /** 268 * Allows you to use a custom org.apache.camel.builder.xml.ResultHandlerFactory which is capable of 269 * using custom org.apache.camel.builder.xml.ResultHandler types. 270 */ 271 public void setResultHandlerFactory(ResultHandlerFactory resultHandlerFactory) { 272 this.resultHandlerFactory = resultHandlerFactory; 273 } 274 275 @ManagedAttribute(description = "Whether or not to throw an exception if the input body is null") 276 public boolean isFailOnNullBody() { 277 return failOnNullBody; 278 } 279 280 /** 281 * Whether or not to throw an exception if the input body is null. 282 */ 283 public void setFailOnNullBody(boolean failOnNullBody) { 284 this.failOnNullBody = failOnNullBody; 285 } 286 287 @ManagedAttribute(description = "What kind of option to use.") 288 public XsltOutput getOutput() { 289 return output; 290 } 291 292 /** 293 * Option to specify which output type to use. 294 * 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. 295 * For file you must specify the filename in the IN header with the key Exchange.XSLT_FILE_NAME which is also CamelXsltFileName. 296 * Also any paths leading to the filename must be created beforehand, otherwise an exception is thrown at runtime. 297 */ 298 public void setOutput(XsltOutput output) { 299 this.output = output; 300 } 301 302 public int getTransformerCacheSize() { 303 return transformerCacheSize; 304 } 305 306 /** 307 * The number of javax.xml.transform.Transformer object that are cached for reuse to avoid calls to Template.newTransformer(). 308 */ 309 public void setTransformerCacheSize(int transformerCacheSize) { 310 this.transformerCacheSize = transformerCacheSize; 311 } 312 313 public ErrorListener getErrorListener() { 314 return errorListener; 315 } 316 317 /** 318 * Allows to configure to use a custom javax.xml.transform.ErrorListener. Beware when doing this then the default error 319 * listener which captures any errors or fatal errors and store information on the Exchange as properties is not in use. 320 * So only use this option for special use-cases. 321 */ 322 public void setErrorListener(ErrorListener errorListener) { 323 this.errorListener = errorListener; 324 } 325 326 @ManagedAttribute(description = "Cache for the resource content (the stylesheet file) when it is loaded.") 327 public boolean isContentCache() { 328 return contentCache; 329 } 330 331 /** 332 * Cache for the resource content (the stylesheet file) when it is loaded. 333 * If set to false Camel will reload the stylesheet file on each message processing. This is good for development. 334 * A cached stylesheet can be forced to reload at runtime via JMX using the clearCachedStylesheet operation. 335 */ 336 public void setContentCache(boolean contentCache) { 337 this.contentCache = contentCache; 338 } 339 340 public URIResolver getUriResolver() { 341 return uriResolver; 342 } 343 344 /** 345 * To use a custom javax.xml.transform.URIResolver 346 */ 347 public void setUriResolver(URIResolver uriResolver) { 348 this.uriResolver = uriResolver; 349 } 350 351 @ManagedAttribute(description = "Whether to allow using StAX as the javax.xml.transform.Source") 352 public boolean isAllowStAX() { 353 return allowStAX; 354 } 355 356 /** 357 * Whether to allow using StAX as the javax.xml.transform.Source. 358 */ 359 public void setAllowStAX(boolean allowStAX) { 360 this.allowStAX = allowStAX; 361 } 362 363 public boolean isDeleteOutputFile() { 364 return deleteOutputFile; 365 } 366 367 /** 368 * If you have output=file then this option dictates whether or not the output file should be deleted when the Exchange 369 * 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. 370 */ 371 public void setDeleteOutputFile(boolean deleteOutputFile) { 372 this.deleteOutputFile = deleteOutputFile; 373 } 374 375 public EntityResolver getEntityResolver() { 376 return entityResolver; 377 } 378 379 /** 380 * To use a custom org.xml.sax.EntityResolver with javax.xml.transform.sax.SAXSource. 381 */ 382 public void setEntityResolver(EntityResolver entityResolver) { 383 this.entityResolver = entityResolver; 384 } 385 386 public Map<String, Object> getParameters() { 387 return parameters; 388 } 389 390 /** 391 * Additional parameters to configure on the javax.xml.transform.Transformer. 392 */ 393 public void setParameters(Map<String, Object> parameters) { 394 this.parameters = parameters; 395 } 396 397 /** 398 * Loads the resource. 399 * 400 * @param resourceUri the resource to load 401 * @throws TransformerException is thrown if error loading resource 402 * @throws IOException is thrown if error loading resource 403 */ 404 protected void loadResource(String resourceUri) throws TransformerException, IOException { 405 LOG.trace("{} loading schema resource: {}", this, resourceUri); 406 Source source = xslt.getUriResolver().resolve(resourceUri, null); 407 if (source == null) { 408 throw new IOException("Cannot load schema resource " + resourceUri); 409 } else { 410 source.setSystemId(resourceUri); 411 xslt.setTransformerSource(source); 412 } 413 // now loaded so clear flag 414 cacheCleared = false; 415 } 416 417 @Override 418 protected void doStart() throws Exception { 419 super.doStart(); 420 421 final CamelContext ctx = getCamelContext(); 422 final ClassResolver resolver = ctx.getClassResolver(); 423 final Injector injector = ctx.getInjector(); 424 425 LOG.debug("{} using schema resource: {}", this, resourceUri); 426 427 this.xslt = injector.newInstance(XsltBuilder.class); 428 if (converter != null) { 429 xslt.setConverter(converter); 430 } 431 432 boolean useSaxon = false; 433 if (transformerFactoryClass == null && (saxon || saxonExtensionFunctions != null)) { 434 useSaxon = true; 435 transformerFactoryClass = SAXON_TRANSFORMER_FACTORY_CLASS_NAME; 436 } 437 438 TransformerFactory factory = transformerFactory; 439 if (factory == null && transformerFactoryClass != null) { 440 // provide the class loader of this component to work in OSGi environments 441 Class<TransformerFactory> factoryClass = resolver.resolveMandatoryClass(transformerFactoryClass, TransformerFactory.class, XsltComponent.class.getClassLoader()); 442 LOG.debug("Using TransformerFactoryClass {}", factoryClass); 443 factory = injector.newInstance(factoryClass); 444 445 if (useSaxon) { 446 XsltHelper.registerSaxonConfiguration(ctx, factoryClass, factory, saxonConfiguration); 447 XsltHelper.registerSaxonConfigurationProperties(ctx, factoryClass, factory, saxonConfigurationProperties); 448 XsltHelper.registerSaxonExtensionFunctions(ctx, factoryClass, factory, saxonExtensionFunctions); 449 } 450 } 451 452 if (factory != null) { 453 LOG.debug("Using TransformerFactory {}", factory); 454 xslt.getConverter().setTransformerFactory(factory); 455 } 456 if (resultHandlerFactory != null) { 457 xslt.setResultHandlerFactory(resultHandlerFactory); 458 } 459 if (errorListener != null) { 460 xslt.errorListener(errorListener); 461 } 462 xslt.setFailOnNullBody(failOnNullBody); 463 xslt.transformerCacheSize(transformerCacheSize); 464 xslt.setUriResolver(uriResolver); 465 xslt.setEntityResolver(entityResolver); 466 xslt.setAllowStAX(allowStAX); 467 xslt.setDeleteOutputFile(deleteOutputFile); 468 469 configureOutput(xslt, output.name()); 470 471 // any additional transformer parameters then make a copy to avoid side-effects 472 if (parameters != null) { 473 Map<String, Object> copy = new HashMap<String, Object>(parameters); 474 xslt.setParameters(copy); 475 } 476 477 // must load resource first which sets a template and do a stylesheet compilation to catch errors early 478 loadResource(resourceUri); 479 480 // the processor is the xslt builder 481 setProcessor(xslt); 482 } 483 484 protected void configureOutput(XsltBuilder xslt, String output) throws Exception { 485 if (ObjectHelper.isEmpty(output)) { 486 return; 487 } 488 489 if ("string".equalsIgnoreCase(output)) { 490 xslt.outputString(); 491 } else if ("bytes".equalsIgnoreCase(output)) { 492 xslt.outputBytes(); 493 } else if ("DOM".equalsIgnoreCase(output)) { 494 xslt.outputDOM(); 495 } else if ("file".equalsIgnoreCase(output)) { 496 xslt.outputFile(); 497 } else { 498 throw new IllegalArgumentException("Unknown output type: " + output); 499 } 500 } 501 502 @Override 503 protected void doStop() throws Exception { 504 super.doStop(); 505 ServiceHelper.stopService(xslt); 506 } 507}