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.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.StringToStringMap;
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, StringToStringMap 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 		XmlObject bodyElement = SoapUtils.getBodyElement( messageObject, soapVersion );
168 		if( bodyElement != null )
169 		{
170 			XmlCursor cursor = bodyElement.newCursor();
171 
172 			try
173 			{
174 				if( cursor.toFirstChild() )
175 				{
176 					while( !cursor.isContainer() )
177 						cursor.toNextSibling();
178 
179 					if( cursor.isContainer() )
180 					{
181 						return cursor.getObject();
182 					}
183 				}
184 			}
185 			catch( Exception e )
186 			{
187 				SoapUI.logError( e );
188 			}
189 			finally
190 			{
191 				cursor.dispose();
192 			}
193 		}
194 
195 		return null;
196 	}
197 
198 	@SuppressWarnings( "unchecked" )
199 	public static WsdlOperation findOperationForRequest( SoapVersion soapVersion, String soapAction,
200 			XmlObject requestContent, List<WsdlOperation> operations, boolean requireSoapVersionMatch,
201 			boolean requireSoapActionMatch, Attachment[] attachments ) throws Exception
202 	{
203 		XmlObject contentElm = getContentElement( requestContent, soapVersion );
204 		if( contentElm == null )
205 		{
206 			for( WsdlOperation operation : operations )
207 			{
208 				if( operation.getAction().equals( soapAction )
209 						&& operation.getBindingOperation().getOperation().getInput().getMessage().getParts().size() == 0 )
210 				{
211 					return operation;
212 				}
213 			}
214 		}
215 
216 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
217 		NodeList contentChildNodes = null;
218 
219 		for( int c = 0; c < operations.size(); c++ )
220 		{
221 			WsdlOperation wsdlOperation = operations.get( c );
222 			String action = wsdlOperation.getAction();
223 
224 			// matches soapAction?
225 			if( !requireSoapActionMatch
226 					|| ( ( soapAction == null && wsdlOperation.getAction() == null ) || ( action != null && action
227 							.equals( soapAction ) ) ) )
228 			{
229 				QName qname = wsdlOperation.getRequestBodyElementQName();
230 
231 				if( !contentQName.equals( qname ) )
232 					continue;
233 
234 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
235 
236 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion )
237 				{
238 					continue;
239 				}
240 
241 				// check content
242 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
243 				{
244 					// check that all attachments match
245 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
246 					Message message = bindingOperation.getOperation().getInput().getMessage();
247 					List<Part> parts = message.getOrderedParts( null );
248 
249 					for( int x = 0; x < parts.size(); x++ )
250 					{
251 						// check for attachment part
252 						if( WsdlUtils.isAttachmentInputPart( parts.get( x ), bindingOperation ) )
253 						{
254 							for( Attachment attachment : attachments )
255 							{
256 								if( attachment.getPart().equals( parts.get( x ).getName() ) )
257 								{
258 									parts.remove( x );
259 									x-- ;
260 								}
261 							}
262 						}
263 						else
264 						{
265 							parts.remove( x );
266 							x-- ;
267 						}
268 					}
269 
270 					// matches!
271 					if( parts.isEmpty() )
272 					{
273 						return wsdlOperation;
274 					}
275 				}
276 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
277 				{
278 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
279 					Message message = bindingOperation.getOperation().getInput().getMessage();
280 					List<Part> parts = message.getOrderedParts( null );
281 
282 					if( contentChildNodes == null )
283 						contentChildNodes = XmlUtils.getChildElements( ( Element )contentElm.getDomNode() );
284 
285 					int i = 0;
286 
287 					if( parts.size() > 0 )
288 					{
289 						for( int x = 0; x < parts.size(); x++ )
290 						{
291 							if( WsdlUtils.isAttachmentInputPart( parts.get( x ), bindingOperation ) )
292 							{
293 								for( Attachment attachment : attachments )
294 								{
295 									if( attachment.getPart().equals( parts.get( x ).getName() ) )
296 									{
297 										parts.remove( x );
298 										x-- ;
299 									}
300 								}
301 							}
302 
303 							// ignore header parts for now..
304 							if( WsdlUtils.isHeaderInputPart( parts.get( x ), message, bindingOperation ) )
305 							{
306 								parts.remove( x );
307 								x-- ;
308 							}
309 						}
310 
311 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
312 						{
313 							Node item = contentChildNodes.item( i );
314 							if( item.getNodeType() != Node.ELEMENT_NODE )
315 								continue;
316 
317 							int j = 0;
318 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
319 							{
320 								Part part = parts.get( j );
321 								if( part.getElementName() != null )
322 								{
323 									QName qn = part.getElementName();
324 									if( item.getLocalName().equals( qn.getLocalPart() )
325 											&& item.getNamespaceURI().equals( qn.getNamespaceURI() ) )
326 										break;
327 								}
328 								else
329 								{
330 									if( item.getNodeName().equals( parts.get( j ).getName() ) )
331 										break;
332 								}
333 
334 								j++ ;
335 							}
336 
337 							if( j == parts.size() )
338 								break;
339 
340 							parts.remove( j );
341 						}
342 					}
343 
344 					// match?
345 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
346 					{
347 						return wsdlOperation;
348 					}
349 				}
350 			}
351 		}
352 
353 		throw new DispatchException( "Missing operation for soapAction [" + soapAction + "] and body element ["
354 				+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
355 	}
356 
357 	@SuppressWarnings( "unchecked" )
358 	public static WsdlOperation findOperationForResponse( SoapVersion soapVersion, String soapAction,
359 			XmlObject responseContent, List<WsdlOperation> operations, boolean requireSoapVersionMatch,
360 			boolean requireSoapActionMatch ) throws Exception
361 	{
362 		XmlObject contentElm = getContentElement( responseContent, soapVersion );
363 		if( contentElm == null )
364 			return null;
365 
366 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
367 		NodeList contentChildNodes = null;
368 
369 		for( int c = 0; c < operations.size(); c++ )
370 		{
371 			WsdlOperation wsdlOperation = operations.get( c );
372 			String action = wsdlOperation.getAction();
373 
374 			// matches soapAction?
375 			if( !requireSoapActionMatch
376 					|| ( ( soapAction == null && wsdlOperation.getAction() == null ) || ( action != null && action
377 							.equals( soapAction ) ) ) )
378 			{
379 				QName qname = wsdlOperation.getResponseBodyElementQName();
380 
381 				if( !contentQName.equals( qname ) )
382 					continue;
383 
384 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
385 
386 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion )
387 				{
388 					continue;
389 				}
390 
391 				// check content
392 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
393 				{
394 					// matches!
395 					return wsdlOperation;
396 				}
397 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
398 				{
399 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
400 					Message message = bindingOperation.getOperation().getOutput().getMessage();
401 					List<Part> parts = message.getOrderedParts( null );
402 
403 					if( contentChildNodes == null )
404 						contentChildNodes = XmlUtils.getChildElements( ( Element )contentElm.getDomNode() );
405 
406 					int i = 0;
407 
408 					if( parts.size() > 0 )
409 					{
410 						for( int x = 0; x < parts.size(); x++ )
411 						{
412 							if( WsdlUtils.isAttachmentOutputPart( parts.get( x ), bindingOperation )
413 									|| WsdlUtils.isHeaderOutputPart( parts.get( x ), message, bindingOperation ) )
414 							{
415 								parts.remove( x );
416 								x-- ;
417 							}
418 						}
419 
420 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
421 						{
422 							Node item = contentChildNodes.item( i );
423 							if( item.getNodeType() != Node.ELEMENT_NODE )
424 								continue;
425 
426 							int j = 0;
427 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
428 							{
429 								Part part = parts.get( j );
430 								if( part.getElementName() != null )
431 								{
432 									QName qn = part.getElementName();
433 									if( item.getLocalName().equals( qn.getLocalPart() )
434 											&& item.getNamespaceURI().equals( qn.getNamespaceURI() ) )
435 										break;
436 								}
437 								else
438 								{
439 									if( item.getNodeName().equals( parts.get( j ).getName() ) )
440 										break;
441 								}
442 
443 								j++ ;
444 							}
445 
446 							if( j == parts.size() )
447 								break;
448 
449 							parts.remove( j );
450 						}
451 					}
452 
453 					// match?
454 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
455 					{
456 						return wsdlOperation;
457 					}
458 				}
459 			}
460 		}
461 
462 		throw new DispatchException( "Missing response operation for soapAction [" + soapAction + "] and body element ["
463 				+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
464 	}
465 
466 	public static String removeEmptySoapHeaders( String content, SoapVersion soapVersion ) throws XmlException
467 	{
468 		XmlObject xmlObject = XmlObject.Factory.parse( content );
469 		XmlObject[] selectPath = xmlObject.selectPath( "declare namespace soap='" + soapVersion.getEnvelopeNamespace()
470 				+ "';/soap:Envelope/soap:Header" );
471 		if( selectPath.length > 0 )
472 		{
473 			Node domNode = selectPath[0].getDomNode();
474 			if( !domNode.hasChildNodes() && !domNode.hasAttributes() )
475 			{
476 				domNode.getParentNode().removeChild( domNode );
477 				return xmlObject.xmlText();
478 			}
479 		}
480 
481 		return content;
482 	}
483 
484 	public static SoapVersion deduceSoapVersion( String requestContentType, String requestContent )
485 	{
486 		try
487 		{
488 			return deduceSoapVersion( requestContentType, XmlObject.Factory.parse( requestContent ) );
489 		}
490 		catch( XmlException e )
491 		{
492 			return deduceSoapVersion( requestContentType, ( XmlObject )null );
493 		}
494 	}
495 	
496 	public static String transferSoapHeaders( String requestContent, String newRequest, SoapVersion soapVersion )
497 	{
498 		try
499 		{
500 			XmlObject source = XmlObject.Factory.parse( requestContent );
501 			String headerXPath = "declare namespace ns='" + soapVersion.getEnvelopeNamespace() + "'; //ns:Header";
502 			XmlObject[] header = source.selectPath( headerXPath );
503 			if( header.length == 1 )
504 			{
505 				Element headerElm = ( Element )header[0].getDomNode();
506 				NodeList childNodes = headerElm.getChildNodes();
507 				if( childNodes.getLength() > 0 )
508 				{
509 					XmlObject dest = XmlObject.Factory.parse( newRequest );
510 					header = dest.selectPath( headerXPath );
511 					Element destElm = null;
512 
513 					if( header.length == 0 )
514 					{
515 						Element docElm = ( ( Document )dest.getDomNode() ).getDocumentElement();
516 
517 						destElm = ( Element )docElm.insertBefore( docElm.getOwnerDocument().createElementNS(
518 								soapVersion.getEnvelopeNamespace(), docElm.getPrefix() + ":Header" ), XmlUtils
519 								.getFirstChildElementNS( docElm, soapVersion.getBodyQName() ) );
520 					}
521 					else
522 					{
523 						destElm = ( Element )header[0].getDomNode();
524 					}
525 
526 					for( int c = 0; c < childNodes.getLength(); c++ )
527 					{
528 						destElm.appendChild( destElm.getOwnerDocument().importNode( childNodes.item( c ), true ) );
529 					}
530 
531 					return dest.xmlText();
532 				}
533 			}
534 		}
535 		catch( XmlException e )
536 		{
537 			SoapUI.logError( e );
538 		}
539 
540 		return newRequest;
541 	}
542 }