001: package com.canoo.webtest.ant;
002:
003: import java.util.Hashtable;
004: import java.util.Iterator;
005: import java.util.Map;
006: import java.util.Vector;
007:
008: import org.apache.commons.lang.StringUtils;
009: import org.apache.log4j.Logger;
010: import org.apache.tools.ant.BuildException;
011: import org.apache.tools.ant.BuildListener;
012: import org.apache.tools.ant.Project;
013: import org.apache.tools.ant.PropertyHelper;
014:
015: import com.canoo.webtest.engine.Context;
016: import com.canoo.webtest.extension.ScriptStep;
017:
018: /**
019: * Helper class for working with Ant and WebTest dynamic properties.<p>
020: *
021: * This property helper is registered at the start of a WebTest and used by ant to evaluate
022: * properties when configuring tasks.<br/>
023: * It is able to evaluate traditional Ant properties like ${my.property} as well as
024: * WebTest dynamic properties like #{my.dynamic.property}.<br/>
025: * It will notify build listeners implementing {@link IPropertyExpansionListener} of the property expansion.
026: * @author Marc Guillemot
027: */
028: public class WebtestPropertyHelper extends PropertyHelper {
029: private static final Logger LOG = Logger
030: .getLogger(WebtestPropertyHelper.class);
031: private final PropertyHelper fWrappedHelper;
032: private Project fProject;
033:
034: /**
035: * Wrapps the property helper defined for the project.
036: * @param project the project containing the original helper to decorate
037: */
038: public WebtestPropertyHelper(final Project project) {
039: fWrappedHelper = PropertyHelper.getPropertyHelper(project);
040: setProject(project);
041: }
042:
043: /**
044: * Define a new property helper for the project
045: * @param propertyHelper the new helper
046: */
047: protected static void definePropertyHelper(final Project project,
048: final PropertyHelper propertyHelper) {
049: // first remove reference to avoid Warning message
050: project.getReferences().remove("ant.PropertyHelper");
051: project.addReference("ant.PropertyHelper", propertyHelper);
052: }
053:
054: /**
055: * Configures the special WebTest property helper for the current project
056: * @param project the project which property helper should be wrapped
057: */
058: public static void configureWebtestPropertyHelper(
059: final Project project) {
060: final WebtestPropertyHelper propertyHelper = new WebtestPropertyHelper(
061: project);
062: propertyHelper.setProject(project);
063: definePropertyHelper(project, propertyHelper);
064: }
065:
066: /**
067: * @see org.apache.tools.ant.PropertyHelper#copyInheritedProperties(org.apache.tools.ant.Project)
068: */
069: public void copyInheritedProperties(final Project other) {
070: fWrappedHelper.copyInheritedProperties(other);
071: }
072:
073: /**
074: * @see org.apache.tools.ant.PropertyHelper#copyUserProperties(org.apache.tools.ant.Project)
075: */
076: public void copyUserProperties(final Project other) {
077: fWrappedHelper.copyUserProperties(other);
078: }
079:
080: /**
081: * @see org.apache.tools.ant.PropertyHelper#getNext()
082: */
083: public PropertyHelper getNext() {
084: return fWrappedHelper.getNext();
085: }
086:
087: /**
088: * @see org.apache.tools.ant.PropertyHelper#getProperties()
089: */
090: public Hashtable getProperties() {
091: return fWrappedHelper.getProperties();
092: }
093:
094: /**
095: * @see org.apache.tools.ant.PropertyHelper#getProperty(java.lang.String, java.lang.String)
096: */
097: public Object getProperty(final String ns, final String name) {
098: return fWrappedHelper.getProperty(ns, name);
099: }
100:
101: /**
102: * @see org.apache.tools.ant.PropertyHelper#getPropertyHook(java.lang.String, java.lang.String, boolean)
103: */
104: public Object getPropertyHook(final String ns, final String name,
105: final boolean user) {
106: return fWrappedHelper.getPropertyHook(ns, name, user);
107: }
108:
109: /**
110: * @see org.apache.tools.ant.PropertyHelper#getUserProperties()
111: */
112: public Hashtable getUserProperties() {
113: return fWrappedHelper.getUserProperties();
114: }
115:
116: /**
117: * @see org.apache.tools.ant.PropertyHelper#getUserProperty(java.lang.String, java.lang.String)
118: */
119: public Object getUserProperty(final String ns, final String name) {
120: return fWrappedHelper.getUserProperty(ns, name);
121: }
122:
123: /**
124: * @see org.apache.tools.ant.PropertyHelper#parsePropertyString(java.lang.String, java.util.Vector, java.util.Vector)
125: */
126: public void parsePropertyString(final String value,
127: final Vector fragments, final Vector propertyRefs)
128: throws BuildException {
129: fWrappedHelper.parsePropertyString(value, fragments,
130: propertyRefs);
131: }
132:
133: /**
134: * @see org.apache.tools.ant.PropertyHelper#setInheritedProperty(java.lang.String, java.lang.String, java.lang.Object)
135: */
136: public void setInheritedProperty(final String ns,
137: final String name, final Object value) {
138: fWrappedHelper.setInheritedProperty(ns, name, value);
139: }
140:
141: /**
142: * @see org.apache.tools.ant.PropertyHelper#setNewProperty(java.lang.String, java.lang.String, java.lang.Object)
143: */
144: public void setNewProperty(final String ns, final String name,
145: final Object value) {
146: fWrappedHelper.setNewProperty(ns, name, value);
147: }
148:
149: /**
150: * @see org.apache.tools.ant.PropertyHelper#setNext(org.apache.tools.ant.PropertyHelper)
151: */
152: public void setNext(final PropertyHelper next) {
153: fWrappedHelper.setNext(next);
154: }
155:
156: /**
157: * @see org.apache.tools.ant.PropertyHelper#setProject(org.apache.tools.ant.Project)
158: */
159: public void setProject(final Project p) {
160: fProject = p;
161: super .setProject(p);
162: fWrappedHelper.setProject(p);
163: }
164:
165: /**
166: * @see org.apache.tools.ant.PropertyHelper#setProperty(java.lang.String, java.lang.String, java.lang.Object, boolean)
167: */
168: public boolean setProperty(final String ns, final String name,
169: final Object value, final boolean verbose) {
170: return fWrappedHelper.setProperty(ns, name, value, verbose);
171: }
172:
173: /**
174: * @see org.apache.tools.ant.PropertyHelper#setPropertyHook(java.lang.String, java.lang.String, java.lang.Object, boolean, boolean, boolean)
175: */
176: public boolean setPropertyHook(final String ns, final String name,
177: final Object value, final boolean inherited,
178: final boolean user, final boolean isNew) {
179: return fWrappedHelper.setPropertyHook(ns, name, value,
180: inherited, user, isNew);
181: }
182:
183: /**
184: * @see org.apache.tools.ant.PropertyHelper#setUserProperty(java.lang.String, java.lang.String, java.lang.Object)
185: */
186: public void setUserProperty(final String ns, final String name,
187: final Object value) {
188: fWrappedHelper.setUserProperty(ns, name, value);
189: }
190:
191: /**
192: * Handles ${my.property} as well as WebTest dynamic properties like #{my.dynamic.property}
193: * @see org.apache.tools.ant.PropertyHelper#replaceProperties(java.lang.String, java.lang.String, java.util.Hashtable)
194: */
195: public String replaceProperties(final String ns,
196: final String value, final Hashtable keys)
197: throws BuildException {
198: if (value == null)
199: return null;
200: final String str = replacePropertiesInternal(ns, value, keys);
201:
202: // the evaluated properties need to be written in the report
203: // perhaps would it be better to notify the StepExecutionListener directly?
204: // in a first time without hard coupling
205: if (!str.equals(value)) {
206: LOG.debug("Notifying listeners of properties expansion: "
207: + value + " -> " + str);
208: for (final Iterator iter = fProject.getBuildListeners()
209: .iterator(); iter.hasNext();) {
210: final BuildListener listener = (BuildListener) iter
211: .next();
212: if (listener instanceof IPropertyExpansionListener) {
213: ((IPropertyExpansionListener) listener)
214: .propertiesExpanded(value, str);
215: }
216: }
217:
218: }
219: return str;
220: }
221:
222: private String replacePropertiesInternal(final String ns,
223: final String value, final Hashtable keys) {
224: final int length = value.length();
225: if (length < 4)
226: return value;
227:
228: int posStart = value.indexOf('{', 1);
229: while (posStart != -1) {
230: final char prec = value.charAt(posStart - 1);
231: if (prec == '$' || prec == '#') {
232: int posClosing = getMatchingClosing(value, posStart);
233: if (posClosing != -1) {
234: final String propertyContent = value.substring(
235: posStart + 1, posClosing);
236: final String expandedContent = replacePropertiesInternal(
237: ns, propertyContent, keys);
238: final String replacement;
239: if (prec == '#') {
240: final String propValue = getDynamicPropertyValue(expandedContent);
241: if (propValue == null) {
242: replacement = "#{" + expandedContent + "}";
243: } else {
244: replacement = propValue;
245: }
246: } else // $
247: {
248: replacement = super .replaceProperties(ns, "${"
249: + expandedContent + "}", keys);
250: }
251: return value.substring(0, posStart - 1)
252: + replacement
253: + replacePropertiesInternal(ns, value
254: .substring(posClosing + 1), keys);
255: }
256: }
257:
258: posStart = value.indexOf('{', posStart + 1);
259: }
260:
261: return value;
262: }
263:
264: /**
265: * Gets the first unmatched "}" from the start position
266: * @param value the string to look in
267: * @param posStart the position of the opening { to match
268: * @return -1 if none found
269: */
270: private int getMatchingClosing(final String value, int posStart) {
271: final int length = value.length();
272: int i = posStart + 1;
273: int opened = 0;
274: while (i < length) {
275: final char c = value.charAt(i);
276: if (c == '}') {
277: if (opened == 0)
278: return i;
279: opened--;
280: } else if (c == '{')
281: opened++;
282: ++i;
283: }
284: return -1;
285: }
286:
287: private String getDynamicPropertyValue(final String propName) {
288: if (propName.startsWith("script:")) {
289: final Context context = WebtestTask.getThreadContext();
290: if (context == null || context.getRunner() == null) {
291: return null;
292: }
293: final String expr = StringUtils.substringAfter(propName,
294: "script:");
295: return ScriptStep.evalScriptExpression(context, expr, null);
296: } else
297: return (String) getDynamicProperties().get(propName);
298: }
299:
300: /**
301: * Gets the dynamic properties map for the current webtest
302: * @return the map of properties
303: */
304: Map getDynamicProperties() {
305: return WebtestTask.getThreadContext().getWebtest()
306: .getDynamicProperties();
307: }
308:
309: /**
310: * This method handles the replacement of dynamic properties in a given String.
311: * It checks if any of the currently existing property names occur in the string.
312: * If a match is detected, it is replaced by its value. If the value is
313: * <code>null</code>, it is replaced by an empty string. The search looks for
314: * textual matches of the property name using an ant-like notation, i.e.
315: * in the form of "#{xxx}".
316: *
317: * @param properties the properties
318: * @param text The string to expand
319: * @deprecated Use @link Project#replaceProperties(msg)} instead
320: */
321: public static String expandDynamicProperties(final Map properties,
322: final String text) {
323: if (StringUtils.isEmpty(text)) {
324: return text;
325: }
326: String result = text;
327: for (final Iterator i = properties.entrySet().iterator(); i
328: .hasNext();) {
329: final Map.Entry currentProperty = (Map.Entry) i.next();
330: final String propName = "#{" + currentProperty.getKey()
331: + "}";
332:
333: for (int index = result.indexOf(propName); index >= 0;) {
334: final String propValue = String.valueOf(currentProperty
335: .getValue());
336: final StringBuffer newResult = new StringBuffer(result
337: .substring(0, index));
338: newResult.append(propValue);
339: newResult.append(result.substring(index
340: + propName.length(), result.length()));
341: result = newResult.toString();
342: index = result.indexOf(propName, index
343: + propValue.length());
344: }
345: }
346: return result;
347: }
348: }
|