blob: f648b24cdd307cb6f9380838304f0a021743c677 [file] [log] [blame]
/*
* 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.axis2.addressing;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMAttribute;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMXMLBuilderFactory;
import org.apache.axiom.util.UIDGenerator;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.externalize.ExternalizeConstants;
import org.apache.axis2.context.externalize.SafeObjectInputStream;
import org.apache.axis2.context.externalize.SafeObjectOutputStream;
import org.apache.axis2.context.externalize.SafeSerializable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Class EndpointReference
* This class models the WS-A EndpointReferenceType. But this can be used without any WS-A handlers as well
* Since the models for this in Submission and Final versions are different, lets make this to comply with
* WS-A Final version. So any information found with WS-A submission will be "pumped" in to this model.
*/
public class EndpointReference implements Externalizable, SafeSerializable {
private static final long serialVersionUID = 5278892171162372439L;
private static final Log log = LogFactory.getLog(EndpointReference.class);
// supported revision levels, add a new level to manage compatible changes
private static final int REVISION_2 = 2;
// current revision level of this object
private static final int revisionID = REVISION_2;
private static final String myClassName = "EndpointReference";
/**
* An ID which can be used to correlate operations on an instance of
* this object in the log files
*/
private String logCorrelationIDString;
/**
* The list of URIs that should be considered equivalent to
* the WS-Addressing anonymous URI
*/
private static volatile List<String> anonymousEquivalentURIs = new ArrayList<String>();
/**
* <EndpointReference>
* <Address>xs:anyURI</Address>
* <ReferenceParameters>xs:any*</ReferenceParameters>
* <MetaData>xs:any*</MetaData>
* <!-- In addition to this, EPR can contain any number of OMElements -->
* </EndpointReference>
*/
private String name;
private String address;
private ArrayList<OMAttribute> addressAttributes;
private ArrayList<OMNode> metaData;
private ArrayList<OMAttribute> metaDataAttributes;
private Map<QName, OMElement> referenceParameters;
private ArrayList<OMElement> extensibleElements;
private ArrayList<OMAttribute> attributes;
/**
* No-Arg Constructor
* Required for Externalizable objects
*/
public EndpointReference() {}
/**
* Adds a parameter to the list of anonymous equivalent URIs.
* @param anonymousEquivalentURI any URI that has an address that
* begins with this value will be considered to be anonymous
*/
public static void addAnonymousEquivalentURI(String anonymousEquivalentURI){
if (log.isTraceEnabled())
log.trace("addAnonymousEquivalentURI: " + anonymousEquivalentURI);
// Avoid synchronization in hasAnonymousAddress by using the
// technique outlined at http://is.gd/gv3
synchronized (anonymousEquivalentURIs) {
ArrayList<String> newList = new ArrayList<String>(anonymousEquivalentURIs);
newList.add(anonymousEquivalentURI);
anonymousEquivalentURIs = newList;
}
}
/**
* @param address
*/
public EndpointReference(String address) {
this.address = address;
}
/**
* @param omElement
*/
public void addReferenceParameter(OMElement omElement) {
if (omElement == null) {
return;
}
if (referenceParameters == null) {
referenceParameters = new HashMap<QName, OMElement>();
}
referenceParameters.put(omElement.getQName(), omElement);
}
/**
* @param qname
* @param value - the text of the OMElement. Remember that this is a convenient method for the user,
* which has limited capability. If you want more power use @See EndpointReference#addReferenceParameter(OMElement)
*/
public void addReferenceParameter(QName qname, String value) {
if (qname == null) {
return;
}
OMElement omElement = OMAbstractFactory.getOMFactory().createOMElement(qname, null);
omElement.setText(value);
addReferenceParameter(omElement);
}
/**
* This will return a Map of reference parameters with QName as the key and an OMElement
* as the value
*
* @return - map of the reference parameters, where the key is the QName of the reference parameter
* and the value is an OMElement
*/
public Map<QName, OMElement> getAllReferenceParameters() {
return referenceParameters;
}
public String getAddress() {
return address;
}
/**
* @param address - xs:anyURI
*/
public void setAddress(String address) {
this.address = address;
}
public ArrayList<OMAttribute> getAddressAttributes() {
return addressAttributes;
}
public void setAddressAttributes(ArrayList<OMAttribute> al) {
addressAttributes = al;
}
public ArrayList<OMAttribute> getMetadataAttributes() {
return metaDataAttributes;
}
public void setMetadataAttributes(ArrayList<OMAttribute> al) {
metaDataAttributes = al;
}
/**
* This method identifies whether the address is a WS-Addressing spec defined
* anonymous URI.
*
* @return true if the address is a WS-Addressing spec defined anonymous URI.
*
* @see #hasAnonymousAddress()
*/
public boolean isWSAddressingAnonymous() {
return (AddressingConstants.Final.WSA_ANONYMOUS_URL.equals(address) ||
AddressingConstants.Submission.WSA_ANONYMOUS_URL.equals(address));
}
/**
* This method is used to identify when response messages should be sent using
* the back channel of a two-way transport.
*
* @return true if the address is a WS-Addressing spec defined anonymous URI,
* or starts with a string that is specified to be equivalent to an anonymous
* URI.
*
* @see #addAnonymousEquivalentURI(String)
*/
public boolean hasAnonymousAddress() {
boolean result = isWSAddressingAnonymous();
if(!result && address != null) {
// If the address is not WS-A anonymous it might still be considered anonymous
// Avoid synchronization by using the
// technique outlined at http://is.gd/gv3
List<String> localList = anonymousEquivalentURIs;
if(!localList.isEmpty()){
Iterator<String> it = localList.iterator();
while(it.hasNext()){
result = address.startsWith((String)it.next());
if(result){
break;
}
}
}
}
if (log.isTraceEnabled()) {
log.trace("hasAnonymousAddress: " + address + " is Anonymous: " + result);
}
return result;
}
/**
* hasNoneAddress
*
* @return true if the address is the 'None URI' from the final addressing spec.
*/
public boolean hasNoneAddress() {
boolean result = AddressingConstants.Final.WSA_NONE_URI.equals(address);
if (log.isTraceEnabled()) {
log.trace("hasNoneAddress: " + address + " is None: " + result);
}
return result;
}
/**
* @param localName
* @param ns
* @param value
*/
public void addAttribute(String localName, OMNamespace ns, String value) {
if (attributes == null) {
attributes = new ArrayList<OMAttribute>();
}
attributes.add(OMAbstractFactory.getOMFactory().createOMAttribute(localName, ns, value));
}
public ArrayList<OMAttribute> getAttributes() {
return attributes;
}
/**
* @param omAttribute
*/
public void addAttribute(OMAttribute omAttribute) {
if (attributes == null) {
attributes = new ArrayList<OMAttribute>();
}
attributes.add(omAttribute);
}
public ArrayList<OMElement> getExtensibleElements() {
return extensibleElements;
}
/**
* {any}
*
* @param extensibleElements
*/
public void setExtensibleElements(ArrayList<OMElement> extensibleElements) {
this.extensibleElements = extensibleElements;
}
public void addExtensibleElement(OMElement extensibleElement) {
if (extensibleElement != null) {
if (this.extensibleElements == null) {
this.extensibleElements = new ArrayList<OMElement>();
}
this.extensibleElements.add(extensibleElement);
}
}
public ArrayList<OMNode> getMetaData() {
return metaData;
}
public void addMetaData(OMNode metaData) {
if (metaData != null) {
if (this.metaData == null) {
this.metaData = new ArrayList<OMNode>();
}
this.metaData.add(metaData);
}
}
/**
* @deprecated
*/
public String getName() {
return name;
}
/**
* @param name
* @deprecated
*/
public void setName(String name) {
this.name = name;
}
/**
* Set a Map with QName as the key and an OMElement
* as the value
*
* @param referenceParameters
*/
public void setReferenceParameters(Map<QName, OMElement> referenceParameters) {
this.referenceParameters = referenceParameters;
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer buffer = new StringBuffer("Address: " + address);
if (addressAttributes != null) {
buffer.append(", Address Attributes: ").append(addressAttributes);
}
if (metaData != null) {
buffer.append(", Metadata: ").append(metaData);
}
if (metaDataAttributes != null) {
buffer.append(", Metadata Attributes: ").append(metaDataAttributes);
}
if (referenceParameters != null) {
buffer.append(", Reference Parameters: ").append(referenceParameters);
}
if (extensibleElements != null) {
buffer.append(", Extensibility elements: ").append(extensibleElements);
}
if (attributes != null) {
buffer.append(", Attributes: ").append(attributes);
}
return buffer.toString();
}
/**
* Compares key parts of the state from the current instance of
* this class with the specified instance to see if they are
* equivalent.
* <p/>
* This differs from the java.lang.Object.equals() method in
* that the equals() method generally looks at both the
* object identity (location in memory) and the object state
* (data).
* <p/>
*
* @param epr The object to compare with
* @return TRUE if this object is equivalent with the specified object
* that is, key fields match
* FALSE, otherwise
*/
public boolean isEquivalent(EndpointReference epr) {
// NOTE: the input object is expected to exist (ie, be non-null)
if ((this.name != null) && (epr.getName() != null)) {
if (!this.name.equals(epr.getName())) {
return false;
}
} else if ((this.name == null) && (epr.getName() == null)) {
// continue
} else {
// mismatch
return false;
}
if ((this.address != null) && (epr.getAddress() != null)) {
if (!this.address.equals(epr.getAddress())) {
return false;
}
} else if ((this.address == null) && (epr.getAddress() == null)) {
// continue
} else {
// mismatch
return false;
}
// TODO: is a strict test ok to use?
ArrayList<OMNode> eprMetaData = epr.getMetaData();
if ((this.metaData != null) && (eprMetaData != null)) {
if (!this.metaData.equals(eprMetaData)) {
// This is a strict test
// Returns true if and only if the specified object
// is also a list, both lists have the same size, and
// all corresponding pairs of elements in the two lists
// are equal, ie, two lists are defined to be equal if
// they contain the same elements in the same order.
return false;
}
} else if ((this.metaData == null) && (eprMetaData == null)) {
// keep going
} else {
// one of the lists is null
return false;
}
ArrayList<OMElement> eprExtensibleElements = epr.getExtensibleElements();
if ((this.extensibleElements != null) && (eprExtensibleElements != null)) {
if (!this.extensibleElements.equals(eprExtensibleElements)) {
// This is a strict test
// Returns true if and only if the specified object
// is also a list, both lists have the same size, and
// all corresponding pairs of elements in the two lists
// are equal, ie, two lists are defined to be equal if
// they contain the same elements in the same order.
return false;
}
} else if ((this.extensibleElements == null) && (eprExtensibleElements == null)) {
// keep going
} else {
// one of the lists is null
return false;
}
ArrayList<OMAttribute> eprAttributes = epr.getAttributes();
if ((this.attributes != null) && (eprAttributes != null)) {
if (!this.attributes.equals(eprAttributes)) {
// This is a strict test
// Returns true if and only if the specified object
// is also a list, both lists have the same size, and
// all corresponding pairs of elements in the two lists
// are equal, ie, two lists are defined to be equal if
// they contain the same elements in the same order.
return false;
}
} else if ((this.attributes == null) && (eprAttributes == null)) {
// keep going
} else {
// one of the lists is null
return false;
}
// TODO: check the Map referenceParameters for equivalency
return true;
}
//REVIEW: The following code is rather heavyweight, because we have to build
// the OM tree -- it would probably be better to have two serialization/deserialization
// paths and therefore, for trivial EPRs, store a smaller amount of info
/**
* Write the EPR to the specified OutputStream. Because of potential
* OMElements/Attributes, we need to actually serialize the OM structures
* (at least in some cases.)
*/
public synchronized void writeExternal(java.io.ObjectOutput o)
throws IOException {
SafeObjectOutputStream out = SafeObjectOutputStream.install(o);
// revision ID
out.writeInt(revisionID);
// Correlation ID
String logCorrelationIDString = getLogCorrelationIDString();
// String object id
out.writeObject(logCorrelationIDString);
// Write out the content as xml
out.writeUTF("start xml"); // write marker
OMElement om =
EndpointReferenceHelper.toOM(OMAbstractFactory.getOMFactory(),
this,
new QName("urn:axis2", "omepr", "ser"),
AddressingConstants.Final.WSA_NAMESPACE);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
om.serialize(baos);
} catch (Exception e) {
IOException ioe = new IOException("Unable to serialize the EndpointReference with logCorrelationID ["
+logCorrelationIDString+"]");
ioe.initCause(e);
if (log.isDebugEnabled()) {
log.debug("writeObject(): Unable to serialize the EPR with logCorrelationID ["
+logCorrelationIDString+"] original error ["+e.getClass().getName()
+"] message ["+e.getMessage()+"]",e);
}
throw ioe;
}
out.writeInt(baos.size());
out.write(baos.toByteArray());
out.writeUTF("end xml"); // write marker
if (log.isDebugEnabled()) {
byte[] buffer = baos.toByteArray();
String content = new String(buffer);
log.debug("writeObject(): EPR logCorrelationID ["+logCorrelationIDString+"] "
+" EPR content size ["+baos.size()+"]"
+" EPR content ["+content+"]");
}
}
/**
* Read the EPR to the specified InputStream.
*/
public void readExternal(java.io.ObjectInput inObject)
throws IOException, ClassNotFoundException {
SafeObjectInputStream in = SafeObjectInputStream.install(inObject);
// revision ID
int revID = in.readInt();
// make sure the object data is in a revision level we can handle
if (revID != REVISION_2) {
throw new ClassNotFoundException(ExternalizeConstants.UNSUPPORTED_REVID);
}
// String object id
logCorrelationIDString = (String) in.readObject();
// Read xml content
in.readUTF(); // read marker
int numBytes = in.readInt();
byte[] serBytes = new byte[numBytes];
// read the data from the input stream
int bytesRead = 0;
int numberOfBytesLastRead;
while (bytesRead < numBytes) {
numberOfBytesLastRead = in.read(serBytes, bytesRead, numBytes - bytesRead);
if (numberOfBytesLastRead == -1) {
// TODO: What should we do if the reconstitution fails?
// For now, log the event and throw an exception
if (log.isDebugEnabled()) {
log.debug("readObject(): EPR logCorrelationID ["+logCorrelationIDString+"] "
+ " ***WARNING*** unexpected end to data: data read from input stream ["
+ bytesRead + "] expected data size [" + numBytes + "]");
}
IOException ioe = new IOException("Unable to deserialize the EndpointReference with logCorrelationID ["
+logCorrelationIDString+"]"
+" Cause: Unexpected end to data from input stream");
throw ioe;
}
bytesRead += numberOfBytesLastRead;
}
if (bytesRead == 0) {
IOException ioe = new IOException("Unable to deserialize the EndpointReference with logCorrelationID ["
+logCorrelationIDString+"]"
+" Cause: No data from input stream");
throw ioe;
}
in.readUTF(); // read marker
ByteArrayInputStream bais = new ByteArrayInputStream(serBytes);
if (log.isDebugEnabled()) {
String content = new String(serBytes);
log.debug("readObject(): EPR logCorrelationID ["+logCorrelationIDString+"] "
+" expected content size ["+numBytes+"]"
+" content size ["+content.length()+"]"
+" EPR buffered content ["+content+"]");
}
XMLStreamReader xmlReader = null;
try {
OMElement om = OMXMLBuilderFactory.createOMBuilder(bais).getDocumentElement();
// expand the OM so we can close the stream reader
om.build();
// trace point
if (log.isDebugEnabled()) {
log.debug(myClassName + ":readObject(): "
+ " EPR ["+logCorrelationIDString + "]"
+ " EPR OM content ["+om.toString()+ "]");
}
EndpointReferenceHelper.fromOM(this, om, AddressingConstants.Final.WSA_NAMESPACE);
} catch (Exception e) {
IOException ioe = new IOException("Unable to deserialize the EndpointReference with logCorrelationID ["
+logCorrelationIDString+"]");
ioe.initCause(e);
if (log.isDebugEnabled()) {
log.debug("readObject(): Unable to deserialize the EPR with logCorrelationID ["
+logCorrelationIDString+"] original error ["+e.getClass().getName()
+"] message ["+e.getMessage()+"]",e);
}
throw ioe;
} finally {
// Make sure that the reader is properly closed
// Note that closing a ByteArrayInputStream has no effect
if (xmlReader != null) {
try {
xmlReader.close();
} catch (Exception e2) {
IOException ioe2 = new IOException("Unable to close the XMLStreamReader for the EndpointReference with logCorrelationID ["
+logCorrelationIDString+"]");
ioe2.initCause(e2);
if (log.isDebugEnabled()) {
log.debug("readObject(): Unable to close the XMLStreamReader for the EPR with logCorrelationID ["
+logCorrelationIDString+"] original error ["+e2.getClass().getName()
+"] message ["+e2.getMessage()+"]",e2);
}
throw ioe2;
}
}
}
}
/**
* Get the ID associated with this object instance.
*
* @return A string that can be output to a log file as an identifier
* for this object instance. It is suitable for matching related log
* entries.
*/
public String getLogCorrelationIDString() {
if (logCorrelationIDString == null) {
logCorrelationIDString = myClassName + "@" + UIDGenerator.generateUID();
}
return logCorrelationIDString;
}
}