View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.impl.wsdl.teststeps;
14  
15  import com.eviware.soapui.config.PropertyTransferConfig;
16  import com.eviware.soapui.model.TestModelItem;
17  import com.eviware.soapui.model.TestPropertyHolder;
18  import com.eviware.soapui.model.iface.SubmitContext;
19  import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
20  import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
21  import com.eviware.soapui.model.support.TestPropertyListenerAdapter;
22  import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
23  import com.eviware.soapui.model.testsuite.TestCase;
24  import com.eviware.soapui.model.testsuite.TestProperty;
25  import com.eviware.soapui.model.testsuite.TestStep;
26  import com.eviware.soapui.support.PropertyChangeNotifier;
27  import com.eviware.soapui.support.resolver.ChooseAnotherPropertySourceResolver;
28  import com.eviware.soapui.support.resolver.ChooseAnotherPropertyTargetResolver;
29  import com.eviware.soapui.support.resolver.CreateMissingPropertyResolver;
30  import com.eviware.soapui.support.resolver.ResolveContext;
31  import com.eviware.soapui.support.resolver.defaultaction.DisablePropertyTransferAction;
32  import com.eviware.soapui.support.xml.XmlUtils;
33  import org.apache.log4j.Logger;
34  import org.apache.xmlbeans.XmlCursor;
35  import org.apache.xmlbeans.XmlCursor.TokenType;
36  import org.apache.xmlbeans.XmlException;
37  import org.apache.xmlbeans.XmlObject;
38  import org.apache.xmlbeans.XmlOptions;
39  import org.w3c.dom.DocumentFragment;
40  import org.w3c.dom.Element;
41  import org.w3c.dom.Node;
42  
43  import java.beans.PropertyChangeEvent;
44  import java.beans.PropertyChangeListener;
45  import java.beans.PropertyChangeSupport;
46  import java.util.ArrayList;
47  import java.util.List;
48  
49  /***
50   * Class for transferring a property value between 2 test steps. This class is
51   * relatively complex due to backwards compatibility issues and to gracefull
52   * handling of references test steps and properties.
53   * 
54   * @author Ole.Matzura
55   */
56  
57  public class PropertyTransfer implements PropertyChangeNotifier
58  {
59  	private final static Logger log = Logger.getLogger(PropertyTransfer.class);
60  
61  	public final static String SOURCE_PATH_PROPERTY = PropertyTransfer.class.getName() + "@sourcePath";
62  	public final static String SOURCE_TYPE_PROPERTY = PropertyTransfer.class.getName() + "@sourceProperty";
63  	public final static String SOURCE_STEP_PROPERTY = PropertyTransfer.class.getName() + "@sourceStep";
64  	public final static String TARGET_PATH_PROPERTY = PropertyTransfer.class.getName() + "@targetPath";
65  	public final static String TARGET_TYPE_PROPERTY = PropertyTransfer.class.getName() + "@targetProperty";
66  	public final static String TARGET_STEP_PROPERTY = PropertyTransfer.class.getName() + "@targetStep";
67  	public final static String NAME_PROPERTY = PropertyTransfer.class.getName() + "@name";
68  	public final static String DISABLED_PROPERTY = PropertyTransfer.class.getName() + "@disabled";
69  	public final static String CONFIG_PROPERTY = PropertyTransfer.class.getName() + "@config";
70  
71  	private TestStep testStep;
72  
73  	// create local copies since a deleted/changed valuetransfer can be referred
74  	// to from a result
75  	private PropertyTransferConfig config;
76  	private String sourcePath;
77  	private String sourceType;
78  	private String targetPath;
79  	private String name;
80  	private String targetType;
81  	private String sourceStep;
82  	private String targetStep;
83  
84  	private TestPropertyHolder currentTargetStep;
85  	private TestPropertyHolder currentSourceStep;
86  	private TestProperty currentTargetProperty;
87  	private TestProperty currentSourceProperty;
88  
89  	private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
90  	private StepNameChangeListener stepNameChangeListener = new StepNameChangeListener();
91  	private InternalTestPropertyListener propertyNameChangeListener = new InternalTestPropertyListener();
92  	private TestCase testCase;
93  
94  	private InternalTestSuiteListener testSuiteListener = new InternalTestSuiteListener();
95  
96  	public PropertyTransfer(TestStep testStep)
97  	{
98  		this(testStep, PropertyTransferConfig.Factory.newInstance());
99  	}
100 
101 	public PropertyTransfer(TestStep testStep, PropertyTransferConfig config)
102 	{
103 		this.testStep = testStep;
104 
105 		if (testStep != null)
106 		{
107 			this.testCase = testStep.getTestCase();
108 			testCase.getTestSuite().addTestSuiteListener(testSuiteListener);
109 		}
110 
111 		setConfig(config);
112 	}
113 
114 	void setConfigOnMove(PropertyTransferConfig config)
115 	{
116 		this.config = config;
117 	}
118 
119 	void setConfig(PropertyTransferConfig config)
120 	{
121 		releaseListeners();
122 
123 		this.config = config;
124 
125 		if (!config.isSetSetNullOnMissingSource())
126 		{
127 			config.setSetNullOnMissingSource(true);
128 		}
129 
130 		if (!config.isSetTransferTextContent())
131 		{
132 			config.setTransferTextContent(true);
133 		}
134 
135 		sourceStep = config.getSourceStep();
136 		if (sourceStep == null)
137 		{
138 			sourceStep = getSourceStepName();
139 			if (sourceStep != null)
140 				config.setSourceStep(sourceStep);
141 		}
142 
143 		currentSourceStep = getPropertyHolder(sourceStep);
144 
145 		sourceType = config.getSourceType();
146 		currentSourceProperty = currentSourceStep == null || sourceType == null ? null : currentSourceStep
147 				.getProperty(sourceType);
148 
149 		sourcePath = config.getSourcePath();
150 
151 		targetStep = config.getTargetStep();
152 		if (targetStep == null)
153 		{
154 			targetStep = getTargetStepName();
155 			if (targetStep != null)
156 				config.setTargetStep(targetStep);
157 		}
158 
159 		currentTargetStep = getPropertyHolder(targetStep);
160 
161 		targetType = config.getTargetType();
162 		currentTargetProperty = currentTargetStep == null || targetType == null ? null : currentTargetStep
163 				.getProperty(targetType);
164 
165 		targetPath = config.getTargetPath();
166 
167 		name = config.getName();
168 		initListeners();
169 
170 		propertyChangeSupport.firePropertyChange(CONFIG_PROPERTY, null, null);
171 	}
172 
173 	private void initListeners()
174 	{
175 		if (currentSourceStep != null)
176 		{
177 			if (currentSourceStep instanceof TestStep)
178 				((TestStep) currentSourceStep).addPropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
179 
180 			currentSourceStep.addTestPropertyListener(propertyNameChangeListener);
181 		}
182 
183 		if (currentTargetStep != null)
184 		{
185 			if (currentTargetStep instanceof TestStep)
186 				((TestStep) currentTargetStep).addPropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
187 
188 			currentTargetStep.addTestPropertyListener(propertyNameChangeListener);
189 		}
190 	}
191 
192 	public void releaseListeners()
193 	{
194 		if (currentSourceStep != null)
195 		{
196 			if (currentSourceStep instanceof TestStep)
197 				((TestStep) currentSourceStep).removePropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
198 
199 			currentSourceStep.removeTestPropertyListener(propertyNameChangeListener);
200 		}
201 
202 		if (currentTargetStep != null)
203 		{
204 			if (currentTargetStep instanceof TestStep)
205 				((TestStep) currentTargetStep).removePropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
206 
207 			currentTargetStep.removeTestPropertyListener(propertyNameChangeListener);
208 		}
209 
210 		PropertyChangeListener[] listeners = propertyChangeSupport.getPropertyChangeListeners();
211 		for (PropertyChangeListener listener : listeners)
212 			propertyChangeSupport.removePropertyChangeListener(listener);
213 	}
214 
215 	public void release()
216 	{
217 		releaseListeners();
218 		testCase.getTestSuite().removeTestSuiteListener(testSuiteListener);
219 	}
220 
221 	public PropertyTransferConfig getConfig()
222 	{
223 		return config;
224 	}
225 
226 	public String getSourcePath()
227 	{
228 		return sourcePath;
229 	}
230 
231 	public String getTargetPath()
232 	{
233 		return targetPath;
234 	}
235 
236 	public TestProperty getSourceProperty()
237 	{
238 		if (sourceType == null)
239 			return null;
240 
241 		if (currentSourceProperty != null)
242 			return currentSourceProperty;
243 
244 		TestPropertyHolder actualSourceStep = getSourceStep();
245 		return actualSourceStep == null ? null : actualSourceStep.getProperty(sourceType);
246 	}
247 
248 	public String[] transferProperties(SubmitContext context) throws PropertyTransferException
249 	{
250 		TestProperty sourceProperty = getSourceProperty();
251 		TestProperty targetProperty = getTargetProperty();
252 
253 		try
254 		{
255 			if (sourceProperty == null)
256 				throw new Exception("Missing source property");
257 			if (targetProperty == null)
258 				throw new Exception("Missing target property");
259 			if (sourceProperty.getValue() == null && !getSetNullOnMissingSource() && !getIgnoreEmpty())
260 				throw new Exception("Source property is null");
261 
262 			if (!hasSourcePath() && !hasTargetPath())
263 			{
264 				if (!getIgnoreEmpty() || (sourceProperty.getValue() != null && sourceProperty.getValue().length() > 0))
265 					return transferStringToString(sourceProperty, targetProperty);
266 			}
267 			else if (hasSourcePath() && hasTargetPath())
268 			{
269 				return transferXPathToXml(sourceProperty, targetProperty, context);
270 			}
271 			else if (hasSourcePath() && !hasTargetPath())
272 			{
273 				return new String[] { transferXPathToString(sourceProperty, targetProperty, context) };
274 			}
275 			else if (!hasSourcePath() && hasTargetPath())
276 			{
277 				if (!getIgnoreEmpty() || (sourceProperty.getValue() != null && sourceProperty.getValue().length() > 0))
278 					return transferStringToXml(sourceProperty, targetProperty, context);
279 			}
280 		}
281 		catch (Exception e)
282 		{
283 			throw new PropertyTransferException(e.getMessage(), getSourceStepName(), sourceProperty, getTargetStepName(),
284 					targetProperty);
285 		}
286 
287 		return new String[0];
288 	}
289 
290 	private boolean hasTargetPath()
291 	{
292 		String path = getTargetPath();
293 		return path != null && path.trim().length() > 0;
294 	}
295 
296 	private boolean hasSourcePath()
297 	{
298 		String path = getSourcePath();
299 		return path != null && path.trim().length() > 0;
300 	}
301 
302 	protected String[] transferStringToString(TestProperty sourceProperty, TestProperty targetProperty)
303 	{
304 		String value = sourceProperty.getValue();
305 		targetProperty.setValue(value);
306 		return new String[] { value };
307 	}
308 
309 	protected String[] transferXPathToXml(TestProperty sourceProperty, TestProperty targetProperty, SubmitContext context)
310 			throws XmlException, Exception
311 	{
312 		String sourcePropertyValue = sourceProperty.getValue();
313 		XmlObject sourceXmlObject = sourcePropertyValue == null ? null : XmlObject.Factory.parse(sourcePropertyValue);
314 		XmlCursor sourceXml = sourceXmlObject == null ? null : sourceXmlObject.newCursor();
315 
316 		String targetPropertyValue = targetProperty.getValue();
317 		XmlObject targetXmlObject = XmlObject.Factory.parse(targetPropertyValue);
318 		XmlCursor targetXml = targetXmlObject.newCursor();
319 
320 		XmlCursor lastSource = null;
321 
322 		try
323 		{
324 			List<String> result = new ArrayList<String>();
325 
326 			String tp = PropertyExpansionUtils.expandProperties(context, getTargetPath());
327 			targetXml.selectPath(tp);
328 
329 			if (!targetXml.hasNextSelection())
330 				throw new Exception("Missing match for Target XPath [" + tp + "]");
331 
332 			if (sourceXml == null)
333 			{
334 				if (getSetNullOnMissingSource())
335 				{
336 					while (targetXml.toNextSelection())
337 					{
338 						result.add(setNodeValue(null, targetXml.getDomNode()));
339 						if (!getTransferToAll())
340 							break;
341 					}
342 				}
343 			}
344 			else if (getUseXQuery())
345 			{
346 				String sp = PropertyExpansionUtils.expandProperties(context, getSourcePath());
347 				XmlCursor resultCursor = sourceXml.execQuery(sp);
348 				sourceXml.dispose();
349 				sourceXml = resultCursor;
350 
351 				if (sourceXml.toNextToken() != TokenType.START)
352 				{
353 					if (getSetNullOnMissingSource())
354 					{
355 						while (targetXml.toNextSelection())
356 						{
357 							result.add(setNodeValue(null, targetXml.getDomNode()));
358 							if (!getTransferToAll())
359 								break;
360 						}
361 					}
362 					else if (!getIgnoreEmpty())
363 						throw new Exception("Missing match for Source XQuery [" + sp + "]");
364 				}
365 
366 				boolean hasTarget = targetXml.toNextSelection();
367 
368 				if (hasTarget)
369 				{
370 					lastSource = sourceXml.newCursor();
371 					result.add(transferXmlValue(sourceXml, targetXml));
372 				}
373 			}
374 			else
375 			{
376 				String sp = PropertyExpansionUtils.expandProperties(context, getSourcePath());
377 				sourceXml.selectPath(sp);
378 
379 				if (!sourceXml.hasNextSelection())
380 				{
381 					if (getSetNullOnMissingSource())
382 					{
383 						while (targetXml.toNextSelection())
384 						{
385 							result.add(setNodeValue(null, targetXml.getDomNode()));
386 							if (!getTransferToAll())
387 								break;
388 						}
389 					}
390 					else if (!getIgnoreEmpty())
391 						throw new Exception("Missing match for Source XPath [" + sp + "]");
392 				}
393 				else
394 				{
395 					boolean hasSource = sourceXml.toNextSelection();
396 					boolean hasTarget = targetXml.toNextSelection();
397 
398 					while (hasSource && hasTarget)
399 					{
400 						if (lastSource != null)
401 							lastSource.dispose();
402 
403 						lastSource = sourceXml.newCursor();
404 						result.add(transferXmlValue(sourceXml, targetXml));
405 
406 						hasSource = sourceXml.toNextSelection();
407 						hasTarget = targetXml.toNextSelection();
408 					}
409 
410 					if (getTransferToAll() && !hasSource && hasTarget && lastSource != null)
411 					{
412 						while (hasTarget)
413 						{
414 							result.add(transferXmlValue(lastSource, targetXml));
415 							hasTarget = targetXml.toNextSelection();
416 						}
417 					}
418 				}
419 			}
420 
421 			if (result.size() > 0)
422 			{
423 				targetProperty.setValue(targetXmlObject.xmlText(new XmlOptions().setSaveAggressiveNamespaces()));
424 			}
425 
426 			return result.toArray(new String[result.size()]);
427 		}
428 		finally
429 		{
430 			sourceXml.dispose();
431 			targetXml.dispose();
432 
433 			if (lastSource != null)
434 				lastSource.dispose();
435 		}
436 	}
437 
438 	protected String[] transferStringToXml(TestProperty sourceProperty, TestProperty targetProperty,
439 			SubmitContext context) throws XmlException, Exception
440 	{
441 		XmlObject targetXml = XmlObject.Factory.parse(targetProperty.getValue());
442 		XmlCursor targetCursor = targetXml.newCursor();
443 
444 		try
445 		{
446 			List<String> result = new ArrayList<String>();
447 
448 			String tp = PropertyExpansionUtils.expandProperties(context, getTargetPath());
449 			targetCursor.selectPath(tp);
450 
451 			if (!targetCursor.toNextSelection())
452 				throw new Exception("Missing match for Target XPath [" + tp + "]");
453 
454 			String value = sourceProperty.getValue();
455 
456 			Node targetNode = targetCursor.getDomNode();
457 			setNodeValue(value, targetNode);
458 
459 			result.add(value);
460 
461 			if (getTransferToAll())
462 			{
463 				while (targetCursor.toNextSelection())
464 				{
465 					targetNode = targetCursor.getDomNode();
466 					setNodeValue(value, targetNode);
467 
468 					result.add(value);
469 				}
470 			}
471 
472 			targetProperty.setValue(targetXml.xmlText(new XmlOptions().setSaveAggressiveNamespaces()));
473 
474 			return result.toArray(new String[result.size()]);
475 		}
476 		finally
477 		{
478 			targetCursor.dispose();
479 		}
480 	}
481 
482 	private String setNodeValue(String value, Node node) throws Exception
483 	{
484 		short targetNodeType = node.getNodeType();
485 
486 		if (targetNodeType == Node.DOCUMENT_FRAGMENT_NODE)
487 		{
488 			node = ((DocumentFragment) node).getFirstChild();
489 			if (node != null)
490 				targetNodeType = node.getNodeType();
491 			else
492 				throw new Exception("Missing source value for " + getSourcePropertyName());
493 		}
494 
495 		if (targetNodeType == Node.TEXT_NODE || targetNodeType == Node.ATTRIBUTE_NODE)
496 		{
497 			node.setNodeValue(value);
498 		}
499 		else if (targetNodeType == Node.ELEMENT_NODE)
500 		{
501 			XmlUtils.setElementText((Element) node, value);
502 		}
503 		else
504 		{
505 			throw new Exception("Failed to set value to node [" + node.toString() + "] of type [" + targetNodeType + "]");
506 		}
507 
508 		return value;
509 	}
510 
511 	protected String transferXPathToString(TestProperty sourceProperty, TestProperty targetProperty,
512 			SubmitContext context) throws XmlException, Exception
513 	{
514 		String sourceValue = sourceProperty.getValue();
515 
516 		if (sourceValue == null)
517 		{
518 			if (!getIgnoreEmpty())
519 				throw new Exception("Missing source value");
520 
521 			if (getSetNullOnMissingSource())
522 				targetProperty.setValue(null);
523 		}
524 
525 		XmlObject sourceXml = sourceValue == null ? null : XmlObject.Factory.parse(sourceValue);
526 		XmlCursor sourceCursor = sourceValue == null ? null : sourceXml.newCursor();
527 
528 		try
529 		{
530 			String value = null;
531 
532 			String xquery = PropertyExpansionUtils.expandProperties(context, getSourcePath());
533 			if (getUseXQuery())
534 			{
535 				XmlCursor resultCursor = sourceCursor.execQuery(xquery);
536 				sourceCursor.dispose();
537 				sourceCursor = resultCursor;
538 			}
539 			else
540 			{
541 				sourceCursor.selectPath(xquery);
542 			}
543 
544 			if (!getUseXQuery() && !sourceCursor.toNextSelection())
545 			{
546 				if (!getSetNullOnMissingSource() && !getIgnoreEmpty())
547 					throw new Exception("Missing match for Source XPath [" + xquery + "]");
548 			}
549 			else if (getUseXQuery() && sourceCursor.toNextToken() != TokenType.START)
550 			{
551 				if (!getSetNullOnMissingSource() && !getIgnoreEmpty())
552 					throw new Exception("Missing match for Source XQuery [" + xquery + "]");
553 			}
554 			else
555 			{
556 				Node sourceNode = sourceCursor.getDomNode();
557 				short sourceNodeType = sourceNode.getNodeType();
558 
559 				if (sourceNodeType == Node.DOCUMENT_FRAGMENT_NODE)
560 				{
561 					sourceNode = ((DocumentFragment) sourceNode).getFirstChild();
562 					if (sourceNode != null)
563 						sourceNodeType = sourceNode.getNodeType();
564 					else
565 						throw new Exception("Missing source value for " + getSourcePropertyName());
566 				}
567 
568 				if (sourceNodeType == Node.TEXT_NODE || sourceNodeType == Node.ATTRIBUTE_NODE)
569 				{
570 					value = sourceNode.getNodeValue();
571 				}
572 				else if (sourceNodeType == Node.ELEMENT_NODE)
573 				{
574 					if (getTransferTextContent())
575 					{
576 						value = XmlUtils.getElementText((Element) sourceNode);
577 					}
578 
579 					if (value == null || !getTransferTextContent())
580 					{
581 						value = sourceCursor.getObject().xmlText(
582 								new XmlOptions().setSaveOuter().setSaveAggressiveNamespaces());
583 					}
584 				}
585 			}
586 
587 			if (!getIgnoreEmpty() || (value != null && value.length() > 0))
588 				targetProperty.setValue(value);
589 			else
590 				value = "";
591 
592 			return value;
593 		}
594 		finally
595 		{
596 			sourceCursor.dispose();
597 		}
598 	}
599 
600 	/***
601 	 * Method called for transferring between 2 xml properties..
602 	 */
603 
604 	private String transferXmlValue(XmlCursor source, XmlCursor dest) throws Exception
605 	{
606 		// just copy if nodes are of same type
607 		Node destNode = dest.getDomNode();
608 		Node sourceNode = source.getDomNode();
609 		short destNodeType = destNode.getNodeType();
610 		short sourceNodeType = sourceNode.getNodeType();
611 		String value = null;
612 
613 		if (sourceNodeType == Node.DOCUMENT_FRAGMENT_NODE)
614 		{
615 			sourceNode = ((DocumentFragment) sourceNode).getFirstChild();
616 			if (sourceNode != null)
617 				sourceNodeType = sourceNode.getNodeType();
618 			else
619 				throw new Exception("Missing source value for " + source);
620 		}
621 
622 		// same type of node?
623 		if (destNodeType == sourceNodeType)
624 		{
625 			if (destNodeType == Node.TEXT_NODE || destNodeType == Node.ATTRIBUTE_NODE)
626 			{
627 				value = sourceNode.getNodeValue();
628 				if (!getIgnoreEmpty() || (value != null && value.length() > 0))
629 					destNode.setNodeValue(value);
630 			}
631 			else if (config.getTransferTextContent() && destNodeType == Node.ELEMENT_NODE)
632 			{
633 				value = XmlUtils.getElementText((Element) sourceNode);
634 				if (value == null && sourceNode.getFirstChild() != null)
635 				{
636 					value = source.getObject().xmlText(new XmlOptions().setSaveOuter().setSaveAggressiveNamespaces());
637 					destNode.getParentNode()
638 							.replaceChild(destNode.getOwnerDocument().importNode(sourceNode, true), destNode);
639 				}
640 				else if (!getIgnoreEmpty() || (value != null && value.length() > 0))
641 					XmlUtils.setElementText((Element) destNode, value);
642 			}
643 			else
644 			{
645 				destNode = destNode.getParentNode().replaceChild(destNode.getOwnerDocument().importNode(sourceNode, true),
646 						destNode);
647 
648 				value = dest.xmlText();
649 			}
650 		}
651 		// text to attribute?
652 		else if ((sourceNodeType == Node.TEXT_NODE && destNodeType == Node.ATTRIBUTE_NODE)
653 				|| (sourceNodeType == Node.ATTRIBUTE_NODE && destNodeType == Node.TEXT_NODE))
654 		{
655 			value = sourceNode.getNodeValue();
656 			if (!getIgnoreEmpty() || (value != null && value.length() > 0))
657 				destNode.setNodeValue(value);
658 		}
659 		else if (sourceNodeType == Node.ELEMENT_NODE && destNodeType == Node.ATTRIBUTE_NODE
660 				|| destNodeType == Node.TEXT_NODE)
661 		{
662 			value = XmlUtils.getElementText((Element) sourceNode);
663 			if (!getIgnoreEmpty() || (value != null && value.length() > 0))
664 				destNode.setNodeValue(value);
665 		}
666 		else if (destNodeType == Node.ELEMENT_NODE && sourceNodeType == Node.ATTRIBUTE_NODE
667 				|| sourceNodeType == Node.TEXT_NODE)
668 		{
669 			// hmm.. not sure xmlbeans handles this ok
670 			value = sourceNode.getNodeValue();
671 			if (!getIgnoreEmpty() || (value != null && value.length() > 0))
672 				XmlUtils.setElementText((Element) destNode, value);
673 		}
674 
675 		return value;
676 	}
677 
678 	/***
679 	 * Returns the name of the source property.
680 	 */
681 
682 	public String getSourcePropertyName()
683 	{
684 		if (sourceType == null)
685 			return null;
686 
687 		if (currentSourceProperty != null)
688 			return currentSourceProperty.getName();
689 
690 		TestPropertyHolder actualSourceStep = getSourceStep();
691 		if (actualSourceStep == null)
692 			return sourceType;
693 
694 		TestProperty property = actualSourceStep.getProperty(sourceType);
695 		return property == null ? sourceType : property.getName();
696 	}
697 
698 	public void setSourcePropertyName(String name)
699 	{
700 		String old = getSourcePropertyName();
701 
702 		// check for change
703 		if ((name == null && old == null) || (name != null && old != null && name.equals(old)))
704 			return;
705 
706 		// update
707 		sourceType = name;
708 		config.setSourceType(name);
709 
710 		// update actual property
711 		TestPropertyHolder sourceStep2 = getSourceStep();
712 		currentSourceProperty = sourceStep2 != null && sourceType != null ? sourceStep2.getProperty(sourceType) : null;
713 
714 		// notify!
715 		propertyChangeSupport.firePropertyChange(SOURCE_TYPE_PROPERTY, old, name);
716 	}
717 
718 	public TestProperty getTargetProperty()
719 	{
720 		if (targetType == null)
721 			return null;
722 
723 		if (currentTargetProperty != null)
724 			return currentTargetProperty;
725 
726 		TestPropertyHolder actualTargetStep = getTargetStep();
727 		return actualTargetStep == null ? null : actualTargetStep.getProperty(targetType);
728 	}
729 
730 	public String getTargetPropertyName()
731 	{
732 		if (targetType == null)
733 			return null;
734 
735 		if (currentTargetProperty != null)
736 			return currentTargetProperty.getName();
737 
738 		TestPropertyHolder actualTargetStep = getTargetStep();
739 		TestProperty property = actualTargetStep == null ? null : actualTargetStep.getProperty(targetType);
740 		return actualTargetStep == null || property == null ? targetType : property.getName();
741 	}
742 
743 	public void setTargetPropertyName(String name)
744 	{
745 		String old = getTargetPropertyName();
746 
747 		// check for change
748 		if ((name == null && old == null) || (name != null && old != null && name.equals(old)))
749 			return;
750 
751 		// update
752 		targetType = name;
753 		config.setTargetType(name);
754 
755 		// update actual property
756 		TestPropertyHolder targetStep2 = getTargetStep();
757 
758 		currentTargetProperty = targetStep2 != null && targetType != null ? targetStep2.getProperty(targetType) : null;
759 
760 		// notify!
761 		propertyChangeSupport.firePropertyChange(TARGET_TYPE_PROPERTY, old, name);
762 	}
763 
764 	public String getName()
765 	{
766 		return config.getName();
767 	}
768 
769 	public void setSourcePath(String path)
770 	{
771 		String old = sourcePath;
772 		sourcePath = path;
773 		config.setSourcePath(path);
774 		propertyChangeSupport.firePropertyChange(SOURCE_PATH_PROPERTY, old, path);
775 	}
776 
777 	public void setTargetPath(String path)
778 	{
779 		String old = targetPath;
780 		targetPath = path;
781 		config.setTargetPath(path);
782 		propertyChangeSupport.firePropertyChange(TARGET_PATH_PROPERTY, old, path);
783 	}
784 
785 	public void setName(String name)
786 	{
787 		String old = this.name;
788 		this.name = name;
789 		config.setName(name);
790 		propertyChangeSupport.firePropertyChange(NAME_PROPERTY, old, name);
791 	}
792 
793 	public TestPropertyHolder getSourceStep()
794 	{
795 		return getPropertyHolder(getSourceStepName());
796 	}
797 
798 	public String getSourceStepName()
799 	{
800 		if (sourceStep != null)
801 			return sourceStep;
802 
803 		if (testCase == null)
804 			return null;
805 
806 		TestModelItem step = testCase.findPreviousStepOfType(this.testStep, WsdlTestRequestStep.class);
807 		return step == null ? null : step.getName();
808 	}
809 
810 	public void setSourceStepName(String sourceStep)
811 	{
812 		String old = getSourceStepName();
813 
814 		// check for change
815 		if ((sourceStep == null && old == null) || (sourceStep != null && old != null && sourceStep.equals(old)))
816 			return;
817 
818 		if (sourceStep == null)
819 			log.debug("Setting sourceStep for transfer [" + getName() + "] to null");
820 
821 		this.sourceStep = sourceStep;
822 		config.setSourceStep(sourceStep);
823 
824 		if (currentSourceStep != null)
825 		{
826 			if (currentSourceStep instanceof TestStep)
827 				((TestStep) currentSourceStep).removePropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
828 
829 			currentSourceStep.removeTestPropertyListener(propertyNameChangeListener);
830 		}
831 
832 		currentSourceStep = getPropertyHolder(sourceStep);
833 		if (currentSourceStep != null)
834 		{
835 			if (currentSourceStep instanceof TestStep)
836 				((TestStep) currentSourceStep).addPropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
837 
838 			currentSourceStep.addTestPropertyListener(propertyNameChangeListener);
839 		}
840 		else
841 			log.warn("Failed to get sourceStep [" + sourceStep + "]");
842 
843 		propertyChangeSupport.firePropertyChange(SOURCE_STEP_PROPERTY, old, sourceStep);
844 		setSourcePropertyName(null);
845 	}
846 
847 	public TestPropertyHolder getTargetStep()
848 	{
849 		return getPropertyHolder(getTargetStepName());
850 	}
851 
852 	public String getTargetStepName()
853 	{
854 		if (targetStep != null)
855 			return targetStep;
856 
857 		if (testCase == null)
858 			return null;
859 
860 		TestModelItem step = testCase.findNextStepOfType(this.testStep, WsdlTestRequestStep.class);
861 		return step == null ? null : step.getName();
862 	}
863 
864 	public void setTargetStepName(String targetStep)
865 	{
866 		String old = getTargetStepName();
867 
868 		// check for change
869 		if ((targetStep == null && old == null) || (targetStep != null && old != null && targetStep.equals(old)))
870 			return;
871 
872 		if (targetStep == null)
873 			log.debug("Setting targetStep for transfer [" + getName() + "] to null");
874 
875 		this.targetStep = targetStep;
876 		config.setTargetStep(targetStep);
877 
878 		if (currentTargetStep != null)
879 		{
880 			if (currentTargetStep instanceof TestStep)
881 				((TestStep) currentTargetStep).removePropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
882 
883 			currentTargetStep.removeTestPropertyListener(propertyNameChangeListener);
884 		}
885 
886 		currentTargetStep = getPropertyHolder(targetStep);
887 		if (currentTargetStep != null)
888 		{
889 			if (currentTargetStep instanceof TestStep)
890 				((TestStep) currentTargetStep).addPropertyChangeListener(TestStep.NAME_PROPERTY, stepNameChangeListener);
891 
892 			currentTargetStep.addTestPropertyListener(propertyNameChangeListener);
893 		}
894 		else
895 			log.warn("Failed to get targetStep [" + targetStep + "]");
896 
897 		propertyChangeSupport.firePropertyChange(TARGET_STEP_PROPERTY, old, targetStep);
898 		setTargetPropertyName(null);
899 	}
900 
901 	private TestPropertyHolder getPropertyHolder(String name)
902 	{
903 		if (name == null || testCase == null)
904 			return null;
905 
906 		if (name.charAt(0) == PropertyExpansion.SCOPE_PREFIX)
907 		{
908 			if (name.equals(PropertyExpansion.GLOBAL_REFERENCE))
909 				return PropertyExpansionUtils.getGlobalProperties();
910 
911 			if (name.equals(PropertyExpansion.PROJECT_REFERENCE))
912 				return testCase.getTestSuite().getProject();
913 
914 			if (name.equals(PropertyExpansion.TESTSUITE_REFERENCE))
915 				return testCase.getTestSuite();
916 
917 			if (name.equals(PropertyExpansion.TESTCASE_REFERENCE))
918 				return testCase;
919 		}
920 
921 		return testStep.getTestCase().getTestStepByName(name);
922 	}
923 
924 	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
925 	{
926 		propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
927 	}
928 
929 	public void addPropertyChangeListener(PropertyChangeListener listener)
930 	{
931 		propertyChangeSupport.addPropertyChangeListener(listener);
932 	}
933 
934 	public void removePropertyChangeListener(PropertyChangeListener listener)
935 	{
936 		propertyChangeSupport.removePropertyChangeListener(listener);
937 	}
938 
939 	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
940 	{
941 		propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
942 	}
943 
944 	public boolean getFailOnError()
945 	{
946 		return config.getFailOnError();
947 	}
948 
949 	public void setFailOnError(boolean failOnError)
950 	{
951 		config.setFailOnError(failOnError);
952 	}
953 
954 	public boolean getTransferToAll()
955 	{
956 		return config.getTransferToAll();
957 	}
958 
959 	public void setTransferToAll(boolean transferToAll)
960 	{
961 		config.setTransferToAll(transferToAll);
962 	}
963 
964 	public boolean getUseXQuery()
965 	{
966 		return config.getUseXQuery();
967 	}
968 
969 	public void setUseXQuery(boolean useXQuery)
970 	{
971 		config.setUseXQuery(useXQuery);
972 	}
973 
974 	public boolean getIgnoreEmpty()
975 	{
976 		return config.getIgnoreEmpty();
977 	}
978 
979 	public void setIgnoreEmpty(boolean ignoreEmpty)
980 	{
981 		config.setIgnoreEmpty(ignoreEmpty);
982 	}
983 
984 	public boolean getSetNullOnMissingSource()
985 	{
986 		return config.getSetNullOnMissingSource();
987 	}
988 
989 	public void setSetNullOnMissingSource(boolean setNullOnMissingSource)
990 	{
991 		config.setSetNullOnMissingSource(setNullOnMissingSource);
992 	}
993 
994 	public boolean getTransferTextContent()
995 	{
996 		return config.getTransferTextContent();
997 	}
998 
999 	public void setTransferTextContent(boolean transferTextContent)
1000 	{
1001 		config.setTransferTextContent(transferTextContent);
1002 	}
1003 
1004 	public boolean isDisabled()
1005 	{
1006 		return config.getDisabled();
1007 	}
1008 
1009 	public void setDisabled(boolean disabled)
1010 	{
1011 		config.setDisabled(disabled);
1012 	}
1013 
1014 	public boolean getTransferChildNodes()
1015 	{
1016 		return config.getTransferChildNodes();
1017 	}
1018 
1019 	public void setTransferChildNodes(boolean b)
1020 	{
1021 		config.setTransferChildNodes(b);
1022 	}
1023 
1024 	private final class InternalTestSuiteListener extends TestSuiteListenerAdapter
1025 	{
1026 		public void testStepRemoved(TestStep testStep, int index)
1027 		{
1028 			if (testStep.getTestCase() == testCase)
1029 			{
1030 				String stepName = testStep.getName();
1031 				if (stepName.equals(sourceStep))
1032 				{
1033 					setSourceStepName(null);
1034 				}
1035 
1036 				if (stepName.equals(targetStep))
1037 				{
1038 					setTargetStepName(null);
1039 				}
1040 			}
1041 		}
1042 	}
1043 
1044 	/***
1045 	 * Handle changes to source/target testStep names
1046 	 * 
1047 	 * @author Ole.Matzura
1048 	 */
1049 
1050 	private class StepNameChangeListener implements PropertyChangeListener
1051 	{
1052 		public void propertyChange(PropertyChangeEvent evt)
1053 		{
1054 			String oldName = (String) evt.getOldValue();
1055 			String newValue = (String) evt.getNewValue();
1056 
1057 			if (newValue == null)
1058 			{
1059 				log.error("Tried to change stepname to null!");
1060 				Thread.dumpStack();
1061 				return;
1062 			}
1063 
1064 			if (oldName.equals(sourceStep) && currentSourceStep instanceof TestStep)
1065 			{
1066 				sourceStep = newValue;
1067 				config.setSourceStep(sourceStep);
1068 				propertyChangeSupport.firePropertyChange(SOURCE_STEP_PROPERTY, oldName, sourceStep);
1069 			}
1070 
1071 			if (oldName.equals(targetStep) && currentTargetStep instanceof TestStep)
1072 			{
1073 				targetStep = newValue;
1074 				config.setTargetStep(targetStep);
1075 				propertyChangeSupport.firePropertyChange(TARGET_STEP_PROPERTY, oldName, targetStep);
1076 			}
1077 		}
1078 	}
1079 
1080 	/***
1081 	 * Handle changes to source/target property names
1082 	 * 
1083 	 * @author Ole.Matzura
1084 	 */
1085 
1086 	private class InternalTestPropertyListener extends TestPropertyListenerAdapter
1087 	{
1088 		public void propertyRenamed(String oldName, String newName)
1089 		{
1090 			if (oldName.equals(sourceType))
1091 			{
1092 				sourceType = newName;
1093 				config.setSourceType(sourceType);
1094 				propertyChangeSupport.firePropertyChange(SOURCE_TYPE_PROPERTY, oldName, sourceType);
1095 			}
1096 
1097 			if (oldName.equals(targetType))
1098 			{
1099 				targetType = newName;
1100 				config.setTargetType(targetType);
1101 				propertyChangeSupport.firePropertyChange(TARGET_TYPE_PROPERTY, oldName, targetType);
1102 			}
1103 		}
1104 
1105 		public void propertyRemoved(String name)
1106 		{
1107 			if (name.equals(sourceType))
1108 			{
1109 				log.warn("source property for transfer [" + getName() + "] in teststep [" + testStep.getName() + "/"
1110 						+ testStep.getTestCase().getName() + "/" + testStep.getTestCase().getTestSuite().getName()
1111 						+ "] set to null, was [" + name + "]");
1112 
1113 				currentSourceProperty = null;
1114 				setSourcePropertyName(null);
1115 			}
1116 
1117 			if (name.equals(targetType))
1118 			{
1119 				log.warn("target property for transfer [" + getName() + "] in teststep [" + testStep.getName() + "/"
1120 						+ testStep.getTestCase().getName() + "/" + testStep.getTestCase().getTestSuite().getName()
1121 						+ "] set to null, was [" + name + "]");
1122 
1123 				currentTargetProperty = null;
1124 				setTargetPropertyName(null);
1125 			}
1126 		}
1127 	}
1128 
1129 	@SuppressWarnings("unchecked")
1130 	public void resolver(ResolveContext context, PropertyTransfersTestStep parent)
1131 	{
1132 		if (getSourceProperty() == null)
1133 		{
1134 			context.addPathToResolve(parent, "Resolve source property", getConfig().getSourceStep(),
1135 					new DisablePropertyTransferAction(this)).addResolvers(new CreateMissingPropertyResolver(this,parent), new ChooseAnotherPropertySourceResolver(this, parent));
1136 		}
1137 
1138 		if (getTargetProperty() == null)
1139 		{
1140 			context.addPathToResolve(parent, "Resolve target property", getConfig().getTargetStep(),
1141 					new DisablePropertyTransferAction(this)).addResolvers(new CreateMissingPropertyResolver(this,parent), new ChooseAnotherPropertyTargetResolver(this, parent));
1142 		}
1143 
1144 	}
1145 }