View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 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.support.wsdl;
    14
    15  import java.util.ArrayList;
    16  import java.util.Iterator;
    17  import java.util.List;
    18  import java.util.Map;
    19
    20  import javax.wsdl.Binding;
    21  import javax.wsdl.BindingFault;
    22  import javax.wsdl.BindingOperation;
    23  import javax.wsdl.Part;
    24  import javax.wsdl.Port;
    25  import javax.wsdl.Service;
    26  import javax.wsdl.extensions.mime.MIMEContent;
    27  import javax.xml.namespace.QName;
    28
    29  import org.apache.log4j.Logger;
    30  import org.apache.xmlbeans.SchemaGlobalElement;
    31  import org.apache.xmlbeans.SchemaType;
    32  import org.apache.xmlbeans.XmlCursor;
    33  import org.apache.xmlbeans.XmlError;
    34  import org.apache.xmlbeans.XmlException;
    35  import org.apache.xmlbeans.XmlLineNumber;
    36  import org.apache.xmlbeans.XmlObject;
    37  import org.apache.xmlbeans.XmlOptions;
    38  import org.apache.xmlbeans.XmlValidationError;
    39  import org.w3c.dom.Element;
    40
    41  import com.eviware.soapui.SoapUI;
    42  import com.eviware.soapui.impl.wsdl.WsdlOperation;
    43  import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
    44  import com.eviware.soapui.impl.wsdl.teststeps.assertions.AssertionError;
    45  import com.eviware.soapui.model.iface.Attachment;
    46  import com.eviware.soapui.settings.WsdlSettings;
    47  import com.eviware.soapui.support.xml.XmlUtils;
    48
    49  /***
    50   * Class for validating SOAP requests/responses against their definition and schema, requires that
    51   * the messages follow basic-profile requirements
    52   *
    53   * @author Ole.Matzura
    54   */
    55
    56  public class WsdlValidator
    57  {
    58  	private final WsdlContext wsdlContext;
    59     private final static Logger log = Logger.getLogger( WsdlValidator.class );
    60
    61  	public WsdlValidator( WsdlContext wsdlContext )
    62     {
    63  		this.wsdlContext = wsdlContext;
    64  	}
    65
    66  	@SuppressWarnings("unchecked")
    67  	public AssertionError [] assertRequest( WsdlMessageExchange messageExchange, boolean envelopeOnly )
    68  	{
    69  		List<XmlError> errors = new ArrayList<XmlError>();
    70  		try
    71  		{
    72  			String requestContent = messageExchange.getRequestContent();
    73  			wsdlContext.getSoapVersion().validateSoapEnvelope(requestContent, errors);
    74
    75  			if (errors.isEmpty() && !envelopeOnly )
    76  			{
    77  				wsdlContext.getSoapVersion().validateSoapEnvelope(requestContent, errors);
    78  				WsdlOperation operation = messageExchange.getOperation();
    79  				String operationName = operation.getBindingOperationName();
    80  				BindingOperation bindingOperation = findBindingOperation(operationName);
    81  				if (bindingOperation == null)
    82  				{
    83  					errors.add(XmlError.forMessage("Missing operation ["
    84  							+ operationName + "] in wsdl definition"));
    85  				}
    86  				else
    87  				{
    88  					Part[] inputParts = WsdlUtils.getInputParts(bindingOperation);
    89  					validateMessage(messageExchange, requestContent, bindingOperation, inputParts, errors, false);
    90  //					validateInputAttachments(request, errors, bindingOperation, inputParts);
    91  				}
    92  			}
    93  		}
    94        catch( XmlException e )
    95        {
    96        	errors.addAll( e.getErrors() );
    97        	errors.add( XmlError.forMessage( e.getMessage() ));
    98        }
    99  		catch (Exception e)
    100 		{
    101 			errors.add( XmlError.forMessage( e.getMessage() ));
    102 		}
    103
    104 		return convertErrors( errors );
    105 	}
    106
    107 	private void validateInputAttachments(WsdlMessageExchange messageExchange, List<XmlError> errors, BindingOperation bindingOperation, Part[] inputParts)
    108 	{
    109 		for( Part part : inputParts )
    110 		{
    111 			MIMEContent[] contents = WsdlUtils.getInputMultipartContent( part, bindingOperation );
    112 			if( contents.length == 0 )
    113 				continue;
    114
    115 			Attachment [] attachments = messageExchange.getRequestAttachmentsForPart( part.getName() );
    116 			if( attachments.length == 0 )
    117 			{
    118 				errors.add(XmlError.forMessage("Missing attachment for part [" + part.getName() + "]" ));
    119 			}
    120 			else if( attachments.length == 1 )
    121 			{
    122 				Attachment attachment = attachments[0];
    123 				String types = "";
    124 				for( MIMEContent content : contents )
    125 				{
    126 					String type = content.getType();
    127 					if( type.equals( attachment.getContentType() ) || type.toUpperCase().startsWith( "MULTIPART" ))
    128 					{
    129 						types = null;
    130 						break;
    131 					}
    132 					if( types.length() > 0 )
    133 						types += ",";
    134
    135 					types += type;
    136 				}
    137
    138 				if( types != null )
    139 				{
    140 					String msg = "Missing attachment for part [" + part.getName() +"] with content-type [" + types + "]," +
    141 							" content type is [" + attachment.getContentType() + "]";
    142
    143 					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ))
    144 						log.warn( msg );
    145 					else
    146 						errors.add(XmlError.forMessage(msg ));
    147 				}
    148 			}
    149 			else
    150 			{
    151 				String types = "";
    152 				for( MIMEContent content : contents )
    153 				{
    154 					String type = content.getType();
    155 					if( type.toUpperCase().startsWith( "MULTIPART" ))
    156 					{
    157 						types = null;
    158 						break;
    159 					}
    160 					if( types.length() > 0 )
    161 						types += ",";
    162
    163 					types += type;
    164 				}
    165
    166 				if( types == null )
    167 				{
    168 					String msg = "Too many attachments for part [" + part.getName() + "] with content-type [" + types + "]";
    169 					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ))
    170 						log.warn( msg );
    171 					else
    172 						errors.add(XmlError.forMessage(msg ));
    173 				}
    174 			}
    175
    176 			if( attachments.length > 0 )
    177 				validateAttachmentsReadability( errors, attachments );
    178 		}
    179 	}
    180
    181 	private void validateOutputAttachments(WsdlMessageExchange messageExchange, XmlObject xml, List<XmlError> errors, BindingOperation bindingOperation, Part[] outputParts) throws Exception
    182 	{
    183 		for( Part part : outputParts )
    184 		{
    185 			MIMEContent[] contents = WsdlUtils.getOutputMultipartContent( part, bindingOperation );
    186 			if( contents.length == 0 )
    187 				continue;
    188
    189 			Attachment [] attachments = messageExchange.getResponseAttachmentsForPart( part.getName() );
    190
    191 			// check for rpc
    192 			if( attachments.length == 0 && WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ))
    193 			{
    194 				XmlObject[] rpcBodyPart = getRpcBodyPart( bindingOperation, xml, true );
    195 				if( rpcBodyPart.length == 1 )
    196 				{
    197 					XmlObject[] children = rpcBodyPart[0].selectChildren( new QName( part.getName() ));
    198 					if( children.length == 1 )
    199 					{
    200 						String href = ((Element)children[0].getDomNode()).getAttribute( "href" );
    201 						if( href != null )
    202 						{
    203 							if( href.startsWith( "cid:" ))
    204 								href = href.substring( 4 );
    205
    206 							attachments = messageExchange.getResponseAttachmentsForPart( href );
    207 						}
    208 					}
    209 				}
    210 			}
    211
    212 			if( attachments.length == 0 )
    213 			{
    214 				errors.add(XmlError.forMessage("Missing attachment for part [" + part.getName() + "]" ));
    215 			}
    216 			else if( attachments.length == 1 )
    217 			{
    218 				Attachment attachment = attachments[0];
    219 				String types = "";
    220 				for( MIMEContent content : contents )
    221 				{
    222 					String type = content.getType();
    223 					if( type.equals( attachment.getContentType() ) || type.toUpperCase().startsWith( "MULTIPART" ))
    224 					{
    225 						types = null;
    226 						break;
    227 					}
    228
    229 					if( types.length() > 0 )
    230 						types += ",";
    231
    232 					types += type;
    233 				}
    234
    235 				if( types != null)
    236 				{
    237 					String msg = "Missing attachment for part [" + part.getName() +
    238 											"] with content-type [" + types + "], content type is [" + attachment.getContentType() + "]";
    239
    240 					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ))
    241 						log.warn( msg );
    242 					else
    243 						errors.add(XmlError.forMessage(msg ));
    244 				}
    245 			}
    246 			else
    247 			{
    248 				String types = "";
    249 				for( MIMEContent content : contents )
    250 				{
    251 					String type = content.getType();
    252 					if( type.toUpperCase().startsWith( "MULTIPART" ))
    253 					{
    254 						types = null;
    255 						break;
    256 					}
    257
    258 					if( types.length() > 0 )
    259 						types += ",";
    260
    261 					types += type;
    262 				}
    263
    264 				if( types != null )
    265 				{
    266 					String msg = "Too many attachments for part [" + part.getName() + "] with content-type [" + types + "]";
    267
    268 					if( SoapUI.getSettings().getBoolean( WsdlSettings.ALLOW_INCORRECT_CONTENTTYPE ))
    269 						log.warn( msg );
    270 					else
    271 						errors.add(XmlError.forMessage(msg ));
    272 				}
    273 			}
    274
    275 			if( attachments.length > 0 )
    276 				validateAttachmentsReadability( errors, attachments );
    277 		}
    278 	}
    279
    280 	private void validateAttachmentsReadability( List<XmlError> errors, Attachment[] attachments )
    281 	{
    282 		for( Attachment attachment : attachments )
    283 		{
    284 			try
    285 			{
    286 				attachment.getInputStream();
    287 			}
    288 			catch( Exception e )
    289 			{
    290 				errors.add(XmlError.forMessage(e.toString() ));
    291 			}
    292 		}
    293 	}
    294
    295    public XmlObject [] getMessageParts( String messageContent, String operationName, boolean isResponse ) throws Exception
    296    {
    297    	BindingOperation bindingOperation = findBindingOperation(operationName);
    298 		if (bindingOperation == null)
    299 		{
    300 			throw new Exception("Missing operation ["	+ operationName + "] in wsdl definition");
    301 		}
    302
    303 		 if( !wsdlContext.hasSchemaTypes() )
    304        {
    305 			 throw new Exception("Missing schema types for message");
    306        }
    307
    308        XmlObject msgXml = XmlObject.Factory.parse( messageContent );
    309        Part[] parts = isResponse ? WsdlUtils.getOutputParts( bindingOperation ) : WsdlUtils.getInputParts(bindingOperation);
    310        if( parts == null || parts.length == 0 )
    311       	 throw new Exception( "Missing parts for operation [" + operationName + "]" );
    312
    313        List<XmlObject> result = new ArrayList<XmlObject>();
    314
    315        if( WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ))
    316        {
    317       	 //  	 get root element
    318           XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    319                    		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    320                 "declare namespace ns='" + wsdlContext.getDefinition().getTargetNamespace() + "';" +
    321                 "$this/env:Envelope/env:Body/ns:" + bindingOperation.getName() + (isResponse ? "Response" : ""));
    322
    323           if( paths.length != 1 )
    324           {
    325          	 throw new Exception("Missing message wrapper element [" +
    326                    wsdlContext.getDefinition().getTargetNamespace() + "@" + bindingOperation.getName() + (isResponse ? "Response" : ""));
    327           }
    328           else
    329           {
    330              XmlObject wrapper = paths[0];
    331
    332              for (int i = 0; i < parts.length; i++)
    333              {
    334                 Part part = parts[i];
    335                 if( (isResponse && WsdlUtils.isAttachmentOutputPart( part, bindingOperation )) ||
    336                	  (!isResponse && WsdlUtils.isAttachmentInputPart( part, bindingOperation )))
    337                	 continue;
    338
    339                 QName partName = part.getElementName();
    340                 if( partName == null )
    341                	 partName = new QName( part.getName() );
    342
    343                 XmlObject[] children = wrapper.selectChildren( partName );
    344                 if( children.length != 1 )
    345                 {
    346                    log.error("Missing message part [" + part.getName() + "]" );
    347                 }
    348                 else
    349                 {
    350                    QName typeName = part.getTypeName();
    351                    if( typeName == null )
    352                    {
    353                   	 typeName = partName;
    354                   	 SchemaGlobalElement type = wsdlContext.getSchemaTypeLoader().findElement( typeName );
    355
    356                   	 if( type != null )
    357 	                   {
    358 	                   	 result.add( children[0].copy().changeType( type.getType() ));
    359 	                   }
    360 	                   else log.error( "Missing element [" + typeName + "] in associated schema for part [" + part.getName() + "]" );
    361                    }
    362                    else
    363                    {
    364 	                   SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
    365 	                   if( type != null )
    366 	                   {
    367 	                   	 result.add( children[0].copy().changeType( type ));
    368 	                   }
    369 	                   else log.error( "Missing type [" + typeName + "] in associated schema for part [" + part.getName() + "]" );
    370                    }
    371                 }
    372              }
    373           }
    374        }
    375        else
    376        {
    377           Part part = parts[0];
    378           QName elementName = part.getElementName();
    379           if( elementName != null )
    380           {
    381           	// just check for correct message element, other elements are avoided (should create an error)
    382              XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    383                    		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    384                    "declare namespace ns='" + elementName.getNamespaceURI() + "';" +
    385                    "$this/env:Envelope/env:Body/ns:" + elementName.getLocalPart() );
    386
    387              if( paths.length == 1 )
    388              {
    389                 SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
    390                 if( elm != null )
    391                 {
    392     					result.add( paths[0].copy().changeType( elm.getType() ));
    393                 }
    394                 else throw new Exception("Missing part type in associated schema" );
    395              }
    396              else throw new Exception("Missing message part with name [" + elementName + "]" );
    397           }
    398        }
    399
    400    	return result.toArray( new XmlObject[result.size()] );
    401    }
    402
    403
    404 	@SuppressWarnings({ "unchecked", "unchecked" })
    405 	public void validateXml(String request, List<XmlError> errors )
    406 	{
    407 		try
    408 		{
    409 			XmlOptions xmlOptions = new XmlOptions();
    410 			xmlOptions.setLoadLineNumbers();
    411 			xmlOptions.setErrorListener(errors);
    412 			xmlOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT);
    413 			XmlObject.Factory.parse(request, xmlOptions);
    414 		}
    415       catch( XmlException e )
    416       {
    417       	errors.addAll( e.getErrors() );
    418       	errors.add( XmlError.forMessage( e.getMessage() ));
    419       }
    420 		catch (Exception e)
    421 		{
    422 			errors.add( XmlError.forMessage( e.getMessage() ));
    423 		}
    424 	}
    425
    426 	private AssertionError[] convertErrors(List<XmlError> errors)
    427 	{
    428       if( errors.size() > 0 )
    429       {
    430          List<AssertionError> response = new ArrayList<AssertionError>();
    431          for (Iterator<XmlError> i = errors.iterator(); i.hasNext();)
    432          {
    433             XmlError error = i.next();
    434
    435             if( error instanceof XmlValidationError )
    436             {
    437 	            XmlValidationError e = ((XmlValidationError)error);
    438 					QName offendingQName = e.getOffendingQName();
    439 					if( offendingQName != null )
    440 					{
    441 						if( offendingQName.equals( new QName( wsdlContext.getSoapVersion().getEnvelopeNamespace(), "encodingStyle")))
    442 						{
    443 							log.debug( "ignoring encodingStyle validation..");
    444 							continue;
    445 						}
    446 						else if( offendingQName.equals( new QName( wsdlContext.getSoapVersion().getEnvelopeNamespace(), "mustUnderstand")))
    447 						{
    448 							log.debug( "ignoring mustUnderstand validation..");
    449 							continue;
    450 						}
    451 					}
    452             }
    453
    454 				AssertionError assertionError = new AssertionError(error);
    455             if( !response.contains( assertionError ))
    456             	response.add( assertionError );
    457          }
    458
    459          return response.toArray( new AssertionError[response.size()] );
    460       }
    461
    462       return new AssertionError[0];
    463 	}
    464
    465 	@SuppressWarnings("unchecked")
    466 	public void validateMessage( WsdlMessageExchange messageExchange, String message, BindingOperation bindingOperation, Part [] parts, List<XmlError> errors, boolean isResponse )
    467 	{
    468 		try
    469       {
    470          if( !wsdlContext.hasSchemaTypes() )
    471          {
    472             errors.add( XmlError.forMessage( "Missing schema types for message"));
    473          }
    474          else
    475          {
    476          	if( !WsdlUtils.isOutputSoapEncoded( bindingOperation))
    477             {
    478          		XmlOptions xmlOptions = new XmlOptions();
    479                xmlOptions.setLoadLineNumbers();
    480                xmlOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT);
    481                XmlObject xml = XmlObject.Factory.parse( message, xmlOptions );
    482
    483                XmlObject[] paths = xml.selectPath( "declare namespace env='" +
    484                		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    485                      "$this/env:Envelope/env:Body/env:Fault");
    486
    487                if( paths.length > 0 )
    488                {
    489                   validateSoapFault( bindingOperation, paths[0], errors );
    490                }
    491                else if( WsdlUtils.isRpc( wsdlContext.getDefinition(), bindingOperation ))
    492                {
    493                   validateRpcLiteral( bindingOperation, parts, xml, errors, isResponse );
    494                }
    495                else
    496                {
    497                   validateDocLiteral( bindingOperation, parts, xml, errors, isResponse );
    498                }
    499
    500                if( isResponse )
    501                	validateOutputAttachments( messageExchange, xml, errors, bindingOperation, parts );
    502                else
    503                	validateInputAttachments( messageExchange, errors, bindingOperation, parts );
    504             }
    505          	else errors.add( XmlError.forMessage( "Validation of SOAP-Encoded messages not supported"));
    506          }
    507       }
    508       catch ( XmlException e )
    509       {
    510       	errors.addAll( e.getErrors() );
    511       	errors.add( XmlError.forMessage( e.getMessage() ));
    512       }
    513       catch (Exception e)
    514       {
    515       	errors.add( XmlError.forMessage( e.getMessage() ));
    516       }
    517 	}
    518
    519 	private BindingOperation findBindingOperation(String operationName) throws Exception
    520 	{
    521 		Map services = wsdlContext.getDefinition().getAllServices();
    522 		Iterator i = services.keySet().iterator();
    523 		while( i.hasNext() )
    524 		{
    525 			Service service = (Service) wsdlContext.getDefinition().getService( (QName) i.next());
    526 			Map ports = service.getPorts();
    527
    528 			Iterator iterator = ports.keySet().iterator();
    529 			while( iterator.hasNext() )
    530 			{
    531 				Port port = (Port) service.getPort( (String) iterator.next() );
    532 				BindingOperation bindingOperation = port.getBinding().getBindingOperation( operationName, null, null );
    533 				if( bindingOperation != null ) return bindingOperation;
    534 			}
    535 		}
    536
    537 		Map bindings = wsdlContext.getDefinition().getAllBindings();
    538 		i = bindings.keySet().iterator();
    539 		while( i.hasNext() )
    540 		{
    541 			Binding binding = (Binding) bindings.get( i.next() );
    542 			BindingOperation bindingOperation = binding.getBindingOperation( operationName, null, null );
    543 			if( bindingOperation != null ) return bindingOperation;
    544 		}
    545
    546 		return null;
    547 	}
    548
    549 	@SuppressWarnings("unchecked")
    550 	public AssertionError [] assertResponse( WsdlMessageExchange messageExchange, boolean envelopeOnly )
    551 	{
    552 		List<XmlError> errors = new ArrayList<XmlError>();
    553 		try
    554 		{
    555 			String response = messageExchange.getResponseContent();
    556 			wsdlContext.getSoapVersion().validateSoapEnvelope(response, errors);
    557
    558 			if (errors.isEmpty() && !envelopeOnly )
    559 			{
    560 				WsdlOperation operation = messageExchange.getOperation();
    561 				String operationName = operation.getBindingOperationName();
    562 				BindingOperation bindingOperation = findBindingOperation(operationName);
    563 				if (bindingOperation == null)
    564 				{
    565 					errors.add(XmlError.forMessage("Missing operation ["
    566 							+ operationName + "] in wsdl definition"));
    567 				}
    568 				else
    569 				{
    570 					Part[] outputParts = WsdlUtils.getOutputParts(bindingOperation);
    571 					validateMessage(messageExchange, response, bindingOperation, outputParts, errors, true);
    572 				}
    573 			}
    574 		}
    575       catch ( XmlException e )
    576       {
    577       	errors.addAll( e.getErrors() );
    578       	errors.add( XmlError.forMessage( e.getMessage() ));
    579       }
    580       catch (Exception e)
    581       {
    582       	errors.add( XmlError.forMessage( e.getMessage() ));
    583       }
    584
    585 		return convertErrors( errors );
    586 	}
    587
    588 	private void validateDocLiteral(BindingOperation bindingOperation, Part[] parts, XmlObject msgXml, List<XmlError> errors, boolean isResponse) throws Exception
    589    {
    590 		Part part = null;
    591
    592 		// start by finding body part
    593 		for( int c = 0; c < parts.length; c++ )
    594 		{
    595 			// content part?
    596 			if( (isResponse && !WsdlUtils.isAttachmentOutputPart( parts[c], bindingOperation )) ||
    597 				 (!isResponse && !WsdlUtils.isAttachmentInputPart( parts[c], bindingOperation )))
    598 			{
    599 				// already found?
    600 				if( part != null )
    601 				{
    602 		         errors.add( XmlError.forMessage("DocLiteral message must contain 1 body part definition" ));
    603 		         return;
    604 				}
    605
    606 				part = parts[c];
    607 			}
    608 		}
    609
    610       QName elementName = part.getElementName();
    611       if( elementName != null )
    612       {
    613       	// just check for correct message element, other elements are avoided (should create an error)
    614          XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    615                		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    616                "declare namespace ns='" + elementName.getNamespaceURI() + "';" +
    617                "$this/env:Envelope/env:Body/ns:" + elementName.getLocalPart() );
    618
    619          if( paths.length == 1 )
    620          {
    621             SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
    622             if( elm != null )
    623             {
    624 					validateMessageBody(errors, elm.getType(), paths[0]);
    625             }
    626             else errors.add( XmlError.forMessage("Missing part type in associated schema") );
    627          }
    628          else errors.add( XmlError.forMessage("Missing message part with name [" + elementName + "]" ));
    629       }
    630       else if( part.getTypeName() != null )
    631       {
    632          QName typeName = part.getTypeName();
    633
    634          XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    635                		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    636                "declare namespace ns='" + typeName.getNamespaceURI() + "';" +
    637                "$this/env:Envelope/env:Body/ns:" + part.getName() );
    638
    639          if( paths.length == 1 )
    640          {
    641             SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
    642             if( type != null )
    643             {
    644             	validateMessageBody( errors, type, paths[0] );
    645                //XmlObject obj = paths[0].copy().changeType( type );
    646                //obj.validate( new XmlOptions().setErrorListener( errors ));
    647             }
    648             else errors.add(XmlError.forMessage( "Missing part type in associated schema") );
    649          }
    650          else errors.add( XmlError.forMessage("Missing message part with name:type [" +
    651                part.getName() + ":" + typeName + "]" ));
    652       }
    653    }
    654
    655 	private void validateMessageBody(List<XmlError> errors, SchemaType type, XmlObject msg) throws XmlException
    656 	{
    657 		// need to create new body element of correct type from xml text
    658 		// since we want to retain line-numbers
    659 		XmlOptions xmlOptions = new XmlOptions();
    660 		xmlOptions.setLoadLineNumbers();
    661 		xmlOptions.setLoadLineNumbers(XmlOptions.LOAD_LINE_NUMBERS_END_ELEMENT);
    662
    663 		String xmlText = msg.copy().changeType( type ).xmlText( xmlOptions.setSaveOuter());
    664 		XmlObject obj = type.getTypeSystem().parse( xmlText, type, xmlOptions );
    665 		obj = obj.changeType( type );
    666
    667 		// create internal error list
    668 		List list = new ArrayList();
    669
    670 		xmlOptions = new XmlOptions();
    671 		xmlOptions.setErrorListener( list );
    672 		xmlOptions.setValidateTreatLaxAsSkip();
    673 		obj.validate( xmlOptions );
    674
    675 		// transfer errors for "real" line numbers
    676 		for( int c = 0; c < list.size(); c++ )
    677 		{
    678 			XmlError error = (XmlError) list.get( c );
    679
    680 			if( error instanceof XmlValidationError )
    681 			{
    682 				XmlValidationError validationError = ((XmlValidationError)error);
    683
    684 				if( wsdlContext.getSoapVersion().shouldIgnore( validationError ))
    685 					continue;
    686
    687 				// ignore cid: related errors
    688 				if( validationError.getErrorCode().equals( "base64Binary") || validationError.getErrorCode().equals( "hexBinary"))
    689 				{
    690 					XmlCursor cursor = validationError.getCursorLocation();
    691 					if( cursor.toParent() )
    692 					{
    693 						String text = cursor.getTextValue();
    694
    695 						// special handling for soapui/MTOM -> add option for disabling?
    696 						if( text.startsWith( "cid:" ) || text.startsWith( "file:" ))
    697 						{
    698 							// ignore
    699 							continue;
    700 						}
    701 					}
    702 				}
    703 			}
    704
    705 		   int line = error.getLine() == -1 ? 0 : error.getLine()-1;
    706 			errors.add( XmlError.forLocation( error.getMessage(), error.getSourceName(),
    707 		   		getLine( msg ) + line, error.getColumn(), error.getOffset() ));
    708 		}
    709 	}
    710
    711    private int getLine(XmlObject object)
    712 	{
    713    	List list = new ArrayList();
    714 		object.newCursor().getAllBookmarkRefs( list );
    715 		for( int c = 0; c < list.size(); c++ )
    716 		{
    717 			if( list.get( c ) instanceof XmlLineNumber )
    718 			{
    719 				return ((XmlLineNumber)list.get(c)).getLine();
    720 			}
    721 		}
    722
    723 		return -1;
    724 	}
    725
    726 	private void validateRpcLiteral(BindingOperation bindingOperation, Part[] parts, XmlObject msgXml, List<XmlError> errors, boolean isResponse ) throws Exception
    727    {
    728       if( parts.length == 0 )
    729          return;
    730
    731       XmlObject[] bodyParts = getRpcBodyPart(bindingOperation, msgXml, isResponse);
    732
    733       if( bodyParts.length != 1 )
    734       {
    735          errors.add( XmlError.forMessage("Missing message wrapper element [" +
    736                wsdlContext.getDefinition().getTargetNamespace() + "@" + bindingOperation.getName()
    737                + (isResponse ? "Response" : "" )));
    738       }
    739       else
    740       {
    741          XmlObject wrapper = bodyParts[0];
    742
    743          for (int i = 0; i < parts.length; i++)
    744          {
    745             Part part = parts[i];
    746
    747             // skip attachment parts
    748             if( isResponse )
    749             {
    750             	if( WsdlUtils.isAttachmentOutputPart( part, bindingOperation ) )
    751             		continue;
    752             }
    753             else
    754             {
    755             	if( WsdlUtils.isAttachmentInputPart( part, bindingOperation ) )
    756             		continue;
    757             }
    758
    759             // find part in message
    760             XmlObject[] children = wrapper.selectChildren( new QName( part.getName() ));
    761
    762             // not found?
    763             if( children.length != 1 )
    764             {
    765             	// try element name (loophole in basic-profile spec?)
    766             	QName elementName = part.getElementName();
    767             	if( elementName != null )
    768             	{
    769             		bodyParts = msgXml.selectPath( "declare namespace env='" +
    770                   		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    771                         "declare namespace ns='" + wsdlContext.getDefinition().getTargetNamespace() + "';" +
    772                         "declare namespace ns2='" + elementName.getNamespaceURI() + "';" +
    773                         "$this/env:Envelope/env:Body/ns:" + bindingOperation.getName() + (isResponse ? "Response" : "" ) +
    774                         "/ns2:" + elementName.getLocalPart() );
    775
    776 		            if( bodyParts.length == 1 )
    777 		            {
    778 		               SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
    779 		               if( elm != null )
    780 		               {
    781 		   					validateMessageBody(errors, elm.getType(), bodyParts[0]);
    782 		               }
    783 		               else errors.add( XmlError.forMessage("Missing part type in associated schema for [" + elementName + "]" ) );
    784 		            }
    785 		            else errors.add( XmlError.forMessage("Missing message part with name [" + elementName + "]" ));
    786             	}
    787             	else
    788             	{
    789             		errors.add( XmlError.forMessage("Missing message part [" + part.getName() + "]" ));
    790             	}
    791             }
    792             else
    793             {
    794                QName typeName = part.getTypeName();
    795                SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
    796                if( type != null )
    797                {
    798                	validateMessageBody( errors, type, children[0]);
    799                }
    800                else errors.add( XmlError.forMessage("Missing type in associated schema for part [" + part.getName() + "]" ));
    801             }
    802          }
    803       }
    804    }
    805
    806 	private XmlObject[] getRpcBodyPart(BindingOperation bindingOperation, XmlObject msgXml, boolean isResponse) throws Exception
    807 	{
    808 		// rpc requests should use the operation name as root element and soapbind namespaceuri attribute as ns
    809 		String ns = WsdlUtils.getSoapBodyNamespace( isResponse ?
    810 				bindingOperation.getBindingOutput().getExtensibilityElements() :
    811       		bindingOperation.getBindingInput().getExtensibilityElements() );
    812
    813 		if( ns == null || ns.trim().length() == 0 )
    814          ns = wsdlContext.getDefinition().getTargetNamespace();
    815
    816       // get root element
    817       XmlObject[] paths = msgXml.selectPath( "declare namespace env='" + wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    818             "declare namespace ns='" + ns + "';" + "$this/env:Envelope/env:Body/ns:" +
    819             bindingOperation.getName() + (isResponse ? "Response" : "" ));
    820 		return paths;
    821 	}
    822
    823    @SuppressWarnings("unchecked")
    824 	private void validateSoapFault(BindingOperation bindingOperation, XmlObject msgXml, List<XmlError> errors) throws Exception
    825    {
    826       Map faults = bindingOperation.getBindingFaults();
    827       Iterator<BindingFault> i = faults.values().iterator();
    828
    829       while( i.hasNext() )
    830       {
    831          BindingFault bindingFault = i.next();
    832          String faultName = bindingFault.getName();
    833
    834          Part[] faultParts = WsdlUtils.getFaultParts( bindingOperation, faultName );
    835          if( faultParts.length == 0 )
    836          {
    837          	log.warn( "Missing fault parts in wsdl for fault [" + faultName + "] in bindingOperation [" + bindingOperation.getName() + "]" );
    838          	continue;
    839          }
    840
    841          if( faultParts.length != 1 )
    842          {
    843          	log.info( "Too many fault parts in wsdl for fault [" + faultName + "] in bindingOperation [" + bindingOperation.getName() + "]" );
    844          	continue;
    845          }
    846
    847       	Part part = faultParts[0];
    848          QName elementName = part.getElementName();
    849          if( elementName != null )
    850          {
    851             XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    852                		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    853                   "declare namespace ns='" + elementName.getNamespaceURI() + "';" +
    854                   "//env:Fault/detail/ns:" + elementName.getLocalPart() );
    855
    856             if( paths.length == 1 )
    857             {
    858                SchemaGlobalElement elm = wsdlContext.getSchemaTypeLoader().findElement( elementName );
    859                if( elm != null )
    860                {
    861                	validateMessageBody( errors, elm.getType(), paths[0]);
    862                }
    863                else errors.add( XmlError.forMessage("Missing fault part element [" + elementName + "] for fault [" +
    864                			part.getName() + "] in associated schema") );
    865
    866                return;
    867             }
    868          }
    869          // this is not allowed by Basic Profile.. remove?
    870          else if( part.getTypeName() != null )
    871          {
    872             QName typeName = part.getTypeName();
    873
    874             XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    875                		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';" +
    876                   "declare namespace ns='" + typeName.getNamespaceURI() + "';" +
    877                   "//env:Fault/detail/ns:" + part.getName() );
    878
    879             if( paths.length == 1 )
    880             {
    881                SchemaType type = wsdlContext.getSchemaTypeLoader().findType( typeName );
    882                if( type != null )
    883                {
    884                	validateMessageBody( errors, type, paths[0]);
    885                }
    886                else errors.add( XmlError.forMessage("Missing fault part type [" + typeName + "] for fault [" +
    887                			part.getName() + "] in associated schema") );
    888
    889                return;
    890             }
    891          }
    892       }
    893
    894       // if we get here, no matching fault was found.. this is not an error but should be warned..
    895       XmlObject[] paths = msgXml.selectPath( "declare namespace env='" +
    896          		wsdlContext.getSoapVersion().getEnvelopeNamespace() + "';//env:Fault/detail" );
    897
    898       if( paths.length == 0 )
    899       	log.warn( "Missing matching Fault in wsdl for bindingOperation [" + bindingOperation.getName() + "]" );
    900 		else
    901 		{
    902 			String xmlText = paths[0].xmlText( new XmlOptions().setSaveOuter() );
    903 			log.warn( "Missing matching Fault in wsdl for Fault Detail element [" + XmlUtils.removeUnneccessaryNamespaces( xmlText ) +
    904       				"] in bindingOperation [" + bindingOperation.getName() + "]" );
    905 		}
    906    }
    907 }