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