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