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