View Javadoc

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