package com.canoo.webtest.ant;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.PropertyHelper;
import com.canoo.webtest.engine.Context;
import com.canoo.webtest.extension.ScriptStep;
/**
* Helper class for working with Ant and WebTest dynamic properties.<p>
*
* This property helper is registered at the start of a WebTest and used by ant to evaluate
* properties when configuring tasks.<br/>
* It is able to evaluate traditional Ant properties like ${my.property} as well as
* WebTest dynamic properties like #{my.dynamic.property}.<br/>
* It will notify build listeners implementing {@link IPropertyExpansionListener} of the property expansion.
* @author Marc Guillemot
*/
public class WebtestPropertyHelper extends PropertyHelper
{
private static final Logger LOG = Logger.getLogger(WebtestPropertyHelper.class);
private final PropertyHelper fWrappedHelper;
private Project fProject;
/**
* Wrapps the property helper defined for the project.
* @param project the project containing the original helper to decorate
*/
public WebtestPropertyHelper(final Project project)
{
fWrappedHelper = PropertyHelper.getPropertyHelper(project);
setProject(project);
}
/**
* Define a new property helper for the project
* @param propertyHelper the new helper
*/
protected static void definePropertyHelper(final Project project, final PropertyHelper propertyHelper) {
// first remove reference to avoid Warning message
project.getReferences().remove("ant.PropertyHelper");
project.addReference("ant.PropertyHelper", propertyHelper);
}
/**
* Configures the special WebTest property helper for the current project
* @param project the project which property helper should be wrapped
*/
public static void configureWebtestPropertyHelper(final Project project) {
final WebtestPropertyHelper propertyHelper = new WebtestPropertyHelper(project);
propertyHelper.setProject(project);
definePropertyHelper(project, propertyHelper);
}
/**
* @see org.apache.tools.ant.PropertyHelper#copyInheritedProperties(org.apache.tools.ant.Project)
*/
public void copyInheritedProperties(final Project other)
{
fWrappedHelper.copyInheritedProperties(other);
}
/**
* @see org.apache.tools.ant.PropertyHelper#copyUserProperties(org.apache.tools.ant.Project)
*/
public void copyUserProperties(final Project other)
{
fWrappedHelper.copyUserProperties(other);
}
/**
* @see org.apache.tools.ant.PropertyHelper#getNext()
*/
public PropertyHelper getNext()
{
return fWrappedHelper.getNext();
}
/**
* @see org.apache.tools.ant.PropertyHelper#getProperties()
*/
public Hashtable getProperties()
{
return fWrappedHelper.getProperties();
}
/**
* @see org.apache.tools.ant.PropertyHelper#getProperty(java.lang.String, java.lang.String)
*/
public Object getProperty(final String ns, final String name)
{
return fWrappedHelper.getProperty(ns, name);
}
/**
* @see org.apache.tools.ant.PropertyHelper#getPropertyHook(java.lang.String, java.lang.String, boolean)
*/
public Object getPropertyHook(final String ns, final String name, final boolean user)
{
return fWrappedHelper.getPropertyHook(ns, name, user);
}
/**
* @see org.apache.tools.ant.PropertyHelper#getUserProperties()
*/
public Hashtable getUserProperties()
{
return fWrappedHelper.getUserProperties();
}
/**
* @see org.apache.tools.ant.PropertyHelper#getUserProperty(java.lang.String, java.lang.String)
*/
public Object getUserProperty(final String ns, final String name)
{
return fWrappedHelper.getUserProperty(ns, name);
}
/**
* @see org.apache.tools.ant.PropertyHelper#parsePropertyString(java.lang.String, java.util.Vector, java.util.Vector)
*/
public void parsePropertyString(final String value, final Vector fragments, final Vector propertyRefs) throws BuildException
{
fWrappedHelper.parsePropertyString(value, fragments, propertyRefs);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setInheritedProperty(java.lang.String, java.lang.String, java.lang.Object)
*/
public void setInheritedProperty(final String ns, final String name, final Object value)
{
fWrappedHelper.setInheritedProperty(ns, name, value);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setNewProperty(java.lang.String, java.lang.String, java.lang.Object)
*/
public void setNewProperty(final String ns, final String name, final Object value)
{
fWrappedHelper.setNewProperty(ns, name, value);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setNext(org.apache.tools.ant.PropertyHelper)
*/
public void setNext(final PropertyHelper next)
{
fWrappedHelper.setNext(next);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setProject(org.apache.tools.ant.Project)
*/
public void setProject(final Project p)
{
fProject = p;
super.setProject(p);
fWrappedHelper.setProject(p);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setProperty(java.lang.String, java.lang.String, java.lang.Object, boolean)
*/
public boolean setProperty(final String ns, final String name, final Object value, final boolean verbose)
{
return fWrappedHelper.setProperty(ns, name, value, verbose);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setPropertyHook(java.lang.String, java.lang.String, java.lang.Object, boolean, boolean, boolean)
*/
public boolean setPropertyHook(final String ns, final String name, final Object value, final boolean inherited, final boolean user, final boolean isNew)
{
return fWrappedHelper.setPropertyHook(ns, name, value, inherited, user, isNew);
}
/**
* @see org.apache.tools.ant.PropertyHelper#setUserProperty(java.lang.String, java.lang.String, java.lang.Object)
*/
public void setUserProperty(final String ns, final String name, final Object value)
{
fWrappedHelper.setUserProperty(ns, name, value);
}
/**
* Handles ${my.property} as well as WebTest dynamic properties like #{my.dynamic.property}
* @see org.apache.tools.ant.PropertyHelper#replaceProperties(java.lang.String, java.lang.String, java.util.Hashtable)
*/
public String replaceProperties(final String ns, final String value, final Hashtable keys) throws BuildException
{
if (value == null)
return null;
final String str = replacePropertiesInternal(ns, value, keys);
// the evaluated properties need to be written in the report
// perhaps would it be better to notify the StepExecutionListener directly?
// in a first time without hard coupling
if (!str.equals(value))
{
LOG.debug("Notifying listeners of properties expansion: " + value + " -> " + str);
for (final Iterator iter=fProject.getBuildListeners().iterator(); iter.hasNext();)
{
final BuildListener listener = (BuildListener) iter.next();
if (listener instanceof IPropertyExpansionListener)
{
((IPropertyExpansionListener) listener).propertiesExpanded(value, str);
}
}
}
return str;
}
private String replacePropertiesInternal(final String ns, final String value, final Hashtable keys) {
final int length = value.length();
if (length < 4)
return value;
int posStart = value.indexOf('{', 1);
while (posStart != -1)
{
final char prec = value.charAt(posStart - 1);
if (prec == '$' || prec == '#')
{
int posClosing = getMatchingClosing(value, posStart);
if (posClosing != -1)
{
final String propertyContent = value.substring(posStart+1, posClosing);
final String expandedContent = replacePropertiesInternal(ns, propertyContent, keys);
final String replacement;
if (prec == '#')
{
final String propValue = getDynamicPropertyValue(expandedContent);
if (propValue == null)
{
replacement = "#{" + expandedContent + "}";
}
else
{
replacement = propValue;
}
}
else // $
{
replacement = super.replaceProperties(ns, "${" + expandedContent + "}", keys);
}
return value.substring(0, posStart -1)
+ replacement + replacePropertiesInternal(ns, value.substring(posClosing+1), keys);
}
}
posStart = value.indexOf('{', posStart+1);
}
return value;
}
/**
* Gets the first unmatched "}" from the start position
* @param value the string to look in
* @param posStart the position of the opening { to match
* @return -1 if none found
*/
private int getMatchingClosing(final String value, int posStart) {
final int length = value.length();
int i = posStart + 1;
int opened = 0;
while (i < length)
{
final char c = value.charAt(i);
if (c == '}')
{
if (opened == 0)
return i;
opened--;
}
else if (c == '{')
opened++;
++i;
}
return -1;
}
private String getDynamicPropertyValue(final String propName) {
if (propName.startsWith("script:"))
{
final Context context = WebtestTask.getThreadContext();
if (context == null || context.getRunner() == null) {
return null;
}
final String expr = StringUtils.substringAfter(propName, "script:");
return ScriptStep.evalScriptExpression(context, expr, null);
}
else
return (String) getDynamicProperties().get(propName);
}
/**
* Gets the dynamic properties map for the current webtest
* @return the map of properties
*/
Map getDynamicProperties() {
return WebtestTask.getThreadContext().getWebtest().getDynamicProperties();
}
/**
* This method handles the replacement of dynamic properties in a given String.
* It checks if any of the currently existing property names occur in the string.
* If a match is detected, it is replaced by its value. If the value is
* <code>null</code>, it is replaced by an empty string. The search looks for
* textual matches of the property name using an ant-like notation, i.e.
* in the form of "#{xxx}".
*
* @param properties the properties
* @param text The string to expand
* @deprecated Use @link Project#replaceProperties(msg)} instead
*/
public static String expandDynamicProperties(final Map properties, final String text) {
if (StringUtils.isEmpty(text)) {
return text;
}
String result = text;
for (final Iterator i = properties.entrySet().iterator(); i.hasNext();) {
final Map.Entry currentProperty = (Map.Entry) i.next();
final String propName = "#{" + currentProperty.getKey() + "}";
for (int index = result.indexOf(propName); index >= 0;) {
final String propValue = String.valueOf(currentProperty.getValue());
final StringBuffer newResult = new StringBuffer(result.substring(0, index));
newResult.append(propValue);
newResult.append(result.substring(index + propName.length(), result.length()));
result = newResult.toString();
index = result.indexOf(propName, index + propValue.length());
}
}
return result;
}
}