/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *  See the GNU Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wsdl.teststeps;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlCursor.TokenType;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.eviware.soapui.config.PropertyTransferConfig;
import com.eviware.soapui.impl.support.http.HttpRequestTestStep;
import com.eviware.soapui.model.TestPropertyHolder;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.TestPropertyListenerAdapter;
import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.support.PropertyChangeNotifier;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.resolver.ChooseAnotherPropertySourceResolver;
import com.eviware.soapui.support.resolver.ChooseAnotherPropertyTargetResolver;
import com.eviware.soapui.support.resolver.CreateMissingPropertyResolver;
import com.eviware.soapui.support.resolver.DisablePropertyTransferResolver;
import com.eviware.soapui.support.resolver.ResolveContext;
import com.eviware.soapui.support.resolver.ResolveContext.PathToResolve;
import com.eviware.soapui.support.xml.XmlUtils;

/**
 * Class for transferring a property value between 2 test steps. This class is
 * relatively complex due to backwards compatibility issues and to gracefull
 * handling of references test steps and properties.
 * 
 * @author Ole.Matzura
 */

public class PropertyTransfer implements PropertyChangeNotifier
{
	private final static Logger log = Logger.getLogger( PropertyTransfer.class );

	public final static String SOURCE_PATH_PROPERTY = PropertyTransfer.class.getName() + "@sourcePath";
	public final static String SOURCE_TYPE_PROPERTY = PropertyTransfer.class.getName() + "@sourceProperty";
	public final static String SOURCE_STEP_PROPERTY = PropertyTransfer.class.getName() + "@sourceStep";
	public final static String TARGET_PATH_PROPERTY = PropertyTransfer.class.getName() + "@targetPath";
	public final static String TARGET_TYPE_PROPERTY = PropertyTransfer.class.getName() + "@targetProperty";
	public final static String TARGET_STEP_PROPERTY = PropertyTransfer.class.getName() + "@targetStep";
	public final static String NAME_PROPERTY = PropertyTransfer.class.getName() + "@name";
	public final static String DISABLED_PROPERTY = PropertyTransfer.class.getName() + "@disabled";
	public final static String CONFIG_PROPERTY = PropertyTransfer.class.getName() + "@config";

	private TestStep testStep;

	// create local copies since a deleted/changed valuetransfer can be referred
	// to from a result
	private PropertyTransferConfig config;
	private String sourcePath;
	private String sourceType;
	private String targetPath;
	private String name;
	private String targetType;
	private String sourceStep;
	private String targetStep;

	private TestPropertyHolder currentTargetStep;
	private TestPropertyHolder currentSourceStep;
	private TestProperty currentTargetProperty;
	private TestProperty currentSourceProperty;

	private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport( this );
	private StepNameChangeListener stepNameChangeListener = new StepNameChangeListener();
	private InternalTestPropertyListener propertyNameChangeListener = new InternalTestPropertyListener();
	private TestCase testCase;

	private InternalTestSuiteListener testSuiteListener = new InternalTestSuiteListener();

	public PropertyTransfer( TestStep testStep )
	{
		this( testStep, PropertyTransferConfig.Factory.newInstance() );
	}

	public PropertyTransfer( TestStep testStep, PropertyTransferConfig config )
	{
		this.testStep = testStep;

		if( testStep != null )
		{
			this.testCase = testStep.getTestCase();
			testCase.getTestSuite().addTestSuiteListener( testSuiteListener );
		}

		setConfig( config );
	}

	void setConfigOnMove( PropertyTransferConfig config )
	{
		this.config = config;
	}

	void setConfig( PropertyTransferConfig config )
	{
		releaseListeners();

		this.config = config;

		if( !config.isSetSetNullOnMissingSource() )
		{
			config.setSetNullOnMissingSource( true );
		}

		if( !config.isSetTransferTextContent() )
		{
			config.setTransferTextContent( true );
		}

		sourceStep = config.getSourceStep();
		if( sourceStep == null )
		{
			sourceStep = getSourceStepName();
			if( sourceStep != null )
				config.setSourceStep( sourceStep );
		}
		else
			sourceStep = sourceStep.trim();

		currentSourceStep = getPropertyHolder( sourceStep );

		sourceType = config.getSourceType();
		currentSourceProperty = currentSourceStep == null || sourceType == null ? null : currentSourceStep
				.getProperty( sourceType );

		sourcePath = config.getSourcePath();

		targetStep = config.getTargetStep();
		if( targetStep == null )
		{
			targetStep = getTargetStepName();
			if( targetStep != null )
				config.setTargetStep( targetStep );
		}
		else
			targetStep = targetStep.trim();

		currentTargetStep = getPropertyHolder( targetStep );

		targetType = config.getTargetType();
		currentTargetProperty = currentTargetStep == null || targetType == null ? null : currentTargetStep
				.getProperty( targetType );

		targetPath = config.getTargetPath();

		name = config.getName();
		initListeners();

		propertyChangeSupport.firePropertyChange( CONFIG_PROPERTY, null, null );
	}

