View Javadoc

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