View Javadoc

1   /*
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.soap.addressing.version;
18  
19  import java.net.URI;
20  import java.net.URISyntaxException;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Properties;
26  import javax.xml.namespace.QName;
27  import javax.xml.parsers.DocumentBuilder;
28  import javax.xml.parsers.DocumentBuilderFactory;
29  import javax.xml.parsers.ParserConfigurationException;
30  import javax.xml.transform.Result;
31  import javax.xml.transform.TransformerException;
32  import javax.xml.transform.dom.DOMResult;
33  import javax.xml.transform.dom.DOMSource;
34  
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  
39  import org.springframework.util.StringUtils;
40  import org.springframework.ws.soap.SoapFault;
41  import org.springframework.ws.soap.SoapHeader;
42  import org.springframework.ws.soap.SoapHeaderElement;
43  import org.springframework.ws.soap.SoapMessage;
44  import org.springframework.ws.soap.addressing.AddressingException;
45  import org.springframework.ws.soap.addressing.core.EndpointReference;
46  import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;
47  import org.springframework.ws.soap.soap11.Soap11Body;
48  import org.springframework.ws.soap.soap12.Soap12Body;
49  import org.springframework.ws.soap.soap12.Soap12Fault;
50  import org.springframework.xml.namespace.QNameUtils;
51  import org.springframework.xml.transform.TransformerObjectSupport;
52  import org.springframework.xml.xpath.XPathExpression;
53  import org.springframework.xml.xpath.XPathExpressionFactory;
54  
55  /**
56   * Abstract base class for {@link AddressingVersion} implementations. Uses {@link XPathExpression}s to retrieve
57   * addressing information.
58   *
59   * @author Arjen Poutsma
60   * @since 1.5.0
61   */
62  public abstract class AbstractAddressingVersion extends TransformerObjectSupport implements AddressingVersion {
63  
64      private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
65  
66      private final XPathExpression toExpression;
67  
68      private final XPathExpression actionExpression;
69  
70      private final XPathExpression messageIdExpression;
71  
72      private final XPathExpression fromExpression;
73  
74      private final XPathExpression replyToExpression;
75  
76      private final XPathExpression faultToExpression;
77  
78      private final XPathExpression addressExpression;
79  
80      private final XPathExpression referencePropertiesExpression;
81  
82      private final XPathExpression referenceParametersExpression;
83  
84      protected AbstractAddressingVersion() {
85          Properties namespaces = new Properties();
86          namespaces.setProperty(getNamespacePrefix(), getNamespaceUri());
87          toExpression = createNormalizedExpression(getToName(), namespaces);
88          actionExpression = createNormalizedExpression(getActionName(), namespaces);
89          messageIdExpression = createNormalizedExpression(getMessageIdName(), namespaces);
90          fromExpression = createExpression(getFromName(), namespaces);
91          replyToExpression = createExpression(getReplyToName(), namespaces);
92          faultToExpression = createExpression(getFaultToName(), namespaces);
93          addressExpression = createNormalizedExpression(getAddressName(), namespaces);
94          if (getReferencePropertiesName() != null) {
95              referencePropertiesExpression = createChildrenExpression(getReferencePropertiesName(), namespaces);
96          }
97          else {
98              referencePropertiesExpression = null;
99          }
100         if (getReferenceParametersName() != null) {
101             referenceParametersExpression = createChildrenExpression(getReferenceParametersName(), namespaces);
102         }
103         else {
104             referenceParametersExpression = null;
105         }
106     }
107 
108     private XPathExpression createExpression(QName name, Properties namespaces) {
109         String expression = name.getPrefix() + ":" + name.getLocalPart();
110         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
111     }
112 
113     private XPathExpression createNormalizedExpression(QName name, Properties namespaces) {
114         String expression = "normalize-space(" + name.getPrefix() + ":" + name.getLocalPart() + ")";
115         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
116     }
117 
118     private XPathExpression createChildrenExpression(QName name, Properties namespaces) {
119         String expression = name.getPrefix() + ":" + name.getLocalPart() + "/*";
120         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
121     }
122 
123     public MessageAddressingProperties getMessageAddressingProperties(SoapMessage message) {
124         Element headerElement = getSoapHeaderElement(message);
125         URI to = getUri(headerElement, toExpression);
126         if (to == null) {
127             to = getDefaultTo();
128         }
129         EndpointReference from = getEndpointReference(fromExpression.evaluateAsNode(headerElement));
130         EndpointReference replyTo = getEndpointReference(replyToExpression.evaluateAsNode(headerElement));
131         if (replyTo == null) {
132             replyTo = getDefaultReplyTo(from);
133         }
134         EndpointReference faultTo = getEndpointReference(faultToExpression.evaluateAsNode(headerElement));
135         if (faultTo == null) {
136             faultTo = replyTo;
137         }
138         URI action = getUri(headerElement, actionExpression);
139         URI messageId = getUri(headerElement, messageIdExpression);
140         return new MessageAddressingProperties(to, from, replyTo, faultTo, action, messageId);
141     }
142 
143     private URI getUri(Node node, XPathExpression expression) {
144         String messageId = expression.evaluateAsString(node);
145         if (!StringUtils.hasLength(messageId)) {
146             return null;
147         }
148         try {
149             return new URI(messageId);
150         }
151         catch (URISyntaxException e) {
152             return null;
153         }
154     }
155 
156     private Element getSoapHeaderElement(SoapMessage message) {
157         SoapHeader header = message.getSoapHeader();
158         if (header.getSource() instanceof DOMSource) {
159             DOMSource domSource = (DOMSource) header.getSource();
160             if (domSource.getNode() != null && domSource.getNode().getNodeType() == Node.ELEMENT_NODE) {
161                 return (Element) domSource.getNode();
162             }
163         }
164         try {
165             DOMResult domResult = new DOMResult();
166             transform(header.getSource(), domResult);
167             Document document = (Document) domResult.getNode();
168             return document.getDocumentElement();
169         }
170         catch (TransformerException ex) {
171             throw new AddressingException("Could not transform SoapHeader to Document", ex);
172         }
173     }
174 
175     /** Given a ReplyTo, FaultTo, or From node, returns an endpoint reference. */
176     private EndpointReference getEndpointReference(Node node) {
177         if (node == null) {
178             return null;
179         }
180         URI address = getUri(node, addressExpression);
181         if (address == null) {
182             return null;
183         }
184         List referenceProperties =
185                 referencePropertiesExpression != null ? referencePropertiesExpression.evaluateAsNodeList(node) :
186                         Collections.EMPTY_LIST;
187         List referenceParameters =
188                 referenceParametersExpression != null ? referenceParametersExpression.evaluateAsNodeList(node) :
189                         Collections.EMPTY_LIST;
190         return new EndpointReference(address, referenceProperties, referenceParameters);
191     }
192 
193     public void addAddressingHeaders(SoapMessage message, MessageAddressingProperties map) {
194         SoapHeader header = message.getSoapHeader();
195         header.addNamespaceDeclaration(getNamespacePrefix(), getNamespaceUri());
196         // To
197         if (map.getTo() != null) {
198             SoapHeaderElement to = header.addHeaderElement(getToName());
199             to.setText(map.getTo().toString());
200             to.setMustUnderstand(true);
201         }
202         // From
203         if (map.getFrom() != null) {
204             SoapHeaderElement from = header.addHeaderElement(getFromName());
205             addEndpointReference(from, map.getFrom());
206         }
207         //ReplyTo
208         if (map.getReplyTo() != null) {
209             SoapHeaderElement replyTo = header.addHeaderElement(getReplyToName());
210             addEndpointReference(replyTo, map.getReplyTo());
211         }
212         // FaultTo
213         if (map.getFaultTo() != null) {
214             SoapHeaderElement faultTo = header.addHeaderElement(getFaultToName());
215             addEndpointReference(faultTo, map.getFaultTo());
216         }
217         // Action
218         SoapHeaderElement action = header.addHeaderElement(getActionName());
219         action.setText(map.getAction().toString());
220         // MessageID
221         if (map.getMessageId() != null) {
222             SoapHeaderElement messageId = header.addHeaderElement(getMessageIdName());
223             messageId.setText(map.getMessageId().toString());
224         }
225         // RelatesTo
226         if (map.getRelatesTo() != null) {
227             SoapHeaderElement relatesTo = header.addHeaderElement(getRelatesToName());
228             relatesTo.setText(map.getRelatesTo().toString());
229         }
230         addReferenceNodes(header.getResult(), map.getReferenceParameters());
231         addReferenceNodes(header.getResult(), map.getReferenceProperties());
232     }
233 
234     public final boolean understands(SoapHeaderElement headerElement) {
235         return getNamespaceUri().equals(headerElement.getName().getNamespaceURI());
236     }
237 
238     /** Adds ReplyTo, FaultTo, or From EPR to the given header Element. */
239     protected void addEndpointReference(SoapHeaderElement headerElement, EndpointReference epr) {
240         if (epr == null || epr.getAddress() == null) {
241             return;
242         }
243         try {
244             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
245             Document document = documentBuilder.newDocument();
246             Element address = document.createElementNS(getNamespaceUri(), QNameUtils.toQualifiedName(getAddressName()));
247             address.setTextContent(epr.getAddress().toString());
248             transform(new DOMSource(address), headerElement.getResult());
249             if (getReferenceParametersName() != null && !epr.getReferenceParameters().isEmpty()) {
250                 Element referenceParams = document.createElementNS(getNamespaceUri(),
251                         QNameUtils.toQualifiedName(getReferenceParametersName()));
252                 addReferenceNodes(new DOMResult(referenceParams), epr.getReferenceParameters());
253                 transform(new DOMSource(referenceParams), headerElement.getResult());
254             }
255             if (getReferencePropertiesName() != null && !epr.getReferenceProperties().isEmpty()) {
256                 Element referenceProps = document.createElementNS(getNamespaceUri(),
257                         QNameUtils.toQualifiedName(getReferencePropertiesName()));
258                 addReferenceNodes(new DOMResult(referenceProps), epr.getReferenceProperties());
259                 transform(new DOMSource(referenceProps), headerElement.getResult());
260             }
261         }
262         catch (ParserConfigurationException ex) {
263             throw new AddressingException("Could not add Endpoint Reference [" + epr + "] to header element", ex);
264         }
265         catch (TransformerException ex) {
266             throw new AddressingException("Could not add reference properties/parameters to message", ex);
267         }
268     }
269 
270     protected void addReferenceNodes(Result result, List nodes) {
271         try {
272             for (Iterator iterator = nodes.iterator(); iterator.hasNext();) {
273                 Node node = (Node) iterator.next();
274                 DOMSource source = new DOMSource(node);
275                 transform(source, result);
276             }
277         }
278         catch (TransformerException ex) {
279             throw new AddressingException("Could not add reference properties/parameters to message", ex);
280         }
281     }
282 
283     public final SoapFault addInvalidAddressingHeaderFault(SoapMessage message) {
284         return addAddressingFault(message, getInvalidAddressingHeaderFaultSubcode(),
285                 getInvalidAddressingHeaderFaultReason());
286     }
287 
288     public final SoapFault addMessageAddressingHeaderRequiredFault(SoapMessage message) {
289         return addAddressingFault(message, getMessageAddressingHeaderRequiredFaultSubcode(),
290                 getMessageAddressingHeaderRequiredFaultReason());
291     }
292 
293     private SoapFault addAddressingFault(SoapMessage message, QName subcode, String reason) {
294         if (message.getSoapBody() instanceof Soap11Body) {
295             Soap11Body soapBody = (Soap11Body) message.getSoapBody();
296             return soapBody.addFault(subcode, reason, Locale.ENGLISH);
297         }
298         else if (message.getSoapBody() instanceof Soap12Body) {
299             Soap12Body soapBody = (Soap12Body) message.getSoapBody();
300             Soap12Fault soapFault = (Soap12Fault) soapBody.addClientOrSenderFault(reason, Locale.ENGLISH);
301             soapFault.addFaultSubcode(subcode);
302             return soapFault;
303         }
304         return null;
305     }
306 
307     /*
308     * Address URIs
309     */
310 
311     public final boolean hasAnonymousAddress(EndpointReference epr) {
312         URI anonymous = getAnonymous();
313         return anonymous != null && anonymous.equals(epr.getAddress());
314     }
315 
316     public final boolean hasNoneAddress(EndpointReference epr) {
317         URI none = getNone();
318         return none != null && none.equals(epr.getAddress());
319     }
320 
321     /** Returns the prefix associated with the WS-Addressing namespace handled by this specification. */
322     protected String getNamespacePrefix() {
323         return "wsa";
324     }
325 
326     /** Returns the WS-Addressing namespace handled by this specification. */
327     protected abstract String getNamespaceUri();
328 
329     /*
330      * Message addressing properties
331      */
332 
333     /** Returns the qualified name of the <code>To</code> addressing header. */
334     protected QName getToName() {
335         return QNameUtils.createQName(getNamespaceUri(), "To", getNamespacePrefix());
336     }
337 
338     /** Returns the qualified name of the <code>From</code> addressing header. */
339     protected QName getFromName() {
340         return QNameUtils.createQName(getNamespaceUri(), "From", getNamespacePrefix());
341     }
342 
343     /** Returns the qualified name of the <code>ReplyTo</code> addressing header. */
344     protected QName getReplyToName() {
345         return QNameUtils.createQName(getNamespaceUri(), "ReplyTo", getNamespacePrefix());
346     }
347 
348     /** Returns the qualified name of the <code>FaultTo</code> addressing header. */
349     protected QName getFaultToName() {
350         return QNameUtils.createQName(getNamespaceUri(), "FaultTo", getNamespacePrefix());
351     }
352 
353     /** Returns the qualified name of the <code>Action</code> addressing header. */
354     protected QName getActionName() {
355         return QNameUtils.createQName(getNamespaceUri(), "Action", getNamespacePrefix());
356     }
357 
358     /** Returns the qualified name of the <code>MessageID</code> addressing header. */
359     protected QName getMessageIdName() {
360         return QNameUtils.createQName(getNamespaceUri(), "MessageID", getNamespacePrefix());
361     }
362 
363     /** Returns the qualified name of the <code>RelatesTo</code> addressing header. */
364     protected QName getRelatesToName() {
365         return QNameUtils.createQName(getNamespaceUri(), "RelatesTo", getNamespacePrefix());
366     }
367 
368     /** Returns the qualified name of the <code>RelatesTo</code> addressing header. */
369     protected QName getRelationshipTypeName() {
370         return new QName("RelationshipType");
371     }
372 
373     /**
374      * Returns the qualified name of the <code>ReferenceProperties</code> in the endpoint reference. Returns
375      * <code>null</code> when reference properties are not supported by this version of the spec.
376      */
377     protected QName getReferencePropertiesName() {
378         return QNameUtils.createQName(getNamespaceUri(), "ReferenceProperties", getNamespacePrefix());
379     }
380 
381     /**
382      * Returns the qualified name of the <code>ReferenceParameters</code> in the endpoint reference. Returns
383      * <code>null</code> when reference parameters are not supported by this version of the spec.
384      */
385     protected QName getReferenceParametersName() {
386         return QNameUtils.createQName(getNamespaceUri(), "ReferenceParameters", getNamespacePrefix());
387     }
388 
389     /*
390      * Endpoint Reference
391      */
392 
393     /** The qualified name of the <code>Address</code> in <code>EndpointReference</code>. */
394     protected QName getAddressName() {
395         return QNameUtils.createQName(getNamespaceUri(), "Address", getNamespacePrefix());
396     }
397 
398     /** Returns the default To URI. */
399     protected abstract URI getDefaultTo();
400 
401     /** Returns the default ReplyTo EPR. Can be based on the From EPR, or the anonymous URI. */
402     protected abstract EndpointReference getDefaultReplyTo(EndpointReference from);
403 
404     /*
405      * Address URIs
406      */
407 
408     /** Returns the anonymous URI. */
409     protected abstract URI getAnonymous();
410 
411     /** Returns the none URI, or <code>null</code> if the spec does not define it. */
412     protected abstract URI getNone();
413 
414     /*
415      * Faults
416      */
417 
418     /** Returns the qualified name of the fault subcode that indicates that a header is missing. */
419     protected abstract QName getMessageAddressingHeaderRequiredFaultSubcode();
420 
421     /** Returns the reason of the fault that indicates that a header is missing. */
422     protected abstract String getMessageAddressingHeaderRequiredFaultReason();
423 
424     /** Returns the qualified name of the fault subcode that indicates that a header is invalid. */
425     protected abstract QName getInvalidAddressingHeaderFaultSubcode();
426 
427     /** Returns the reason of the fault that indicates that a header is invalid. */
428     protected abstract String getInvalidAddressingHeaderFaultReason();
429 }