	private void initListeners()
	{
		if( currentSourceStep != null )
		{
			if( currentSourceStep instanceof TestStep )
				( ( TestStep )currentSourceStep )
						.addPropertyChangeListener( TestStep.NAME_PROPERTY, stepNameChangeListener );

			currentSourceStep.addTestPropertyListener( propertyNameChangeListener );
		}

		if( currentTargetStep != null )
		{
			if( currentTargetStep instanceof TestStep )
				( ( TestStep )currentTargetStep )
						.addPropertyChangeListener( TestStep.NAME_PROPERTY, stepNameChangeListener );

			currentTargetStep.addTestPropertyListener( propertyNameChangeListener );
		}
	}

	public void releaseListeners()
	{
		if( currentSourceStep != null )
		{
			if( currentSourceStep instanceof TestStep )
				( ( TestStep )currentSourceStep ).removePropertyChangeListener( TestStep.NAME_PROPERTY,
						stepNameChangeListener );

			currentSourceStep.removeTestPropertyListener( propertyNameChangeListener );
		}

		if( currentTargetStep != null )
		{
			if( currentTargetStep instanceof TestStep )
				( ( TestStep )currentTargetStep ).removePropertyChangeListener( TestStep.NAME_PROPERTY,
						stepNameChangeListener );

			currentTargetStep.removeTestPropertyListener( propertyNameChangeListener );
		}

		PropertyChangeListener[] listeners = propertyChangeSupport.getPropertyChangeListeners();
		for( PropertyChangeListener listener : listeners )
			propertyChangeSupport.removePropertyChangeListener( listener );
	}

	public void release()
	{
		releaseListeners();
		testCase.getTestSuite().removeTestSuiteListener( testSuiteListener );
	}

	public PropertyTransferConfig getConfig()
	{
		return config;
	}

	public String getSourcePath()
	{
		return sourcePath;
	}

	public String getTargetPath()
	{
		return targetPath;
	}

	public TestProperty getSourceProperty()
	{
		if( sourceType == null )
			return null;

		if( currentSourceProperty != null )
			return currentSourceProperty;

		TestPropertyHolder actualSourceStep = getSourceStep();
		return actualSourceStep == null ? null : actualSourceStep.getProperty( sourceType );
	}

	public String[] transferProperties( SubmitContext context ) throws PropertyTransferException
	{
		TestProperty sourceProperty = getSourceProperty();
		TestProperty targetProperty = getTargetProperty();

		try
		{
			if( sourceProperty == null )
				throw new Exception( "Missing source property" );
			if( targetProperty == null )
				throw new Exception( "Missing target property" );
			if( sourceProperty.getValue() == null && !getSetNullOnMissingSource() && !getIgnoreEmpty() )
				throw new Exception( "Source property is null" );

			if( !hasSourcePath() && !hasTargetPath() )
			{
				if( !getIgnoreEmpty() || ( sourceProperty.getValue() != null && sourceProperty.getValue().length() > 0 ) )
					return transferStringToString( sourceProperty, targetProperty );
			}
			else if( hasSourcePath() && hasTargetPath() )
			{
				return transferXPathToXml( sourceProperty, targetProperty, context );
			}
			else if( hasSourcePath() && !hasTargetPath() )
			{
				return new String[] { transferXPathToString( sourceProperty, targetProperty, context ) };
			}
			else if( !hasSourcePath() && hasTargetPath() )
			{
				if( !getIgnoreEmpty() || ( sourceProperty.getValue() != null && sourceProperty.getValue().length() > 0 ) )
					return transferStringToXml( sourceProperty, targetProperty, context );
			}
		}
		catch( Exception e )
		{
			throw new PropertyTransferException( e.getMessage(), getSourceStepName(), sourceProperty, getTargetStepName(),
					targetProperty );
		}

		return new String[0];
	}

	private boolean hasTargetPath()
	{
		String path = getTargetPath();
		return path != null && path.trim().length() > 0;
	}

	private boolean hasSourcePath()
	{
		String path = getSourcePath();
		return path != null && path.trim().length() > 0;
	}

	protected String[] transferStringToString( TestProperty sourceProperty, TestProperty targetProperty )
	{
		String value = sourceProperty.getValue();

		if( StringUtils.hasContent( value ) && getEntitize() )
			value = XmlUtils.entitize( value );

		targetProperty.setValue( value );
		return new String[] { value };
	}

