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.util.toolbox; 018 019import java.io.IOException; 020import java.util.concurrent.RejectedExecutionException; 021import javax.xml.transform.Source; 022import javax.xml.transform.TransformerException; 023import javax.xml.transform.TransformerFactory; 024import javax.xml.transform.URIResolver; 025 026import org.w3c.dom.Document; 027 028import org.apache.camel.CamelContext; 029import org.apache.camel.CamelContextAware; 030import org.apache.camel.Exchange; 031import org.apache.camel.builder.xml.XsltBuilder; 032import org.apache.camel.builder.xml.XsltUriResolver; 033import org.apache.camel.component.xslt.XsltEndpoint; 034import org.apache.camel.component.xslt.XsltOutput; 035import org.apache.camel.processor.aggregate.AggregationStrategy; 036import org.apache.camel.support.ServiceSupport; 037import org.apache.camel.util.ObjectHelper; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * The XSLT Aggregation Strategy enables you to use XSL stylesheets to aggregate messages. 043 * <p> 044 * Since XSLT does not directly support providing multiple XML payloads as an input, this aggregator injects 045 * the new incoming XML document (<tt>newExchange</tt>) into the <tt>oldExchange</tt> as an exchange property of 046 * type {@link Document}. The old exchange therefore remains accessible as the root context. 047 * This exchange property can then be accessed from your XSLT by declaring an {@code <xsl:param />} at the top 048 * of your stylesheet: 049 * 050 * <code> 051 * <xsl:param name="new-exchange" /> 052 * 053 * <xsl:template match="/"> 054 * <abc> 055 * <xsl:copy-of select="/ElementFromOldExchange" /> 056 * <xsl:copy-of select="$new-exchange/ElementFromNewExchange" /> 057 * </abc> 058 * </xsl:template> 059 * </code> 060 * 061 * The exchange property name defaults to <tt>new-exchange</tt> but can be 062 * changed through {@link #setPropertyName(String)}. 063 * <p> 064 * Some code bits have been copied from the {@link org.apache.camel.component.xslt.XsltEndpoint}. 065 */ 066public class XsltAggregationStrategy extends ServiceSupport implements AggregationStrategy, CamelContextAware { 067 068 private static final Logger LOG = LoggerFactory.getLogger(XsltAggregationStrategy.class); 069 private static final String DEFAULT_PROPERTY_NAME = "new-exchange"; 070 071 private volatile XsltBuilder xslt; 072 private volatile URIResolver uriResolver; 073 private CamelContext camelContext; 074 075 private String propertyName; 076 private String xslFile; 077 private String transformerFactoryClass; 078 private XsltOutput output = XsltOutput.string; 079 080 /** 081 * Constructor. 082 * 083 * @param xslFileLocation location of the XSL transformation 084 */ 085 public XsltAggregationStrategy(String xslFileLocation) { 086 this.xslFile = xslFileLocation; 087 } 088 089 @Override 090 public CamelContext getCamelContext() { 091 return camelContext; 092 } 093 094 @Override 095 public void setCamelContext(CamelContext camelContext) { 096 this.camelContext = camelContext; 097 } 098 099 @Override 100 public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { 101 // guard against unlikely NPE 102 if (newExchange == null) { 103 return oldExchange; 104 } 105 106 // in the first call to this aggregation, do not call the XSLT but instead store the 107 // incoming exchange 108 if (oldExchange == null) { 109 return newExchange; 110 } 111 112 if (!isRunAllowed()) { 113 throw new RejectedExecutionException(); 114 } 115 116 try { 117 oldExchange.setProperty(propertyName, newExchange.getIn().getBody(Document.class)); 118 xslt.process(oldExchange); 119 return oldExchange; 120 } catch (Throwable e) { 121 oldExchange.setException(e); 122 } 123 124 return oldExchange; 125 } 126 127 public void setOutput(XsltOutput output) { 128 this.output = output; 129 } 130 131 public void setXslt(XsltBuilder xslt) { 132 this.xslt = xslt; 133 } 134 135 public void setUriResolver(URIResolver uriResolver) { 136 this.uriResolver = uriResolver; 137 } 138 139 public void setTransformerFactoryClass(String transformerFactoryClass) { 140 this.transformerFactoryClass = transformerFactoryClass; 141 } 142 143 public String getPropertyName() { 144 return propertyName; 145 } 146 147 public void setPropertyName(String propertyName) { 148 this.propertyName = propertyName; 149 } 150 151 @Deprecated 152 protected void initialize(CamelContext context) throws Exception { 153 this.camelContext = context; 154 } 155 156 protected void configureOutput(XsltBuilder xslt, String output) throws Exception { 157 if (ObjectHelper.isEmpty(output)) { 158 return; 159 } 160 161 if ("string".equalsIgnoreCase(output)) { 162 xslt.outputString(); 163 } else if ("bytes".equalsIgnoreCase(output)) { 164 xslt.outputBytes(); 165 } else if ("DOM".equalsIgnoreCase(output)) { 166 xslt.outputDOM(); 167 } else if ("file".equalsIgnoreCase(output)) { 168 xslt.outputFile(); 169 } else { 170 throw new IllegalArgumentException("Unknown output type: " + output); 171 } 172 } 173 174 /** 175 * Loads the resource. 176 * 177 * @param resourceUri the resource to load 178 * @throws TransformerException is thrown if error loading resource 179 * @throws IOException is thrown if error loading resource 180 */ 181 protected void loadResource(String resourceUri) throws TransformerException, IOException { 182 LOG.trace("{} loading schema resource: {}", this, resourceUri); 183 Source source = xslt.getUriResolver().resolve(resourceUri, null); 184 if (source == null) { 185 throw new IOException("Cannot load schema resource " + resourceUri); 186 } else { 187 xslt.setTransformerSource(source); 188 } 189 } 190 191 // --- fluent builders --- 192 public static XsltAggregationStrategy create(String xslFile) { 193 return new XsltAggregationStrategy(xslFile); 194 } 195 196 public XsltAggregationStrategy withPropertyName(String propertyName) { 197 setPropertyName(propertyName); 198 return this; 199 } 200 201 public XsltAggregationStrategy withOutput(XsltOutput output) { 202 setOutput(output); 203 return this; 204 } 205 206 public XsltAggregationStrategy withUriResolver(URIResolver resolver) { 207 setUriResolver(resolver); 208 return this; 209 } 210 211 public XsltAggregationStrategy withTransformerFactoryClass(String clazz) { 212 setTransformerFactoryClass(clazz); 213 return this; 214 } 215 216 public XsltAggregationStrategy withSaxon() { 217 setTransformerFactoryClass(XsltEndpoint.SAXON_TRANSFORMER_FACTORY_CLASS_NAME); 218 return this; 219 } 220 221 @Override 222 protected void doStart() throws Exception { 223 ObjectHelper.notNull(camelContext, "CamelContext", this); 224 225 // set the default property name if not set 226 this.propertyName = ObjectHelper.isNotEmpty(propertyName) ? propertyName : DEFAULT_PROPERTY_NAME; 227 228 // initialize the XsltBuilder 229 this.xslt = camelContext.getInjector().newInstance(XsltBuilder.class); 230 231 if (transformerFactoryClass != null) { 232 Class<?> factoryClass = camelContext.getClassResolver().resolveMandatoryClass(transformerFactoryClass, 233 XsltAggregationStrategy.class.getClassLoader()); 234 TransformerFactory factory = (TransformerFactory) camelContext.getInjector().newInstance(factoryClass); 235 xslt.getConverter().setTransformerFactory(factory); 236 } 237 238 if (uriResolver == null) { 239 uriResolver = new XsltUriResolver(camelContext, xslFile); 240 } 241 242 xslt.setUriResolver(uriResolver); 243 xslt.setFailOnNullBody(true); 244 xslt.transformerCacheSize(0); 245 xslt.setAllowStAX(true); 246 247 configureOutput(xslt, output.name()); 248 loadResource(xslFile); 249 } 250 251 @Override 252 protected void doStop() throws Exception { 253 // noop 254 } 255}