/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.protocol;
import java.util.HashMap;
import java.util.Iterator;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Integer;
import com.slamd.asn1.ASN1OctetString;
import com.slamd.asn1.ASN1Sequence;
import com.slamd.common.Constants;
import com.slamd.common.SLAMDException;
import com.slamd.job.JobClass;
/**
* This class defines the basic envelope for a message used to communicate
* between the SLAMD server and one of the client types. A SLAMD message
* contains a message ID (which is an integer), a message type (which is a class
* name), and the payload (which varies based on the message type). For the
* purposes of extensibility, it may also contain a set of name-value pairs that
* may be used to provide additional information about the message and/or the
* way it should be handled.
*
*
* @author Neil A. Wilson
*/
public abstract class SLAMDMessage
{
// The set of name-value pairs for the "extra" properties associated with this
// message. The name should be a string and the value an object.
private HashMap<String,String> extraProperties;
// The message ID for this SLAMD message.
private int messageID;
/**
* Creates a new instance of this SLAMD message without any of its properties
* set. This is only intended for use in the process of decoding messages
* and should only be used when the <CODE>initializeDecodedMessage</CODE>
* method is going to be called immediately after invoking this constructor.
*/
protected SLAMDMessage()
{
// No implementation is required.
}
/**
* Creates a new instance of this SLAMD message with the provided message ID
* and optional set of properties.
*
* @param messageID The message ID for this SLAMD message.
* @param extraProperties The "extra" properties for this SLAMD message.
* Both the names and values for the properties must
* be strings.
*/
protected SLAMDMessage(int messageID, HashMap<String,String> extraProperties)
{
this.messageID = messageID;
if (extraProperties == null)
{
this.extraProperties = new HashMap<String,String>();
}
else
{
this.extraProperties = extraProperties;
}
}
/**
* Initializes this decoded message with the provided message ID and extra
* properties. This method should only be called when decoding an encoded
* message, and it should only be called immediately after invoking the
* default constructor.
*
* @param messageID The message ID for this SLAMD message.
* @param extraProperties The "extra" properties for this SLAMD message.
* Both the names and values for the properties must
* be strings.
*/
private void initializeDecodedMessage(int messageID,
HashMap<String,String> extraProperties)
{
this.messageID = messageID;
if (extraProperties == null)
{
this.extraProperties = new HashMap<String,String>();
}
else
{
this.extraProperties = extraProperties;
}
}
/**
* Retrieves the message ID for this SLAMD message.
*
* @return The message ID for this SLAMD message.
*/
public int getMessageID()
{
return messageID;
}
/**
* Retrieves the set of "extra" properties for this SLAMD message. The
* contents of the returned map may be altered by the caller.
*
* @return The set of "extra" properties for this SLAMD message.
*/
public HashMap getExtraProperties()
{
return extraProperties;
}
/**
* Retrieves the value of the "extra" property with the specified name.
*
* @param name The name of the property to retrieve.
*
* @return The value of the "extra" property with the specified name, or
* <CODE>null</CODE> if there is no such property.
*/
public String getExtraProperty(String name)
{
Object value = extraProperties.get(name);
if (value == null)
{
return null;
}
return value.toString();
}
/**
* Encodes this SLAMD message to an ASN.1 element that may be transferred
* between the SLAMD server and a client.
*
* @return The encoded SLAMD message.
*/
public final ASN1Element encode()
{
ASN1Element[] elements = new ASN1Element[4];
elements[0] = new ASN1Integer(messageID);
elements[1] = new ASN1OctetString(getClass().getName());
elements[2] = encodeMessagePayload();
if (extraProperties.isEmpty())
{
elements[3] = new ASN1Sequence();
}
else
{
ASN1Element[] propsElements = new ASN1Element[extraProperties.size()];
int pos = 0;
Iterator<String> iterator = extraProperties.keySet().iterator();
while (iterator.hasNext())
{
String name = iterator.next();
String value;
Object valueObj = extraProperties.get(name);
if (valueObj == null)
{
value = null;
}
else
{
value = valueObj.toString();
}
propsElements[pos++] = encodeNameValuePair(name,
new ASN1OctetString(value));
}
elements[3] = new ASN1Sequence(propsElements);
}
return new ASN1Sequence(elements);
}
/**
* Decodes the provided ASN.1 element as a SLAMD message.
*
* @param messageElement The ASN.1 element containing the encoded SLAMD
* message.
*
* @return The decoded SLAMD message.
*
* @throws SLAMDException If a problem occurs while attempting to decode the
* provided ASN.1 element as a SLAMD message.
*/
public static SLAMDMessage decode(ASN1Element messageElement)
throws SLAMDException
{
try
{
ASN1Element[] elements = messageElement.decodeAsSequence().getElements();
// First, get the message ID.
int messageID;
try
{
messageID = elements[0].decodeAsInteger().getIntValue();
}
catch (Exception e)
{
throw new SLAMDException("Cannot decode the first message element as " +
"the message ID: " +
JobClass.stackTraceToString(e), e);
}
// Then, get the class name.
String className;
try
{
className = elements[1].decodeAsOctetString().getStringValue();
}
catch (Exception e)
{
throw new SLAMDException("Cannot decode the second message element " +
"as the message class: " +
JobClass.stackTraceToString(e), e);
}
// If there are any "extra" message properties, then get them.
HashMap<String,String> extraProperties;
try
{
ASN1Element[] propElements =
elements[3].decodeAsSequence().getElements();
extraProperties = new HashMap<String,String>(propElements.length);
for (int i=0; i < propElements.length; i++)
{
ASN1Element[] e = propElements[i].decodeAsSequence().getElements();
String name = e[0].decodeAsOctetString().getStringValue();
String value = e[1].decodeAsOctetString().getStringValue();
extraProperties.put(name, value);
}
}
catch (Exception e)
{
throw new SLAMDException("Error while attempting to parse additional " +
"message properties: " +
JobClass.stackTraceToString(e), e);
}
// Load, instantiate, and initialize the message class.
Class<?> messageClass;
try
{
messageClass = Constants.classForName(className);
}
catch (Exception e)
{
throw new SLAMDException("Cannot load specified message class " +
className + ": " +
JobClass.stackTraceToString(e), e);
}
SLAMDMessage message;
try
{
message = (SLAMDMessage) messageClass.newInstance();
message.initializeDecodedMessage(messageID, extraProperties);
}
catch (Exception e)
{
throw new SLAMDException("Cannot create an instance of class " +
className + " as a SLAMD message: " +
JobClass.stackTraceToString(e), e);
}
// Decode the payload and return the message.
message.decodeMessagePayload(elements[2]);
return message;
}
catch (SLAMDException se)
{
throw se;
}
catch (Exception e)
{
throw new SLAMDException("Cannot deocde the provided ASN.1 element as " +
"a SLAMD message: " +
JobClass.stackTraceToString(e), e);
}
}
/**
* Encodes the payload component of this SLAMD message to an ASN.1 element for
* inclusion in the message envelope.
*
* @return The ASN.1 element containing the encoded message payload.
*/
public abstract ASN1Element encodeMessagePayload();
/**
* Decodes the provided ASN.1 element and uses it as the payload for this
* SLAMD message.
*
* @param payloadElement The ASN.1 element to decode as the payload for this
* SLAMD message.
*
* @throws SLAMDException If a problem occurs while attempting to decode the
* provided ASN.1 element as the payload for this
* SLAMD message.
*/
public abstract void decodeMessagePayload(ASN1Element payloadElement)
throws SLAMDException;
/**
* Appends a string representation of the payload for this SLAMD message to
* the provided buffer. The string representation may contain multiple lines,
* but the last line should not end with an end-of-line marker.
*
* @param buffer The buffer to which the string representation is to be
* appended.
* @param indent The number of spaces to indent the payload content.
*/
public abstract void payloadToString(StringBuilder buffer, int indent);
/**
* Retrieves a string representation of this SLAMD message.
*
* @return A string representation of this SLAMD message.
*/
@Override()
public String toString()
{
StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this SLAMD message to the provided
* buffer.
*
* @param buffer The buffer to which the string representation of this SLAMD
* message should be appended.
*/
public void toString(StringBuilder buffer)
{
String EOL = Constants.EOL;
buffer.append("SLAMDMessage");
buffer.append(EOL);
buffer.append(" Message ID: ");
buffer.append(messageID);
buffer.append(EOL);
buffer.append(" Message Type: ");
buffer.append(getClass().getName());
buffer.append(EOL);
buffer.append(" Message Payload:");
buffer.append(EOL);
payloadToString(buffer, 10);
buffer.append(EOL);
if ((extraProperties != null) && (! extraProperties.isEmpty()))
{
buffer.append(" Extra Properties:");
buffer.append(EOL);
Iterator iterator = extraProperties.keySet().iterator();
while (iterator.hasNext())
{
String name = (String) iterator.next();
String value;
Object valueObj = extraProperties.get(name);
if (valueObj == null)
{
value = "";
}
else
{
value = valueObj.toString();
}
buffer.append(" ");
buffer.append(name);
buffer.append('=');
buffer.append(value);
buffer.append(EOL);
}
}
}
/**
* Encodes the provided name and value into an ASN.1 sequence.
*
* @param name The name to include in the sequence.
* @param value The encoded value to place in the sequence.
*
* @return The ASN.1 sequence element containing the encoded name-value pair.
*/
public static ASN1Sequence encodeNameValuePair(String name,
ASN1Element value)
{
ASN1Element[] elements =
{
new ASN1OctetString(name),
value
};
return new ASN1Sequence(elements);
}
/**
* Decodes the provided ASN.1 element as a sequence containing a name and
* value.
*
* @param sequenceElement The ASN.1 sequence element to decode as a
* name-value pair.
* @param nameBuffer The buffer to which the decoded element name will
* be appended.
*
* @return The ASN.1 element containing the encoded value for the name-value
* pair.
*
* @throws SLAMDException If a problem occurs while attempting to decode the
* provided element as a name-value pair.
*/
public static ASN1Element decodeNameValuePair(ASN1Element sequenceElement,
StringBuilder nameBuffer)
throws SLAMDException
{
ASN1Element[] elements;
try
{
elements = sequenceElement.decodeAsSequence().getElements();
}
catch (Exception e)
{
throw new SLAMDException("Cannot decode the provided element as an " +
"ASN.1 sequence: " + e, e);
}
if (elements.length != 2)
{
throw new SLAMDException("The provided ASN.1 sequence contained an " +
"invalid number of elements (expected 2, got " +
elements.length + ").");
}
String name;
try
{
name = elements[0].decodeAsOctetString().getStringValue();
}
catch (Exception e)
{
throw new SLAMDException("Cannot decode the first sequence element as " +
"an octet string containing the property " +
"name: " + e, e);
}
nameBuffer.append(name);
return elements[1];
}
/**
* Decodes the provided ASN.1 element as a sequence of name-value pair
* elements.
*
* @param sequenceElement The ASN.1 element containing the encoded sequence
* of name-value pair elements.
*
* @return The set of decoded name-value pairs as a mapping between the
* property name and the encoded element.
*
* @throws SLAMDException If a problem occurs while attempting to decode the
* name-value pair elements.
*/
public static HashMap<String,ASN1Element> decodeNameValuePairSequence(
ASN1Element sequenceElement)
throws SLAMDException
{
ASN1Element[] elements;
try
{
elements = sequenceElement.decodeAsSequence().getElements();
}
catch (Exception e)
{
throw new SLAMDException("Unable to decode the provided ASN.1 element " +
"as a sequence of name-value pair elements: " +
e, e);
}
HashMap<String,ASN1Element> propertyMap =
new HashMap<String,ASN1Element>(elements.length);
for (int i=0; i < elements.length; i++)
{
StringBuilder nameBuffer = new StringBuilder();
ASN1Element valueElement = decodeNameValuePair(elements[i], nameBuffer);
String name = nameBuffer.toString();
if (propertyMap.containsKey(name))
{
throw new SLAMDException("Multiple occurrences of property " + name +
"found in the provided set of name-value " +
"pairs.");
}
propertyMap.put(name, valueElement);
}
return propertyMap;
}
}