	protected String[] transferXPathToXml( TestProperty sourceProperty, TestProperty targetProperty,
			SubmitContext context ) throws Exception
	{
		XmlCursor sourceXml;
		try
		{
			String sourcePropertyValue = sourceProperty.getValue();
			// XmlObject sourceXmlObject = sourcePropertyValue == null ? null :
			// XmlObject.Factory.parse( sourcePropertyValue );
			XmlObject sourceXmlObject = sourcePropertyValue == null ? null : XmlUtils
					.createXmlObject( sourcePropertyValue );
			sourceXml = sourceXmlObject == null ? null : sourceXmlObject.newCursor();
		}
		catch( XmlException e )
		{
			throw new Exception( "Error parsing source property [" + e.getMessage() + "]" );
		}

		XmlObject targetXmlObject;
		XmlCursor targetXml;
		try
		{
			String targetPropertyValue = targetProperty.getValue();
			// targetXmlObject = XmlObject.Factory.parse( targetPropertyValue );
			targetXmlObject = XmlUtils.createXmlObject( targetPropertyValue );
			targetXml = targetXmlObject.newCursor();
		}
		catch( XmlException e )
		{
			throw new Exception( "Error parsing target property [" + e.getMessage() + "]" );
		}

		XmlCursor lastSource = null;

		try
		{
			List<String> result = new ArrayList<String>();

			String tp = PropertyExpander.expandProperties( context, getTargetPath() );
			targetXml.selectPath( tp );

			if( !targetXml.hasNextSelection() )
				throw new Exception( "Missing match for Target XPath [" + tp + "]" );

			if( sourceXml == null )
			{
				if( getSetNullOnMissingSource() )
				{
					while( targetXml.toNextSelection() )
					{
						result.add( setNodeValue( null, targetXml.getDomNode() ) );
						if( !getTransferToAll() )
							break;
					}
				}
			}
			else if( getUseXQuery() )
			{
				String sp = PropertyExpander.expandProperties( context, getSourcePath() );
				XmlCursor resultCursor = sourceXml.execQuery( sp );
				sourceXml.dispose();
				sourceXml = resultCursor;

				if( sourceXml.toNextToken() != TokenType.START )
				{
					if( getSetNullOnMissingSource() )
					{
						while( targetXml.toNextSelection() )
						{
							result.add( setNodeValue( null, targetXml.getDomNode() ) );
							if( !getTransferToAll() )
								break;
						}
					}
					else if( !getIgnoreEmpty() )
						throw new Exception( "Missing match for Source XQuery [" + sp + "]" );
				}

				boolean hasTarget = targetXml.toNextSelection();

				if( hasTarget )
				{
					lastSource = sourceXml.newCursor();
					result.add( transferXmlValue( sourceXml, targetXml ) );
				}
			}
			else
			{
				String sp = PropertyExpander.expandProperties( context, getSourcePath() );
				sourceXml.selectPath( sp );

				if( !sourceXml.hasNextSelection() )
				{
					if( getSetNullOnMissingSource() )
					{
						while( targetXml.toNextSelection() )
						{
							result.add( setNodeValue( null, targetXml.getDomNode() ) );
							if( !getTransferToAll() )
								break;
						}
					}
					else if( !getIgnoreEmpty() )
						throw new Exception( "Missing match for Source XPath [" + sp + "]" );
				}
				else
				{
					boolean hasSource = sourceXml.toNextSelection();
					boolean hasTarget = targetXml.toNextSelection();

					while( hasSource && hasTarget )
					{
						if( lastSource != null )
							lastSource.dispose();

						lastSource = sourceXml.newCursor();
						result.add( transferXmlValue( sourceXml, targetXml ) );

						hasSource = sourceXml.toNextSelection();
						hasTarget = targetXml.toNextSelection();
					}

					if( getTransferToAll() && !hasSource && hasTarget && lastSource != null )
					{
						while( hasTarget )
						{
							result.add( transferXmlValue( lastSource, targetXml ) );
							hasTarget = targetXml.toNextSelection();
						}
					}
				}
			}

			if( result.size() > 0 )
			{
				String value = targetXmlObject.xmlText( new XmlOptions().setSaveAggressiveNamespaces() );
				// if( getEntitize() )
				// value = XmlUtils.entitize( value );

				targetProperty.setValue( value );
			}

			return result.toArray( new String[result.size()] );
		}
		finally
		{
			sourceXml.dispose();
			targetXml.dispose();

			if( lastSource != null )
				lastSource.dispose();
		}
	}

