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