1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.synapse.mediators.transform; 21 22 import org.apache.axiom.om.OMAbstractFactory; 23 import org.apache.axiom.om.OMElement; 24 import org.apache.axiom.om.OMNode; 25 import org.apache.axiom.om.impl.builder.StAXOMBuilder; 26 import org.apache.axiom.om.impl.dom.DOOMAbstractFactory; 27 import org.apache.axiom.om.impl.dom.jaxp.DocumentBuilderFactoryImpl; 28 import org.apache.axiom.om.util.ElementHelper; 29 import org.apache.axiom.om.util.StAXUtils; 30 import org.apache.axiom.soap.SOAP11Constants; 31 import org.apache.axiom.soap.SOAP12Constants; 32 import org.apache.axiom.soap.SOAPEnvelope; 33 import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder; 34 import org.apache.axis2.AxisFault; 35 import org.apache.synapse.MessageContext; 36 import org.apache.synapse.SynapseException; 37 import org.apache.synapse.config.Entry; 38 import org.apache.synapse.config.SynapseConfigUtils; 39 import org.apache.synapse.mediators.AbstractMediator; 40 import org.apache.synapse.mediators.MediatorProperty; 41 import org.apache.synapse.util.xpath.SynapseXPath; 42 import org.apache.synapse.util.AXIOMUtils; 43 import org.apache.synapse.util.TemporaryData; 44 import org.apache.synapse.util.TextFileDataSource; 45 import org.jaxen.JaxenException; 46 import org.w3c.dom.Element; 47 import org.w3c.dom.Node; 48 49 import javax.xml.parsers.ParserConfigurationException; 50 import javax.xml.stream.XMLStreamException; 51 import javax.xml.stream.XMLStreamReader; 52 import javax.xml.transform; 53 import javax.xml.transform.dom.DOMResult; 54 import javax.xml.transform.dom.DOMSource; 55 import javax.xml.transform.stream.StreamResult; 56 import java.io; 57 import java.nio.charset.Charset; 58 import java.util.ArrayList; 59 import java.util.Iterator; 60 import java.util.List; 61 62 /** 63 * The XSLT mediator performs an XSLT transformation requested, using 64 * the current message. The source attribute (if available) specifies the source element 65 * on which the transformation would be applied. It will default to the first child of 66 * the messages' SOAP body, if it is omitted. 67 * 68 * Additional properties passed into this mediator would become parameters for XSLT. 69 * Additional features passed into this mediator would become features except for 70 * "http://ws.apache.org/ns/synapse/transform/feature/dom" for the Transformer Factory, which 71 * is used to decide between using DOM and Streams during the transformation process. By default 72 * this is turned on as an optimization, but should be set to false if issues are detected 73 * 74 * Note: Set the TransformerFactory system property to generate and use translets 75 * -Djavax.xml.transform.TransformerFactory=org.apache.xalan.xsltc.trax.TransformerFactoryImpl 76 * 77 */ 78 public class XSLTMediator extends AbstractMediator { 79 80 /** 81 * The feature for which deciding swiching between DOM and Stream during the 82 * transformation process 83 */ 84 public static final String USE_DOM_SOURCE_AND_RESULTS = 85 "http://ws.apache.org/ns/synapse/transform/feature/dom"; 86 /** 87 * The resource key/name which refers to the XSLT to be used for the transformation 88 */ 89 private String xsltKey = null; 90 91 /** Variable to hold source XPath string to use for debugging */ 92 private String sourceXPathString = null; 93 94 /** 95 * The (optional) XPath expression which yields the source element for a transformation 96 */ 97 private SynapseXPath source = null; 98 99 /** 100 * The name of the message context property to store the transformation result 101 */ 102 private String targetPropertyName = null; 103 104 /** 105 * Any parameters which should be passed into the XSLT transformation 106 */ 107 private List<MediatorProperty> properties = new ArrayList<MediatorProperty>(); 108 109 /** 110 * Any features which should be set to the TransformerFactory by explicitly 111 */ 112 private List<MediatorProperty> explicitFeatures = new ArrayList<MediatorProperty>(); 113 114 /** 115 * The Template instance used to create a Transformer object. This is thread-safe 116 * 117 * @see javax.xml.transform.Templates 118 */ 119 private Templates cachedTemplates = null; 120 121 /** 122 * The TransformerFactory instance which use to create Templates...This is not thread-safe. 123 * @see javax.xml.transform.TransformerFactory 124 */ 125 private final TransformerFactory transFact = TransformerFactory.newInstance(); 126 127 /** 128 * Lock used to ensure thread-safe creation and use of the above Transformer 129 */ 130 private final Object transformerLock = new Object(); 131 132 /** 133 * Is it need to use DOMSource and DOMResult? 134 */ 135 private boolean useDOMSourceAndResults = false; 136 137 /** 138 * Default XPath for the selection of the element for the evaluation of the XSLT over 139 */ 140 public static final String DEFAULT_XPATH = "s11:Body/child::*[position()=1] | " + 141 "s12:Body/child::*[position()=1]"; 142 143 public XSLTMediator() { 144 // create the default XPath 145 try { 146 this.source = new SynapseXPath(DEFAULT_XPATH); 147 this.source.addNamespace("s11", SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI); 148 this.source.addNamespace("s12", SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI); 149 } catch (JaxenException e) { 150 String msg = "Error creating default source XPath expression : " + DEFAULT_XPATH; 151 log.error(msg, e); 152 throw new SynapseException(msg, e); 153 } 154 } 155 156 /** 157 * Transforms this message (or its element specified as the source) using the 158 * given XSLT transformation 159 * 160 * @param synCtx the current message where the transformation will apply 161 * @return true always 162 */ 163 public boolean mediate(MessageContext synCtx) { 164 165 boolean traceOn = isTraceOn(synCtx); 166 boolean traceOrDebugOn = isTraceOrDebugOn(traceOn); 167 168 if (traceOrDebugOn) { 169 traceOrDebug(traceOn, "Start : XSLT mediator"); 170 171 if (traceOn && trace.isTraceEnabled()) { 172 trace.trace("Message : " + synCtx.getEnvelope()); 173 } 174 } 175 176 try { 177 performXSLT(synCtx, traceOrDebugOn, traceOn); 178 179 } catch (Exception e) { 180 handleException("Unable to perform XSLT transformation using : " + xsltKey + 181 " against source XPath : " + 182 (sourceXPathString == null ? DEFAULT_XPATH : " source XPath : " + 183 sourceXPathString), e, synCtx); 184 185 } 186 187 if (traceOrDebugOn) { 188 traceOrDebug(traceOn, "End : XSLT mediator"); 189 } 190 191 return true; 192 } 193 194 /** 195 * Perform actual XSLT transformation 196 * @param synCtx current message 197 * @param traceOrDebugOn is trace or debug on? 198 * @param traceOn is trace on? 199 */ 200 private void performXSLT(MessageContext synCtx, final boolean traceOrDebugOn, 201 final boolean traceOn) { 202 203 boolean reCreate = false; 204 OMNode sourceNode = getTransformSource(synCtx); 205 TemporaryData tempTargetData = null; 206 OutputStream osForTarget; 207 boolean isSoapEnvelope = (sourceNode == synCtx.getEnvelope()); 208 boolean isSoapBody = (sourceNode == synCtx.getEnvelope().getBody()); 209 210 if (traceOrDebugOn) { 211 trace.trace("Transformation source : " + sourceNode.toString()); 212 } 213 214 Source transformSrc; 215 Result transformTgt = null; 216 217 if (useDOMSourceAndResults) { 218 if (traceOrDebugOn) { 219 traceOrDebug(traceOn, "Using a DOMSource for transformation"); 220 } 221 222 // for fast transformations create a DOMSource - ** may not work always though ** 223 transformSrc = new DOMSource( 224 ((Element) ElementHelper.importOMElement((OMElement) sourceNode, 225 DOOMAbstractFactory.getOMFactory())).getOwnerDocument()); 226 DocumentBuilderFactoryImpl.setDOOMRequired(true); 227 228 try { 229 transformTgt = new DOMResult( 230 DocumentBuilderFactoryImpl.newInstance().newDocumentBuilder().newDocument()); 231 } catch (ParserConfigurationException e) { 232 handleException("Error creating a DOMResult for the transformation," + 233 " Consider setting optimization feature : " + USE_DOM_SOURCE_AND_RESULTS + 234 " off", e, synCtx); 235 } 236 237 } else { 238 if (traceOrDebugOn) { 239 traceOrDebug(traceOn, "Using byte array serialization for transformation"); 240 } 241 242 transformSrc = AXIOMUtils.asSource(sourceNode); 243 244 tempTargetData = synCtx.getEnvironment().createTemporaryData(); 245 osForTarget = tempTargetData.getOutputStream(); 246 transformTgt = new StreamResult(osForTarget); 247 } 248 249 if (transformTgt == null) { 250 if (traceOrDebugOn) { 251 traceOrDebug(traceOn, "Was unable to get a javax.xml.transform.Result created"); 252 } 253 return; 254 } 255 256 // build transformer - if necessary 257 Entry dp = synCtx.getConfiguration().getEntryDefinition(xsltKey); 258 259 // if the xsltKey refers to a dynamic resource 260 if (dp != null && dp.isDynamic()) { 261 if (!dp.isCached() || dp.isExpired()) { 262 reCreate = true; 263 } 264 } 265 266 synchronized (transformerLock) { 267 if (reCreate || cachedTemplates == null) { 268 try { 269 cachedTemplates = transFact.newTemplates( 270 SynapseConfigUtils.getStreamSource(synCtx.getEntry(xsltKey))); 271 if (cachedTemplates == null) { 272 handleException("Error compiling the XSLT with key : " + xsltKey, synCtx); 273 } 274 } catch (Exception e) { 275 handleException("Error creating XSLT transformer using : " 276 + xsltKey, e, synCtx); 277 } 278 } 279 } 280 281 try { 282 // perform transformation 283 Transformer transformer = cachedTemplates.newTransformer(); 284 if (!properties.isEmpty()) { 285 // set the parameters which will pass to the Transformation 286 for (MediatorProperty prop : properties) { 287 if (prop != null) { 288 if (prop.getValue() != null) { 289 transformer.setParameter(prop.getName(), prop.getValue()); 290 } else { 291 transformer.setParameter(prop.getName(), 292 prop.getExpression().stringValueOf(synCtx)); 293 } 294 } 295 } 296 } 297 298 transformer.setErrorListener(new ErrorListener() { 299 300 public void warning(TransformerException e) throws TransformerException { 301 302 if (traceOrDebugOn) { 303 traceOrDebugWarn( 304 traceOn, "Warning encountered during transformation : " + e); 305 } 306 } 307 308 public void error(TransformerException e) throws TransformerException { 309 log.error("Error occured in XSLT transformation : " + e); 310 throw e; 311 } 312 313 public void fatalError(TransformerException e) throws TransformerException { 314 log.error("Fatal error occured in the XSLT transformation : " + e); 315 throw e; 316 } 317 }); 318 319 transformer.transform(transformSrc, transformTgt); 320 321 if (traceOrDebugOn) { 322 traceOrDebug(traceOn, "Transformation completed - processing result"); 323 } 324 325 // get the result OMElement 326 OMElement result = null; 327 if (transformTgt instanceof DOMResult) { 328 329 Node node = ((DOMResult) transformTgt).getNode(); 330 if (node == null) { 331 if (traceOrDebugOn) { 332 traceOrDebug(traceOn, ("Transformation result (DOMResult) was null")); 333 } 334 return; 335 } 336 337 Node resultNode = node.getFirstChild(); 338 if (resultNode == null) { 339 if (traceOrDebugOn) { 340 traceOrDebug(traceOn, ("Transformation result (DOMResult) was empty")); 341 } 342 return; 343 } 344 345 result = ElementHelper.importOMElement( 346 (OMElement) resultNode, OMAbstractFactory.getOMFactory()); 347 348 } else { 349 350 String outputMethod = transformer.getOutputProperty(OutputKeys.METHOD); 351 String encoding = transformer.getOutputProperty(OutputKeys.ENCODING); 352 353 if (traceOrDebugOn) { 354 traceOrDebug(traceOn, "output method: " + outputMethod 355 + "; encoding: " + encoding); 356 } 357 358 if ("text".equals(outputMethod)) { 359 result = handleNonXMLResult(tempTargetData, Charset.forName(encoding), 360 traceOrDebugOn, traceOn); 361 } else { 362 try { 363 XMLStreamReader reader = StAXUtils.createXMLStreamReader( 364 tempTargetData.getInputStream()); 365 if (isSoapEnvelope) { 366 result = new StAXSOAPModelBuilder(reader).getSOAPEnvelope(); 367 } else { 368 result = new StAXOMBuilder(reader).getDocumentElement(); 369 } 370 371 } catch (XMLStreamException e) { 372 handleException( 373 "Error building result element from XSLT transformation", e, synCtx); 374 375 } catch (IOException e) { 376 handleException("Error reading temporary data", e, synCtx); 377 } 378 } 379 } 380 381 if (result == null) { 382 if (traceOrDebugOn) { 383 traceOrDebug(traceOn, "Transformation result was null"); 384 } 385 return; 386 } else { 387 if (traceOn && trace.isTraceEnabled()) { 388 trace.trace("Transformation result : " + result.toString()); 389 } 390 } 391 392 if (targetPropertyName != null) { 393 // add result XML as a message context property to the message 394 if (traceOrDebugOn) { 395 traceOrDebug(traceOn, "Adding result as message context property : " + 396 targetPropertyName); 397 } 398 synCtx.setProperty(targetPropertyName, result); 399 } else { 400 if (traceOrDebugOn) { 401 traceOrDebug(traceOn, "Replace " + 402 (isSoapEnvelope ? "SOAP envelope" : isSoapBody ? "SOAP body" : "node") 403 + " with result"); 404 } 405 406 if (isSoapEnvelope) { 407 try { 408 synCtx.setEnvelope((SOAPEnvelope) result); 409 } catch (AxisFault ex) { 410 handleException("Unable to replace SOAP envelope with result", ex, synCtx); 411 } 412 413 } else if (isSoapBody) { 414 for (Iterator iter = synCtx.getEnvelope().getBody().getChildElements(); 415 iter.hasNext(); ) { 416 OMElement child = (OMElement) iter.next(); 417 child.detach(); 418 } 419 420 for (Iterator iter = result.getChildElements(); iter.hasNext(); ) { 421 OMElement child = (OMElement) iter.next(); 422 synCtx.getEnvelope().getBody().addChild(child); 423 } 424 425 } else { 426 sourceNode.insertSiblingAfter(result); 427 sourceNode.detach(); 428 } 429 } 430 431 } catch (TransformerException e) { 432 handleException("Error performing XSLT transformation using : " + xsltKey, e, synCtx); 433 } 434 } 435 436 /** 437 * Return the OMNode to be used for the transformation. If a source XPath is not specified, 438 * this will default to the first child of the SOAP body i.e. - //*:Envelope/*:Body/child::* 439 * 440 * @param synCtx the message context 441 * @return the OMNode against which the transformation should be performed 442 */ 443 private OMNode getTransformSource(MessageContext synCtx) { 444 445 try { 446 Object o = source.evaluate(synCtx); 447 if (o instanceof OMNode) { 448 return (OMNode) o; 449 } else if (o instanceof List && !((List) o).isEmpty()) { 450 return (OMNode) ((List) o).get(0); // Always fetches *only* the first 451 } else { 452 handleException("The evaluation of the XPath expression " 453 + source + " did not result in an OMNode", synCtx); 454 } 455 } catch (JaxenException e) { 456 handleException("Error evaluating XPath expression : " + source, e, synCtx); 457 } 458 return null; 459 } 460 461 public SynapseXPath getSource() { 462 return source; 463 } 464 465 public void setSource(SynapseXPath source) { 466 this.source = source; 467 } 468 469 public String getXsltKey() { 470 return xsltKey; 471 } 472 473 public void setXsltKey(String xsltKey) { 474 this.xsltKey = xsltKey; 475 } 476 477 public void addProperty(MediatorProperty p) { 478 properties.add(p); 479 } 480 481 /** 482 * to add a feature which need to set to the TransformerFactory 483 * @param featureName The name of the feature 484 * @param isFeatureEnable should this feature enable? 485 */ 486 487 public void addFeature(String featureName, boolean isFeatureEnable) { 488 try { 489 MediatorProperty mp = new MediatorProperty(); 490 mp.setName(featureName); 491 if (isFeatureEnable) { 492 mp.setValue("true"); 493 } else { 494 mp.setValue("false"); 495 } 496 explicitFeatures.add(mp); 497 if (USE_DOM_SOURCE_AND_RESULTS.equals(featureName)) { 498 useDOMSourceAndResults = isFeatureEnable; 499 } else { 500 transFact.setFeature(featureName, isFeatureEnable); 501 } 502 } catch (TransformerConfigurationException e) { 503 String msg = "Error occured when setting features to the TransformerFactory"; 504 log.error(msg, e); 505 throw new SynapseException(msg, e); 506 } 507 } 508 509 /** 510 * If the transformation results in a non-XML payload, use standard wrapper elements 511 * to wrap the text payload so that other mediators could still process the result 512 * @param tempData the encoded text payload 513 * @param charset the encoding of the payload 514 * @param traceOrDebugOn is tracing on debug logging on? 515 * @param traceOn is tracing on? 516 * @return an OMElement wrapping the text payload 517 */ 518 private OMElement handleNonXMLResult(TemporaryData tempData, Charset charset, 519 boolean traceOrDebugOn, boolean traceOn) { 520 521 if (traceOrDebugOn) { 522 traceOrDebug(traceOn, "Processing non SOAP/XML (text) transformation result"); 523 } 524 if (traceOn && trace.isTraceEnabled()) { 525 trace.trace("Wrapping text transformation result"); 526 } 527 528 return TextFileDataSource.createOMSourcedElement(tempData, charset); 529 } 530 531 /** 532 * 533 * @return Returns the features explicitly set to the TransformerFactory through this mediator 534 */ 535 public List<MediatorProperty> getFeatures(){ 536 return explicitFeatures; 537 } 538 539 public void addAllProperties(List<MediatorProperty> list) { 540 properties.addAll(list); 541 } 542 543 public List<MediatorProperty> getProperties() { 544 return properties; 545 } 546 547 public void setSourceXPathString(String sourceXPathString) { 548 this.sourceXPathString = sourceXPathString; 549 } 550 551 public String getTargetPropertyName() { 552 return targetPropertyName; 553 } 554 555 public void setTargetPropertyName(String targetPropertyName) { 556 this.targetPropertyName = targetPropertyName; 557 } 558 559 } 560 561