	protected String[] transferStringToXml( TestProperty sourceProperty, TestProperty targetProperty,
			SubmitContext context ) throws XmlException, Exception
	{
		if( !StringUtils.hasContent( targetProperty.getValue() ) )
			throw new Exception( "Missing target property value" );

		XmlObject targetXml = XmlObject.Factory.parse( targetProperty.getValue() );
		XmlCursor targetCursor = targetXml.newCursor();

		try
		{
			List<String> result = new ArrayList<String>();

			String tp = PropertyExpander.expandProperties( context, getTargetPath() );
			targetCursor.selectPath( tp );

			if( !targetCursor.toNextSelection() )
				throw new Exception( "Missing match for Target XPath [" + tp + "]" );

			String value = sourceProperty.getValue();

			Node targetNode = targetCursor.getDomNode();
			setNodeValue( value, targetNode );

			result.add( value );

			if( getTransferToAll() )
			{
				while( targetCursor.toNextSelection() )
				{
					targetNode = targetCursor.getDomNode();
					setNodeValue( value, targetNode );

					result.add( value );
				}
			}

			targetProperty.setValue( targetXml.xmlText( new XmlOptions().setSaveAggressiveNamespaces() ) );

			return result.toArray( new String[result.size()] );
		}
		finally
		{
			targetCursor.dispose();
		}
	}

	private String setNodeValue( String value, Node node ) throws Exception
	{
		short targetNodeType = node.getNodeType();

		if( targetNodeType == Node.DOCUMENT_FRAGMENT_NODE )
		{
			node = node.getFirstChild();
			if( node != null )
				targetNodeType = node.getNodeType();
			else
				throw new Exception( "Missing source value for " + getSourcePropertyName() );
		}

		if( !XmlUtils.setNodeValue( node, value ) )
		// if( targetNodeType == Node.TEXT_NODE || targetNodeType ==
		// Node.ATTRIBUTE_NODE )
		// {
		// node.setNodeValue( value );
		// }
		// else if( targetNodeType == Node.ELEMENT_NODE )
		// {
		// XmlUtils.setElementText( (Element) node, value );
		// }
		// else
		{
			throw new Exception( "Failed to set value to node [" + node.toString() + "] of type [" + targetNodeType + "]" );
		}

		return value;
	}

	protected String transferXPathToString( TestProperty sourceProperty, TestProperty targetProperty,
			SubmitContext context ) throws Exception
	{
		String sourceValue = sourceProperty.getValue();

		if( !StringUtils.hasContent( sourceValue ) )
		{
			if( !getIgnoreEmpty() )
				throw new Exception( "Missing source value" );

			if( getSetNullOnMissingSource() )
				targetProperty.setValue( null );

			return null;
		}

		// XmlObject sourceXml = sourceValue == null ? null :
		// XmlObject.Factory.parse( sourceValue );
		XmlObject sourceXml = sourceValue == null ? null : XmlUtils.createXmlObject( sourceValue );
		XmlCursor sourceCursor = sourceValue == null ? null : sourceXml.newCursor();

		try
		{
			String value = null;

			String xquery = PropertyExpander.expandProperties( context, getSourcePath() );
			if( getUseXQuery() )
			{
				XmlCursor resultCursor = sourceCursor.execQuery( xquery );
				sourceCursor.dispose();
				sourceCursor = resultCursor;
			}
			else
			{
				sourceCursor.selectPath( xquery );
			}

			if( !getUseXQuery() && !sourceCursor.toNextSelection() )
			{
				if( !getSetNullOnMissingSource() && !getIgnoreEmpty() )
					throw new Exception( "Missing match for Source XPath [" + xquery + "]" );
			}
			else if( getUseXQuery() && sourceCursor.toNextToken() != TokenType.START )
			{
				if( !getSetNullOnMissingSource() && !getIgnoreEmpty() )
					throw new Exception( "Missing match for Source XQuery [" + xquery + "]" );
			}
			else if( sourceCursor != null )
			{
				Node sourceNode = sourceCursor.getDomNode();
				short sourceNodeType = sourceNode.getNodeType();

				if( sourceNodeType == Node.DOCUMENT_FRAGMENT_NODE )
				{
					sourceNode = sourceNode.getFirstChild();
					if( sourceNode != null )
						sourceNodeType = sourceNode.getNodeType();
					else
						throw new Exception( "Missing source value for " + getSourcePropertyName() );
				}

				if( sourceNodeType == Node.TEXT_NODE || sourceNodeType == Node.ATTRIBUTE_NODE )
				{
					value = sourceNode.getNodeValue();
				}
				else if( sourceNodeType == Node.ELEMENT_NODE )
				{
					if( getTransferTextContent() )
					{
						value = XmlUtils.getElementText( ( Element )sourceNode );
					}

					if( value == null || !getTransferTextContent() )
					{
						value = sourceCursor.getObject().xmlText(
								new XmlOptions().setSaveOuter().setSaveAggressiveNamespaces() );
					}
				}
			}

			if( !getIgnoreEmpty() || ( value != null && value.length() > 0 )
					|| ( getSetNullOnMissingSource() && !StringUtils.hasContent( value ) ) )
			{
				if( StringUtils.hasContent( value ) && getEntitize() )
					value = XmlUtils.entitize( value );

				targetProperty.setValue( value );
			}
			else
				value = "";

			return value;
		}
		finally
		{
			sourceCursor.dispose();
		}
	}

