/*
* Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.wso2.carbon.mediator.cache.digest;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMDocument;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.OMProcessingInstruction;
import org.apache.axiom.om.OMAttribute;
import org.apache.axis2.context.MessageContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.mediator.cache.CachingException;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.ArrayList;
import java.util.Arrays;
/**
* This is the default DigestGenerator for the cache and this implements the
* <a href="http://www.ietf.org/rfc/rfc2803.txt">DOMHASH algorithm</a> over an XML node
* to implement retrieving a unique key for the normalized xml node.
*
*/
public class DOMHASHGenerator implements DigestGenerator {
/** String representing the MD5 digest algorithm */
public static final String MD5_DIGEST_ALGORITHM = "MD5";
/** String representing the SHA digest algorithm */
public static final String SHA_DIGEST_ALGORITHM = "SHA";
/** String representing the SHA1 digest algorithm */
public static final String SHA1_DIGEST_ALGORITHM = "SHA1";
/** Holds the log for the logging */
private static final Log log = LogFactory.getLog(DOMHASHGenerator.class);
/**
* This is the implementation of the getDigest method and will implement the DOMHASH
* algorithm based XML node identifications. This will consider only the SOAP payload
* and this does not consider the SOAP headers in generating the digets. So, in effect
* this will uniquely identify the SOAP messages with the same payload.
*
* @param msgContext - MessageContext on which the XML node identifier will be generated
* @return Object representing the DOMHASH value of the normalized XML node
* @throws CachingException if there is an error in generating the digest key
*
* #getDigest(org.apache.axis2.context.MessageContext)
*/
public String getDigest(MessageContext msgContext) throws CachingException {
OMNode request = msgContext.getEnvelope().getBody();
if (request != null) {
byte[] digest = getDigest(request, MD5_DIGEST_ALGORITHM);
return digest != null ? getStringRepresentation(digest) : null;
} else {
return null;
}
}
/**
* This is an overloaded method for the digest generation for OMNode
*
* @param node - OMNode to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided node
* @throws CachingException if there is an error in generating the digest
*/
public byte[] getDigest(OMNode node, String digestAlgorithm) throws CachingException {
if (node.getType() == OMNode.ELEMENT_NODE) {
return getDigest((OMElement) node, digestAlgorithm);
} else if (node.getType() == OMNode.TEXT_NODE) {
return getDigest((OMText) node, digestAlgorithm);
} else if (node.getType() == OMNode.PI_NODE) {
return getDigest((OMProcessingInstruction) node, digestAlgorithm);
} else {
return new byte[0];
}
}
/**
* This is an overloaded method for the digest generation for OMDocument
*
* @param document - OMDocument to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided document
* @throws CachingException if there is an io error or the specified algorithm is incorrect
*/
public byte[] getDigest(OMDocument document, String digestAlgorithm) throws CachingException {
byte[] digest = new byte[0];
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(9);
Collection childNodes = getValidElements(document);
dos.writeInt(childNodes.size());
for (Iterator itr = childNodes.iterator(); itr.hasNext();) {
OMNode node = (OMNode) itr.next();
if (node.getType() == OMNode.PI_NODE)
dos.write(getDigest((OMProcessingInstruction) node, digestAlgorithm));
else if (
node.getType() == OMNode.ELEMENT_NODE)
dos.write(getDigest((OMElement) node, digestAlgorithm));
}
dos.close();
md.update(baos.toByteArray());
digest = md.digest();
} catch (NoSuchAlgorithmException e) {
handleException("Can not locate the algorithm " +
"provided for the digest generation : " + digestAlgorithm, e);
} catch (IOException e) {
handleException("Error in calculating the " +
"digest value for the OMDocument : " + document, e);
}
return digest;
}
/**
* This is an overloaded method for the digest generation for OMElement
*
* @param element - OMElement to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided element
* @throws CachingException if there is an io error or the specified algorithm is incorrect
*/
public byte[] getDigest(OMElement element, String digestAlgorithm) throws CachingException {
byte[] digest = new byte[0];
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(1);
dos.write(getExpandedName(element).getBytes("UnicodeBigUnmarked"));
dos.write((byte) 0);
dos.write((byte) 0);
Collection attrs = getAttributesWithoutNS(element);
dos.writeInt(attrs.size());
Iterator itr = attrs.iterator();
while (itr.hasNext())
dos.write(getDigest((OMAttribute) itr.next(), digestAlgorithm));
OMNode node = element.getFirstOMChild();
// adjoining Texts are merged,
// there is no 0-length Text, and
// comment nodes are removed.
int length = 0;
itr = element.getChildElements();
while (itr.hasNext()) {
length++;
itr.next();
}
dos.writeInt(length);
while (node != null) {
dos.write(getDigest(node, digestAlgorithm));
node = node.getNextOMSibling();
}
dos.close();
md.update(baos.toByteArray());
digest = md.digest();
} catch (NoSuchAlgorithmException e) {
handleException("Can not locate the algorithm " +
"provided for the digest generation : " + digestAlgorithm, e);
} catch (IOException e) {
handleException("Error in calculating the " +
"digest value for the OMElement : " + element, e);
}
return digest;
}
/**
* This method is an overloaded method for the digest generation for OMProcessingInstruction
*
* @param pi - OMProcessingInstruction to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided pi
* @throws CachingException if the specified algorithm is incorrect or the encoding
* is not supported by the processor
*/
public byte[] getDigest(OMProcessingInstruction pi, String digestAlgorithm)
throws CachingException {
byte[] digest = new byte[0];
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 7);
md.update(pi.getTarget().getBytes("UnicodeBigUnmarked"));
md.update((byte) 0);
md.update((byte) 0);
md.update(pi.getValue().getBytes("UnicodeBigUnmarked"));
digest = md.digest();
} catch (NoSuchAlgorithmException e) {
handleException("Can not locate the algorithm " +
"provided for the digest generation : " + digestAlgorithm, e);
} catch (UnsupportedEncodingException e) {
handleException("Error in generating the digest " +
"using the provided encoding : UnicodeBigUnmarked", e);
}
return digest;
}
/**
* This is an overloaded method for the digest generation for OMAttribute
*
* @param attribute - OMAttribute to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided attribute
* @throws CachingException if the specified algorithm is incorrect or the encoding
* is not supported by the processor
*/
public byte[] getDigest(OMAttribute attribute, String digestAlgorithm) throws CachingException {
byte[] digest = new byte[0];
if (!(attribute.getLocalName().equals("xmlns") ||
attribute.getLocalName().startsWith("xmlns:"))) {
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 2);
md.update(getExpandedName(attribute).getBytes("UnicodeBigUnmarked"));
md.update((byte) 0);
md.update((byte) 0);
md.update(attribute.getAttributeValue().getBytes("UnicodeBigUnmarked"));
digest = md.digest();
} catch (NoSuchAlgorithmException e) {
handleException("Can not locate the algorithm " +
"provided for the digest generation : " + digestAlgorithm, e);
} catch (UnsupportedEncodingException e) {
handleException("Error in generating the digest " +
"using the provided encoding : UnicodeBigUnmarked", e);
}
}
return digest;
}
/**
* This method is an overloaded method for the digest generation for OMText
*
* @param text - OMText to be subjected to the key generation
* @param digestAlgorithm - digest algorithm as a String
* @return byte[] representing the calculated digest over the provided text
* @throws CachingException if the specified algorithm is incorrect or the encoding
* is not supported by the processor
*/
public byte[] getDigest(OMText text, String digestAlgorithm) throws CachingException {
byte[] digest = new byte[0];
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 3);
md.update(text.getText().getBytes("UnicodeBigUnmarked"));
digest = md.digest();
} catch (NoSuchAlgorithmException e) {
handleException("Can not locate the algorithm " +
"provided for the digest generation : " + digestAlgorithm, e);
} catch (UnsupportedEncodingException e) {
handleException("Error in generating the digest " +
"using the provided encoding : UnicodeBigUnmarked", e);
}
return digest;
}
/**
* This is an overloaded method for getting the expanded name as namespaceURI followed by
* the local name for OMElement
*
* @param element - OMElement of which the expanded name is retrieved
* @return expanded name of OMElement as an String in the form {ns-uri:local-name}
*/
public String getExpandedName(OMElement element) {
if (element.getNamespace() != null) {
return element.getNamespace().getNamespaceURI() + ":" + element.getLocalName();
} else {
return element.getLocalName();
}
}
/**
* This is an overloaded method for getting the expanded name as namespaceURI followed by
* the local name for OMAttribute
*
* @param attribute - OMAttribute of which the expanded name is retrieved
* @return expanded name of the OMAttribute as an String in the form {ns-uri:local-name}
*/
public String getExpandedName(OMAttribute attribute) {
if (attribute.getNamespace() != null) {
return attribute.getNamespace().getNamespaceURI() + ":" + attribute.getLocalName();
} else {
return attribute.getLocalName();
}
}
/**
* Gets the collection of attributes which are none namespace declarations for an OMElement
* sorted according to the expanded names of the attributes
*
* @param element - OMElement of which the none ns declaration attributes to be retrieved
* @return the collection of attributes which are none namespace declarations
*/
public Collection getAttributesWithoutNS(OMElement element) {
SortedMap map = new TreeMap();
Iterator itr = element.getAllAttributes();
while (itr.hasNext()) {
OMAttribute attribute = (OMAttribute) itr.next();
if (!(attribute.getLocalName().equals("xmlns") ||
attribute.getLocalName().startsWith("xmlns:"))) {
map.put(getExpandedName(attribute), attribute);
}
}
return map.values();
}
/**
* Gets the valid element collection of an OMDocument. This returns only the OMElement
* and OMProcessingInstruction nodes
*
* @param document - OMDocument of which the valid elements to be retrieved
* @return the collection of OMProcessingInstructions and OMElements in the provided document
*/
public Collection getValidElements(OMDocument document) {
ArrayList list = new ArrayList();
Iterator itr = document.getChildren();
while (itr.hasNext()) {
OMNode node = (OMNode) itr.next();
if (node.getType() == OMNode.ELEMENT_NODE || node.getType() == OMNode.PI_NODE)
list.add(node);
}
return list;
}
/**
* Gets the String representation of the byte array
*
* @param array - byte[] of which the String representation is required
* @return the String representation of the byte[]
*/
public String getStringRepresentation(byte[] array) {
StringBuffer strBuff = new StringBuffer(array.length);
for (int i = 0; i < array.length; i++) {
strBuff.append(array[i]);
}
return strBuff.toString();
}
/**
* Compares two OMNodes for the XML equality
*
* @param node - OMNode to be compared
* @param comparingNode - OMNode to be compared
* @param digestAlgorithm - digest algorithm as a String to be used in the comparison
* @return boolean true if the two OMNodes are XML equal, and false otherwise
* @throws CachingException if there is an error in generating the digest key
*/
public boolean compareOMNode(OMNode node, OMNode comparingNode, String digestAlgorithm)
throws CachingException {
return Arrays.equals(getDigest(node, digestAlgorithm),
getDigest(comparingNode, digestAlgorithm));
}
/**
* Compares two OMDocuments for the XML equality
*
* @param document - OMDocument to be compared
* @param comparingDocument - OMDocument to be compared
* @param digestAlgorithm - digest algorithm as a String to be used in the comparison
* @return boolean true if the two OMDocuments are XML equal, and false otherwise
* @throws CachingException if there is an error in generating the digest key
*/
public boolean compareOMDocument(OMDocument document, OMDocument comparingDocument,
String digestAlgorithm) throws CachingException {
return Arrays.equals(getDigest(document, digestAlgorithm),
getDigest(comparingDocument, digestAlgorithm));
}
/**
* Compares two OMAttributes for the XML equality
*
* @param attribute - OMAttribute to be compared
* @param comparingAttribute - OMAttribute to be compared
* @param digestAlgorithm - digest algorithm as a String to be used in the comparison
* @return boolean true if the two OMAttributes are XML equal, and false otherwise
* @throws CachingException if there is an error in generating the digest key
*/
public boolean compareOMAttribute(OMAttribute attribute, OMAttribute comparingAttribute,
String digestAlgorithm) throws CachingException {
return Arrays.equals(getDigest(attribute, digestAlgorithm),
getDigest(comparingAttribute, digestAlgorithm));
}
private void handleException(String message, Throwable cause) throws CachingException {
log.debug(message, cause);
throw new CachingException(message, cause);
}
}