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