-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
XsltEndpoint.java
503 lines (432 loc) · 17.7 KB
/
XsltEndpoint.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.xslt;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import org.xml.sax.EntityResolver;
import org.apache.camel.CamelContext;
import org.apache.camel.Component;
import org.apache.camel.Exchange;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedOperation;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.builder.xml.ResultHandlerFactory;
import org.apache.camel.builder.xml.XsltBuilder;
import org.apache.camel.converter.jaxp.XmlConverter;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.Injector;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriPath;
import org.apache.camel.support.EndpointHelper;
import org.apache.camel.support.ProcessorEndpoint;
import org.apache.camel.support.ServiceHelper;
import org.apache.camel.util.ObjectHelper;
/**
* Transforms the message using a XSLT template.
*/
@ManagedResource(description = "Managed XsltEndpoint")
@UriEndpoint(firstVersion = "1.3.0", scheme = "xslt", title = "XSLT", syntax = "xslt:resourceUri", producerOnly = true, label = "core,transformation")
public class XsltEndpoint extends ProcessorEndpoint {
public static final String SAXON_TRANSFORMER_FACTORY_CLASS_NAME = "net.sf.saxon.TransformerFactoryImpl";
private volatile boolean cacheCleared;
private volatile XsltBuilder xslt;
private Map<String, Object> parameters;
@UriPath @Metadata(required = true)
private String resourceUri;
@UriParam(defaultValue = "true")
private boolean contentCache = true;
@UriParam(label = "advanced")
private XmlConverter converter;
@UriParam(label = "advanced")
private String transformerFactoryClass;
@UriParam(label = "advanced")
private TransformerFactory transformerFactory;
@UriParam
private boolean saxon;
@UriParam(label = "advanced")
private Object saxonConfiguration;
@Metadata(label = "advanced")
private Map<String, Object> saxonConfigurationProperties = new HashMap<>();
@UriParam(label = "advanced", javaType = "java.lang.String")
private List<Object> saxonExtensionFunctions;
@UriParam(label = "advanced")
private ResultHandlerFactory resultHandlerFactory;
@UriParam(defaultValue = "true")
private boolean failOnNullBody = true;
@UriParam(defaultValue = "string")
private XsltOutput output = XsltOutput.string;
@UriParam(defaultValue = "0")
private int transformerCacheSize;
@UriParam(label = "advanced")
private ErrorListener errorListener;
@UriParam(label = "advanced")
private URIResolver uriResolver;
@UriParam(defaultValue = "true", displayName = "Allow StAX")
private boolean allowStAX = true;
@UriParam
private boolean deleteOutputFile;
@UriParam(label = "advanced")
private EntityResolver entityResolver;
public XsltEndpoint(String endpointUri, Component component) {
super(endpointUri, component);
}
@ManagedOperation(description = "Clears the cached XSLT stylesheet, forcing to re-load the stylesheet on next request")
public void clearCachedStylesheet() {
this.cacheCleared = true;
}
@ManagedAttribute(description = "Whether the XSLT stylesheet is cached")
public boolean isCacheStylesheet() {
return contentCache;
}
public XsltEndpoint findOrCreateEndpoint(String uri, String newResourceUri) {
String newUri = uri.replace(resourceUri, newResourceUri);
log.trace("Getting endpoint with URI: {}", newUri);
return getCamelContext().getEndpoint(newUri, XsltEndpoint.class);
}
@Override
protected void onExchange(Exchange exchange) throws Exception {
if (!contentCache || cacheCleared) {
loadResource(resourceUri);
}
super.onExchange(exchange);
}
public boolean isCacheCleared() {
return cacheCleared;
}
public void setCacheCleared(boolean cacheCleared) {
this.cacheCleared = cacheCleared;
}
public XsltBuilder getXslt() {
return xslt;
}
public void setXslt(XsltBuilder xslt) {
this.xslt = xslt;
}
@ManagedAttribute(description = "Path to the template")
public String getResourceUri() {
return resourceUri;
}
/**
* Path to the template.
* <p/>
* The following is supported by the default URIResolver.
* You can prefix with: classpath, file, http, ref, or bean.
* classpath, file and http loads the resource using these protocols (classpath is default).
* ref will lookup the resource in the registry.
* bean will call a method on a bean to be used as the resource.
* For bean you can specify the method name after dot, eg bean:myBean.myMethod
*
* @param resourceUri the resource path
*/
public void setResourceUri(String resourceUri) {
this.resourceUri = resourceUri;
}
public XmlConverter getConverter() {
return converter;
}
/**
* To use a custom implementation of {@link org.apache.camel.converter.jaxp.XmlConverter}
*/
public void setConverter(XmlConverter converter) {
this.converter = converter;
}
public String getTransformerFactoryClass() {
return transformerFactoryClass;
}
/**
* To use a custom XSLT transformer factory, specified as a FQN class name
*/
public void setTransformerFactoryClass(String transformerFactoryClass) {
this.transformerFactoryClass = transformerFactoryClass;
}
public TransformerFactory getTransformerFactory() {
return transformerFactory;
}
/**
* To use a custom XSLT transformer factory
*/
public void setTransformerFactory(TransformerFactory transformerFactory) {
this.transformerFactory = transformerFactory;
}
@ManagedAttribute(description = "Whether to use Saxon as the transformerFactoryClass")
public boolean isSaxon() {
return saxon;
}
/**
* Whether to use Saxon as the transformerFactoryClass.
* If enabled then the class net.sf.saxon.TransformerFactoryImpl. You would need to add Saxon to the classpath.
*/
public void setSaxon(boolean saxon) {
this.saxon = saxon;
}
public List<Object> getSaxonExtensionFunctions() {
return saxonExtensionFunctions;
}
/**
* Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition.
* You would need to add camel-saxon to the classpath.
* The function is looked up in the registry, where you can comma to separate multiple values to lookup.
*/
public void setSaxonExtensionFunctions(List<Object> extensionFunctions) {
this.saxonExtensionFunctions = extensionFunctions;
}
/**
* Allows you to use a custom net.sf.saxon.lib.ExtensionFunctionDefinition.
* You would need to add camel-saxon to the classpath.
* The function is looked up in the registry, where you can comma to separate multiple values to lookup.
*/
public void setSaxonExtensionFunctions(String extensionFunctions) {
this.saxonExtensionFunctions = EndpointHelper.resolveReferenceListParameter(
getCamelContext(),
extensionFunctions,
Object.class
);
}
public Object getSaxonConfiguration() {
return saxonConfiguration;
}
/**
* To use a custom Saxon configuration
*/
public void setSaxonConfiguration(Object saxonConfiguration) {
this.saxonConfiguration = saxonConfiguration;
}
public Map<String, Object> getSaxonConfigurationProperties() {
return saxonConfigurationProperties;
}
/**
* To set custom Saxon configuration properties
*/
public void setSaxonConfigurationProperties(Map<String, Object> configurationProperties) {
this.saxonConfigurationProperties = configurationProperties;
}
public ResultHandlerFactory getResultHandlerFactory() {
return resultHandlerFactory;
}
/**
* Allows you to use a custom org.apache.camel.builder.xml.ResultHandlerFactory which is capable of
* using custom org.apache.camel.builder.xml.ResultHandler types.
*/
public void setResultHandlerFactory(ResultHandlerFactory resultHandlerFactory) {
this.resultHandlerFactory = resultHandlerFactory;
}
@ManagedAttribute(description = "Whether or not to throw an exception if the input body is null")
public boolean isFailOnNullBody() {
return failOnNullBody;
}
/**
* Whether or not to throw an exception if the input body is null.
*/
public void setFailOnNullBody(boolean failOnNullBody) {
this.failOnNullBody = failOnNullBody;
}
@ManagedAttribute(description = "What kind of option to use.")
public XsltOutput getOutput() {
return output;
}
/**
* Option to specify which output type to use.
* 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.
* For file you must specify the filename in the IN header with the key Exchange.XSLT_FILE_NAME which is also CamelXsltFileName.
* Also any paths leading to the filename must be created beforehand, otherwise an exception is thrown at runtime.
*/
public void setOutput(XsltOutput output) {
this.output = output;
}
public int getTransformerCacheSize() {
return transformerCacheSize;
}
/**
* The number of javax.xml.transform.Transformer object that are cached for reuse to avoid calls to Template.newTransformer().
*/
public void setTransformerCacheSize(int transformerCacheSize) {
this.transformerCacheSize = transformerCacheSize;
}
public ErrorListener getErrorListener() {
return errorListener;
}
/**
* Allows to configure to use a custom javax.xml.transform.ErrorListener. Beware when doing this then the default error
* listener which captures any errors or fatal errors and store information on the Exchange as properties is not in use.
* So only use this option for special use-cases.
*/
public void setErrorListener(ErrorListener errorListener) {
this.errorListener = errorListener;
}
@ManagedAttribute(description = "Cache for the resource content (the stylesheet file) when it is loaded.")
public boolean isContentCache() {
return contentCache;
}
/**
* Cache for the resource content (the stylesheet file) when it is loaded.
* If set to false Camel will reload the stylesheet file on each message processing. This is good for development.
* A cached stylesheet can be forced to reload at runtime via JMX using the clearCachedStylesheet operation.
*/
public void setContentCache(boolean contentCache) {
this.contentCache = contentCache;
}
public URIResolver getUriResolver() {
return uriResolver;
}
/**
* To use a custom javax.xml.transform.URIResolver
*/
public void setUriResolver(URIResolver uriResolver) {
this.uriResolver = uriResolver;
}
@ManagedAttribute(description = "Whether to allow using StAX as the javax.xml.transform.Source")
public boolean isAllowStAX() {
return allowStAX;
}
/**
* Whether to allow using StAX as the javax.xml.transform.Source.
*/
public void setAllowStAX(boolean allowStAX) {
this.allowStAX = allowStAX;
}
public boolean isDeleteOutputFile() {
return deleteOutputFile;
}
/**
* If you have output=file then this option dictates whether or not the output file should be deleted when the Exchange
* 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.
*/
public void setDeleteOutputFile(boolean deleteOutputFile) {
this.deleteOutputFile = deleteOutputFile;
}
public EntityResolver getEntityResolver() {
return entityResolver;
}
/**
* To use a custom org.xml.sax.EntityResolver with javax.xml.transform.sax.SAXSource.
*/
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
public Map<String, Object> getParameters() {
return parameters;
}
/**
* Additional parameters to configure on the javax.xml.transform.Transformer.
*/
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
/**
* Loads the resource.
*
* @param resourceUri the resource to load
* @throws TransformerException is thrown if error loading resource
* @throws IOException is thrown if error loading resource
*/
protected void loadResource(String resourceUri) throws TransformerException, IOException {
log.trace("{} loading schema resource: {}", this, resourceUri);
Source source = xslt.getUriResolver().resolve(resourceUri, null);
if (source == null) {
throw new IOException("Cannot load schema resource " + resourceUri);
} else {
xslt.setTransformerSource(source);
}
// now loaded so clear flag
cacheCleared = false;
}
@Override
protected void doStart() throws Exception {
super.doStart();
final CamelContext ctx = getCamelContext();
final ClassResolver resolver = ctx.getClassResolver();
final Injector injector = ctx.getInjector();
log.debug("{} using schema resource: {}", this, resourceUri);
this.xslt = injector.newInstance(XsltBuilder.class);
if (converter != null) {
xslt.setConverter(converter);
}
boolean useSaxon = false;
if (transformerFactoryClass == null && (saxon || saxonExtensionFunctions != null)) {
useSaxon = true;
transformerFactoryClass = SAXON_TRANSFORMER_FACTORY_CLASS_NAME;
}
TransformerFactory factory = transformerFactory;
if (factory == null && transformerFactoryClass != null) {
// provide the class loader of this component to work in OSGi environments
Class<TransformerFactory> factoryClass = resolver.resolveMandatoryClass(transformerFactoryClass, TransformerFactory.class, XsltComponent.class.getClassLoader());
log.debug("Using TransformerFactoryClass {}", factoryClass);
factory = injector.newInstance(factoryClass);
if (useSaxon) {
XsltHelper.registerSaxonConfiguration(ctx, factoryClass, factory, saxonConfiguration);
XsltHelper.registerSaxonConfigurationProperties(ctx, factoryClass, factory, saxonConfigurationProperties);
XsltHelper.registerSaxonExtensionFunctions(ctx, factoryClass, factory, saxonExtensionFunctions);
}
}
if (factory != null) {
log.debug("Using TransformerFactory {}", factory);
xslt.getConverter().setTransformerFactory(factory);
}
if (resultHandlerFactory != null) {
xslt.setResultHandlerFactory(resultHandlerFactory);
}
if (errorListener != null) {
xslt.errorListener(errorListener);
}
xslt.setFailOnNullBody(failOnNullBody);
xslt.transformerCacheSize(transformerCacheSize);
xslt.setUriResolver(uriResolver);
xslt.setEntityResolver(entityResolver);
xslt.setAllowStAX(allowStAX);
xslt.setDeleteOutputFile(deleteOutputFile);
configureOutput(xslt, output.name());
// any additional transformer parameters then make a copy to avoid side-effects
if (parameters != null) {
Map<String, Object> copy = new HashMap<>(parameters);
xslt.setParameters(copy);
}
// must load resource first which sets a template and do a stylesheet compilation to catch errors early
loadResource(resourceUri);
// the processor is the xslt builder
setProcessor(xslt);
}
protected void configureOutput(XsltBuilder xslt, String output) throws Exception {
if (ObjectHelper.isEmpty(output)) {
return;
}
if ("string".equalsIgnoreCase(output)) {
xslt.outputString();
} else if ("bytes".equalsIgnoreCase(output)) {
xslt.outputBytes();
} else if ("DOM".equalsIgnoreCase(output)) {
xslt.outputDOM();
} else if ("file".equalsIgnoreCase(output)) {
xslt.outputFile();
} else {
throw new IllegalArgumentException("Unknown output type: " + output);
}
}
@Override
protected void doStop() throws Exception {
super.doStop();
ServiceHelper.stopService(xslt);
}
}