/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ws.soap.addressing.version;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.springframework.util.StringUtils;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapHeader;
import org.springframework.ws.soap.SoapHeaderElement;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.addressing.AddressingException;
import org.springframework.ws.soap.addressing.core.EndpointReference;
import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;
import org.springframework.ws.soap.soap11.Soap11Body;
import org.springframework.ws.soap.soap12.Soap12Body;
import org.springframework.ws.soap.soap12.Soap12Fault;
import org.springframework.xml.namespace.QNameUtils;
import org.springframework.xml.transform.TransformerObjectSupport;
import org.springframework.xml.xpath.XPathExpression;
import org.springframework.xml.xpath.XPathExpressionFactory;
/**
* Abstract base class for {@link AddressingVersion} implementations. Uses {@link XPathExpression}s to retrieve
* addressing information.
*
* @author Arjen Poutsma
* @since 1.5.0
*/
public abstract class AbstractAddressingVersion extends TransformerObjectSupport implements AddressingVersion {
private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
private final XPathExpression toExpression;
private final XPathExpression actionExpression;
private final XPathExpression messageIdExpression;
private final XPathExpression fromExpression;
private final XPathExpression replyToExpression;
private final XPathExpression faultToExpression;
private final XPathExpression addressExpression;
private final XPathExpression referencePropertiesExpression;
private final XPathExpression referenceParametersExpression;
protected AbstractAddressingVersion() {
Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put(getNamespacePrefix(), getNamespaceUri());
toExpression = createNormalizedExpression(getToName(), namespaces);
actionExpression = createNormalizedExpression(getActionName(), namespaces);
messageIdExpression = createNormalizedExpression(getMessageIdName(), namespaces);
fromExpression = createExpression(getFromName(), namespaces);
replyToExpression = createExpression(getReplyToName(), namespaces);
faultToExpression = createExpression(getFaultToName(), namespaces);
addressExpression = createNormalizedExpression(getAddressName(), namespaces);
if (getReferencePropertiesName() != null) {
referencePropertiesExpression = createChildrenExpression(getReferencePropertiesName(), namespaces);
}
else {
referencePropertiesExpression = null;
}
if (getReferenceParametersName() != null) {
referenceParametersExpression = createChildrenExpression(getReferenceParametersName(), namespaces);
}
else {
referenceParametersExpression = null;
}
}
private XPathExpression createExpression(QName name, Map<String, String> namespaces) {
String expression = name.getPrefix() + ":" + name.getLocalPart();
return XPathExpressionFactory.createXPathExpression(expression, namespaces);
}
private XPathExpression createNormalizedExpression(QName name, Map<String, String> namespaces) {
String expression = "normalize-space(" + name.getPrefix() + ":" + name.getLocalPart() + ")";
return XPathExpressionFactory.createXPathExpression(expression, namespaces);
}
private XPathExpression createChildrenExpression(QName name, Map<String, String> namespaces) {
String expression = name.getPrefix() + ":" + name.getLocalPart() + "/*";
return XPathExpressionFactory.createXPathExpression(expression, namespaces);
}
@Override
public MessageAddressingProperties getMessageAddressingProperties(SoapMessage message) {
Element headerElement = getSoapHeaderElement(message);
URI to = getUri(headerElement, toExpression);
if (to == null) {
to = getDefaultTo();
}
EndpointReference from = getEndpointReference(fromExpression.evaluateAsNode(headerElement));
EndpointReference replyTo = getEndpointReference(replyToExpression.evaluateAsNode(headerElement));
if (replyTo == null) {
replyTo = getDefaultReplyTo(from);
}
EndpointReference faultTo = getEndpointReference(faultToExpression.evaluateAsNode(headerElement));
if (faultTo == null) {
faultTo = replyTo;
}
URI action = getUri(headerElement, actionExpression);
URI messageId = getUri(headerElement, messageIdExpression);
return new MessageAddressingProperties(to, from, replyTo, faultTo, action, messageId);
}
private URI getUri(Node node, XPathExpression expression) {
String messageId = expression.evaluateAsString(node);
if (!StringUtils.hasLength(messageId)) {
return null;
}
try {
return new URI(messageId);
}
catch (URISyntaxException e) {
return null;
}
}
private Element getSoapHeaderElement(SoapMessage message) {
Source source = message.getSoapHeader().getSource();
if (source instanceof DOMSource) {
DOMSource domSource = (DOMSource) source;
if (domSource.getNode() != null && domSource.getNode().getNodeType() == Node.ELEMENT_NODE) {
return (Element) domSource.getNode();
}
}
try {
DOMResult domResult = new DOMResult();
transform(source, domResult);
Document document = (Document) domResult.getNode();
return document.getDocumentElement();
}
catch (TransformerException ex) {
throw new AddressingException("Could not transform SoapHeader to Document", ex);
}
}
/** Given a ReplyTo, FaultTo, or From node, returns an endpoint reference. */
private EndpointReference getEndpointReference(Node node) {
if (node == null) {
return null;
}
URI address = getUri(node, addressExpression);
if (address == null) {
return null;
}
List<Node> referenceProperties =
referencePropertiesExpression != null ? referencePropertiesExpression.evaluateAsNodeList(node) :
Collections.<Node>emptyList();
List<Node> referenceParameters =
referenceParametersExpression != null ? referenceParametersExpression.evaluateAsNodeList(node) :
Collections.<Node>emptyList();
return new EndpointReference(address, referenceProperties, referenceParameters);
}
@Override
public void addAddressingHeaders(SoapMessage message, MessageAddressingProperties map) {
SoapHeader header = message.getSoapHeader();
header.addNamespaceDeclaration(getNamespacePrefix(), getNamespaceUri());
// To
if (map.getTo() != null) {
SoapHeaderElement to = header.addHeaderElement(getToName());
to.setText(map.getTo().toString());
to.setMustUnderstand(true);
}
// From
if (map.getFrom() != null) {
SoapHeaderElement from = header.addHeaderElement(getFromName());
addEndpointReference(from, map.getFrom());
}
//ReplyTo
if (map.getReplyTo() != null) {
SoapHeaderElement replyTo = header.addHeaderElement(getReplyToName());
addEndpointReference(replyTo, map.getReplyTo());
}
// FaultTo
if (map.getFaultTo() != null) {
SoapHeaderElement faultTo = header.addHeaderElement(getFaultToName());
addEndpointReference(faultTo, map.getFaultTo());
}
// Action
SoapHeaderElement action = header.addHeaderElement(getActionName());
action.setText(map.getAction().toString());
// MessageID
if (map.getMessageId() != null) {
SoapHeaderElement messageId = header.addHeaderElement(getMessageIdName());
messageId.setText(map.getMessageId().toString());
}
// RelatesTo
if (map.getRelatesTo() != null) {
SoapHeaderElement relatesTo = header.addHeaderElement(getRelatesToName());
relatesTo.setText(map.getRelatesTo().toString());
}
addReferenceNodes(header.getResult(), map.getReferenceParameters());
addReferenceNodes(header.getResult(), map.getReferenceProperties());
}
@Override
public final boolean understands(SoapHeaderElement headerElement) {
return getNamespaceUri().equals(headerElement.getName().getNamespaceURI());
}
/** Adds ReplyTo, FaultTo, or From EPR to the given header Element. */
protected void addEndpointReference(SoapHeaderElement headerElement, EndpointReference epr) {
if (epr == null || epr.getAddress() == null) {
return;
}
try {
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.newDocument();
Element address = document.createElementNS(getNamespaceUri(), QNameUtils.toQualifiedName(getAddressName()));
address.setTextContent(epr.getAddress().toString());
transform(new DOMSource(address), headerElement.getResult());
if (getReferenceParametersName() != null && !epr.getReferenceParameters().isEmpty()) {
Element referenceParams = document.createElementNS(getNamespaceUri(),
QNameUtils.toQualifiedName(getReferenceParametersName()));
addReferenceNodes(new DOMResult(referenceParams), epr.getReferenceParameters());
transform(new DOMSource(referenceParams), headerElement.getResult());
}
if (getReferencePropertiesName() != null && !epr.getReferenceProperties().isEmpty()) {
Element referenceProps = document.createElementNS(getNamespaceUri(),
QNameUtils.toQualifiedName(getReferencePropertiesName()));
addReferenceNodes(new DOMResult(referenceProps), epr.getReferenceProperties());
transform(new DOMSource(referenceProps), headerElement.getResult());
}
}
catch (ParserConfigurationException ex) {
throw new AddressingException("Could not add Endpoint Reference [" + epr + "] to header element", ex);
}
catch (TransformerException ex) {
throw new AddressingException("Could not add reference properties/parameters to message", ex);
}
}
protected void addReferenceNodes(Result result, List<Node> nodes) {
try {
for (Node node : nodes) {
DOMSource source = new DOMSource(node);
transform(source, result);
}
}
catch (TransformerException ex) {
throw new AddressingException("Could not add reference properties/parameters to message", ex);
}
}
@Override
public final SoapFault addInvalidAddressingHeaderFault(SoapMessage message) {
return addAddressingFault(message, getInvalidAddressingHeaderFaultSubcode(),
getInvalidAddressingHeaderFaultReason());
}
@Override
public final SoapFault addMessageAddressingHeaderRequiredFault(SoapMessage message) {
return addAddressingFault(message, getMessageAddressingHeaderRequiredFaultSubcode(),
getMessageAddressingHeaderRequiredFaultReason());
}
private SoapFault addAddressingFault(SoapMessage message, QName subcode, String reason) {
if (message.getSoapBody() instanceof Soap11Body) {
Soap11Body soapBody = (Soap11Body) message.getSoapBody();
return soapBody.addFault(subcode, reason, Locale.ENGLISH);
}
else if (message.getSoapBody() instanceof Soap12Body) {
Soap12Body soapBody = (Soap12Body) message.getSoapBody();
Soap12Fault soapFault =
soapBody.addClientOrSenderFault(reason, Locale.ENGLISH);
soapFault.addFaultSubcode(subcode);
return soapFault;
}
return null;
}
/*
* Address URIs
*/
@Override
public final boolean hasAnonymousAddress(EndpointReference epr) {
URI anonymous = getAnonymous();
return anonymous != null && anonymous.equals(epr.getAddress());
}
@Override
public final boolean hasNoneAddress(EndpointReference epr) {
URI none = getNone();
return none != null && none.equals(epr.getAddress());
}
/** Returns the prefix associated with the WS-Addressing namespace handled by this specification. */
protected String getNamespacePrefix() {
return "wsa";
}
/** Returns the WS-Addressing namespace handled by this specification. */
protected abstract String getNamespaceUri();
/*
* Message addressing properties
*/
/** Returns the qualified name of the {@code To} addressing header. */
protected QName getToName() {
return new QName(getNamespaceUri(), "To", getNamespacePrefix());
}
/** Returns the qualified name of the {@code From} addressing header. */
protected QName getFromName() {
return new QName(getNamespaceUri(), "From", getNamespacePrefix());
}
/** Returns the qualified name of the {@code ReplyTo} addressing header. */
protected QName getReplyToName() {
return new QName(getNamespaceUri(), "ReplyTo", getNamespacePrefix());
}
/** Returns the qualified name of the {@code FaultTo} addressing header. */
protected QName getFaultToName() {
return new QName(getNamespaceUri(), "FaultTo", getNamespacePrefix());
}
/** Returns the qualified name of the {@code Action} addressing header. */
protected QName getActionName() {
return new QName(getNamespaceUri(), "Action", getNamespacePrefix());
}
/** Returns the qualified name of the {@code MessageID} addressing header. */
protected QName getMessageIdName() {
return new QName(getNamespaceUri(), "MessageID", getNamespacePrefix());
}
/** Returns the qualified name of the {@code RelatesTo} addressing header. */
protected QName getRelatesToName() {
return new QName(getNamespaceUri(), "RelatesTo", getNamespacePrefix());
}
/** Returns the qualified name of the {@code RelatesTo} addressing header. */
protected QName getRelationshipTypeName() {
return new QName("RelationshipType");
}
/**
* Returns the qualified name of the {@code ReferenceProperties} in the endpoint reference. Returns
* {@code null} when reference properties are not supported by this version of the spec.
*/
protected QName getReferencePropertiesName() {
return new QName(getNamespaceUri(), "ReferenceProperties", getNamespacePrefix());
}
/**
* Returns the qualified name of the {@code ReferenceParameters} in the endpoint reference. Returns
* {@code null} when reference parameters are not supported by this version of the spec.
*/
protected QName getReferenceParametersName() {
return new QName(getNamespaceUri(), "ReferenceParameters", getNamespacePrefix());
}
/*
* Endpoint Reference
*/
/** The qualified name of the {@code Address} in {@code EndpointReference}. */
protected QName getAddressName() {
return new QName(getNamespaceUri(), "Address", getNamespacePrefix());
}
/** Returns the default To URI. */
protected abstract URI getDefaultTo();
/** Returns the default ReplyTo EPR. Can be based on the From EPR, or the anonymous URI. */
protected abstract EndpointReference getDefaultReplyTo(EndpointReference from);
/*
* Address URIs
*/
/** Returns the anonymous URI. */
protected abstract URI getAnonymous();
/** Returns the none URI, or {@code null} if the spec does not define it. */
protected abstract URI getNone();
/*
* Faults
*/
/** Returns the qualified name of the fault subcode that indicates that a header is missing. */
protected abstract QName getMessageAddressingHeaderRequiredFaultSubcode();
/** Returns the reason of the fault that indicates that a header is missing. */
protected abstract String getMessageAddressingHeaderRequiredFaultReason();
/** Returns the qualified name of the fault subcode that indicates that a header is invalid. */
protected abstract QName getInvalidAddressingHeaderFaultSubcode();
/** Returns the reason of the fault that indicates that a header is invalid. */
protected abstract String getInvalidAddressingHeaderFaultReason();
}