	/**
	 * Method called for transferring between 2 xml properties..
	 */

	private String transferXmlValue( XmlCursor source, XmlCursor dest ) throws Exception
	{
		// just copy if nodes are of same type
		Node destNode = dest.getDomNode();
		Node sourceNode = source.getDomNode();
		short destNodeType = destNode.getNodeType();
		short sourceNodeType = sourceNode.getNodeType();
		String value = null;

		if( getTransferChildNodes() )
		{
			while( destNode.hasChildNodes() )
			{
				destNode.removeChild( destNode.getFirstChild() );
			}

			NodeList childNodes = sourceNode.getChildNodes();
			for( int c = 0; c < childNodes.getLength(); c++ )
			{
				destNode.appendChild( destNode.getOwnerDocument().importNode( childNodes.item( c ), true ) );
			}

			return XmlUtils.serialize( destNode, false );
		}

		if( sourceNodeType == Node.DOCUMENT_FRAGMENT_NODE )
		{
			sourceNode = sourceNode.getFirstChild();
			if( sourceNode != null )
				sourceNodeType = sourceNode.getNodeType();
			else
				throw new Exception( "Missing source value for " + source );
		}

		// same type of node?
		if( destNodeType == sourceNodeType )
		{
			if( destNodeType == Node.TEXT_NODE || destNodeType == Node.ATTRIBUTE_NODE )
			{
				value = sourceNode.getNodeValue();
				if( !getIgnoreEmpty() || ( value != null && value.length() > 0 ) )
				{
					if( getEntitize() )
						value = XmlUtils.entitize( value );

					destNode.setNodeValue( value );
				}
			}
			else if( config.getTransferTextContent() && destNodeType == Node.ELEMENT_NODE )
			{
				value = XmlUtils.getElementText( ( Element )sourceNode );
				if( value == null && sourceNode.getFirstChild() != null )
				{
					value = source.getObject().xmlText( new XmlOptions().setSaveOuter().setSaveAggressiveNamespaces() );

					if( getEntitize() )
						value = XmlUtils.entitize( value );

					destNode.getParentNode().replaceChild( destNode.getOwnerDocument().importNode( sourceNode, true ),
							destNode );
				}
				else if( !getIgnoreEmpty() || ( value != null && value.length() > 0 ) )
				{
					if( getEntitize() )
						value = XmlUtils.entitize( value );

					XmlUtils.setElementText( ( Element )destNode, value );
				}
			}
			else
			{
				destNode = destNode.getParentNode().replaceChild(
						destNode.getOwnerDocument().importNode( sourceNode, true ), destNode );

				value = dest.xmlText();
			}
		}
		// text to attribute?
		else if( ( sourceNodeType == Node.TEXT_NODE && destNodeType == Node.ATTRIBUTE_NODE )
				|| ( sourceNodeType == Node.ATTRIBUTE_NODE && destNodeType == Node.TEXT_NODE ) )
		{
			value = sourceNode.getNodeValue();
			if( !getIgnoreEmpty() || ( value != null && value.length() > 0 ) )
			{
				if( getEntitize() )
					value = XmlUtils.entitize( value );

				destNode.setNodeValue( value );
			}
		}
		else if( sourceNodeType == Node.ELEMENT_NODE && destNodeType == Node.ATTRIBUTE_NODE
				|| destNodeType == Node.TEXT_NODE )
		{
			value = XmlUtils.getElementText( ( Element )sourceNode );
			if( !getIgnoreEmpty() || ( value != null && value.length() > 0 ) )
			{
				if( getEntitize() )
					value = XmlUtils.entitize( value );

				destNode.setNodeValue( value );
			}
		}
		else if( destNodeType == Node.ELEMENT_NODE && sourceNodeType == Node.ATTRIBUTE_NODE
				|| sourceNodeType == Node.TEXT_NODE )
		{
			// hmm.. not sure xmlbeans handles this ok
			value = sourceNode.getNodeValue();
			if( !getIgnoreEmpty() || ( value != null && value.length() > 0 ) )
			{
				if( getEntitize() )
					value = XmlUtils.entitize( value );

				XmlUtils.setElementText( ( Element )destNode, value );
			}
		}

		return value;
	}

	/**
	 * Returns the name of the source property.
	 */

