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() + "';" +
897             "//env:Fault/detail" );
898       
899       if( paths.length == 0 )
900       	log.warn( "Missing matching Fault in wsdl for bindingOperation [" + bindingOperation.getName() + "]" );
901       else
902       	log.warn( "Missing matching Fault in wsdl for Fault Detail element [" + 
903       				XmlUtils.getFirstChildElement( ( Element ) paths[0].getDomNode() ).getNodeName() + 
904       				"] in bindingOperation [" + bindingOperation.getName() + "]" );
905    }
906 }