View Javadoc

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