View Javadoc

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