/*
 *  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.support.wsdl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.wsdl.Binding;
import javax.wsdl.BindingFault;
import javax.wsdl.BindingOperation;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.Service;
import javax.wsdl.extensions.mime.MIMEContent;
import javax.xml.namespace.QName;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.SchemaGlobalElement;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlError;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlValidationError;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.WsdlOperation;
import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
import com.eviware.soapui.model.iface.Attachment;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.settings.WsdlSettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.xml.XmlUtils;

/**
 * Class for validating SOAP requests/responses against their definition and
 * schema, requires that the messages follow basic-profile requirements
 * 
 * @author Ole.Matzura
 */

public class WsdlValidator
{
	private final WsdlContext wsdlContext;
	private final static Logger log = Logger.getLogger( WsdlValidator.class );

	public WsdlValidator( WsdlContext wsdlContext )
	{
		this.wsdlContext = wsdlContext;
	}

	public AssertionError[] assertRequest( WsdlMessageExchange messageExchange, boolean envelopeOnly )
	{
		List<XmlError> errors = new ArrayList<XmlError>();
		try
		{
			String requestContent = messageExchange.getRequestContent();
			wsdlContext.getSoapVersion().validateSoapEnvelope( requestContent, errors );

			if( errors.isEmpty() && !envelopeOnly )
			{
				wsdlContext.getSoapVersion().validateSoapEnvelope( requestContent, errors );
				WsdlOperation operation = messageExchange.getOperation();
				BindingOperation bindingOperation = operation.getBindingOperation();
				if( bindingOperation == null )
				{
					errors.add( XmlError.forMessage( "Missing operation [" + operation.getBindingOperationName()
							+ "] in wsdl definition" ) );
				}
				else
				{
					Part[] inputParts = WsdlUtils.getInputParts( bindingOperation );
					validateMessage( messageExchange, requestContent, bindingOperation, inputParts, errors, false );
					// validateInputAttachments(request, errors, bindingOperation,
					// inputParts);
				}
			}
		}
		catch( Exception e )
		{
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}

		return convertErrors( errors );
	}

