View Javadoc

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