View Javadoc

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