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