/*
* 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.parameter;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import com.slamd.asn1.ASN1Boolean;
import com.slamd.asn1.ASN1Element;
import com.slamd.asn1.ASN1Exception;
import com.slamd.asn1.ASN1Null;
import com.slamd.asn1.ASN1OctetString;
import com.slamd.asn1.ASN1Sequence;
import com.slamd.common.Constants;
import com.slamd.common.SLAMDException;
/**
* This class defines a generic parameter that can be passed to a job to
* customize the way that it operates. It also defines functions that can be
* used to request and accept the value in the context of an HTML form.
*
*
* @author Neil A. Wilson
*/
public abstract class Parameter
implements Cloneable
{
/**
* Indicates whether this parameter is required to have a value.
*/
protected boolean isRequired;
/**
* Indicates whether this parameter should be considered sensitive.
*/
protected boolean isSensitive;
/**
* The value for this parameter, which is subject to validation.
*/
protected Object value;
/**
* A description for this parameter that can provide additional information
* about it than just the name.
*/
protected String description;
/**
* The name that will be used to reference the parameter any time a name
* needs to be displayed to the end user.
*/
protected String displayName;
/**
* The name that will be used to reference the parameter internally and in
* HTML forms.
*/
protected String name;
/**
* Creates a new instance of the Parameter to be used when decoding values
* transported over the network, and should not be used by jobs to create
* parameters. If any initialization is needed that will not be covered by
* calls to the <CODE>setName</CODE>, <CODE>setDisplayName</CODE>,
* <CODE>setDescription</CODE>, or <CODE>setValueFromString</CODE>, then it
* should be done here.
*/
public Parameter()
{
// No implementation required.
}
/**
* Creates a new instance of the Parameter based on the provided information.
* This is only to be called by subclasses that actually implement valid
* Parameter objects. Note that if a value is provided, no validation is
* performed on it to verify that it is acceptable.
*
* @param name The name of the parameter as it is used internally.
* @param displayName The name of the parameter as it is to be displayed to
* the end user.
* @param description A description of this parameter that provides more
* information than just the name.
* @param isRequired Indicates whether this parameter must have a value.
* @param value The value for this parameter.
*/
public Parameter(String name, String displayName, String description,
boolean isRequired, Object value)
{
this.name = name;
this.displayName = displayName;
this.description = description;
this.isRequired = isRequired;
this.value = value;
this.isSensitive = false;
}
/**
* Retrieves the name of this parameter as it is used internally.
*
* @return The name of this parameter as it is used internally.
*/
public final String getName()
{
return name;
}
/**
* Specifies the name to use for this parameter.
*
* @param name The name to use for this parameter.
*/
public final void setName(String name)
{
this.name = name;
}
/**
* Retrieves the name of this parameter as it is displayed to the end user.
*
* @return The name of this parameter as it is displayed to the end user.
*/
public final String getDisplayName()
{
return displayName;
}
/**
* Specifies the name of this parameter as it should be displayed to the end
* user.
*
* @param displayName The name of this parameter as it should be displayed
* to the end user.
*/
public final void setDisplayName(String displayName)
{
this.displayName = displayName;
}
/**
* Retrieves the description for this parameter.
*
* @return The description for this parameter.
*/
public final String getDescription()
{
return description;
}
/**
* Specifies the description for this parameter.
*
* @param description The description for this parameter.
*/
public final void setDescription(String description)
{
this.description = description;
}
/**
* Indicates whether this parameter is required to have a value.
*
* @return <CODE>true</CODE> if this parameter is required to have a value,
* or <CODE>false</CODE> if it does not need a value.
*/
public final boolean isRequired()
{
return isRequired;
}
/**
* Specifies whether this parameter is required to have a value.
*
* @param isRequired Indicates whether this parameter is required to have
* a value.
*/
public final void setRequired(boolean isRequired)
{
this.isRequired = isRequired;
}
/**
* Indicates whether this parameter should be considered sensitive.
*
* @return <CODE>true</CODE> if this parameter should be considered
* sensitive, or <CODE>false</CODE> if not.
*/
public boolean isSensitive()
{
return isSensitive;
}
/**
* Specifies whether this parameter should be considered sensitive.
*
* @param isSensitive Specifies whether this parameter should be considered
* sensitive.
*/
public void setSensitive(boolean isSensitive)
{
this.isSensitive = isSensitive;
}
/**
* Retrieves the value for this parameter.
*
* @return The value for this parameter.
*/
public Object getValue()
{
return value;
}
/**
* Specifies the value to use for this parameter.
*
* @param value The value to use for this parameter.
*
* @throws InvalidValueException If the specified value is not acceptable
* for use in this parameter.
*/
public void setValue(Object value)
throws InvalidValueException
{
String invalidReason = getInvalidReason(value);
if (invalidReason == null)
{
this.value = value;
}
else
{
throw new InvalidValueException(invalidReason);
}
}
/**
* Sets the value for this parameter from the information in the provided
* parameter. Note that the provided parameter must be of the same type
* as this parameter or no action will be taken.
*
* @param parameter The parameter from which to take the value for this
* parameter.
*/
public abstract void setValueFrom(Parameter parameter);
/**
* Retrieves a string representation of the value of this parameter.
*
* @return A string representation of the value of this parameter.
*/
public abstract String getValueString();
/**
* Specifies the value to use for this parameter from the provided String.
*
* @param valueString The string representation of the value to use for this
* parameter.
*
* @throws InvalidValueException If the provided value cannot be used to
* provide a value for this parameter.
*/
public abstract void setValueFromString(String valueString)
throws InvalidValueException;
/**
* Indicates whether a value has been specified for this parameter.
*
* @return <CODE>true</CODE> if a value has been specified for this parameter
* or <CODE>false</CODE> if not.
*/
public boolean hasValue()
{
return (value != null);
}
/**
* Indicates whether the current value for this parameter is valid.
*
* @return <CODE>true</CODE> if the current value for this parameter is
* valid, or <CODE>false</CODE> if it is not.
*/
public final boolean isValid()
{
return isValid(value);
}
/**
* Indicates whether the specified value would be a valid value for this
* parameter.
*
* @param value The value for which to make the determination.
*
* @return <CODE>true</CODE> if the specified value would be a valid value
* for this parameter, or <CODE>false</CODE> if it would not be.
*/
public final boolean isValid(Object value)
{
return (getInvalidReason(value) == null);
}
/**
* Retrieves the reason that the current value is not valid.
*
* @return The reason that the current value is not valid, or
* <CODE>null</CODE> if it is valid.
*/
public final String getInvalidReason()
{
return getInvalidReason(value);
}
/**
* Retrieves the reason that the specified value is not valid.
*
* @param value The value for which to make the determination.
*
* @return The reason that the specified value is not valid, or
* <CODE>null</CODE> if it is valid.
*/
public abstract String getInvalidReason(Object value);
/**
* Retrieves a String that can be used when displaying the value of this
* parameter to the end user.
*
* @return A String that can be used when displaying the value of this
* parameter to the end user.
*/
public abstract String getDisplayValue();
/**
* Retrieves a String that can be used when displaying the value of this
* parameter to the end user in the context of an HTML page.
*
* @return A String that can be used when displaying the value of this
* parameter to the end user in the context of an HTML page.
*/
public String getHTMLDisplayValue()
{
return Constants.makeHTMLSafe(getDisplayValue());
}
/**
* Retrieves the value of this parameter as it would be submitted by a browser
* posting the request.
*
* @return The value of this parameter as it would be submitted by a browser
* posting the request.
*/
public String getHTMLPostValue()
{
if (hasValue())
{
try
{
return URLEncoder.encode(getValueString(), "UTF-8");
}
catch (Exception e)
{
// This should never happen.
return null;
}
}
else
{
return null;
}
}
/**
* Retrieves a string of text that can be used to request a value for this
* parameter using an HTML form. Note that this should just be for the input
* field itself and should not use the display name or have any special marker
* to indicate whether the value is required or not, as those are to be added
* by whatever is generating the HTML page.
*
* @param prefix The prefix that should be placed in front of the parameter
* name as the name of the form element.
*
* @return A string of text that can be used to request a value for this
* parameter using an HTML form.
*/
public abstract String getHTMLInputForm(String prefix);
/**
* Specifies the value of this parameter based on the provided text that would
* be returned from posting an HTML form.
*
* @param values The set of values for this parameter contained in the
* servlet request.
*
* @throws InvalidValueException If the specified value is not acceptable
* for this parameter.
*/
public abstract void htmlInputFormToValue(String[] values)
throws InvalidValueException;
/**
* Specifies the value of this parameter based on the provided servlet
* request.
*
* @param request The servlet request from which to obtain the value(s) for
* this parameter.
*
* @throws InvalidValueException If the provided request does not contain a
* valid value for this parameter.
*/
public void htmlInputFormToValue(HttpServletRequest request)
throws InvalidValueException
{
String[] values =
request.getParameterValues(Constants.SERVLET_PARAM_JOB_PARAM_PREFIX +
name);
htmlInputFormToValue(values);
}
/**
* Retrieves a string representation of the content that should be included in
* an HTML form in which this parameter should be provided as a hidden
* element.
*
* @param prefix The prefix to use for the parameter name.
*
* @return A string representation of this parameter as a hidden element in
* an HTML form.
*/
public abstract String generateHidden(String prefix);
/**
* Retrieves a string representation of this parameter.
*
* @return A string representation of this parameter.
*/
@Override()
public String toString()
{
return name + ": " + getDisplayValue();
}
/**
* Creates a clone of this parameter.
*
* @return A clone of this parameter.
*/
@Override()
public abstract Parameter clone();
/**
* Decodes the provided ASN.1 element as a parameter.
*
* @param element The ASN.1 element to decode.
*
* @return The parameter decoded from the ASN.1 element.
*
* @throws SLAMDException If the provided ASN.1 element cannot be decoded
* as a parameter.
*/
public static Parameter decode(ASN1Element element)
throws SLAMDException
{
ASN1Sequence parameterSequence = null;
try
{
parameterSequence = element.decodeAsSequence();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("Unable to decode the ASN.1 element as a " +
"sequence", ae);
}
ASN1Element[] elements = parameterSequence.getElements();
if (elements.length < 3)
{
throw new SLAMDException("The number of elements in the parameter " +
"sequence is not at least 3");
}
String className = null;
try
{
className = elements[0].decodeAsOctetString().getStringValue();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("The first element of the parameter sequence " +
"is not an enumerated value", ae);
}
String name = null;
try
{
name = elements[1].decodeAsOctetString().getStringValue();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("The second element of the parameter sequence " +
"is not an octet string", ae);
}
String valueStr = null;
try
{
valueStr = elements[2].decodeAsOctetString().getStringValue();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("The third element of the parameter sequence " +
"is not an octet string", ae);
}
String displayName = null;
if (elements.length > 3)
{
try
{
displayName = elements[3].decodeAsOctetString().getStringValue();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("The fourth element of the parameter " +
"sequence is not an octet string", ae);
}
}
String description = null;
if (elements.length > 4)
{
try
{
description = elements[4].decodeAsOctetString().getStringValue();
}
catch (ASN1Exception ae)
{
throw new SLAMDException("The fifth element of the parameter " +
"sequence is not an octet string", ae);
}
}
boolean isRequired = false;
if (elements.length > 5)
{
try
{
isRequired = elements[5].decodeAsBoolean().getBooleanValue();
}
catch (ASN1Exception e)
{
throw new SLAMDException("The sixth element of the parameter " +
"sequence is not a Boolean.");
}
}
boolean isSensitive = false;
if (elements.length > 6)
{
try
{
isSensitive = elements[6].decodeAsBoolean().getBooleanValue();
}
catch (ASN1Exception e)
{
throw new SLAMDException("The seventh element of the parameter " +
"sequence is not a Boolean.");
}
}
String[] choices = null;
if (elements.length > 7)
{
try
{
if (elements[7].getType() == ASN1Element.ASN1_SEQUENCE_TYPE)
{
ASN1Element[] choiceElements =
elements[7].decodeAsSequence().getElements();
choices = new String[choiceElements.length];
for (int i=0; i < choices.length; i++)
{
choices[i] =
choiceElements[i].decodeAsOctetString().getStringValue();
}
}
}
catch (ASN1Exception e)
{
throw new SLAMDException("The eighth element of the parameter " +
"sequence is not a sequence of octet " +
"strings or null element.");
}
}
try
{
Class<?> parameterClass = Constants.classForName(className);
Parameter p = (Parameter) parameterClass.newInstance();
p.setName(name);
p.setValueFromString(valueStr);
if (displayName != null)
{
p.setDisplayName(displayName);
}
if (description != null)
{
p.setDescription(description);
}
p.setRequired(isRequired);
p.setSensitive(isSensitive);
if (choices != null)
{
if (p instanceof MultiChoiceParameter)
{
MultiChoiceParameter mcp = (MultiChoiceParameter) p;
mcp.setChoices(choices);
}
else if (p instanceof IntegerWithUnitParameter)
{
IntegerWithUnitParameter iwup = (IntegerWithUnitParameter) p;
iwup.setChoices(choices);
}
}
return p;
}
catch (Exception e)
{
throw new SLAMDException("Could not create the parameter instance: " +
e, e);
}
}
/**
* Encodes this parameter as an ASN.1 element. The ASN.1 syntax for a
* parameter is:
* <BR><BR>
* <CODE>Parameter ::= SEQUENCE {</CODE>
* <CODE> className OCTET STRING,</CODE>
* <CODE> name OCTET STRING,</CODE>
* <CODE> value OCTET STRING,</CODE>
* <CODE> displayName OCTET STRING OPTIONAL,</CODE>
* <CODE> description OCTET STRING OPTIONAL,</CODE>
* <CODE> isRequired BOOLEAN OPTIONAL,</CODE>
* <CODE> isSensitive BOOLEAN OPTIONAL,</CODE>
* <CODE> allowedValues SEQUENCE OF OCTET STRING OPTIONAL }</CODE>
* <BR>
*
* @return The encoded ASN.1 element.
*/
public final ASN1Element encode()
{
ASN1Element[] parameterElements;
if (this instanceof MultiChoiceParameter)
{
MultiChoiceParameter mcp = (MultiChoiceParameter) this;
String[] choices = mcp.getChoices();
ASN1Element[] allowedValueElements = new ASN1Element[choices.length];
for (int i=0; i < allowedValueElements.length; i++)
{
allowedValueElements[i] = new ASN1OctetString(choices[i]);
}
parameterElements = new ASN1Element[]
{
new ASN1OctetString(getClass().getName()),
new ASN1OctetString(name),
new ASN1OctetString(getValueString()),
new ASN1OctetString(displayName),
new ASN1OctetString(description),
new ASN1Boolean(isRequired),
new ASN1Boolean(isSensitive),
new ASN1Sequence(allowedValueElements)
};
}
else if (this instanceof IntegerWithUnitParameter)
{
IntegerWithUnitParameter iwup = (IntegerWithUnitParameter) this;
String[] choices = iwup.getChoices();
ASN1Element[] allowedValueElements = new ASN1Element[choices.length];
for (int i=0; i < allowedValueElements.length; i++)
{
allowedValueElements[i] = new ASN1OctetString(choices[i]);
}
parameterElements = new ASN1Element[]
{
new ASN1OctetString(getClass().getName()),
new ASN1OctetString(name),
new ASN1OctetString(getValueString()),
new ASN1OctetString(displayName),
new ASN1OctetString(description),
new ASN1Boolean(isRequired),
new ASN1Boolean(isSensitive),
new ASN1Sequence(allowedValueElements)
};
}
else
{
parameterElements = new ASN1Element[]
{
new ASN1OctetString(getClass().getName()),
new ASN1OctetString(name),
new ASN1OctetString(getValueString()),
new ASN1OctetString(displayName),
new ASN1OctetString(description),
new ASN1Boolean(isRequired),
new ASN1Boolean(isSensitive),
new ASN1Null()
};
}
return new ASN1Sequence(parameterElements);
}
}