	public String getSourcePropertyName()
	{
		if( sourceType == null )
			return null;

		if( currentSourceProperty != null )
			return currentSourceProperty.getName();

		TestPropertyHolder actualSourceStep = getSourceStep();
		if( actualSourceStep == null )
			return sourceType;

		TestProperty property = actualSourceStep.getProperty( sourceType );
		return property == null ? sourceType : property.getName();
	}

	public void setSourcePropertyName( String name )
	{
		String old = getSourcePropertyName();

		// check for change
		if( ( name == null && old == null ) || ( name != null && old != null && name.equals( old ) ) )
			return;

		// update
		sourceType = name;
		config.setSourceType( name );

		// update actual property
		TestPropertyHolder sourceStep2 = getSourceStep();
		currentSourceProperty = sourceStep2 != null && sourceType != null ? sourceStep2.getProperty( sourceType ) : null;

		// notify!
		propertyChangeSupport.firePropertyChange( SOURCE_TYPE_PROPERTY, old, name );
	}

	public TestProperty getTargetProperty()
	{
		if( targetType == null )
			return null;

		if( currentTargetProperty != null )
			return currentTargetProperty;

		TestPropertyHolder actualTargetStep = getTargetStep();
		return actualTargetStep == null ? null : actualTargetStep.getProperty( targetType );
	}

	public String getTargetPropertyName()
	{
		if( targetType == null )
			return null;

		if( currentTargetProperty != null )
			return currentTargetProperty.getName();

		TestPropertyHolder actualTargetStep = getTargetStep();
		TestProperty property = actualTargetStep == null ? null : actualTargetStep.getProperty( targetType );
		return actualTargetStep == null || property == null ? targetType : property.getName();
	}

	public void setTargetPropertyName( String name )
	{
		String old = getTargetPropertyName();

		// check for change
		if( ( name == null && old == null ) || ( name != null && old != null && name.equals( old ) ) )
			return;

		// update
		targetType = name;
		config.setTargetType( name );

		// update actual property
		TestPropertyHolder targetStep2 = getTargetStep();

		currentTargetProperty = targetStep2 != null && targetType != null ? targetStep2.getProperty( targetType ) : null;

		// notify!
		propertyChangeSupport.firePropertyChange( TARGET_TYPE_PROPERTY, old, name );
	}

	public String getName()
	{
		return config.getName();
	}

	public void setSourcePath( String path )
	{
		String old = sourcePath;
		sourcePath = path;
		config.setSourcePath( path );
		propertyChangeSupport.firePropertyChange( SOURCE_PATH_PROPERTY, old, path );
	}

	public void setTargetPath( String path )
	{
		String old = targetPath;
		targetPath = path;
		config.setTargetPath( path );
		propertyChangeSupport.firePropertyChange( TARGET_PATH_PROPERTY, old, path );
	}

	public void setName( String name )
	{
		String old = this.name;
		this.name = name;
		config.setName( name );
		propertyChangeSupport.firePropertyChange( NAME_PROPERTY, old, name );
	}

	public TestPropertyHolder getSourceStep()
	{
		return getPropertyHolder( getSourceStepName() );
	}

	public String getSourceStepName()
	{
		if( sourceStep != null )
			return sourceStep;

		if( testCase == null )
			return null;

		HttpRequestTestStep step = testCase.findPreviousStepOfType( this.testStep, HttpRequestTestStep.class );
		return step == null ? null : step.getName();
	}

	public void setSourceStepName( String sourceStep )
	{
		String old = getSourceStepName();

		// check for change
		if( ( sourceStep == null && old == null ) || ( sourceStep != null && old != null && sourceStep.equals( old ) ) )
			return;

		if( sourceStep == null )
			log.debug( "Setting sourceStep for transfer [" + getName() + "] to null" );

		this.sourceStep = sourceStep;
		config.setSourceStep( sourceStep );

		if( currentSourceStep != null )
		{
			if( currentSourceStep instanceof TestStep )
				( ( TestStep )currentSourceStep ).removePropertyChangeListener( TestStep.NAME_PROPERTY,
						stepNameChangeListener );

			currentSourceStep.removeTestPropertyListener( propertyNameChangeListener );
		}

		currentSourceStep = getPropertyHolder( sourceStep );
		if( currentSourceStep != null )
		{
			if( currentSourceStep instanceof TestStep )
				( ( TestStep )currentSourceStep )
						.addPropertyChangeListener( TestStep.NAME_PROPERTY, stepNameChangeListener );

			currentSourceStep.addTestPropertyListener( propertyNameChangeListener );
		}
		else
			log.warn( "Failed to get sourceStep [" + sourceStep + "]" );

		propertyChangeSupport.firePropertyChange( SOURCE_STEP_PROPERTY, old, sourceStep );
		setSourcePropertyName( null );
	}

	public TestPropertyHolder getTargetStep()
	{
		return getPropertyHolder( getTargetStepName() );
	}

