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.soap;
14  
15  import com.eviware.soapui.SoapUI;
16  import com.eviware.soapui.impl.wsdl.WsdlOperation;
17  import com.eviware.soapui.impl.wsdl.mock.DispatchException;
18  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
19  import com.eviware.soapui.support.StringUtils;
20  import com.eviware.soapui.support.types.StringToStringMap;
21  import com.eviware.soapui.support.xml.XmlUtils;
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 javax.wsdl.BindingOperation;
31  import javax.wsdl.Message;
32  import javax.wsdl.Part;
33  import javax.xml.namespace.QName;
34  import java.util.List;
35  
36  /***
37   * SOAP-related utility-methods..
38   * 
39   * @author ole.matzura
40   */
41  
42  public class SoapUtils
43  {
44  	public static boolean isSoapFault( String responseContent, SoapVersion soapVersion ) throws XmlException
45  	{
46  		if( StringUtils.isNullOrEmpty( responseContent ))
47  			return false;
48  		
49  		// check manually before resource intensive xpath
50  		if( responseContent.indexOf( ":Fault" ) > 0 || responseContent.indexOf( "<Fault" ) > 0 )
51  		{
52  		   XmlObject xml = XmlObject.Factory.parse( responseContent );
53  		   XmlObject[] paths = xml.selectPath( "declare namespace env='" + soapVersion.getEnvelopeNamespace() + "';" + 
54  		         "//env:Fault");
55  		   if( paths.length > 0 )
56  		      return true;
57  		}
58  		
59  		return false;
60  	}
61  
62  	/***
63  	 * Init soapversion from content-type header.. should envelope be checked and/or override? 
64  	 * @param xmlObject 
65  	 */
66  	
67  	public static SoapVersion deduceSoapVersion( String contentType, XmlObject xmlObject )
68  	{
69  		if( xmlObject != null )
70  		{
71  			Element elm = ((Document)(xmlObject.getDomNode())).getDocumentElement();
72  			if( elm.getLocalName().equals("Envelope"))
73  			{
74  				if( elm.getNamespaceURI().equals( SoapVersion.Soap11.getEnvelopeNamespace()))
75  					return SoapVersion.Soap11;
76  				else if( elm.getNamespaceURI().equals( SoapVersion.Soap12.getEnvelopeNamespace()))
77  					return SoapVersion.Soap12;
78  			}
79  		}
80  		
81  		SoapVersion soapVersion = null;
82  		
83  		if( StringUtils.isNullOrEmpty( contentType ) )
84  			return null;
85  		
86  		soapVersion = contentType.startsWith( SoapVersion.Soap11.getContentType() ) ? SoapVersion.Soap11 : null;
87  		soapVersion = soapVersion == null && contentType.startsWith( SoapVersion.Soap12.getContentType() ) ? SoapVersion.Soap12 : soapVersion;
88  		if( soapVersion == null && contentType.startsWith( "application/xop+xml" ))
89  		{
90  			if( contentType.indexOf(  "type=\"" + SoapVersion.Soap11.getContentType() + "\"" ) > 0 )
91  				soapVersion = SoapVersion.Soap11;
92  			else if( contentType.indexOf(  "type=\"" + SoapVersion.Soap12.getContentType() + "\"" ) > 0 )
93  				soapVersion = SoapVersion.Soap12;
94  		}
95  	
96  		return soapVersion;
97  	}
98  
99  	public static String getSoapAction( SoapVersion soapVersion, StringToStringMap headers )
100 	{
101 		String soapAction = null;
102 		String contentType = headers.get( "Content-Type" );
103 		
104 		if( soapVersion == SoapVersion.Soap11 )
105 		{
106 			soapAction = headers.get( "SOAPAction" );
107 		}
108 		else if( soapVersion == SoapVersion.Soap12 )
109 		{
110 			int ix = contentType.indexOf( "action=" );
111 			if( ix > 0 )
112 			{
113 				int endIx = contentType.indexOf( ';', ix );
114 				soapAction = endIx == -1 ? contentType.substring( ix + 7 ) : contentType.substring( ix + 7, endIx );
115 			}
116 		}
117 		
118 		soapAction = StringUtils.unquote( soapAction );
119 		
120 		return soapAction;
121 	}
122 
123 	public static XmlObject getBodyElement(XmlObject messageObject, SoapVersion soapVersion ) throws XmlException
124 	{
125 		XmlObject[] envelope = messageObject.selectChildren( soapVersion.getEnvelopeQName() );
126 		if( envelope.length != 1 )
127 		    throw new XmlException( "Missing/Invalid SOAP Envelope, expecting [" + soapVersion.getEnvelopeQName() + "]" );
128 		
129 		XmlObject[] body = envelope[0].selectChildren( soapVersion.getBodyQName() );
130 		if( body.length != 1 )
131 		    throw new XmlException( "Missing/Invalid SOAP Body, expecting [" + soapVersion.getBodyQName() + "]" );
132 		
133 		return body[0];
134 	}
135 
136 	public static XmlObject getHeaderElement(XmlObject messageObject, SoapVersion soapVersion, boolean create ) throws XmlException
137 	{
138 		XmlObject[] envelope = messageObject.selectChildren( soapVersion.getEnvelopeQName() );
139 		if( envelope.length != 1 )
140 		    throw new XmlException( "Missing/Invalid SOAP Envelope, expecting [" + soapVersion.getEnvelopeQName() + "]" );
141 		
142 		QName headerQName = soapVersion.getHeaderQName();
143 		XmlObject[] header = envelope[0].selectChildren( headerQName );
144 		if( header.length == 0 && create )
145 		{
146          Element elm = (Element) envelope[0].getDomNode();
147          Element headerElement = elm.getOwnerDocument().createElementNS(
148          		headerQName.getNamespaceURI(), headerQName.getLocalPart() );
149          
150 			elm.insertBefore(headerElement, elm.getFirstChild());
151          
152          header = envelope[0].selectChildren( headerQName );
153 		}
154 		
155 		return header.length == 0 ? null : header[0];
156 	}
157 	
158 	
159 	public static XmlObject getContentElement( XmlObject messageObject, SoapVersion soapVersion ) throws XmlException
160 	{
161 		XmlObject bodyElement = SoapUtils.getBodyElement(messageObject, soapVersion);
162 		if( bodyElement != null )
163 		{
164 			XmlCursor cursor = bodyElement.newCursor();
165 			
166 			try
167 			{
168 				if( cursor.toFirstChild() )
169 				{
170 					while( !cursor.isContainer() )
171 						cursor.toNextSibling();
172 
173 					if( cursor.isContainer() )
174 					{
175 						return cursor.getObject();
176 					}
177 				}
178 			}
179 			catch( Exception e )
180 			{
181 				SoapUI.logError( e );
182 			}
183 			finally
184 			{
185 				cursor.dispose();
186 			}
187 		}
188 		
189 		return null;
190 	}
191 
192 	@SuppressWarnings("unchecked")
193 	public static WsdlOperation findOperationForRequest( SoapVersion soapVersion, String soapAction, XmlObject requestContent, 
194 				List<WsdlOperation> operations, boolean requireSoapVersionMatch, boolean requireSoapActionMatch ) throws Exception
195 	{
196 		XmlObject contentElm = getContentElement( requestContent, soapVersion );
197 		if( contentElm == null )
198 			throw new DispatchException( "Missing content element in body" );
199 	
200 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
201 		NodeList contentChildNodes = null;
202 	
203 		for( int c = 0; c < operations.size(); c++ )
204 		{
205 			WsdlOperation wsdlOperation = operations.get( c );
206 			String action = wsdlOperation.getAction();
207 
208 			// matches soapAction?
209 			if( !requireSoapActionMatch || (
210 						( soapAction == null && wsdlOperation.getAction() == null )	|| 
211 						( action != null && action.equals( soapAction )) 
212 						))
213 			{
214 				QName qname = wsdlOperation.getRequestBodyElementQName();
215 
216 				if( !contentQName.equals( qname ) )
217 					continue;
218 				
219 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
220 				
221 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion ) 
222 				{
223 					continue;
224 				}
225 
226 				// check content
227 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
228 				{
229 					// matches!
230 					return wsdlOperation;
231 				}
232 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
233 				{
234 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
235 					Message message = bindingOperation.getOperation().getInput().getMessage();
236 					List<Part> parts = message.getOrderedParts( null );
237 
238 					if( contentChildNodes == null )
239 						contentChildNodes = XmlUtils.getChildElements( ( Element ) contentElm.getDomNode() );
240 
241 					int i = 0;
242 
243 					if( parts.size() > 0 )
244 					{
245 						for( int x = 0; x < parts.size(); x++ )
246 						{
247 							if( WsdlUtils.isAttachmentInputPart( parts.get( x ), bindingOperation ) ||
248 								 WsdlUtils.isHeaderInputPart( parts.get( x ), message, bindingOperation ))
249 							{
250 								parts.remove( x );
251 								x--;
252 							}
253 						}
254 
255 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
256 						{
257 							Node item = contentChildNodes.item( i );
258 							if( item.getNodeType() != Node.ELEMENT_NODE )
259 								continue;
260 
261 							int j = 0;
262 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
263 							{
264 								j++;
265 							}
266 
267 							if( j == parts.size() )
268 								break;
269 
270 							parts.remove( j );
271 						}
272 					}
273 
274 					// match?
275 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
276 					{
277 						return wsdlOperation;
278 					}
279 				}
280 			}
281 		}
282 
283 		throw new DispatchException( "Missing operation for soapAction [" + soapAction + "] and body element ["
284 					+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
285 	}
286 
287    @SuppressWarnings("unchecked")
288 	public static WsdlOperation findOperationForResponse( SoapVersion soapVersion, String soapAction, XmlObject responseContent,
289 				List<WsdlOperation> operations, boolean requireSoapVersionMatch, boolean requireSoapActionMatch ) throws Exception
290 	{
291 		XmlObject contentElm = getContentElement( responseContent, soapVersion );
292 		if( contentElm == null )
293 			throw new DispatchException( "Missing content element in body" );
294 
295 		QName contentQName = XmlUtils.getQName( contentElm.getDomNode() );
296 		NodeList contentChildNodes = null;
297 
298 		for( int c = 0; c < operations.size(); c++ )
299 		{
300 			WsdlOperation wsdlOperation = operations.get( c );
301 			String action = wsdlOperation.getAction();
302 
303 			// matches soapAction?
304 			if( !requireSoapActionMatch || (
305 						( soapAction == null && wsdlOperation.getAction() == null )	||
306 						( action != null && action.equals( soapAction ))
307 						))
308 			{
309 				QName qname = wsdlOperation.getResponseBodyElementQName();
310 
311 				if( !contentQName.equals( qname ) )
312 					continue;
313 
314 				SoapVersion ifaceSoapVersion = wsdlOperation.getInterface().getSoapVersion();
315 
316 				if( requireSoapVersionMatch && ifaceSoapVersion != soapVersion )
317 				{
318 					continue;
319 				}
320 
321 				// check content
322 				if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_DOCUMENT ) )
323 				{
324 					// matches!
325 					return wsdlOperation;
326 				}
327 				else if( wsdlOperation.getStyle().equals( WsdlOperation.STYLE_RPC ) )
328 				{
329 					BindingOperation bindingOperation = wsdlOperation.getBindingOperation();
330 					Message message = bindingOperation.getOperation().getOutput().getMessage();
331 					List<Part> parts = message.getOrderedParts( null );
332 
333 					if( contentChildNodes == null )
334 						contentChildNodes = XmlUtils.getChildElements( ( Element ) contentElm.getDomNode() );
335 
336 					int i = 0;
337 
338 					if( parts.size() > 0 )
339 					{
340 						for( int x = 0; x < parts.size(); x++ )
341 						{
342 							if( WsdlUtils.isAttachmentOutputPart( parts.get( x ), bindingOperation ) ||
343 								 WsdlUtils.isHeaderOutputPart( parts.get( x ), message, bindingOperation ))
344 							{
345 								parts.remove( x );
346 								x--;
347 							}
348 						}
349 
350 						for( ; i < contentChildNodes.getLength() && !parts.isEmpty(); i++ )
351 						{
352 							Node item = contentChildNodes.item( i );
353 							if( item.getNodeType() != Node.ELEMENT_NODE )
354 								continue;
355 
356 							int j = 0;
357 							while( ( j < parts.size() ) && ( !item.getNodeName().equals( parts.get( j ).getName() ) ) )
358 							{
359 								j++;
360 							}
361 
362 							if( j == parts.size() )
363 								break;
364 
365 							parts.remove( j );
366 						}
367 					}
368 
369 					// match?
370 					if( i == contentChildNodes.getLength() && parts.isEmpty() )
371 					{
372 						return wsdlOperation;
373 					}
374 				}
375 			}
376 		}
377 
378 		throw new DispatchException( "Missing response operation for soapAction [" + soapAction + "] and body element ["
379 					+ contentQName + "] with SOAP Version [" + soapVersion + "]" );
380 	}
381 
382 
383 	public static String removeEmptySoapHeaders( String content, SoapVersion soapVersion ) throws XmlException
384 	{
385 		XmlObject xmlObject = XmlObject.Factory.parse( content );
386 		XmlObject[] selectPath = xmlObject.selectPath( "declare namespace soap='" + soapVersion.getEnvelopeNamespace() + "';/soap:Envelope/soap:Header" );
387 		if( selectPath.length > 0 )
388 		{
389 			Node domNode = selectPath[0].getDomNode();
390 			if( !domNode.hasChildNodes() && !domNode.hasAttributes())
391 			{
392 				domNode.getParentNode().removeChild( domNode );
393 				return xmlObject.xmlText();
394 			}
395 		}
396 		
397 		return content;
398 	}
399 
400 	public static SoapVersion deduceSoapVersion(String requestContentType, String requestContent)
401 	{
402 		try
403 		{
404 			return deduceSoapVersion(requestContentType, XmlObject.Factory.parse(requestContent));
405 		}
406 		catch (XmlException e)
407 		{
408 			return deduceSoapVersion(requestContentType,(XmlObject)null);
409 		}
410 	}
411 }