/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.muse.ws.addressing;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.QName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.muse.util.MultiMap;
import org.apache.muse.util.messages.Messages;
import org.apache.muse.util.messages.MessagesFactory;
import org.apache.muse.util.uuid.RandomUuidFactory;
import org.apache.muse.util.xml.XmlSerializable;
import org.apache.muse.util.xml.XmlUtils;
import org.apache.muse.ws.addressing.soap.SoapFault;
import org.apache.muse.ws.addressing.soap.SoapConstants;
/**
*
* MessageHeaders provides WS-Addressing processing for SOAP message headers.
* It can be used to parse SOAP headers according to WS-A spec and read the
* values <b>or</b> create a new set of headers that can be serialized to a
* set of valid SOAP headers.
* <br><br>
* This class references the W3C DOM API rather than the SAAJ API
* because SOAPElements can usually be converted into DOM Elements very
* easily, but having a DOM Element does not mean that the user code has
* access to (or wants access to) the SAAJ API and its SOAP factories.
*
* @author Dan Jemiolo (danj)
*
* @see EndpointReference
*
*/
public class MessageHeaders implements XmlSerializable
{
//
// Used to lookup all exception messages
//
private static Messages _MESSAGES = MessagesFactory.get(MessageHeaders.class);
private static final String _REQUEST = "Request";
private static final String _RESPONSE = "Response";
//
// below are the standard WS-A header values for SOAP messages
//
private String _action = null;
//
// all non-WS-A headers are made available separately
//
private Map _customHeadersByQName = new MultiMap();
private EndpointReference _faultTo = null;
private EndpointReference _from = null;
private String _messageID = null;
private String _relationship = null;
private EndpointReference _replyTo = null;
private EndpointReference _to = null;
/**
*
* Creates a valid set of WS-A message headers from the given SOAP Header.
* The wsa:To URI and reference parameter elements become an EPR; this
* EPR can be used to determine which WS-resource is being targeted.
* <br><br>
* An appropriate set of reply headers can be generated with the
* createReplyHeaders(URI) and createFaultHeaders() methods, assuming the
* SOAP Header included the appropriate wsa:From, wsa:ReplyTo, or
* wsa:FaultTo EPR.
*
* @param soapHeaders
* The Element representing a SOAP envelope's Header. The Header
* should contain all of the required WS-A elements.
*
* @throws SoapFault
* <ul>
* <li>
* If either the wsa:To, wsa:From, wsa:ReplyTo, or wsa:FaultTo
* XML is invalid according to the WS-A EPR definition.
* </li>
* <li>
* If either the wsa:To, wsa:MessageID, wsa:RelatesTo, or
* wsa:Action value is not a valid URI.
* </li>
* <li>
* If either the wsa:To, wsa:MessageID, or wsa:Action value is
* undefined.
* </li>
* </ul>
*
* @see #createFaultHeaders()
* @see #createReplyHeaders()
*
*/
public MessageHeaders(Element soapHeaders)
throws SoapFault
{
if (soapHeaders == null)
throw new NullPointerException(_MESSAGES.get("NullSOAPHeader"));
String toURI = XmlUtils.getElementText(soapHeaders, WsaConstants.TO_QNAME);
_action = XmlUtils.getElementText(soapHeaders, WsaConstants.ACTION_QNAME);
_messageID = XmlUtils.getElementText(soapHeaders, WsaConstants.MESSAGE_ID_QNAME);
_relationship = XmlUtils.getElementText(soapHeaders, WsaConstants.RELATES_TO_QNAME);
//
// all of the URIs are required values
//
if (toURI == null)
{
Object[] filler = { WsaConstants.TO_QNAME };
throwInvalidAddressingHeaderFault(_MESSAGES.get("HeaderMissing", filler));
}
if (_action == null)
{
Object[] filler = { WsaConstants.ACTION_QNAME };
throwInvalidAddressingHeaderFault(_MESSAGES.get("HeaderMissing", filler));
}
//
// wsa:To is a URI, but we want to access it as an EPR
//
_to = new EndpointReference(URI.create(toURI));
//
// read in optional EPR values that tell us where to send replies
//
_faultTo = getEPR(soapHeaders, WsaConstants.FAULT_TO_QNAME);
_from = getEPR(soapHeaders, WsaConstants.FROM_QNAME);
_replyTo = getEPR(soapHeaders, WsaConstants.REPLY_TO_QNAME);
if (_messageID == null && (_from != null || _replyTo != null))
throwInvalidAddressingHeaderFault(_MESSAGES.get("MessageIDMissing"));
Element[] children = XmlUtils.getAllElements(soapHeaders);
//
// look at all SOAP headers to determine if they're WS-A reference
// parameters or not
//
for (int n = 0; n < children.length; ++n)
{
QName qname = XmlUtils.getElementQName(children[n]);
//
// check to see if its a WS-A reference parameter
//
String wsaAttr = children[n].getAttributeNS(WsaConstants.NAMESPACE_URI, WsaConstants.IS_REFERENCE_PARAMETER);
if (Boolean.valueOf(wsaAttr) == Boolean.TRUE)
{
//
// remove SOAP junk from parameter XML - this is especially
// important when you start moving the EPR XML to other
// messages/fragments that may not have the SOAP prefixes
//
children[n].removeAttributeNS(SoapConstants.NAMESPACE_URI, SoapConstants.ACTOR);
children[n].removeAttributeNS(SoapConstants.NAMESPACE_URI, SoapConstants.MUST_UNDERSTAND);
_to.addParameter(qname, children[n]);
}
//
// if not, make sure it's not a WS-A element and add it to
// the 'custom headers' collection
//
else if (!qname.getNamespaceURI().equals(WsaConstants.NAMESPACE_URI))
_customHeadersByQName.put(qname, children[n]);
}
}
/**
*
* Creates a new set of WS-A message headers targeted to the given EPR
* and Action. This constructor creates its own unique wsa:MessageID.
*
* @param to
* The EPR that will supply the URI for wsa:To.
*
* @param action
* The wsa:Action URI.
*
*/
public MessageHeaders(EndpointReference to, String action)
{
if (to == null)
throw new NullPointerException(_MESSAGES.get("NullToEPR"));
_to = to;
_action = action;
_messageID = createMessageID();
}
/**
*
* Returns a new set of WS-A message headers based on this object; the
* headers returned can be used when replying to a message with a fault.
* The headers will be targeted to the wsa:FaultTo EPR and the WS-A
* fault action URI. If there is no wsa:FaultTo, wsa:From or the WS-A
* anonymous EPR is used.
* <br><br>
* <b>Note:</b> This method should not be used to create headers for
* "normal" responses - that is done with createReplyHeaders(URI).
*
* @return A new MessageHeaders where:
* <ul>
* <li>the wsa:To is the URI of this object's wsa:FaultTo,
* wsa:From, or the anonymous EPR</li>
* <li>the wsa:From is this object's wsa:To EPR</li>
* <li>the wsa:RelatesTo is the URI of this object's
* wsa:MessageID</li>
* <li>the wsa:Action is the standard WS-A fault URI.</li>
* </ul>
*
* @see #createReplyHeaders()
* @see #getFaultToAddress()
* @see #setFaultToAddress(EndpointReference)
* @see WsaConstants#FAULT_URI
*
*/
public MessageHeaders createFaultHeaders()
{
EndpointReference faultTo = getFaultToAddress();
String messageID = getMessageID();
//
// we can only send back fault headers if wsa:FaultTo or wsa:From exists
//
if (faultTo == null)
{
EndpointReference from = getFromAddress();
if (from == null)
from = WsaConstants.ANONYMOUS_EPR;
faultTo = from;
}
//
// 1. create headers bound for wsa:FaultTo
// 2. associate headers with request MessageID
// 3. set source that was in wsa:To
//
MessageHeaders faultHeaders =
new MessageHeaders(faultTo, WsaConstants.FAULT_URI);
if (messageID != null)
faultHeaders.setRelationship(messageID);
faultHeaders.setFromAddress(getToAddress());
return faultHeaders;
}
/**
*
* @return A unique UUID for use as a wsa:MessageID.
*
*/
private String createMessageID()
{
return RandomUuidFactory.getInstance().createUUID();
}
/**
*
* Returns a new set of WS-A message headers based on this object; the
* headers returned can be used when replying to a message (no fault).
* The headers will be targeted to the wsa:ReplyTo EPR. If there is no
* wsa:ReplyTo, wsa:From or the WS-A anonymous EPR is used.
* <br><br>
* <b>Note:</b> This method should not be used to create headers for
* responses where a fault has occurred - that should be done with
* createFaultHeaders().
*
* @return A new MessageHeaders where:
* <ul>
* <li>the wsa:To is the URI of this object's wsa:ReplyTo,
* wsa:From, or the anonymous EPR</li>
* <li>the wsa:From is this object's wsa:To EPR</li>
* <li>the wsa:RelatesTo is the URI of this object's
* wsa:MessageID</li>
* <li>the wsa:Action is the current URI with a "Response" suffix.</li>
* </ul>
*
* @see #getReplyToAddress()
* @see #setReplyToAddress(EndpointReference)
*
*/
public MessageHeaders createReplyHeaders()
{
EndpointReference replyTo = getReplyToAddress();
String messageID = getMessageID();
//
// we can only send back headers if wsa:ReplyTo or wsa:From exists
//
if (replyTo == null)
{
EndpointReference from = getFromAddress();
if (from == null)
from = WsaConstants.ANONYMOUS_EPR;
replyTo = from;
}
String requestAction = getAction().toString();
String replyAction = null;
int requestSuffix = requestAction.lastIndexOf(_REQUEST);
if (requestSuffix >= 0)
replyAction = requestAction.substring(0, requestSuffix) + _RESPONSE;
else
replyAction = requestAction + _RESPONSE;
//
// 1. create headers bound for wsa:ReplyTo
// 2. associate headers with request MessageID
// 3. set source that was in wsa:To
//
MessageHeaders replyHeaders = new MessageHeaders(replyTo, replyAction);
if (messageID != null)
replyHeaders.setRelationship(messageID);
replyHeaders.setFromAddress(getToAddress());
return replyHeaders;
}
/**
*
* @return The wsa:Action URI. This is required value.
*
*/
public String getAction()
{
return _action;
}
/**
*
* This method is useful if you are only expecting one instance of a given
* SOAP header element and do not want to sort through a Collection or
* Iterator just to get one item.
*
* @param elementName
* The name of the non-WS-A SOAP header to return.
*
* @return The <b>first</b> DOM Element found with the given name, or
* null if no such element was found.
*
*/
public Element getCustomHeader(QName elementName)
{
Collection headers = getCustomHeaders(elementName);
return headers.isEmpty() ? null : (Element)headers.iterator().next();
}
/**
*
* @param elementName
* The name of the non-WS-A SOAP header(s) to return.
*
* @return A collection of DOM Elements with the given name. The collection
* may be empty.
*
*/
public Collection getCustomHeaders(QName elementName)
{
Collection headers = (Collection)_customHeadersByQName.get(elementName);
return headers != null ? headers : Collections.EMPTY_LIST;
}
/**
*
* @return A collection of QNames, one for each SOAP header that a) does
* not have the WS-A namespace, and b) is not a WS-A reference
* parameter. The collection may be empty.
*
*/
public Collection getCustomHeaderNames()
{
return Collections.unmodifiableSet(_customHeadersByQName.keySet());
}
/**
*
* Parses the given XML to create a valid EPR. The EPR XML root element
* must have the given QName.
*
* @param root
* The Element containing the EPR Element as one of its children.
*
* @param qname
* The QName of the child element that defines an EPR.
*
* @return A valid EPR based on the given XML.
*
*/
private EndpointReference getEPR(Element root, QName qname)
throws SoapFault
{
Element epr = XmlUtils.getElement(root, qname);
if (epr == null)
return null;
return new EndpointReference(epr);
}
/**
*
* @return The wsa:FaultTo EPR. The method returns null if the value is
* not set.
*
*/
public EndpointReference getFaultToAddress()
{
return _faultTo;
}
/**
*
* @return The wsa:From EPR. The method returns null if the value is
* not set.
*
*/
public EndpointReference getFromAddress()
{
return _from;
}
/**
*
* @return The unique wsa:MessageID UUID. This is a required value.
*
*/
public String getMessageID()
{
return _messageID;
}
/**
*
* @return The identifier found after the last slash in the wsa:Action
* URI. If the URI is 'http://example.com/spec/DoStuff', the value
* that is returned is 'DoStuff'.
*
*/
public String getMethodName()
{
String actionURI = getAction().toString();
int lastSlash = actionURI.lastIndexOf('/');
String name = actionURI.substring(lastSlash + 1);
int request = name.indexOf("Request");
if (request >= 0)
name = name.substring(0, request);
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
/**
*
* @return The wsa:RelatesTo value. The method returns null if the value
* is not set. wsa:RelatesTo is a UUID that maps to another
* MessageHeader's wsa:MessageID.
*
*/
public String getRelationship()
{
return _relationship;
}
/**
*
* @return This method always returns WsaUtils.REPLY_RELATIONSHIP_QNAME.
*
* @see WsaConstants#REPLY_RELATIONSHIP_QNAME
*
*/
public QName getRelationshipType()
{
return WsaConstants.REPLY_RELATIONSHIP_QNAME;
}
/**
*
* @return The wsa:ReplyTo EPR. The method returns null if the value is
* not set.
*
*/
public EndpointReference getReplyToAddress()
{
return _replyTo;
}
/**
*
* @return An EPR representing the wsa:To URI and the reference properties
* defined in the SOAP Header (if any). This is a required value.
*
*/
public EndpointReference getToAddress()
{
return _to;
}
/**
*
* @param action
* The wsa:Action URI. This cannot be null.
*
*/
protected void setAction(String action)
{
if (action == null)
throw new NullPointerException(_MESSAGES.get("NullActionURI"));
_action = action;
}
/**
*
* @param faultTo
* The wsa:FaultTo EPR - if non-null, a copy of EPR will be made,
* so modifications to the original will have no effect on this
* set of headers.
*
*/
public void setFaultToAddress(EndpointReference faultTo)
{
if (faultTo == null)
_faultTo = null;
//
// need to make our own copy whose root element is wsa:FaultTo
//
else
_faultTo = new EndpointReference(faultTo, WsaConstants.FAULT_TO_QNAME);
}
/**
*
* @param from
* The wsa:From EPR - if non-null, a copy of EPR will be made, so
* modifications to the original will have no effect on this set
* of headers.
*
*/
public void setFromAddress(EndpointReference from)
{
if (from == null)
_from = null;
//
// need to make our own copy whose root element is wsa:From
//
else
_from = new EndpointReference(from, WsaConstants.FROM_QNAME);
}
/**
*
* @param messageID
* The wsa:MessageID. This cannot be null.
*
*/
protected void setMessageID(String messageID)
{
if (messageID == null)
throw new NullPointerException(_MESSAGES.get("NullMessageID"));
_messageID = messageID;
}
/**
*
* @param relationship
* The wsa:RelatesTo ID. This cannot be null;
*
*/
public void setRelationship(String relationship)
{
if (relationship == null)
throw new NullPointerException(_MESSAGES.get("NullRelatesTo"));
_relationship = relationship;
}
/**
*
* @param replyTo
* The wsa:ReplyTo EPR - if non-null, a copy of EPR will be made,
* so modifications to the original will have no effect on this
* set of headers.
*
*/
public void setReplyToAddress(EndpointReference replyTo)
{
if (replyTo == null)
_replyTo = null;
//
// need to make our own copy whose root element is wsa:ReplyTo
//
else
_replyTo = new EndpointReference(replyTo, WsaConstants.REPLY_TO_QNAME);
}
/**
*
* @param to
* An EPR with the address value for the wsa:To URI. This cannot
* be null. No copy of the given EPR is made, so subsequent
* modifications to it will affect this object.
*
*/
protected void setToAddress(EndpointReference to)
{
if (to == null)
throw new NullPointerException(_MESSAGES.get("NullToEPR"));
_to = to;
}
protected void throwInvalidAddressingHeaderFault(String message)
throws SoapFault
{
SoapFault fault = new SoapFault(message);
fault.setCode(SoapConstants.SENDER_QNAME);
fault.setSubCode(WsaConstants.INVALID_HEADER_FAULT_QNAME);
throw fault;
}
/**
*
* @return An XML string representing the collection of WS-A headers.
* The XML would be a valid SOAP Header element.
*
*/
public String toString()
{
return XmlUtils.toString(toXML(), false);
}
/**
*
* @see #toXML(Document)
*
*/
public Element toXML()
{
return toXML(XmlUtils.EMPTY_DOC);
}
/**
*
*
*
* @param doc
* The XML Document to use when creating new elements for the
* SOAP Header XML. The elements that are created will <b>not</b>
* be appended to this Document.
*
* @return A SOAP Header element with all of the valid WS-A headers as
* child elements. There is no guarantee on the order of the WS-A
* headers. All reference parameters and properties in the wsa:To
* EPR will be appended as children of the root SOAP Header element.
*
* @see SoapConstants#HEADER_QNAME
*
*/
public Element toXML(Document doc)
{
if (doc == null)
throw new NullPointerException(_MESSAGES.get("NullXMLDocument"));
Element soapHeaders =
XmlUtils.createElement(doc, SoapConstants.HEADER_QNAME);
//
// wsa:To
//
URI toURI = getToAddress().getAddress();
Element to = XmlUtils.createElement(doc, WsaConstants.TO_QNAME, toURI);
soapHeaders.appendChild(to);
//
// wsa:Action
//
XmlUtils.setElement(soapHeaders, WsaConstants.ACTION_QNAME, getAction());
//
// wsa:MessageID
//
String messageID = getMessageID();
if (messageID != null)
XmlUtils.setElement(soapHeaders, WsaConstants.MESSAGE_ID_QNAME, messageID);
//
// wsa:RelatesTo (optional)
//
String relatesTo = getRelationship();
if (relatesTo != null)
{
Element relationship =
XmlUtils.createElement(doc, WsaConstants.RELATES_TO_QNAME, relatesTo);
String type = XmlUtils.toString(getRelationshipType());
relationship.setAttribute(WsaConstants.RELATIONSHIP_TYPE, type);
soapHeaders.appendChild(relationship);
}
//
// wsa:From, wsa:ReplyTo, and wsa:FaultTo (optional)
//
EndpointReference[] eprs = new EndpointReference[] {
getFromAddress(), getReplyToAddress(), getFaultToAddress()
};
for (int n = 0; n < eprs.length; ++n)
{
if (eprs[n] != null)
{
Element xml = eprs[n].toXML();
xml = (Element)doc.importNode(xml, true);
soapHeaders.appendChild(xml);
}
}
//
// wsa:ReferenceParameters (optional)
//
Element[] parameters = getToAddress().getParameters();
for (int n = 0; n < parameters.length; ++n)
{
Element copy = (Element)doc.importNode(parameters[n], true);
copy.setAttributeNS(WsaConstants.NAMESPACE_URI, WsaConstants.IS_REFERENCE_PARAMETER_QNAME, "true");
soapHeaders.appendChild(copy);
}
//
// non-WS-A headers
//
Iterator i = getCustomHeaderNames().iterator();
while (i.hasNext())
{
QName qname = (QName)i.next();
Iterator j = getCustomHeaders(qname).iterator();
while (j.hasNext())
{
Element next = (Element)j.next();
next = (Element)doc.importNode(next, true);
soapHeaders.appendChild(next);
}
}
return soapHeaders;
}
}