	public String getTargetStepName()
	{
		if( targetStep != null )
			return targetStep;

		if( testCase == null )
			return null;

		HttpRequestTestStep step = testCase.findNextStepOfType( this.testStep, HttpRequestTestStep.class );
		return step == null ? null : step.getName();
	}

	public void setTargetStepName( String targetStep )
	{
		String old = getTargetStepName();

		// check for change
		if( ( targetStep == null && old == null ) || ( targetStep != null && old != null && targetStep.equals( old ) ) )
			return;

		if( targetStep == null )
			log.debug( "Setting targetStep for transfer [" + getName() + "] to null" );

		this.targetStep = targetStep;
		config.setTargetStep( targetStep );

		if( currentTargetStep != null )
		{
			if( currentTargetStep instanceof TestStep )
				( ( TestStep )currentTargetStep ).removePropertyChangeListener( TestStep.NAME_PROPERTY,
						stepNameChangeListener );

			currentTargetStep.removeTestPropertyListener( propertyNameChangeListener );
		}

		currentTargetStep = getPropertyHolder( targetStep );
		if( currentTargetStep != null )
		{
			if( currentTargetStep instanceof TestStep )
				( ( TestStep )currentTargetStep )
						.addPropertyChangeListener( TestStep.NAME_PROPERTY, stepNameChangeListener );

			currentTargetStep.addTestPropertyListener( propertyNameChangeListener );
		}
		else
			log.warn( "Failed to get targetStep [" + targetStep + "]" );

		propertyChangeSupport.firePropertyChange( TARGET_STEP_PROPERTY, old, targetStep );
		setTargetPropertyName( null );
	}

	private TestPropertyHolder getPropertyHolder( String name )
	{
		if( !StringUtils.hasContent( name ) || testCase == null )
			return null;

		if( name.charAt( 0 ) == PropertyExpansion.SCOPE_PREFIX )
		{
			if( name.equals( PropertyExpansion.GLOBAL_REFERENCE ) )
				return PropertyExpansionUtils.getGlobalProperties();

			if( name.equals( PropertyExpansion.PROJECT_REFERENCE ) )
				return testCase.getTestSuite().getProject();

			if( name.equals( PropertyExpansion.TESTSUITE_REFERENCE ) )
				return testCase.getTestSuite();

			if( name.equals( PropertyExpansion.TESTCASE_REFERENCE ) )
				return testCase;
		}

		return testStep.getTestCase().getTestStepByName( name );
	}

	public void addPropertyChangeListener( String propertyName, PropertyChangeListener listener )
	{
		propertyChangeSupport.addPropertyChangeListener( propertyName, listener );
	}

	public void addPropertyChangeListener( PropertyChangeListener listener )
	{
		propertyChangeSupport.addPropertyChangeListener( listener );
	}

	public void removePropertyChangeListener( PropertyChangeListener listener )
	{
		propertyChangeSupport.removePropertyChangeListener( listener );
	}

	public void removePropertyChangeListener( String propertyName, PropertyChangeListener listener )
	{
		propertyChangeSupport.removePropertyChangeListener( propertyName, listener );
	}

	public boolean getFailOnError()
	{
		return config.getFailOnError();
	}

	public void setFailOnError( boolean failOnError )
	{
		config.setFailOnError( failOnError );
	}

	public boolean getTransferToAll()
	{
		return config.getTransferToAll();
	}

	public void setTransferToAll( boolean transferToAll )
	{
		config.setTransferToAll( transferToAll );
	}

	public boolean getUseXQuery()
	{
		return config.getUseXQuery();
	}

	public void setUseXQuery( boolean useXQuery )
	{
		config.setUseXQuery( useXQuery );
	}

	public boolean getEntitize()
	{
		return config.getEntitize();
	}

	public void setEntitize( boolean entitize )
	{
		config.setEntitize( entitize );
	}

	public boolean getIgnoreEmpty()
	{
		return config.getIgnoreEmpty();
	}

	public void setIgnoreEmpty( boolean ignoreEmpty )
	{
		config.setIgnoreEmpty( ignoreEmpty );
	}

	public boolean getSetNullOnMissingSource()
	{
		return config.getSetNullOnMissingSource();
	}

	public void setSetNullOnMissingSource( boolean setNullOnMissingSource )
	{
		config.setSetNullOnMissingSource( setNullOnMissingSource );
	}

	public boolean getTransferTextContent()
	{
		return config.getTransferTextContent();
	}

	public void setTransferTextContent( boolean transferTextContent )
	{
		config.setTransferTextContent( transferTextContent );
	}

	public boolean isDisabled()
	{
		return config.getDisabled();
	}

	public void setDisabled( boolean disabled )
	{
		config.setDisabled( disabled );
	}

