View Javadoc

1   /*
2    * Copyright 2005-2010 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.HashMap;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
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.Source;
32  import javax.xml.transform.TransformerException;
33  import javax.xml.transform.dom.DOMResult;
34  import javax.xml.transform.dom.DOMSource;
35  
36  import org.springframework.util.StringUtils;
37  import org.springframework.ws.soap.SoapFault;
38  import org.springframework.ws.soap.SoapHeader;
39  import org.springframework.ws.soap.SoapHeaderElement;
40  import org.springframework.ws.soap.SoapMessage;
41  import org.springframework.ws.soap.addressing.AddressingException;
42  import org.springframework.ws.soap.addressing.core.EndpointReference;
43  import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;
44  import org.springframework.ws.soap.soap11.Soap11Body;
45  import org.springframework.ws.soap.soap12.Soap12Body;
46  import org.springframework.ws.soap.soap12.Soap12Fault;
47  import org.springframework.xml.namespace.QNameUtils;
48  import org.springframework.xml.transform.TransformerObjectSupport;
49  import org.springframework.xml.xpath.XPathExpression;
50  import org.springframework.xml.xpath.XPathExpressionFactory;
51  
52  import org.w3c.dom.Document;
53  import org.w3c.dom.Element;
54  import org.w3c.dom.Node;
55  
56  /**
57   * Abstract base class for {@link AddressingVersion} implementations. Uses {@link XPathExpression}s to retrieve
58   * addressing information.
59   *
60   * @author Arjen Poutsma
61   * @since 1.5.0
62   */
63  public abstract class AbstractAddressingVersion extends TransformerObjectSupport implements AddressingVersion {
64  
65      private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
66  
67      private final XPathExpression toExpression;
68  
69      private final XPathExpression actionExpression;
70  
71      private final XPathExpression messageIdExpression;
72  
73      private final XPathExpression fromExpression;
74  
75      private final XPathExpression replyToExpression;
76  
77      private final XPathExpression faultToExpression;
78  
79      private final XPathExpression addressExpression;
80  
81      private final XPathExpression referencePropertiesExpression;
82  
83      private final XPathExpression referenceParametersExpression;
84  
85      protected AbstractAddressingVersion() {
86          Map<String, String> namespaces = new HashMap<String, String>();
87          namespaces.put(getNamespacePrefix(), getNamespaceUri());
88          toExpression = createNormalizedExpression(getToName(), namespaces);
89          actionExpression = createNormalizedExpression(getActionName(), namespaces);
90          messageIdExpression = createNormalizedExpression(getMessageIdName(), namespaces);
91          fromExpression = createExpression(getFromName(), namespaces);
92          replyToExpression = createExpression(getReplyToName(), namespaces);
93          faultToExpression = createExpression(getFaultToName(), namespaces);
94          addressExpression = createNormalizedExpression(getAddressName(), namespaces);
95          if (getReferencePropertiesName() != null) {
96              referencePropertiesExpression = createChildrenExpression(getReferencePropertiesName(), namespaces);
97          }
98          else {
99              referencePropertiesExpression = null;
100         }
101         if (getReferenceParametersName() != null) {
102             referenceParametersExpression = createChildrenExpression(getReferenceParametersName(), namespaces);
103         }
104         else {
105             referenceParametersExpression = null;
106         }
107     }
108 
109     private XPathExpression createExpression(QName name, Map<String, String> namespaces) {
110         String expression = name.getPrefix() + ":" + name.getLocalPart();
111         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
112     }
113 
114     private XPathExpression createNormalizedExpression(QName name, Map<String, String> namespaces) {
115         String expression = "normalize-space(" + name.getPrefix() + ":" + name.getLocalPart() + ")";
116         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
117     }
118 
119     private XPathExpression createChildrenExpression(QName name, Map<String, String> namespaces) {
120         String expression = name.getPrefix() + ":" + name.getLocalPart() + "/*";
121         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
122     }
123 
124     public MessageAddressingProperties getMessageAddressingProperties(SoapMessage message) {
125         Element headerElement = getSoapHeaderElement(message);
126         URI to = getUri(headerElement, toExpression);
127         if (to == null) {
128             to = getDefaultTo();
129         }
130         EndpointReference from = getEndpointReference(fromExpression.evaluateAsNode(headerElement));
131         EndpointReference replyTo = getEndpointReference(replyToExpression.evaluateAsNode(headerElement));
132         if (replyTo == null) {
133             replyTo = getDefaultReplyTo(from);
134         }
135         EndpointReference faultTo = getEndpointReference(faultToExpression.evaluateAsNode(headerElement));
136         if (faultTo == null) {
137             faultTo = replyTo;
138         }
139         URI action = getUri(headerElement, actionExpression);
140         URI messageId = getUri(headerElement, messageIdExpression);
141         return new MessageAddressingProperties(to, from, replyTo, faultTo, action, messageId);
142     }
143 
144     private URI getUri(Node node, XPathExpression expression) {
145         String messageId = expression.evaluateAsString(node);
146         if (!StringUtils.hasLength(messageId)) {
147             return null;
148         }
149         try {
150             return new URI(messageId);
151         }
152         catch (URISyntaxException e) {
153             return null;
154         }
155     }
156 
157     private Element getSoapHeaderElement(SoapMessage message) {
158         Source source = message.getSoapHeader().getSource();
159         if (source instanceof DOMSource) {
160             DOMSource domSource = (DOMSource) source;
161             if (domSource.getNode() != null && domSource.getNode().getNodeType() == Node.ELEMENT_NODE) {
162                 return (Element) domSource.getNode();
163             }
164         }
165         try {
166             DOMResult domResult = new DOMResult();
167             transform(source, domResult);
168             Document document = (Document) domResult.getNode();
169             return document.getDocumentElement();
170         }
171         catch (TransformerException ex) {
172             throw new AddressingException("Could not transform SoapHeader to Document", ex);
173         }
174     }
175 
176     /** Given a ReplyTo, FaultTo, or From node, returns an endpoint reference. */
177     private EndpointReference getEndpointReference(Node node) {
178         if (node == null) {
179             return null;
180         }
181         URI address = getUri(node, addressExpression);
182         if (address == null) {
183             return null;
184         }
185         List<Node> referenceProperties =
186                 referencePropertiesExpression != null ? referencePropertiesExpression.evaluateAsNodeList(node) :
187                         Collections.<Node>emptyList();
188         List<Node> referenceParameters =
189                 referenceParametersExpression != null ? referenceParametersExpression.evaluateAsNodeList(node) :
190                         Collections.<Node>emptyList();
191         return new EndpointReference(address, referenceProperties, referenceParameters);
192     }
193 
194     public void addAddressingHeaders(SoapMessage message, MessageAddressingProperties map) {
195         SoapHeader header = message.getSoapHeader();
196         header.addNamespaceDeclaration(getNamespacePrefix(), getNamespaceUri());
197         // To
198         if (map.getTo() != null) {
199             SoapHeaderElement to = header.addHeaderElement(getToName());
200             to.setText(map.getTo().toString());
201             to.setMustUnderstand(true);
202         }
203         // From
204         if (map.getFrom() != null) {
205             SoapHeaderElement from = header.addHeaderElement(getFromName());
206             addEndpointReference(from, map.getFrom());
207         }
208         //ReplyTo
209         if (map.getReplyTo() != null) {
210             SoapHeaderElement replyTo = header.addHeaderElement(getReplyToName());
211             addEndpointReference(replyTo, map.getReplyTo());
212         }
213         // FaultTo
214         if (map.getFaultTo() != null) {
215             SoapHeaderElement faultTo = header.addHeaderElement(getFaultToName());
216             addEndpointReference(faultTo, map.getFaultTo());
217         }
218         // Action
219         SoapHeaderElement action = header.addHeaderElement(getActionName());
220         action.setText(map.getAction().toString());
221         // MessageID
222         if (map.getMessageId() != null) {
223             SoapHeaderElement messageId = header.addHeaderElement(getMessageIdName());
224             messageId.setText(map.getMessageId().toString());
225         }
226         // RelatesTo
227         if (map.getRelatesTo() != null) {
228             SoapHeaderElement relatesTo = header.addHeaderElement(getRelatesToName());
229             relatesTo.setText(map.getRelatesTo().toString());
230         }
231         addReferenceNodes(header.getResult(), map.getReferenceParameters());
232         addReferenceNodes(header.getResult(), map.getReferenceProperties());
233     }
234 
235     public final boolean understands(SoapHeaderElement headerElement) {
236         return getNamespaceUri().equals(headerElement.getName().getNamespaceURI());
237     }
238 
239     /** Adds ReplyTo, FaultTo, or From EPR to the given header Element. */
240     protected void addEndpointReference(SoapHeaderElement headerElement, EndpointReference epr) {
241         if (epr == null || epr.getAddress() == null) {
242             return;
243         }
244         try {
245             DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
246             Document document = documentBuilder.newDocument();
247             Element address = document.createElementNS(getNamespaceUri(), QNameUtils.toQualifiedName(getAddressName()));
248             address.setTextContent(epr.getAddress().toString());
249             transform(new DOMSource(address), headerElement.getResult());
250             if (getReferenceParametersName() != null && !epr.getReferenceParameters().isEmpty()) {
251                 Element referenceParams = document.createElementNS(getNamespaceUri(),
252                         QNameUtils.toQualifiedName(getReferenceParametersName()));
253                 addReferenceNodes(new DOMResult(referenceParams), epr.getReferenceParameters());
254                 transform(new DOMSource(referenceParams), headerElement.getResult());
255             }
256             if (getReferencePropertiesName() != null && !epr.getReferenceProperties().isEmpty()) {
257                 Element referenceProps = document.createElementNS(getNamespaceUri(),
258                         QNameUtils.toQualifiedName(getReferencePropertiesName()));
259                 addReferenceNodes(new DOMResult(referenceProps), epr.getReferenceProperties());
260                 transform(new DOMSource(referenceProps), headerElement.getResult());
261             }
262         }
263         catch (ParserConfigurationException ex) {
264             throw new AddressingException("Could not add Endpoint Reference [" + epr + "] to header element", ex);
265         }
266         catch (TransformerException ex) {
267             throw new AddressingException("Could not add reference properties/parameters to message", ex);
268         }
269     }
270 
271     protected void addReferenceNodes(Result result, List<Node> nodes) {
272         try {
273             for (Node node : nodes) {
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 }