package com.xceptance.xlt.common.util.action.data;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.xceptance.common.util.ParameterCheckUtils;
import com.xceptance.xlt.api.util.XltLogger;
import com.xceptance.xlt.api.validators.HttpResponseCodeValidator;
import com.xceptance.xlt.common.util.bsh.ParameterInterpreter;
/**
* <p>
* Data Container which holds all necessary information to create and validate a single <b>http request</b>.
* </p>
* <ul>
* <li>Supports automatic & dynamic parameter interpretation via {@link ParameterInterpreter}. <br>
* </li>
* <li>Attributes are of type String to allow parameter interpretation and may be parsed to the intended type when they
* are accessed.</li>
* <li>Defaults common used Http request values, if not set explicitly.</li>
* <li>Holds a list of {@link #validations response validations}.</li>
* <li>Holds a list of {@link #store variables} which should be taken out of the response for dynamic parameter
* interpretation.</li>
* </ul>
*
* @author Matthias Mitterreiter
*/
public class URLActionData
{
/**
* Http request name.
*/
private String name;
/**
* <p>
* Type of request. <br>
* Default: {@link #TYPE_ACTION 'A'}. <br>
* </p>
* See {@link #PERMITTEDTYPES permitted request types}.
*/
private String type;
/**
* Intended type: {@link URL} <br>
* The url used for the request. <br>
* May be completed with {@link #parameters query parameters}.
*/
private String url;
/**
* <p>
* Http request method. <br>
* Default : 'GET'. <br>
* </p>
* See {@link #PERMITTEDMETHODS permitted methods}.
*/
private String method;
/**
* <p>
* Intended type: Boolean <br>
* Default: 'true'. <br>
* </p>
* <p>
* Allows further encoding of request {@link #parameters} with the declared {@link #encodingType encoding type}.
* </p>
*/
private String encodeParameters;
/**
* <p>
* Intended type: Boolean <br>
* Default: 'true'. <br>
* </p>
* <p>
* Allows further encoding of request {@link #body} with the declared {@link #encodingType encoding type}.
* </p>
*/
private String encodeBody;
/**
* Intended type: Integer <br>
* Expected HttpResponseCode. <br>
* Default : '200'.
*/
private String httpResponceCode;
/**
* <p>
* Request Body.
* </p>
* May be {@link #encodeBody encoded}.
*/
private String body;
/**
* Not implemented yet
*/
@SuppressWarnings("unused")
private String encodingType;
/**
* Data to validate the Http response.
*/
private List<URLActionDataValidation> validations = Collections.emptyList();
/**
* Data, which should be taken out of the Http response and stored for dynamic parameter interpretation.
*/
private List<URLActionDataStore> store = Collections.emptyList();
/**
* Request parameters. <br>
* Depends on the {@link #method method}, whether they are GET or POST parameters.
*/
private List<NameValuePair> parameters = Collections.emptyList();
/**
* Additional data for the "Cookie" request header field. <br>
* Not implemented yet.
*/
private List<NameValuePair> cookies = Collections.emptyList();
/**
* Request headers.
*/
private List<NameValuePair> headers = Collections.emptyList();
/**
* {@link ParameterInterpreter}.
*/
private final ParameterInterpreter interpreter;
/**
* <p>
* Permitted types of requests:
* <ul>
* <li> {@link #TYPE_ACTION Action}</li>
* <li> {@link #TYPE_STATIC Static action}</li>
* <li> {@link #TYPE_XHR Xhr action}</li>
* <ul>
* </p>
* <p>
* Different types allow a different handling of the request as well as the response.
* </p>
*/
public final static Set<String> PERMITTEDTYPES = new HashSet<String>();
/**
* Supported request methods:
* <ul>
* <li> {@link #METHOD_GET GET}</li>
* <li> {@link #METHOD_POST POST}</li>
* <li> {@link #METHOD_PUT PUT}</li>
* <li> {@link #METHOD_DELETE DELETE}</li>
* <li> {@link #METHOD_HEAD HEAD}</li>
* <li> {@link #METHOD_TRACE TRACE}</li>
* <li> {@link #METHOD_OPTIONS OPTIONS}</li>
* <li> {@link #METHOD_CONNECT CONNECT}</li>
* <ul>
*/
public final static Set<String> PERMITTEDMETHODS = new HashSet<String>();
/**
* Type for a general Http Request.
*/
public static final String TYPE_ACTION = "A";
/**
* Type for a Http requests, that loads static content. <br>
*/
public static final String TYPE_STATIC = "S";
/**
* Type for a XMLHttpRequest
*/
public static final String TYPE_XHR = "Xhr";
public static final String METHOD_POST = "POST";
public static final String METHOD_GET = "GET";
public static final String METHOD_PUT = "PUT";
public static final String METHOD_DELETE = "DELETE";
public static final String METHOD_HEAD = "HEAD";
public static final String METHOD_TRACE = "TRACE";
public static final String METHOD_OPTIONS = "OPTIONS";
static
{
PERMITTEDTYPES.add(TYPE_ACTION);
PERMITTEDTYPES.add(TYPE_XHR);
PERMITTEDTYPES.add(TYPE_STATIC);
PERMITTEDMETHODS.add(METHOD_GET);
PERMITTEDMETHODS.add(METHOD_POST);
PERMITTEDMETHODS.add(METHOD_DELETE);
PERMITTEDMETHODS.add(METHOD_PUT);
PERMITTEDMETHODS.add(METHOD_HEAD);
PERMITTEDMETHODS.add(METHOD_TRACE);
PERMITTEDMETHODS.add(METHOD_OPTIONS);
}
/**
* Takes the minimal set of parameters that are necessary to crate a http request. <br>
* Defaults the rest of the attributes. <br>
*
* @param name
* : {@link #name}
* @param url
* : {@link #url}
* @param interpreter
* : {@link #interpreter}
*/
public URLActionData(final String name, final String url, final ParameterInterpreter interpreter)
{
setName(name);
setUrl(url);
setType(TYPE_ACTION); // default
setMethod(METHOD_GET); // default
setEncodeParameters("true"); // default
setEncodeBody("true"); // default
setHttpResponceCode("200"); // default
ParameterCheckUtils.isNotNull(interpreter, "interpreter");
this.interpreter = interpreter;
}
/**
* For debugging purpose. <br>
* 'err-streams' the attributes of the object. <br>
*/
public void outline()
{
try
{
System.err.println("Action: " + getName());
System.err.println("\t" + "Is Action: " + isAction());
System.err.println("\t" + "Is Static Content: " + isStaticContent());
System.err.println("\t" + "Is Xhr: " + isXHRAction());
System.err.println("\t" + "Method: " + getMethod());
System.err.println("\t" + "URL: " + getUrl().toString());
System.err.println("\t" + "Encode-Parameters: " + encodeParameters());
System.err.println("\t" + "Encode-Body: " + encodeBody());
System.err.println("\t" + "HttpCode: " + getResponseCodeValidator().getHttpResponseCode());
if (body != null)
{
System.err.println("\t" + "Body: " + getBody());
}
if (!parameters.isEmpty())
{
final List<NameValuePair> parameters = getParameters();
System.err.println("\t" + "Parameters: ");
for (final NameValuePair nvp : parameters)
{
System.err.println("\t\t" + nvp.getName() + " : " + nvp.getValue());
}
}
if (!headers.isEmpty())
{
System.err.println("\t" + "Headers: ");
final List<NameValuePair> headers = getHeaders();
for (final NameValuePair nvp : headers)
{
System.err.println("\t\t" + nvp.getName() + " : " + nvp.getValue());
}
}
if (!cookies.isEmpty())
{
System.err.println("\t" + "Cookies: ");
final List<NameValuePair> cookies = getCookies();
for (final NameValuePair nvp : cookies)
{
System.err.println("\t\t" + nvp.getName() + " : " + nvp.getValue());
}
}
if (!validations.isEmpty())
{
System.err.println("\t" + "Validations: ");
for (final URLActionDataValidation v : validations)
{
v.outline();
}
}
if (!store.isEmpty())
{
System.err.println("\tStore:");
for (final URLActionDataStore storeItem : store)
{
storeItem.outline();
}
}
}
catch (final Exception e)
{
e.printStackTrace();
}
}
/**
* Sets if NOT null.
*
* @param httpResponceCode
*/
public void setHttpResponceCode(final String httpResponceCode)
{
if (httpResponceCode != null)
{
this.httpResponceCode = httpResponceCode;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("HttpResponceCode", httpResponceCode));
}
}
/**
* Sets if NOT null.
*
* @param httpResponceCode
*/
public void setHttpResponceCode(final Integer httpResponceCode)
{
if (httpResponceCode != null)
{
this.httpResponceCode = httpResponceCode.toString();
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("HttpResponceCode", this.httpResponceCode));
}
}
/**
* Sets if NOT null, otherwise THROWS.
*
* @throws IllegalArgumentException
* @param url
*/
public void setUrl(final String url)
{
this.url = (url != null) ? url : (String) throwIllegalArgumentException("'Url' cannot be null");
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("URL", this.url));
}
/**
* Sets if NOT null.
*
* @param method
*/
public void setMethod(final String method)
{
if (method != null)
{
this.method = method;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("Method", this.method));
}
}
/**
* Sets if NOT null.
*
* @param encoded
*/
public void setEncodeParameters(final String encoded)
{
if (encoded != null)
{
this.encodeParameters = encoded;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("encodedParameters", this.encodeParameters));
}
}
/**
* Sets if NOT null.
*
* @param encoded
*/
public void setEncodeParameters(final Boolean encoded)
{
if (encoded != null)
{
this.encodeParameters = encoded.toString();
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("encodedParameters", this.encodeParameters));
}
}
/**
* Sets if NOT null.
*
* @param encoded
*/
public void setEncodeBody(final String encoded)
{
if (encoded != null)
{
this.encodeBody = encoded;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("encodedBody", this.encodeBody));
}
}
/**
* Sets if NOT null.
*
* @param encoded
*/
public void setEncodeBody(final Boolean encoded)
{
if (encoded != null)
{
this.encodeBody = encoded.toString();
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("encodedBody", this.encodeBody));
}
}
/**
* Sets if NOT null.
*
* @param type
*/
public void setType(final String type)
{
if (type != null)
{
this.type = type;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("Type", this.type));
}
}
/**
* Sets if NOT null, otherwise THROWS.
*
* @param name
* @throws IllegalArgumentException.
*/
public void setName(final String name)
{
this.name = ((name != null) ? name : (String) throwIllegalArgumentException("Name' cannot be null"));
XltLogger.runTimeLogger.debug(MessageFormat.format("Set Action 'Name' to \"{0}\"", this.name));
}
/**
* Sets if NOT null.
*
* @param validations
*/
public void setValidations(final List<URLActionDataValidation> validations)
{
if (validations != null)
{
this.validations = validations;
XltLogger.runTimeLogger.debug(getSetNewTagMessage("validations"));
for (final URLActionDataValidation validation : validations)
{
XltLogger.runTimeLogger.debug("\t" + validation.getName());
}
}
}
/**
* Sets if NOT null.
*
* @param headers
*/
public void setHeaders(final List<NameValuePair> headers)
{
if (headers != null)
{
this.headers = headers;
XltLogger.runTimeLogger.debug(getSetNewTagMessage("headers"));
for (final NameValuePair header : headers)
{
XltLogger.runTimeLogger.debug("\t" + header.getName() + " : " + header.getValue());
}
}
}
/**
* Sets if NOT null.
*
* @param store
*/
public void setStore(final List<URLActionDataStore> store)
{
if (store != null)
{
this.store = store;
XltLogger.runTimeLogger.debug(getSetNewTagMessage("store"));
for (final URLActionDataStore storeItem : store)
{
XltLogger.runTimeLogger.debug("\t" + storeItem.getName());
}
}
}
/**
* Sets if NOT null.
*
* @param parameters
*/
public void setParameters(final List<NameValuePair> parameters)
{
if (parameters != null)
{
this.parameters = parameters;
XltLogger.runTimeLogger.debug(getSetNewTagMessage("parameters"));
for (final NameValuePair parameter : parameters)
{
XltLogger.runTimeLogger.debug("\t" + parameter.getName() + " : " + parameter.getValue());
}
}
}
/**
* Sets if NOT null.
*
* @param cookies
*/
public void setCookies(final List<NameValuePair> cookies)
{
if (cookies != null)
{
this.cookies = cookies;
XltLogger.runTimeLogger.debug(getSetNewTagMessage("cookies"));
for (final NameValuePair cookie : cookies)
{
XltLogger.runTimeLogger.debug("\t" + cookie.getName() + " : " + cookie.getValue());
}
}
}
/**
* Sets if NOT null.
*
* @param body
*/
public void setBody(final String body)
{
if (body != null)
{
this.body = body;
XltLogger.runTimeLogger.debug(getSetTagToValueMessage("body", this.body));
}
}
/**
* @return {@link #body}, after its dynamic interpretation via {@link #interpreter}.
*/
@Nullable
public String getBody()
{
return interpreter.processDynamicData(this.body);
}
/**
* @return The {@link #type type of request}. <br>
* if type is unknown, it returns {@link #TYPE_ACTION}.
*/
public String getType()
{
String result;
if (isStaticContent())
{
result = TYPE_STATIC;
}
else if (isXHRAction())
{
result = TYPE_XHR;
}
else
{
result = TYPE_ACTION;
}
return result;
}
/**
* @return {@link #url}, after its dynamic interpretation via {@link #interpreter}.
*/
public URL getUrl()
{
try
{
return new URL(interpreter.processDynamicData(url));
}
catch (final MalformedURLException e)
{
throw new IllegalArgumentException("Malformed URL for action" + getName() + ": " + e.getMessage(), e);
}
}
/**
* @return {@link #url}, after its dynamic interpretation via {@link #interpreter}.
*/
public String getUrlString()
{
return getUrl().toString();
}
/**
* @return {@link #httpResponceCode}, after its dynamic interpretation via {@link #interpreter}.
*/
public HttpResponseCodeValidator getResponseCodeValidator()
{
final String dynmaicResponseCode = interpreter.processDynamicData(this.httpResponceCode);
final HttpResponseCodeValidator result = StringUtils.isNotBlank(dynmaicResponseCode)
? new HttpResponseCodeValidator(
Integer.parseInt(dynmaicResponseCode))
: HttpResponseCodeValidator.getInstance();
return result;
}
/**
* @return {@link #httpResponceCode}, after its dynamic interpretation via {@link #interpreter}.
*/
public Integer getHttpResponseCode()
{
final String dynmaicResponseCode = interpreter.processDynamicData(this.httpResponceCode);
final Integer resultInteger = dynmaicResponseCode != null ? Integer.parseInt(dynmaicResponseCode) : 200;
return resultInteger;
}
/**
* @returns {@link #method}, after its dynamic interpretation via {@link #interpreter}. If the value of
* {@link #method} is unsupported, it gets defaulted to {@link #METHOD_GET GET}.
*/
public HttpMethod getMethod()
{
HttpMethod result = HttpMethod.GET;
final String dynamicMethod = interpreter.processDynamicData(this.method);
result = selectMethodFromDynamicData(dynamicMethod);
return result;
}
/**
* @param dynamicMethod
* @return {@link #method} after its dynamic interpretation via {@link #interpreter}. If its value is unsupported,
* {@link #method} gets defaulted to {@link #METHOD_GET GET}.
*/
private HttpMethod selectMethodFromDynamicData(final String dynamicMethod)
{
HttpMethod result;
switch (dynamicMethod)
{
case METHOD_GET:
result = HttpMethod.GET;
break;
case METHOD_POST:
result = HttpMethod.POST;
break;
case METHOD_PUT:
result = HttpMethod.PUT;
break;
case METHOD_DELETE:
result = HttpMethod.DELETE;
break;
case METHOD_HEAD:
result = HttpMethod.HEAD;
break;
case METHOD_TRACE:
result = HttpMethod.TRACE;
break;
case METHOD_OPTIONS:
result = HttpMethod.OPTIONS;
break;
default:
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicMethod, "HttpResponseCode", METHOD_GET));
result = HttpMethod.GET;
}
return result;
}
/**
* @return {@link #validations}.
*/
public List<URLActionDataValidation> getValidations()
{
return this.validations;
}
/**
* @return {@link #store}.
*/
public List<URLActionDataStore> getStore()
{
return this.store;
}
/**
* @return {@link #parameters}, after its dynamic interpretation via {@link #interpreter}.
*/
public List<NameValuePair> getParameters()
{
final List<NameValuePair> result = new ArrayList<NameValuePair>(parameters.size());
for (final NameValuePair pair : parameters)
{
result.add(getDynamicPair(pair));
}
return result;
}
/**
* Sends nvp through the {@link #interpreter}.
*/
private NameValuePair getDynamicPair(final NameValuePair pair)
{
String name = pair.getName();
name = name != null ? interpreter.processDynamicData(pair.getName()) : name;
String value = pair.getValue();
value = value != null ? interpreter.processDynamicData(pair.getValue()) : value;
return new NameValuePair(name, value);
}
/**
* @return {@link #cookies}, after its dynamic interpretation via {@link #interpreter}.
*/
public List<NameValuePair> getCookies()
{
final List<NameValuePair> result = new ArrayList<NameValuePair>(cookies.size());
for (final NameValuePair pair : cookies)
{
result.add(getDynamicPair(pair));
}
return result;
}
/**
* @return {@link #headers}, after its dynamic interpretation via {@link #interpreter}.
*/
public List<NameValuePair> getHeaders()
{
final List<NameValuePair> result = new ArrayList<NameValuePair>(headers.size());
for (final NameValuePair pair : headers)
{
result.add(getDynamicPair(pair));
}
return result;
}
/**
* @return {@link #name} after its dynamic interpretation via {@link #interpreter}.
*/
public String getName()
{
return interpreter.processDynamicData(name);
}
public ParameterInterpreter getInterpreter()
{
return interpreter;
}
/**
* @return value of {@link #encodeParameters} after its dynamic interpretation via {@link #interpreter}.
*/
public Boolean encodeParameters()
{
Boolean result = false; // default
final String dynamicEncoded = interpreter.processDynamicData(this.encodeParameters);
if ("true".equals(dynamicEncoded))
{
result = true;
}
else if ("false".equals(dynamicEncoded))
{
result = false;
}
else
{
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicEncoded, "encodeParameters", "false"));
result = false;
}
return result;
}
/**
* @return value of {@link #encodeBody} after its dynamic interpretation via {@link #interpreter}.
*/
public Boolean encodeBody()
{
Boolean result = true; // default
if (this.encodeBody != null)
{
final String dynamicEncoded = interpreter.processDynamicData(this.encodeBody);
if ("true".equals(dynamicEncoded))
{
result = true;
}
else if ("false".equals(dynamicEncoded))
{
result = false;
}
else
{
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicEncoded, "encodeBody", "false"));
result = false;
}
}
return result;
}
/**
* @return true if {@link #type request} is of type {@link #TYPE_STATIC}.
*/
public boolean isStaticContent()
{
Boolean result = false; // default
final String dynamicType = interpreter.processDynamicData(this.type);
if (TYPE_STATIC.equals(dynamicType))
{
result = true;
}
else if (TYPE_XHR.equals(dynamicType) || TYPE_ACTION.equals(dynamicType))
{
result = false;
}
else
{
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicType, "Type", TYPE_ACTION));
result = false;
}
return result;
}
/**
* @return true if {@link #type request} is of type {@link #TYPE_XHR}.
*/
public boolean isXHRAction()
{
Boolean result = false; // default
final String dynamicType = interpreter.processDynamicData(this.type);
if (TYPE_XHR.equals(dynamicType))
{
result = true;
}
else if (TYPE_ACTION.equals(dynamicType) || TYPE_STATIC.equals(dynamicType))
{
result = false;
}
else
{
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicType, "Type", TYPE_ACTION));
;
result = false;
}
return result;
}
/**
* @return true if {@link #type request} is of type {@link #TYPE_ACTION}.
*/
public Boolean isAction()
{
Boolean result = true; // default
final String dynamicType = interpreter.processDynamicData(this.type);
if (TYPE_ACTION.equals(dynamicType))
{
result = true;
}
else if (TYPE_XHR.equals(dynamicType) || TYPE_STATIC.equals(dynamicType))
{
result = false;
}
else
{
XltLogger.runTimeLogger.warn(getIllegalValueOfTagDefaultingTo(dynamicType, "Type", TYPE_ACTION));
result = true;
}
return result;
}
/**
* @return true if {@link #body} is set.
*/
public Boolean hasBody()
{
Boolean b = false;
if (getBody() != null)
{
b = true;
}
return b;
}
/**
* @param method
* @return if ({@link #PERMITTEDMETHODS method} is permitted) ? true : false.
*/
public static boolean isPermittedMethod(final String method)
{
return PERMITTEDMETHODS.contains(method);
}
/**
* @param type
* @return if ({@link #PERMITTEDTYPES type} is permitted) ? true : false.
*/
public static boolean isPermittedType(final String type)
{
return PERMITTEDTYPES.contains(type);
}
/**
* Add request parameter if NOT null.
*
* @param nvp
*/
public void addParameter(final NameValuePair nvp)
{
if (parameters.isEmpty() && nvp != null)
{
parameters = new ArrayList<NameValuePair>();
}
if (nvp != null)
{
parameters.add(nvp);
XltLogger.runTimeLogger.debug(getAddedToTag("Parameter"));
}
}
/**
* Add a validation if NOT null.
*
* @param validation
*/
public void addValidation(final URLActionDataValidation validation)
{
if (validation != null)
{
if (!(this.validations.isEmpty()))
{
validations = new ArrayList<URLActionDataValidation>();
}
validations.add(validation);
XltLogger.runTimeLogger.debug(getAddedToTag("Validation"));
}
}
/**
* Dirty way of throwing a IllegalArgumentException with the passed message.
*
* @param message
* @return nothing.
*/
private Object throwIllegalArgumentException(final String message)
{
throw new IllegalArgumentException(message);
}
/**
* @param tag
* @param value
* @return Formated message.
*/
private String getSetTagToValueMessage(final String tag, final String value)
{
final String message = MessageFormat.format("Action: \"{0}\", Set \"{1}\" to value: \"{2}\"", this.name, tag, value);
return message;
}
/**
* @param tag
* name
* @return Formated message.
*/
private String getAddedToTag(final String tag)
{
final String message = MessageFormat.format("Action: \"{0}\", Added new \"{1}\"", this.name, tag);
return message;
}
/**
* @param tag
* @return Formated error message.
*/
private String getSetNewTagMessage(final String tag)
{
final String message = MessageFormat.format("Action: \"{0}\", Set new \"{1}\"", this.name, tag);
return message;
}
/**
* @param value
* @param tag
* name
* @param defaultValue
* @return Formated error message.
*/
private String getIllegalValueOfTagDefaultingTo(final String value, final String tag, final String defaultValue)
{
final String message = MessageFormat.format(" Action: \"{0}\", Unsupported value \"{1}\" for \"{2}\", defaulting to \"{3}\"",
this.name, value, tag, defaultValue);
return message;
}
}