	public boolean getTransferChildNodes()
	{
		return config.getTransferChildNodes();
	}

	public void setTransferChildNodes( boolean b )
	{
		config.setTransferChildNodes( b );
	}

	private final class InternalTestSuiteListener extends TestSuiteListenerAdapter
	{
		public void testStepRemoved( TestStep testStep, int index )
		{
			if( testStep.getTestCase() == testCase )
			{
				String stepName = testStep.getName();
				if( stepName.equals( sourceStep ) )
				{
					setSourceStepName( null );
				}

				if( stepName.equals( targetStep ) )
				{
					setTargetStepName( null );
				}
			}
		}
	}

	/**
	 * Handle changes to source/target testStep names
	 * 
	 * @author Ole.Matzura
	 */

	private class StepNameChangeListener implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			String oldName = ( String )evt.getOldValue();
			String newValue = ( String )evt.getNewValue();

			if( newValue == null )
			{
				log.error( "Tried to change stepname to null!" );
				Thread.dumpStack();
				return;
			}

			if( oldName.equals( sourceStep ) && currentSourceStep instanceof TestStep )
			{
				sourceStep = newValue;
				config.setSourceStep( sourceStep );
				propertyChangeSupport.firePropertyChange( SOURCE_STEP_PROPERTY, oldName, sourceStep );
			}

			if( oldName.equals( targetStep ) && currentTargetStep instanceof TestStep )
			{
				targetStep = newValue;
				config.setTargetStep( targetStep );
				propertyChangeSupport.firePropertyChange( TARGET_STEP_PROPERTY, oldName, targetStep );
			}
		}
	}

	/**
	 * Handle changes to source/target property names
	 * 
	 * @author Ole.Matzura
	 */

	private class InternalTestPropertyListener extends TestPropertyListenerAdapter
	{
		public void propertyRenamed( String oldName, String newName )
		{
			if( oldName.equals( sourceType ) )
			{
				sourceType = newName;
				config.setSourceType( sourceType );
				propertyChangeSupport.firePropertyChange( SOURCE_TYPE_PROPERTY, oldName, sourceType );
			}

			if( oldName.equals( targetType ) )
			{
				targetType = newName;
				config.setTargetType( targetType );
				propertyChangeSupport.firePropertyChange( TARGET_TYPE_PROPERTY, oldName, targetType );
			}
		}

		public void propertyRemoved( String name )
		{
			if( name.equals( sourceType ) )
			{
				log.warn( "source property for transfer [" + getName() + "] in teststep [" + testStep.getName() + "/"
						+ testStep.getTestCase().getName() + "/" + testStep.getTestCase().getTestSuite().getName()
						+ "] set to null, was [" + name + "]" );

				currentSourceProperty = null;
				setSourcePropertyName( null );
			}

			if( name.equals( targetType ) )
			{
				log.warn( "target property for transfer [" + getName() + "] in teststep [" + testStep.getName() + "/"
						+ testStep.getTestCase().getName() + "/" + testStep.getTestCase().getTestSuite().getName()
						+ "] set to null, was [" + name + "]" );

				currentTargetProperty = null;
				setTargetPropertyName( null );
			}
		}
	}

	@SuppressWarnings( "unchecked" )
	public void resolve( ResolveContext<?> context, PropertyTransfersTestStep parent )
	{
		if( isDisabled() )
			return;

		if( getSourceProperty() == null )
		{
			if( context.hasThisModelItem( parent, "Resolve source property", getConfig().getSourceStep() ) )
				return;
			context.addPathToResolve( parent, "Resolve source property", getConfig().getSourceStep() ).addResolvers(
					new DisablePropertyTransferResolver( this ), new CreateMissingPropertyResolver( this, parent ),
					new ChooseAnotherPropertySourceResolver( this, parent ) );
		}
		else
		{
			if( context.hasThisModelItem( parent, "Resolve source property", getConfig().getSourceStep() ) )
			{
				PathToResolve path = context.getPath( parent, "Resolve source property", getConfig().getSourceStep() );
				path.setSolved( true );
			}
		}

		if( getTargetProperty() == null )
		{
			if( context.hasThisModelItem( parent, "Resolve target property", getConfig().getTargetStep() ) )
				return;
			context.addPathToResolve( parent, "Resolve target property", getConfig().getTargetStep() ).addResolvers(
					new DisablePropertyTransferResolver( this ), new CreateMissingPropertyResolver( this, parent ),
					new ChooseAnotherPropertyTargetResolver( this, parent ) );
		}
		else
		{
			if( context.hasThisModelItem( parent, "Resolve target property", getConfig().getTargetStep() ) )
			{
				PathToResolve path = context.getPath( parent, "Resolve target property", getConfig().getTargetStep() );
				path.setSolved( true );
			}
		}

	}
}
