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