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.TransformerException;
32  import javax.xml.transform.dom.DOMResult;
33  import javax.xml.transform.dom.DOMSource;
34  
35  import org.springframework.util.StringUtils;
36  import org.springframework.ws.soap.SoapFault;
37  import org.springframework.ws.soap.SoapHeader;
38  import org.springframework.ws.soap.SoapHeaderElement;
39  import org.springframework.ws.soap.SoapMessage;
40  import org.springframework.ws.soap.addressing.AddressingException;
41  import org.springframework.ws.soap.addressing.core.EndpointReference;
42  import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;
43  import org.springframework.ws.soap.soap11.Soap11Body;
44  import org.springframework.ws.soap.soap12.Soap12Body;
45  import org.springframework.ws.soap.soap12.Soap12Fault;
46  import org.springframework.xml.namespace.QNameUtils;
47  import org.springframework.xml.transform.TransformerObjectSupport;
48  import org.springframework.xml.xpath.XPathExpression;
49  import org.springframework.xml.xpath.XPathExpressionFactory;
50  
51  import org.w3c.dom.Document;
52  import org.w3c.dom.Element;
53  import org.w3c.dom.Node;
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          Map<String, String> namespaces = new HashMap<String, String>();
86          namespaces.put(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, Map<String, String> namespaces) {
109         String expression = name.getPrefix() + ":" + name.getLocalPart();
110         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
111     }
112 
113     private XPathExpression createNormalizedExpression(QName name, Map<String, String> namespaces) {
114         String expression = "normalize-space(" + name.getPrefix() + ":" + name.getLocalPart() + ")";
115         return XPathExpressionFactory.createXPathExpression(expression, namespaces);
116     }
117 
118     private XPathExpression createChildrenExpression(QName name, Map<String, String> 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<Node> referenceProperties =
185                 referencePropertiesExpression != null ? referencePropertiesExpression.evaluateAsNodeList(node) :
186                         Collections.<Node>emptyList();
187         List<Node> referenceParameters =
188                 referenceParametersExpression != null ? referenceParametersExpression.evaluateAsNodeList(node) :
189                         Collections.<Node>emptyList();
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<Node> nodes) {
271         try {
272             for (Node node : nodes) {
273                 DOMSource source = new DOMSource(node);
274                 transform(source, result);
275             }
276         }
277         catch (TransformerException ex) {
278             throw new AddressingException("Could not add reference properties/parameters to message", ex);
279         }
280     }
281 
282     public final SoapFault addInvalidAddressingHeaderFault(SoapMessage message) {
283         return addAddressingFault(message, getInvalidAddressingHeaderFaultSubcode(),
284                 getInvalidAddressingHeaderFaultReason());
285     }
286 
287     public final SoapFault addMessageAddressingHeaderRequiredFault(SoapMessage message) {
288         return addAddressingFault(message, getMessageAddressingHeaderRequiredFaultSubcode(),
289                 getMessageAddressingHeaderRequiredFaultReason());
290     }
291 
292     private SoapFault addAddressingFault(SoapMessage message, QName subcode, String reason) {
293         if (message.getSoapBody() instanceof Soap11Body) {
294             Soap11Body soapBody = (Soap11Body) message.getSoapBody();
295             return soapBody.addFault(subcode, reason, Locale.ENGLISH);
296         }
297         else if (message.getSoapBody() instanceof Soap12Body) {
298             Soap12Body soapBody = (Soap12Body) message.getSoapBody();
299             Soap12Fault soapFault = (Soap12Fault) soapBody.addClientOrSenderFault(reason, Locale.ENGLISH);
300             soapFault.addFaultSubcode(subcode);
301             return soapFault;
302         }
303         return null;
304     }
305 
306     /*
307     * Address URIs
308     */
309 
310     public final boolean hasAnonymousAddress(EndpointReference epr) {
311         URI anonymous = getAnonymous();
312         return anonymous != null && anonymous.equals(epr.getAddress());
313     }
314 
315     public final boolean hasNoneAddress(EndpointReference epr) {
316         URI none = getNone();
317         return none != null && none.equals(epr.getAddress());
318     }
319 
320     /** Returns the prefix associated with the WS-Addressing namespace handled by this specification. */
321     protected String getNamespacePrefix() {
322         return "wsa";
323     }
324 
325     /** Returns the WS-Addressing namespace handled by this specification. */
326     protected abstract String getNamespaceUri();
327 
328     /*
329      * Message addressing properties
330      */
331 
332     /** Returns the qualified name of the <code>To</code> addressing header. */
333     protected QName getToName() {
334         return QNameUtils.createQName(getNamespaceUri(), "To", getNamespacePrefix());
335     }
336 
337     /** Returns the qualified name of the <code>From</code> addressing header. */
338     protected QName getFromName() {
339         return QNameUtils.createQName(getNamespaceUri(), "From", getNamespacePrefix());
340     }
341 
342     /** Returns the qualified name of the <code>ReplyTo</code> addressing header. */
343     protected QName getReplyToName() {
344         return QNameUtils.createQName(getNamespaceUri(), "ReplyTo", getNamespacePrefix());
345     }
346 
347     /** Returns the qualified name of the <code>FaultTo</code> addressing header. */
348     protected QName getFaultToName() {
349         return QNameUtils.createQName(getNamespaceUri(), "FaultTo", getNamespacePrefix());
350     }
351 
352     /** Returns the qualified name of the <code>Action</code> addressing header. */
353     protected QName getActionName() {
354         return QNameUtils.createQName(getNamespaceUri(), "Action", getNamespacePrefix());
355     }
356 
357     /** Returns the qualified name of the <code>MessageID</code> addressing header. */
358     protected QName getMessageIdName() {
359         return QNameUtils.createQName(getNamespaceUri(), "MessageID", getNamespacePrefix());
360     }
361 
362     /** Returns the qualified name of the <code>RelatesTo</code> addressing header. */
363     protected QName getRelatesToName() {
364         return QNameUtils.createQName(getNamespaceUri(), "RelatesTo", getNamespacePrefix());
365     }
366 
367     /** Returns the qualified name of the <code>RelatesTo</code> addressing header. */
368     protected QName getRelationshipTypeName() {
369         return new QName("RelationshipType");
370     }
371 
372     /**
373      * Returns the qualified name of the <code>ReferenceProperties</code> in the endpoint reference. Returns
374      * <code>null</code> when reference properties are not supported by this version of the spec.
375      */
376     protected QName getReferencePropertiesName() {
377         return QNameUtils.createQName(getNamespaceUri(), "ReferenceProperties", getNamespacePrefix());
378     }
379 
380     /**
381      * Returns the qualified name of the <code>ReferenceParameters</code> in the endpoint reference. Returns
382      * <code>null</code> when reference parameters are not supported by this version of the spec.
383      */
384     protected QName getReferenceParametersName() {
385         return QNameUtils.createQName(getNamespaceUri(), "ReferenceParameters", getNamespacePrefix());
386     }
387 
388     /*
389      * Endpoint Reference
390      */
391 
392     /** The qualified name of the <code>Address</code> in <code>EndpointReference</code>. */
393     protected QName getAddressName() {
394         return QNameUtils.createQName(getNamespaceUri(), "Address", getNamespacePrefix());
395     }
396 
397     /** Returns the default To URI. */
398     protected abstract URI getDefaultTo();
399 
400     /** Returns the default ReplyTo EPR. Can be based on the From EPR, or the anonymous URI. */
401     protected abstract EndpointReference getDefaultReplyTo(EndpointReference from);
402 
403     /*
404      * Address URIs
405      */
406 
407     /** Returns the anonymous URI. */
408     protected abstract URI getAnonymous();
409 
410     /** Returns the none URI, or <code>null</code> if the spec does not define it. */
411     protected abstract URI getNone();
412 
413     /*
414      * Faults
415      */
416 
417     /** Returns the qualified name of the fault subcode that indicates that a header is missing. */
418     protected abstract QName getMessageAddressingHeaderRequiredFaultSubcode();
419 
420     /** Returns the reason of the fault that indicates that a header is missing. */
421     protected abstract String getMessageAddressingHeaderRequiredFaultReason();
422 
423     /** Returns the qualified name of the fault subcode that indicates that a header is invalid. */
424     protected abstract QName getInvalidAddressingHeaderFaultSubcode();
425 
426     /** Returns the reason of the fault that indicates that a header is invalid. */
427     protected abstract String getInvalidAddressingHeaderFaultReason();
428 }