View Javadoc

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