View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 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.soap;
14  
15  import java.util.List;
16  
17  import javax.wsdl.BindingOperation;
18  import javax.wsdl.Message;
19  import javax.wsdl.Part;
20  import javax.xml.namespace.QName;
21  
22  import org.apache.xmlbeans.XmlCursor;
23  import org.apache.xmlbeans.XmlException;
24  import org.apache.xmlbeans.XmlObject;
25  import org.w3c.dom.Document;
26  import org.w3c.dom.Element;
27  import org.w3c.dom.Node;
28  import org.w3c.dom.NodeList;
29  
30  import com.eviware.soapui.SoapUI;
31  import com.eviware.soapui.impl.wsdl.WsdlOperation;
32  import com.eviware.soapui.impl.wsdl.mock.DispatchException;
33  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
34  import com.eviware.soapui.model.iface.Attachment;
35  import com.eviware.soapui.support.StringUtils;
36  import com.eviware.soapui.support.types.StringToStringsMap;
37  import com.eviware.soapui.support.xml.XmlUtils;
38  
39  /***
40   * SOAP-related utility-methods..
41   * 
42   * @author ole.matzura
43   */
44  
45  public class SoapUtils
46  {
47  	public static boolean isSoapFault( String responseContent, SoapVersion soapVersion ) throws XmlException
48  	{
49  		if( StringUtils.isNullOrEmpty( responseContent ) )
50  			return false;
51  
52  		// check manually before resource intensive xpath
53  		if( responseContent.indexOf( ":Fault" ) > 0 || responseContent.indexOf( "<Fault" ) > 0 )
54  		{
55  			XmlObject xml = XmlObject.Factory.parse( responseContent );
56  			XmlObject[] paths = xml.selectPath( "declare namespace env='" + soapVersion.getEnvelopeNamespace() + "';"
57  					+ "//env:Fault" );
58  			if( paths.length > 0 )
59  				return true;
60  		}
61  
62  		return false;
63  	}
64  
65  	/***
66  	 * Init soapversion from content-type header.. should envelope be checked
67  	 * and/or override?
68  	 * 
69  	 * @param xmlObject
70  	 */
71  
72  	public static SoapVersion deduceSoapVersion( String contentType, XmlObject xmlObject )
73  	{
74  		if( xmlObject != null )
75  		{
76  			Element elm = ( ( Document )( xmlObject.getDomNode() ) ).getDocumentElement();
77  			if( elm.getLocalName().equals( "Envelope" ) )
78  			{
79  				if( elm.getNamespaceURI().equals( SoapVersion.Soap11.getEnvelopeNamespace() ) )
80  					return SoapVersion.Soap11;
81  				else if( elm.getNamespaceURI().equals( SoapVersion.Soap12.getEnvelopeNamespace() ) )
82  					return SoapVersion.Soap12;
83  			}
84  		}
85  
86  		SoapVersion soapVersion = null;
87  
88  		if( StringUtils.isNullOrEmpty( contentType ) )
89  			return null;
90  
91  		soapVersion = contentType.startsWith( SoapVersion.Soap11.getContentType() ) ? SoapVersion.Soap11 : null;
92  		soapVersion = soapVersion == null && contentType.startsWith( SoapVersion.Soap12.getContentType() ) ? SoapVersion.Soap12
93  				: soapVersion;
94  		if( soapVersion == null && contentType.startsWith( "application/xop+xml" ) )
95  		{
96  			if( contentType.indexOf( "type=\"" + SoapVersion.Soap11.getContentType() + "\"" ) > 0 )
97  				soapVersion = SoapVersion.Soap11;
98  			else if( contentType.indexOf( "type=\"" + SoapVersion.Soap12.getContentType() + "\"" ) > 0 )
99  				soapVersion = SoapVersion.Soap12;
100 		}
101 
102 		return soapVersion;
103 	}
104 
105 	public static String getSoapAction( SoapVersion soapVersion, StringToStringsMap headers )
106 	{
107 		String soapAction = null;
108 		String contentType = headers.get( "Content-Type", "" );
109 
110 		if( soapVersion == SoapVersion.Soap11 )
111 		{
112 			soapAction = headers.get( "SOAPAction", "" );
113 		}
114 		else if( soapVersion == SoapVersion.Soap12 )
115 		{
116 			int ix = contentType.indexOf( "action=" );
117 			if( ix > 0 )
118 			{
119 				int endIx = contentType.indexOf( ';', ix );
120 				soapAction = endIx == -1 ? contentType.substring( ix + 7 ) : contentType.substring( ix + 7, endIx );
121 			}
122 		}
123 
124 		soapAction = StringUtils.unquote( soapAction );
125 
126 		return soapAction;
127 	}
128 
129 	public static XmlObject getBodyElement( XmlObject messageObject, SoapVersion soapVersion ) throws XmlException
130 	{
131 		XmlObject[] envelope = messageObject.selectChildren( soapVersion.getEnvelopeQName() );
132 		if( envelope.length != 1 )
133 			throw new XmlException( "Missing/Invalid SOAP Envelope, expecting [" + soapVersion.getEnvelopeQName() + "]" );
134 
135 		XmlObject[] body = envelope[0].selectChildren( soapVersion.getBodyQName() );
136 		if( body.length != 1 )
137 			throw new XmlException( "Missing/Invalid SOAP Body, expecting [" + soapVersion.getBodyQName() + "]" );
138 
139 		return body[0];
140 	}
141 
142 	public static XmlObject getHeaderElement( XmlObject messageObject, SoapVersion soapVersion, boolean create )
143 			throws XmlException
144 	{
145 		XmlObject[] envelope = messageObject.selectChildren( soapVersion.getEnvelopeQName() );
146 		if( envelope.length != 1 )
147 			throw new XmlException( "Missing/Invalid SOAP Envelope, expecting [" + soapVersion.getEnvelopeQName() + "]" );
148 
149 		QName headerQName = soapVersion.getHeaderQName();
150 		XmlObject[] header = envelope[0].selectChildren( headerQName );
151 		if( header.length == 0 && create )
152 		{
153 			Element elm = ( Element )envelope[0].getDomNode();
154 			Element headerElement = elm.getOwnerDocument().createElementNS( headerQName.getNamespaceURI(),
155 					headerQName.getLocalPart() );
156 
157 			elm.insertBefore( headerElement, elm.getFirstChild() );
158 
159 			header = envelope[0].selectChildren( headerQName );
160 		}
161 
162 		return header.length == 0 ? null : header[0];
163 	}
164 
165 	public static XmlObject getContentElement( XmlObject messageObject, SoapVersion soapVersion ) throws XmlException
166 	{
167 		if( messageObject == null )
168 			return null;
169 
170 		XmlObject bodyElement = SoapUtils.getBodyElement( messageObject, soapVersion );
171 		if( bodyElement != null )
172 		{
173 			XmlCursor cursor = bodyElement.newCursor();
174 
175 			try
176 			{
177 				if( cursor.toFirstChild() )
178 				{
179 					while( !cursor.isContainer() )
180 						cursor.toNextSibling();
181 
182 					if( cursor.isContainer() )
183 					{
184 						return cursor.getObject();
185 					}
186 				}
187 			}
188 			catch( Exception e )
189 			{
190 				SoapUI.logError( e );
191 			}
192 			finally
193 			{
194 				cursor.dispose();
195 			}
196 		}
197 
198 		return null;
199 	}
200 
201 	@SuppressWarnings( "unchecked" )
202 	public static WsdlOperation findOperationForRequest( SoapVersion soapVersion, String soapAction,
203 			XmlObject requestContent, List<WsdlOperation> operations, boolean requireSoapVersionMatch,
204 			boolean requireSoapActionMatch, Attachment[] attachments ) throws Exception
205 	{
206 		XmlObject contentElm = getContentElement( requestContent, soapVersion );
207 		if( contentElm == null )
208 		{
209 			for( WsdlOperation operation : operations )
210 			{
211 				if( operation.getAction().equals( soapAction )
212 						&& operation.getBindingOperation().getOperation().getInput().getMessage().getParts().size() == 0 )
213 				{
214 					return operation;
215 				}
216 			}
217 
218 			return null;
219 		}
220 
221 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
222 		NodeList contentChildNodes = null;
223 
224 		for( int c = 0; c < operations.size(); c++ )
225 		{
226 			WsdlOperation wsdlOperation = operations.get( c );
227 			String action = wsdlOperation.getAction();
228 
229 			// matches soapAction?
230 			if( !requireSoapActionMatch
231 					|| ( ( soapAction == null && wsdlOperation.getAction() == null ) || ( action != null && action
232 							.equals( soapAction ) ) ) )
233 			{
234 				QName qname = wsdlOperation.getRequestBodyElementQName();
235 
236 				if( !contentQName.equals( qname ) )
237 					continue;
238 
239 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
240 
241 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion )
242 				{
243 					continue;
244 				}
245 
246 				// check content
247 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
248 				{
249 					// check that all attachments match
250 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
251 					Message message = bindingOperation.getOperation().getInput().getMessage();
252 					List<Part> parts = message.getOrderedParts( null );
253 
254 					for( int x = 0; x < parts.size(); x++ )
255 					{
256 						// check for attachment part
257 						if( WsdlUtils.isAttachmentInputPart( parts.get( x ), bindingOperation ) )
258 						{
259 							for( Attachment attachment : attachments )
260 							{
261 								if( attachment.getPart().equals( parts.get( x ).getName() ) )
262 								{
263 									parts.remove( x );
264 									x-- ;
265 								}
266 							}
267 						}
268 						else
269 						{
270 							parts.remove( x );
271 							x-- ;
272 						}
273 					}
274 
275 					// matches!
276 					if( parts.isEmpty() )
277 					{
278 						return wsdlOperation;
279 					}
280 				}
281 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
282 				{
283 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
284 					Message message = bindingOperation.getOperation().getInput().getMessage();
285 					List<Part> parts = message.getOrderedParts( null );
286 
287 					if( contentChildNodes == null )
288 						contentChildNodes = XmlUtils.getChildElements( ( Element )contentElm.getDomNode() );
289 
290 					int i = 0;
291 
292 					if( parts.size() > 0 )
293 					{
294 						for( int x = 0; x < parts.size(); x++ )
295 						{
296 							if( WsdlUtils.isAttachmentInputPart( parts.get( x ), bindingOperation ) )
297 							{
298 								for( Attachment attachment : attachments )
299 								{
300 									if( attachment.getPart().equals( parts.get( x ).getName() ) )
301 									{
302 										parts.remove( x );
303 										x-- ;
304 									}
305 								}
306 							}
307 
308 							// ignore header parts for now..
309 							if( x >= 0 && WsdlUtils.isHeaderInputPart( parts.get( x ), message, bindingOperation ) )
310 							{
311 								parts.remove( x );
312 								x-- ;
313 							}
314 						}
315 
316 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
317 						{
318 							Node item = contentChildNodes.item( i );
319 							if( item.getNodeType() != Node.ELEMENT_NODE )
320 								continue;
321 
322 							int j = 0;
323 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
324 							{
325 								Part part = parts.get( j );
326 								if( part.getElementName() != null )
327 								{
328 									QName qn = part.getElementName();
329 									if( item.getLocalName().equals( qn.getLocalPart() )
330 											&& item.getNamespaceURI().equals( qn.getNamespaceURI() ) )
331 										break;
332 								}
333 								else
334 								{
335 									if( item.getNodeName().equals( parts.get( j ).getName() ) )
336 										break;
337 								}
338 
339 								j++ ;
340 							}
341 
342 							if( j == parts.size() )
343 								break;
344 
345 							parts.remove( j );
346 						}
347 					}
348 
349 					// match?
350 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
351 					{
352 						return wsdlOperation;
353 					}
354 				}
355 			}
356 		}
357 
358 		throw new DispatchException( "Missing operation for soapAction [" + soapAction + "] and body element ["
359 				+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
360 	}
361 
362 	@SuppressWarnings( "unchecked" )
363 	public static WsdlOperation findOperationForResponse( SoapVersion soapVersion, String soapAction,
364 			XmlObject responseContent, List<WsdlOperation> operations, boolean requireSoapVersionMatch,
365 			boolean requireSoapActionMatch ) throws Exception
366 	{
367 		XmlObject contentElm = getContentElement( responseContent, soapVersion );
368 		if( contentElm == null )
369 			return null;
370 
371 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
372 		NodeList contentChildNodes = null;
373 
374 		for( int c = 0; c < operations.size(); c++ )
375 		{
376 			WsdlOperation wsdlOperation = operations.get( c );
377 			String action = wsdlOperation.getAction();
378 
379 			// matches soapAction?
380 			if( !requireSoapActionMatch
381 					|| ( ( soapAction == null && wsdlOperation.getAction() == null ) || ( action != null && action
382 							.equals( soapAction ) ) ) )
383 			{
384 				QName qname = wsdlOperation.getResponseBodyElementQName();
385 
386 				if( !contentQName.equals( qname ) )
387 					continue;
388 
389 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
390 
391 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion )
392 				{
393 					continue;
394 				}
395 
396 				// check content
397 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
398 				{
399 					// matches!
400 					return wsdlOperation;
401 				}
402 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
403 				{
404 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
405 					Message message = bindingOperation.getOperation().getOutput().getMessage();
406 					List<Part> parts = message.getOrderedParts( null );
407 
408 					if( contentChildNodes == null )
409 						contentChildNodes = XmlUtils.getChildElements( ( Element )contentElm.getDomNode() );
410 
411 					int i = 0;
412 
413 					if( parts.size() > 0 )
414 					{
415 						for( int x = 0; x < parts.size(); x++ )
416 						{
417 							if( WsdlUtils.isAttachmentOutputPart( parts.get( x ), bindingOperation )
418 									|| WsdlUtils.isHeaderOutputPart( parts.get( x ), message, bindingOperation ) )
419 							{
420 								parts.remove( x );
421 								x-- ;
422 							}
423 						}
424 
425 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
426 						{
427 							Node item = contentChildNodes.item( i );
428 							if( item.getNodeType() != Node.ELEMENT_NODE )
429 								continue;
430 
431 							int j = 0;
432 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
433 							{
434 								Part part = parts.get( j );
435 								if( part.getElementName() != null )
436 								{
437 									QName qn = part.getElementName();
438 									if( item.getLocalName().equals( qn.getLocalPart() )
439 											&& item.getNamespaceURI().equals( qn.getNamespaceURI() ) )
440 										break;
441 								}
442 								else
443 								{
444 									if( item.getNodeName().equals( parts.get( j ).getName() ) )
445 										break;
446 								}
447 
448 								j++ ;
449 							}
450 
451 							if( j == parts.size() )
452 								break;
453 
454 							parts.remove( j );
455 						}
456 					}
457 
458 					// match?
459 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
460 					{
461 						return wsdlOperation;
462 					}
463 				}
464 			}
465 		}
466 
467 		throw new DispatchException( "Missing response operation for soapAction [" + soapAction + "] and body element ["
468 				+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
469 	}
470 
471 	public static String removeEmptySoapHeaders( String content, SoapVersion soapVersion ) throws XmlException
472 	{
473 		XmlObject xmlObject = XmlObject.Factory.parse( content );
474 		XmlObject[] selectPath = xmlObject.selectPath( "declare namespace soap='" + soapVersion.getEnvelopeNamespace()
475 				+ "';/soap:Envelope/soap:Header" );
476 		if( selectPath.length > 0 )
477 		{
478 			Node domNode = selectPath[0].getDomNode();
479 			if( !domNode.hasChildNodes() && !domNode.hasAttributes() )
480 			{
481 				domNode.getParentNode().removeChild( domNode );
482 				return xmlObject.xmlText();
483 			}
484 		}
485 
486 		return content;
487 	}
488 
489 	public static SoapVersion deduceSoapVersion( String requestContentType, String requestContent )
490 	{
491 		try
492 		{
493 			return deduceSoapVersion( requestContentType, XmlObject.Factory.parse( requestContent ) );
494 		}
495 		catch( XmlException e )
496 		{
497 			return deduceSoapVersion( requestContentType, ( XmlObject )null );
498 		}
499 	}
500 
501 	public static String transferSoapHeaders( String requestContent, String newRequest, SoapVersion soapVersion )
502 	{
503 		try
504 		{
505 			XmlObject source = XmlObject.Factory.parse( requestContent );
506 			String headerXPath = "declare namespace ns='" + soapVersion.getEnvelopeNamespace() + "'; //ns:Header";
507 			XmlObject[] header = source.selectPath( headerXPath );
508 			if( header.length == 1 )
509 			{
510 				Element headerElm = ( Element )header[0].getDomNode();
511 				NodeList childNodes = headerElm.getChildNodes();
512 				if( childNodes.getLength() > 0 )
513 				{
514 					XmlObject dest = XmlObject.Factory.parse( newRequest );
515 					header = dest.selectPath( headerXPath );
516 					Element destElm = null;
517 
518 					if( header.length == 0 )
519 					{
520 						Element docElm = ( ( Document )dest.getDomNode() ).getDocumentElement();
521 
522 						destElm = ( Element )docElm.insertBefore( docElm.getOwnerDocument().createElementNS(
523 								soapVersion.getEnvelopeNamespace(), docElm.getPrefix() + ":Header" ), XmlUtils
524 								.getFirstChildElementNS( docElm, soapVersion.getBodyQName() ) );
525 					}
526 					else
527 					{
528 						destElm = ( Element )header[0].getDomNode();
529 					}
530 
531 					for( int c = 0; c < childNodes.getLength(); c++ )
532 					{
533 						destElm.appendChild( destElm.getOwnerDocument().importNode( childNodes.item( c ), true ) );
534 					}
535 
536 					return dest.xmlText();
537 				}
538 			}
539 		}
540 		catch( XmlException e )
541 		{
542 			SoapUI.logError( e );
543 		}
544 
545 		return newRequest;
546 	}
547 }