	private void validateInputAttachments( WsdlMessageExchange messageExchange, List<XmlError> errors,
			BindingOperation bindingOperation, Part[] inputParts )
	{
		for( Part part : inputParts )
		{
			MIMEContent[] contents = WsdlUtils.getInputMultipartContent( part, bindingOperation );
			if( contents.length == 0 )
				continue;

			Attachment[] attachments = messageExchange.getRequestAttachmentsForPart( part.getName() );
			if( attachments.length == 0 )
			{
				errors.add( XmlError.forMessage( "Missing attachment for part [" + part.getName() + "]" ) );
			}
			else if( attachments.length == 1 )
			{
				Attachment attachment = attachments[0];
				String types = "";
				for( MIMEContent content : contents )
				{
					String type = content.getType();
					if( type.equals( attachment.getContentType() ) || type.toUpperCase().startsWith( "MULTIPART" ) )
					{
						types = null;
						break;
					}
					if( types.length() > 0 )
						types += ",";

					types += type;
				}

				if( types != null )
				{
					String msg = "Missing attachment for part [" + part.getName() + "] with content-type [" + types + "],"
							+ " content type is [" + attachment.getContentType() + "]";

					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ) )
						log.warn( msg );
					else
						errors.add( XmlError.forMessage( msg ) );
				}
			}
			else
			{
				String types = "";
				for( MIMEContent content : contents )
				{
					String type = content.getType();
					if( type.toUpperCase().startsWith( "MULTIPART" ) )
					{
						types = null;
						break;
					}
					if( types.length() > 0 )
						types += ",";

					types += type;
				}

				if( types == null )
				{
					String msg = "Too many attachments for part [" + part.getName() + "] with content-type [" + types + "]";
					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ) )
						log.warn( msg );
					else
						errors.add( XmlError.forMessage( msg ) );
				}
			}

			if( attachments.length > 0 )
				validateAttachmentsReadability( errors, attachments );
		}
	}

	private void validateOutputAttachments( WsdlMessageExchange messageExchange, XmlObject xml, List<XmlError> errors,
			BindingOperation bindingOperation, Part[] outputParts ) throws Exception
	{
		for( Part part : outputParts )
		{
			MIMEContent[] contents = WsdlUtils.getOutputMultipartContent( part, bindingOperation );
			if( contents.length == 0 )
				continue;

			Attachment[] attachments = messageExchange.getResponseAttachmentsForPart( part.getName() );

			// check for rpc
			if( attachments.length == 0 && WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ) )
			{
				XmlObject[] rpcBodyPart = getRpcBodyPart( bindingOperation, xml, true );
				if( rpcBodyPart.length == 1 )
				{
					XmlObject[] children = rpcBodyPart[0].selectChildren( new QName( part.getName() ) );
					if( children.length == 1 )
					{
						String href = ( ( Element )children[0].getDomNode() ).getAttribute( "href" );
						if( href != null )
						{
							if( href.startsWith( "cid:" ) )
								href = href.substring( 4 );

							attachments = messageExchange.getResponseAttachmentsForPart( href );
						}
					}
				}
			}

			if( attachments.length == 0 )
			{
				errors.add( XmlError.forMessage( "Missing attachment for part [" + part.getName() + "]" ) );
			}
			else if( attachments.length == 1 )
			{
				Attachment attachment = attachments[0];
				String types = "";
				for( MIMEContent content : contents )
				{
					String type = content.getType();
					if( type.equals( attachment.getContentType() ) || type.toUpperCase().startsWith( "MULTIPART" ) )
					{
						types = null;
						break;
					}

					if( types.length() > 0 )
						types += ",";

					types += type;
				}

				if( types != null )
				{
					String msg = "Missing attachment for part [" + part.getName() + "] with content-type [" + types
							+ "], content type is [" + attachment.getContentType() + "]";

					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ) )
						log.warn( msg );
					else
						errors.add( XmlError.forMessage( msg ) );
				}
			}
			else
			{
				String types = "";
				for( MIMEContent content : contents )
				{
					String type = content.getType();
					if( type.toUpperCase().startsWith( "MULTIPART" ) )
					{
						types = null;
						break;
					}

					if( types.length() > 0 )
						types += ",";

					types += type;
				}

				if( types != null )
				{
					String msg = "Too many attachments for part [" + part.getName() + "] with content-type [" + types + "]";

					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ) )
						log.warn( msg );
					else
						errors.add( XmlError.forMessage( msg ) );
				}
			}

			if( attachments.length > 0 )
				validateAttachmentsReadability( errors, attachments );
		}
	}

	private void validateAttachmentsReadability( List<XmlError> errors, Attachment[] attachments )
	{
		for( Attachment attachment : attachments )
		{
			try
			{
				attachment.getInputStream();
			}
			catch( Exception e )
			{
				errors.add( XmlError.forMessage( e.toString() ) );
			}
		}
	}

	public XmlObject[] getMessageParts( String messageContent, String operationName, boolean isResponse )
			throws Exception
	{
		BindingOperation bindingOperation = findBindingOperation( operationName );
		if( bindingOperation == null )
		{
			throw new Exception( "Missing operation [" + operationName + "] in wsdl definition" );
		}

		if( !wsdlContext.hasSchemaTypes() )
		{
			throw new Exception( "Missing schema types for message" );
		}

		// XmlObject msgXml = XmlObject.Factory.parse( messageContent );
		XmlObject msgXml = XmlUtils.createXmlObject( messageContent );
		Part[] parts = isResponse ? WsdlUtils.getOutputParts( bindingOperation ) : WsdlUtils
				.getInputParts( bindingOperation );
		if( parts == null || parts.length == 0 )
			throw new Exception( "Missing parts for operation [" + operationName + "]" );

		List<XmlObject> result = new ArrayList<XmlObject>();

		if( WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ) )
		{
			// get root element
			XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
					+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='"
					+ WsdlUtils.getTargetNamespace( wsdlContext.getDefinition() ) + "';" + "$this/env:Envelope/env:Body/ns:"
					+ bindingOperation.getName() + ( isResponse ? "Response" : "" ) );

			if( paths.length != 1 )
			{
				throw new Exception( "Missing message wrapper element ["
						+ WsdlUtils.getTargetNamespace( wsdlContext.getDefinition() ) + "@" + bindingOperation.getName()
						+ ( isResponse ? "Response]" : "]" ) );
			}
			else
			{
				XmlObject wrapper = paths[0];

				for( int i = 0; i < parts.length; i++ )
				{
					Part part = parts[i];
					if( ( isResponse && WsdlUtils.isAttachmentOutputPart( part, bindingOperation ) )
							|| ( !isResponse && WsdlUtils.isAttachmentInputPart( part, bindingOperation ) ) )
						continue;

					QName partName = part.getElementName();
					if( partName == null )
						partName = new QName( part.getName() );

					XmlObject[] children = wrapper.selectChildren( partName );
					if( children.length != 1 )
					{
						log.error( "Missing message part [" + part.getName() + "]" );
					}
					else
					{
						QName typeName = part.getTypeName();
						if( typeName == null )
						{
							typeName = partName;
							SchemaGlobalElement type = wsdlContext.getSchemaTypeLoader().findElement( typeName );

							if( type != null )
							{
								result.add( children[0].copy().changeType( type.getType() ) );
							}
							else
								log.error( "Missing element [" + typeName + "] in associated schema for part ["
										+ part.getName() + "]" );
						}
						else
						{
							SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
							if( type != null )
							{
								result.add( children[0].copy().changeType( type ) );
							}
							else
								log.error( "Missing type [" + typeName + "] in associated schema for part [" + part.getName()
										+ "]" );
						}
					}
				}
			}
		}
		else
		{
			Part part = parts[0];
			QName elementName = part.getElementName();
			if( elementName != null )
			{
				// just check for correct message element, other elements are
				// avoided (should create an error)
				XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
						+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='"
						+ elementName.getNamespaceURI() + "';" + "$this/env:Envelope/env:Body/ns:"
						+ elementName.getLocalPart() );

				if( paths.length == 1 )
				{
					SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
					if( elm != null )
					{
						result.add( paths[0].copy().changeType( elm.getType() ) );
					}
					else
						throw new Exception( "Missing part type in associated schema" );
				}
				else
					throw new Exception( "Missing message part with name [" + elementName + "]" );
			}
		}

		return result.toArray( new XmlObject[result.size()] );
	}

	@SuppressWarnings( "unchecked" )
	public void validateXml( String request, List<XmlError> errors )
	{
		try
		{
			XmlOptions xmlOptions = new XmlOptions();
			xmlOptions.setLoadLineNumbers();
			xmlOptions.setErrorListener( errors );
			xmlOptions.setLoadLineNumbers( XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT );
			// XmlObject.Factory.parse( request, xmlOptions );
			XmlUtils.createXmlObject( request, xmlOptions );
		}
		catch( XmlException e )
		{
			if( e.getErrors() != null )
				errors.addAll( e.getErrors() );
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}
		catch( Exception e )
		{
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}
	}

	private AssertionError[] convertErrors( List<XmlError> errors )
	{
		if( errors.size() > 0 )
		{
			List<AssertionError> response = new ArrayList<AssertionError>();
			for( Iterator<XmlError> i = errors.iterator(); i.hasNext(); )
			{
				XmlError error = i.next();

				if( error instanceof XmlValidationError )
				{
					XmlValidationError e = ( ( XmlValidationError )error );
					QName offendingQName = e.getOffendingQName();
					if( offendingQName != null )
					{
						if( offendingQName.equals( new QName( wsdlContext.getSoapVersion().getEnvelopeNamespace(),
								"encodingStyle" ) ) )
						{
							log.debug( "ignoring encodingStyle validation.." );
							continue;
						}
						else if( offendingQName.equals( new QName( wsdlContext.getSoapVersion().getEnvelopeNamespace(),
								"mustUnderstand" ) ) )
						{
							log.debug( "ignoring mustUnderstand validation.." );
							continue;
						}
					}
				}

				AssertionError assertionError = new AssertionError( error );
				if( !response.contains( assertionError ) )
					response.add( assertionError );
			}

			return response.toArray( new AssertionError[response.size()] );
		}

		return new AssertionError[0];
	}

	@SuppressWarnings( "unchecked" )
	public void validateMessage( WsdlMessageExchange messageExchange, String message, BindingOperation bindingOperation,
			Part[] parts, List<XmlError> errors, boolean isResponse )
	{
		try
		{
			if( !wsdlContext.hasSchemaTypes() )
			{
				errors.add( XmlError.forMessage( "Missing schema types for message" ) );
			}
			else
			{
				if( !WsdlUtils.isOutputSoapEncoded( bindingOperation ) )
				{
					XmlOptions xmlOptions = new XmlOptions();
					xmlOptions.setLoadLineNumbers();
					xmlOptions.setLoadLineNumbers( XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT );
					// XmlObject xml = XmlObject.Factory.parse( message, xmlOptions
					// );
					XmlObject xml = XmlUtils.createXmlObject( message, xmlOptions );

					XmlObject[] paths = xml.selectPath( "declare namespace env='"
							+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';"
							+ "$this/env:Envelope/env:Body/env:Fault" );

					if( paths.length > 0 )
					{
						validateSoapFault( bindingOperation, paths[0], errors );
					}
					else if( WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ) )
					{
						validateRpcLiteral( bindingOperation, parts, xml, errors, isResponse );
					}
					else
					{
						validateDocLiteral( bindingOperation, parts, xml, errors, isResponse );
					}

					if( isResponse )
						validateOutputAttachments( messageExchange, xml, errors, bindingOperation, parts );
					else
						validateInputAttachments( messageExchange, errors, bindingOperation, parts );
				}
				else
					errors.add( XmlError.forMessage( "Validation of SOAP-Encoded messages not supported" ) );
			}
		}
		catch( XmlException e )
		{
			if( e.getErrors() != null )
				errors.addAll( e.getErrors() );
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}
		catch( Exception e )
		{
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}
	}

	private BindingOperation findBindingOperation( String operationName ) throws Exception
	{
		Map<?, ?> services = wsdlContext.getDefinition().getAllServices();
		Iterator<?> i = services.keySet().iterator();
		while( i.hasNext() )
		{
			Service service = ( Service )wsdlContext.getDefinition().getService( ( QName )i.next() );
			Map<?, ?> ports = service.getPorts();

			Iterator<?> iterator = ports.keySet().iterator();
			while( iterator.hasNext() )
			{
				Port port = ( Port )service.getPort( ( String )iterator.next() );
				Binding binding = port.getBinding();
				if( binding.getQName().equals( wsdlContext.getInterface().getBindingName() ) )
				{
					BindingOperation bindingOperation = binding.getBindingOperation( operationName, null, null );
					if( bindingOperation != null )
						return bindingOperation;
				}
			}
		}

		Map<?, ?> bindings = wsdlContext.getDefinition().getAllBindings();
		i = bindings.keySet().iterator();
		while( i.hasNext() )
		{
			Binding binding = ( Binding )bindings.get( i.next() );
			if( binding.getQName().equals( wsdlContext.getInterface().getBindingName() ) )
			{
				BindingOperation bindingOperation = binding.getBindingOperation( operationName, null, null );
				if( bindingOperation != null )
					return bindingOperation;
			}
		}

		return null;
	}

	public AssertionError[] assertResponse( WsdlMessageExchange messageExchange, boolean envelopeOnly )
	{
		List<XmlError> errors = new ArrayList<XmlError>();
		try
		{
			String response = messageExchange.getResponseContent();

			if( StringUtils.isNullOrEmpty( response ) )
			{
				if( !messageExchange.getOperation().isOneWay() )
				{
					errors.add( XmlError.forMessage( "Response is missing or empty" ) );
				}
			}
			else
			{
				wsdlContext.getSoapVersion().validateSoapEnvelope( response, errors );

				if( errors.isEmpty() && !envelopeOnly )
				{
					WsdlOperation operation = messageExchange.getOperation();
					BindingOperation bindingOperation = operation.getBindingOperation();
					if( bindingOperation == null )
					{
						errors.add( XmlError.forMessage( "Missing operation [" + operation.getBindingOperationName()
								+ "] in wsdl definition" ) );
					}
					else
					{
						Part[] outputParts = WsdlUtils.getOutputParts( bindingOperation );
						validateMessage( messageExchange, response, bindingOperation, outputParts, errors, true );
					}
				}
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
			errors.add( XmlError.forMessage( e.getMessage() ) );
		}

		return convertErrors( errors );
	}

	private void validateDocLiteral( BindingOperation bindingOperation, Part[] parts, XmlObject msgXml,
			List<XmlError> errors, boolean isResponse ) throws Exception
	{
		Part part = null;

		// start by finding body part
		for( int c = 0; c < parts.length; c++ )
		{
			// content part?
			if( ( isResponse && !WsdlUtils.isAttachmentOutputPart( parts[c], bindingOperation ) )
					|| ( !isResponse && !WsdlUtils.isAttachmentInputPart( parts[c], bindingOperation ) ) )
			{
				// already found?
				if( part != null )
				{
					errors.add( XmlError.forMessage( "DocLiteral message must contain 1 body part definition" ) );
					return;
				}

				part = parts[c];
			}
		}

		QName elementName = part.getElementName();
		if( elementName != null )
		{
			// just check for correct message element, other elements are avoided
			// (should create an error)
			XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
					+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='"
					+ elementName.getNamespaceURI() + "';" + "$this/env:Envelope/env:Body/ns:" + elementName.getLocalPart() );

			if( paths.length == 1 )
			{
				SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
				if( elm != null )
				{
					validateMessageBody( errors, elm.getType(), paths[0] );

					// ensure no other elements in body
					NodeList children = XmlUtils.getChildElements( ( Element )paths[0].getDomNode().getParentNode() );
					for( int c = 0; c < children.getLength(); c++ )
					{
						QName childName = XmlUtils.getQName( children.item( c ) );
						if( !elementName.equals( childName ) )
						{
							XmlCursor cur = paths[0].newCursor();
							cur.toParent();
							cur.toChild( childName );
							errors.add( XmlError.forCursor( "Invalid element [" + childName + "] in SOAP Body", cur ) );
							cur.dispose();
						}
					}
				}
				else
					errors.add( XmlError.forMessage( "Missing part type [" + elementName + "] in associated schema" ) );
			}
			else
				errors.add( XmlError.forMessage( "Missing message part with name [" + elementName + "]" ) );
		}
		else if( part.getTypeName() != null )
		{
			QName typeName = part.getTypeName();

			XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
					+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='"
					+ typeName.getNamespaceURI() + "';" + "$this/env:Envelope/env:Body/ns:" + part.getName() );

			if( paths.length == 1 )
			{
				SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
				if( type != null )
				{
					validateMessageBody( errors, type, paths[0] );
					// XmlObject obj = paths[0].copy().changeType( type );
					// obj.validate( new XmlOptions().setErrorListener( errors ));
				}
				else
					errors.add( XmlError.forMessage( "Missing part type in associated schema" ) );
			}
			else
				errors.add( XmlError.forMessage( "Missing message part with name:type [" + part.getName() + ":" + typeName
						+ "]" ) );
		}
	}

	private void validateMessageBody( List<XmlError> errors, SchemaType type, XmlObject msg ) throws XmlException
	{
		// need to create new body element of correct type from xml text
		// since we want to retain line-numbers
		XmlOptions xmlOptions = new XmlOptions();
		xmlOptions.setLoadLineNumbers();
		xmlOptions.setLoadLineNumbers( XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT );

		XmlCursor cur = msg.newCursor();
		Map<String, String> map = new HashMap<String, String>();

		while( cur.hasNextToken() )
		{
			if( cur.toNextToken().isNamespace() )
				map.put( cur.getName().getLocalPart(), cur.getTextValue() );
		}

		xmlOptions.setUseDefaultNamespace();
		xmlOptions.setSaveOuter();

		// problem: prefixes might get redefined/changed when saving which can
		// cause xsi:type refs to
		// reference wrong/non-existing namespace.. solution would probably be to
		// manually walk through document and
		// update xsi:type refs with new prefix. The setUseDefaultNamespace()
		// above helps here but is not a definitive fix

		String xmlText = msg.copy().changeType( type ).xmlText( xmlOptions );

		xmlOptions.setLoadAdditionalNamespaces( map );

		XmlObject obj = type.getTypeSystem().parse( xmlText, type, xmlOptions );
		obj = obj.changeType( type );

		// create internal error list
		ArrayList<Object> list = new ArrayList<Object>();

		xmlOptions = new XmlOptions();
		xmlOptions.setErrorListener( list );
		xmlOptions.setValidateTreatLaxAsSkip();

		try
		{
			obj.validate( xmlOptions );
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
			list.add( "Internal Error - see error log for details - [" + e + "]" );
		}

		// transfer errors for "real" line numbers
		for( int c = 0; c < list.size(); c++ )
		{
			XmlError error = ( XmlError )list.get( c );

			if( error instanceof XmlValidationError )
			{
				XmlValidationError validationError = ( ( XmlValidationError )error );

				if( wsdlContext.getSoapVersion().shouldIgnore( validationError ) )
					continue;

				// ignore cid: related errors
				if( validationError.getErrorCode().equals( "base64Binary" )
						|| validationError.getErrorCode().equals( "hexBinary" ) )
				{
					XmlCursor cursor = validationError.getCursorLocation();
					if( cursor.toParent() )
					{
						String text = cursor.getTextValue();

						// special handling for soapui/MTOM -> add option for
						// disabling?
						if( text.startsWith( "cid:" ) || text.startsWith( "file:" ) )
						{
							// ignore
							continue;
						}
					}
				}
			}

			int line = error.getLine() == -1 ? 0 : error.getLine() - 1;
			errors.add( XmlError.forLocation( error.getMessage(), error.getSourceName(), getLine( msg ) + line,
					error.getColumn(), error.getOffset() ) );
		}
	}

	private int getLine( XmlObject object )
	{
		List<?> list = new ArrayList<Object>();
		object.newCursor().getAllBookmarkRefs( list );
		for( int c = 0; c < list.size(); c++ )
		{
			if( list.get( c ) instanceof XmlLineNumber )
			{
				return ( ( XmlLineNumber )list.get( c ) ).getLine();
			}
		}

		return -1;
	}

	private void validateRpcLiteral( BindingOperation bindingOperation, Part[] parts, XmlObject msgXml,
			List<XmlError> errors, boolean isResponse ) throws Exception
	{
		if( parts.length == 0 )
			return;

		XmlObject[] bodyParts = getRpcBodyPart( bindingOperation, msgXml, isResponse );

		if( bodyParts.length != 1 )
		{
			errors.add( XmlError.forMessage( "Missing message wrapper element ["
					+ WsdlUtils.getTargetNamespace( wsdlContext.getDefinition() ) + "@" + bindingOperation.getName()
					+ ( isResponse ? "Response" : "" ) ) );
		}
		else
		{
			XmlObject wrapper = bodyParts[0];

			for( int i = 0; i < parts.length; i++ )
			{
				Part part = parts[i];

				// skip attachment parts
				if( isResponse )
				{
					if( WsdlUtils.isAttachmentOutputPart( part, bindingOperation ) )
						continue;
				}
				else
				{
					if( WsdlUtils.isAttachmentInputPart( part, bindingOperation ) )
						continue;
				}

				// find part in message
				XmlObject[] children = wrapper.selectChildren( new QName( part.getName() ) );

				// not found?
				if( children.length != 1 )
				{
					// try element name (loophole in basic-profile spec?)
					QName elementName = part.getElementName();
					if( elementName != null )
					{
						bodyParts = msgXml.selectPath( "declare namespace env='"
								+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='"
								+ wsdlContext.getDefinition().getTargetNamespace() + "';" + "declare namespace ns2='"
								+ elementName.getNamespaceURI() + "';" + "$this/env:Envelope/env:Body/ns:"
								+ bindingOperation.getName() + ( isResponse ? "Response" : "" ) + "/ns2:"
								+ elementName.getLocalPart() );

						if( bodyParts.length == 1 )
						{
							SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
							if( elm != null )
							{
								validateMessageBody( errors, elm.getType(), bodyParts[0] );
							}
							else
								errors.add( XmlError.forMessage( "Missing part type in associated schema for [" + elementName
										+ "]" ) );
						}
						else
							errors.add( XmlError.forMessage( "Missing message part with name [" + elementName + "]" ) );
					}
					else
					{
						errors.add( XmlError.forMessage( "Missing message part [" + part.getName() + "]" ) );
					}
				}
				else
				{
					QName typeName = part.getTypeName();
					SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
					if( type != null )
					{
						validateMessageBody( errors, type, children[0] );
					}
					else
						errors.add( XmlError.forMessage( "Missing type in associated schema for part [" + part.getName()
								+ "]" ) );
				}
			}
		}
	}

	private XmlObject[] getRpcBodyPart( BindingOperation bindingOperation, XmlObject msgXml, boolean isResponse )
			throws Exception
	{
		// rpc requests should use the operation name as root element and soapbind
		// namespaceuri attribute as ns
		String ns = WsdlUtils.getSoapBodyNamespace( isResponse ? bindingOperation.getBindingOutput()
				.getExtensibilityElements() : bindingOperation.getBindingInput().getExtensibilityElements() );

		if( ns == null || ns.trim().length() == 0 )
			ns = WsdlUtils.getTargetNamespace( wsdlContext.getDefinition() );

		// get root element
		XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
				+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" + "declare namespace ns='" + ns + "';"
				+ "$this/env:Envelope/env:Body/ns:" + bindingOperation.getName() + ( isResponse ? "Response" : "" ) );
		return paths;
	}

	@SuppressWarnings( "unchecked" )
	private void validateSoapFault( BindingOperation bindingOperation, XmlObject msgXml, List<XmlError> errors )
			throws Exception
	{
		Map faults = bindingOperation.getBindingFaults();
		Iterator<BindingFault> i = faults.values().iterator();

		// create internal error list
		List<?> list = new ArrayList<Object>();

		XmlOptions xmlOptions = new XmlOptions();
		xmlOptions.setErrorListener( list );
		xmlOptions.setValidateTreatLaxAsSkip();
		msgXml.validate( xmlOptions );

		for( Object o : list )
		{
			if( o instanceof XmlError )
				errors.add( ( XmlError )o );
			else
				errors.add( XmlError.forMessage( o.toString() ) );
		}

		while( i.hasNext() )
		{
			BindingFault bindingFault = i.next();
			String faultName = bindingFault.getName();

			Part[] faultParts = WsdlUtils.getFaultParts( bindingOperation, faultName );
			if( faultParts.length == 0 )
			{
				log.warn( "Missing fault parts in wsdl for fault [" + faultName + "] in bindingOperation ["
						+ bindingOperation.getName() + "]" );
				continue;
			}

			if( faultParts.length != 1 )
			{
				log.info( "Too many fault parts in wsdl for fault [" + faultName + "] in bindingOperation ["
						+ bindingOperation.getName() + "]" );
				continue;
			}

			Part part = faultParts[0];
			QName elementName = part.getElementName();

			if( elementName != null )
			{
				XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
						+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "'; declare namespace flt='"
						+ wsdlContext.getSoapVersion().getFaultDetailNamespace() + "';" + "declare namespace ns='"
						+ elementName.getNamespaceURI() + "';" + "//env:Fault/flt:detail/ns:" + elementName.getLocalPart() );

				if( paths.length == 1 )
				{
					SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
					if( elm != null )
					{
						validateMessageBody( errors, elm.getType(), paths[0] );
					}
					else
						errors.add( XmlError.forMessage( "Missing fault part element [" + elementName + "] for fault ["
								+ part.getName() + "] in associated schema" ) );

					return;
				}
			}
			// this is not allowed by Basic Profile.. remove?
			else if( part.getTypeName() != null )
			{
				QName typeName = part.getTypeName();

				XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
						+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "'; declare namespace flt='"
						+ wsdlContext.getSoapVersion().getFaultDetailNamespace() + "';" + "declare namespace ns='"
						+ typeName.getNamespaceURI() + "';" + "//env:Fault/flt:detail/ns:" + part.getName() );

				if( paths.length == 1 )
				{
					SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
					if( type != null )
					{
						validateMessageBody( errors, type, paths[0] );
					}
					else
						errors.add( XmlError.forMessage( "Missing fault part type [" + typeName + "] for fault ["
								+ part.getName() + "] in associated schema" ) );

					return;
				}
			}
		}

		// if we get here, no matching fault was found.. this is not an error but
		// should be warned..
		XmlObject[] paths = msgXml.selectPath( "declare namespace env='"
				+ wsdlContext.getSoapVersion().getEnvelopeNamespace() + "'; declare namespace flt='"
				+ wsdlContext.getSoapVersion().getFaultDetailNamespace() + "';//env:Fault/flt:detail" );

		if( paths.length == 0 )
			log.warn( "Missing matching Fault in wsdl for bindingOperation [" + bindingOperation.getName() + "]" );
		else
		{
			String xmlText = paths[0].xmlText( new XmlOptions().setSaveOuter() );
			log.warn( "Missing matching Fault in wsdl for Fault Detail element ["
					+ XmlUtils.removeUnneccessaryNamespaces( xmlText ) + "] in bindingOperation ["
					+ bindingOperation.getName() + "]" );
		}
	}
}
