/*
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.mediator.cache.digest;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMElement;
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.Map;
/**
* This is the extended implementation of
* <a href="http://www.ietf.org/rfc/rfc2803.txt">DOMHASH algorithm</a> over a HTTP request
* and Payload (XML Node) for retrieving a unique key for the request
*
* @see org.wso2.caching.digest.DigestGenerator
*/
public class REQUESTHASHGenerator extends DOMHASHGenerator {
/** String representing the MD5 digest algorithm */
public static final String MD5_DIGEST_ALGORITHM = "MD5";
private static final Log log = LogFactory.getLog(REQUESTHASHGenerator.class);
/**
* This is the implementation of the getDigest method and will implement the Extended DOMHASH
* algorithm based HTTP request identifications. This will consider To address of the request,
* HTTP headers and XML Payload in generating the digets. So, in effect
* this will uniquely identify the HTTP request with the same To address, Headers and 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
*
* @see org.wso2.caching.digest.DigestGenerator
* #getDigest(org.apache.axis2.context.MessageContext)
*/
public String getDigest(MessageContext msgContext) throws CachingException {
OMNode body = msgContext.getEnvelope().getBody();
String toAddress = null;
if (msgContext.getTo() != null) {
toAddress = msgContext.getTo().getAddress();
}
Map<String, String> headers = (Map) msgContext.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
if (body != null) {
byte[] digest = null;
if (toAddress != null) {
digest = getDigest(body, toAddress, headers, MD5_DIGEST_ALGORITHM);
} else {
digest = getDigest(body, MD5_DIGEST_ALGORITHM);
}
return digest != null ? getStringRepresentation(digest) : null;
} else {
return null;
}
}
/**
* This is an overloaded method for the digest generation for OMNode and request
*
* @param node - OMNode to be subjected to the key generation
* @param toAddress - Request To address to be subjected to the key generation
* @param headers - Header parameters 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 toAddress, Map<String, String> headers,
String digestAlgorithm) throws CachingException {
if (node.getType() == OMNode.ELEMENT_NODE) {
return getDigest((OMElement) node, toAddress, headers, 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 OMElement and request
*
* @param element - OMElement to be subjected to the key generation
* @param toAddress - Request To address to be subjected to the key generation
* @param headers - Header parameters 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 toAddress, Map<String, String> headers,
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);
dos.write(toAddress.getBytes("UnicodeBigUnmarked"));
/*String acceptHeader = headers.get("accept");
acceptHeader = (acceptHeader == null) ? headers.get("Accept") : acceptHeader;
if (acceptHeader != null) {
dos.write(acceptHeader.getBytes("UnicodeBigUnmarked"));
}
String contentTypeHeader = headers.get("content-type");
contentTypeHeader = (contentTypeHeader == null) ? headers.get("Content-Type") : contentTypeHeader;
if (contentTypeHeader != null) {
dos.write(contentTypeHeader.getBytes("UnicodeBigUnmarked"));
}*/
Iterator itr = headers.keySet().iterator();
while (itr.hasNext()) {
String key = (String) itr.next();
String value = headers.get(key);
dos.write(getDigest(key, value, digestAlgorithm));
}
Collection attrs = getAttributesWithoutNS(element);
dos.writeInt(attrs.size());
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, toAddress, headers, 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 is an overloaded method for the digest generation for HTTP header propery
*
* @param key - Key of the header property subjected to the key generation
* @param value - Value of the header property 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(String key,String value, String digestAlgorithm) throws CachingException {
byte[] digest = new byte[0];
if (!key.equalsIgnoreCase("Date") && !key.equalsIgnoreCase("User-Agent")) {
try {
MessageDigest md = MessageDigest.getInstance(digestAlgorithm);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 0);
md.update((byte) 2);
md.update(key.getBytes("UnicodeBigUnmarked"));
if (value != null) {
md.update((byte) 0);
md.update((byte) 0);
md.update(value.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;
}
private void handleException(String message, Throwable cause) throws CachingException {
log.debug(message, cause);
throw new CachingException(message, cause);
}
}