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.mock;
14  
15  import java.io.File;
16  import java.io.FileInputStream;
17  import java.io.IOException;
18  import java.io.PrintWriter;
19  import java.io.StringReader;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  import javax.wsdl.Definition;
31  import javax.wsdl.Import;
32  import javax.wsdl.factory.WSDLFactory;
33  import javax.wsdl.xml.WSDLWriter;
34  
35  import org.xml.sax.InputSource;
36  
37  import com.eviware.soapui.SoapUI;
38  import com.eviware.soapui.impl.WsdlInterfaceFactory;
39  import com.eviware.soapui.impl.support.definition.export.WsdlDefinitionExporter;
40  import com.eviware.soapui.impl.wsdl.WsdlInterface;
41  import com.eviware.soapui.impl.wsdl.WsdlOperation;
42  import com.eviware.soapui.impl.wsdl.support.soap.SoapMessageBuilder;
43  import com.eviware.soapui.impl.wsdl.support.soap.SoapUtils;
44  import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
45  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
46  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
47  import com.eviware.soapui.model.iface.Interface;
48  import com.eviware.soapui.model.mock.MockResult;
49  import com.eviware.soapui.model.mock.MockRunListener;
50  import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
51  import com.eviware.soapui.model.support.AbstractMockRunner;
52  import com.eviware.soapui.model.support.ModelSupport;
53  import com.eviware.soapui.monitor.MockEngine;
54  import com.eviware.soapui.support.StringUtils;
55  import com.eviware.soapui.support.Tools;
56  import com.eviware.soapui.support.editor.inspectors.attachments.ContentTypeHandler;
57  import com.eviware.soapui.support.types.StringToStringMap;
58  import com.eviware.soapui.support.xml.XmlUtils;
59  
60  /***
61   * MockRunner that dispatches Http Requests to their designated
62   * WsdlMockOperation if possible
63   * 
64   * @author ole.matzura
65   */
66  
67  public class WsdlMockRunner extends AbstractMockRunner
68  {
69  	private WsdlMockService mockService;
70  	private final List<WsdlMockResult> mockResults = Collections.synchronizedList( new LinkedList<WsdlMockResult>() );
71  	private long maxResults = 100;
72  	private int removed = 0;
73  	private final WsdlMockRunContext mockContext;
74  	private final Map<String, StringToStringMap> wsdlCache = new HashMap<String, StringToStringMap>();
75  	private boolean running;
76  	private boolean logEnabled = true;
77  
78  	public WsdlMockRunner( WsdlMockService mockService, WsdlTestRunContext context ) throws Exception
79  	{
80  		this.mockService = mockService;
81  
82  		Set<WsdlInterface> interfaces = new HashSet<WsdlInterface>();
83  
84  		for( int i = 0; i < mockService.getMockOperationCount(); i++ )
85  		{
86  			WsdlOperation operation = mockService.getMockOperationAt( i ).getOperation();
87  			if( operation != null )
88  				interfaces.add( operation.getInterface() );
89  		}
90  
91  		for( WsdlInterface iface : interfaces )
92  			iface.getWsdlContext().loadIfNecessary();
93  
94  		mockContext = new WsdlMockRunContext( mockService, context );
95  
96  		mockService.runStartScript( mockContext, this );
97  
98  		SoapUI.getMockEngine().startMockService( this );
99  		running = true;
100 
101 		MockRunListener[] mockRunListeners = mockService.getMockRunListeners();
102 
103 		for( MockRunListener listener : mockRunListeners )
104 		{
105 			listener.onMockRunnerStart( this );
106 		}
107 
108 		initWsdlCache();
109 	}
110 
111 	private void initWsdlCache()
112 	{
113 		for( Interface iface : mockService.getMockedInterfaces() )
114 		{
115 			if( !iface.getInterfaceType().equals( WsdlInterfaceFactory.WSDL_TYPE ) )
116 				continue;
117 
118 			try
119 			{
120 				WsdlDefinitionExporter exporter = new WsdlDefinitionExporter( ( WsdlInterface )iface );
121 
122 				String wsdlPrefix = getInterfacePrefix( iface ).substring( 1 );
123 				StringToStringMap parts = exporter.createFilesForExport( "/" + wsdlPrefix + "&part=" );
124 
125 				for( String key : parts.keySet() )
126 				{
127 					if( key.toLowerCase().endsWith( ".wsdl" ) )
128 					{
129 						InputSource inputSource = new InputSource( new StringReader( parts.get( key ) ) );
130 						String content = WsdlUtils.replacePortEndpoint( ( WsdlInterface )iface, inputSource,
131 								getLocalMockServiceEndpoint() );
132 
133 						if( content != null )
134 							parts.put( key, content );
135 					}
136 				}
137 
138 				wsdlCache.put( iface.getName(), parts );
139 
140 				MockEngine.log.info( "Mounted WSDL for interface [" + iface.getName() + "] at [" + getOverviewUrl() + "]" );
141 			}
142 			catch( Exception e )
143 			{
144 				SoapUI.logError( e );
145 			}
146 		}
147 	}
148 
149 	public String getLocalMockServiceEndpoint()
150 	{
151 		String host = mockService.getHost();
152 		if( StringUtils.isNullOrEmpty( host ) )
153 			host = "127.0.0.1";
154 
155 		return "http://" + host + ":" + mockService.getPort() + mockService.getPath();
156 	}
157 
158 	public String getInterfacePrefix( Interface iface )
159 	{
160 		String wsdlPrefix = getOverviewUrl() + "&interface=" + iface.getName();
161 		return wsdlPrefix;
162 	}
163 
164 	public WsdlMockRunContext getMockContext()
165 	{
166 		return mockContext;
167 	}
168 
169 	public synchronized void addMockResult( WsdlMockResult mockResult )
170 	{
171 		if( maxResults > 0 && logEnabled )
172 			mockResults.add( mockResult );
173 
174 		while( mockResults.size() > maxResults )
175 		{
176 			mockResults.remove( 0 );
177 			removed++ ;
178 		}
179 	}
180 
181 	public boolean isRunning()
182 	{
183 		return running;
184 	}
185 
186 	public void stop()
187 	{
188 		if( !isRunning() )
189 			return;
190 
191 		SoapUI.getMockEngine().stopMockService( this );
192 
193 		MockRunListener[] mockRunListeners = mockService.getMockRunListeners();
194 
195 		for( MockRunListener listener : mockRunListeners )
196 		{
197 			listener.onMockRunnerStop( this );
198 		}
199 
200 		try
201 		{
202 			mockService.runStopScript( mockContext, this );
203 			running = false;
204 		}
205 		catch( Exception e )
206 		{
207 			SoapUI.logError( e );
208 		}
209 	}
210 
211 	public WsdlMockService getMockService()
212 	{
213 		return mockService;
214 	}
215 
216 	public long getMaxResults()
217 	{
218 		return maxResults;
219 	}
220 
221 	public synchronized void setMaxResults( long l )
222 	{
223 		this.maxResults = l;
224 
225 		while( mockResults.size() > l )
226 		{
227 			mockResults.remove( 0 );
228 			removed++ ;
229 		}
230 	}
231 
232 	@Override
233 	public MockResult dispatchHeadRequest( HttpServletRequest request, HttpServletResponse response )
234 			throws DispatchException
235 	{
236 		response.setStatus( HttpServletResponse.SC_OK );
237 		return null;
238 	}
239 
240 	public WsdlMockResult dispatchPostRequest( WsdlMockRequest mockRequest ) throws DispatchException
241 	{
242 		WsdlMockResult result = null;
243 
244 		try
245 		{
246 			long timestamp = System.currentTimeMillis();
247 
248 			SoapVersion soapVersion = mockRequest.getSoapVersion();
249 			if( soapVersion == null )
250 				throw new DispatchException( "Unrecognized SOAP Version" );
251 
252 			String soapAction = mockRequest.getSoapAction();
253 			WsdlOperation operation = null;
254 
255 			if( SoapUtils.isSoapFault( mockRequest.getRequestContent(), soapVersion ) )
256 			{
257 				// we should inspect fault detail and try to find matching operation
258 				// but not for now..
259 				WsdlMockOperation faultMockOperation = mockService.getFaultMockOperation();
260 				if( faultMockOperation != null )
261 					operation = faultMockOperation.getOperation();
262 			}
263 			else
264 			{
265 				try
266 				{
267 					operation = SoapUtils.findOperationForRequest( soapVersion, soapAction, mockRequest
268 							.getRequestXmlObject(), mockService.getMockedOperations(), mockService.isRequireSoapVersion(),
269 							mockService.isRequireSoapAction(), mockRequest.getRequestAttachments() );
270 				}
271 				catch( Exception e )
272 				{
273 					if( mockService.isDispatchResponseMessages() )
274 					{
275 						try
276 						{
277 							operation = SoapUtils.findOperationForResponse( soapVersion, soapAction, mockRequest
278 									.getRequestXmlObject(), mockService.getMockedOperations(), mockService
279 									.isRequireSoapVersion(), mockService.isRequireSoapAction() );
280 
281 							if( operation != null )
282 							{
283 								mockRequest.setResponseMessage( true );
284 							}
285 						}
286 						catch( Exception e2 )
287 						{
288 							throw e;
289 						}
290 					}
291 					else
292 					{
293 						throw e;
294 					}
295 				}
296 			}
297 
298 			if( operation != null )
299 			{
300 				WsdlMockOperation mockOperation = mockService.getMockOperation( operation );
301 				if( mockOperation != null )
302 				{
303 					long startTime = System.nanoTime();
304 					try
305 					{
306 						result = mockOperation.dispatchRequest( mockRequest );
307 					}
308 					catch( DispatchException e )
309 					{
310 						result = new WsdlMockResult( mockRequest );
311 
312 						String fault = SoapMessageBuilder.buildFault( "Server", e.getMessage(), mockRequest.getSoapVersion() );
313 						result.setResponseContent( fault );
314 						result.setMockOperation( mockOperation );
315 
316 						mockRequest.getHttpResponse().getWriter().write( fault );
317 					}
318 
319 					if( mockRequest.getHttpRequest() instanceof org.mortbay.jetty.Request )
320 						( ( org.mortbay.jetty.Request )mockRequest.getHttpRequest() ).setHandled( true );
321 
322 					result.setTimeTaken( ( System.nanoTime() - startTime ) / 1000000 );
323 					result.setTimestamp( timestamp );
324 					addMockResult( result );
325 					return result;
326 				}
327 				else
328 				{
329 					throw new DispatchException( "Failed to find matching operation for request" );
330 				}
331 			}
332 
333 			throw new DispatchException( "Missing operation for soapAction [" + soapAction + "] and body element ["
334 					+ XmlUtils.getQName( mockRequest.getContentElement() ) + "] with SOAP Version ["
335 					+ mockRequest.getSoapVersion() + "]" );
336 		}
337 		catch( Exception e )
338 		{
339 			if( e instanceof DispatchException )
340 				throw ( DispatchException )e;
341 
342 			throw new DispatchException( e );
343 		}
344 	}
345 
346 	public MockResult getMockResultAt( int index )
347 	{
348 		return index <= removed ? null : mockResults.get( index - removed );
349 	}
350 
351 	public int getMockResultCount()
352 	{
353 		return mockResults.size() + removed;
354 	}
355 
356 	public synchronized void clearResults()
357 	{
358 		mockResults.clear();
359 	}
360 
361 	public void release()
362 	{
363 		clearResults();
364 		mockService = null;
365 		mockContext.clear();
366 	}
367 
368 	@Override
369 	public MockResult dispatchRequest( HttpServletRequest request, HttpServletResponse response )
370 			throws DispatchException
371 	{
372 		Object result = null;
373 
374 		try
375 		{
376 			for( MockRunListener listener : mockService.getMockRunListeners() )
377 			{
378 				result = listener.onMockRequest( this, request, response );
379 				if( result instanceof MockResult )
380 					return ( MockResult )result;
381 			}
382 
383 			WsdlMockRequest mockRequest = new WsdlMockRequest( request, response, mockContext );
384 			result = mockService.runOnRequestScript( mockContext, this, mockRequest );
385 			if( !( result instanceof MockResult ) )
386 			{
387 				String method = request.getMethod();
388 
389 				if( method.equals( "POST" ) )
390 					result = dispatchPostRequest( mockRequest );
391 				else
392 					result = super.dispatchRequest( request, response );
393 			}
394 
395 			mockService.runAfterRequestScript( mockContext, this, ( MockResult )result );
396 			return ( MockResult )result;
397 		}
398 		catch( Exception e )
399 		{
400 			if( e instanceof DispatchException )
401 				throw ( DispatchException )e;
402 			else
403 				throw new DispatchException( e );
404 		}
405 		finally
406 		{
407 			if( result instanceof MockResult )
408 			{
409 				for( MockRunListener listener : mockService.getMockRunListeners() )
410 				{
411 					listener.onMockResult( ( MockResult )result );
412 				}
413 			}
414 		}
415 	}
416 
417 	public MockResult dispatchGetRequest( HttpServletRequest request, HttpServletResponse response )
418 			throws DispatchException
419 	{
420 		try
421 		{
422 			String qs = request.getQueryString();
423 			if( qs != null && qs.toUpperCase().startsWith( "WSDL" ) )
424 			{
425 				dispatchWsdlRequest( request, response );
426 			}
427 			else
428 			{
429 				if( qs != null && qs.startsWith( "cmd=" ) )
430 				{
431 					dispatchCommand( request.getParameter( "cmd" ), request, response );
432 				}
433 				else
434 				{
435 					String docroot = PropertyExpander.expandProperties( mockContext, getMockService().getDocroot() );
436 					if( StringUtils.hasContent( docroot ) )
437 					{
438 						try
439 						{
440 							String pathInfo = request.getPathInfo();
441 							if( mockService.getPath().length() > 1 && pathInfo.startsWith( mockService.getPath() ) )
442 								pathInfo = pathInfo.substring( mockService.getPath().length() );
443 
444 							File file = new File( docroot + pathInfo.replace( '/', File.separatorChar ) );
445 							FileInputStream in = new FileInputStream( file );
446 							response.setStatus( HttpServletResponse.SC_OK );
447 							long length = file.length();
448 							response.setContentLength( ( int )length );
449 							response.setContentType( ContentTypeHandler.getContentTypeFromFilename( file.getName() ) );
450 							Tools.readAndWrite( in, length, response.getOutputStream() );
451 						}
452 						catch( Throwable e )
453 						{
454 							throw new DispatchException( e );
455 						}
456 					}
457 				}
458 			}
459 
460 			return null;
461 		}
462 		catch( Exception e )
463 		{
464 			throw new DispatchException( e );
465 		}
466 	}
467 
468 	private void dispatchCommand( String cmd, HttpServletRequest request, HttpServletResponse response ) throws IOException
469 	{
470 		if( "stop".equals( cmd ))
471 		{
472 			response.setStatus( HttpServletResponse.SC_OK );
473 			response.flushBuffer();
474 			
475 			SoapUI.getThreadPool().execute( new Runnable() {
476 
477 				public void run()
478 				{
479 					try
480 					{
481 						Thread.sleep( 500 );
482 					}
483 					catch( InterruptedException e )
484 					{
485 						e.printStackTrace();
486 					}
487 					stop();
488 				}} );
489 		}
490 		else if( "restart".equals( cmd ))
491 		{
492 			response.setStatus( HttpServletResponse.SC_OK );
493 			response.flushBuffer();
494 			
495 			SoapUI.getThreadPool().execute( new Runnable() {
496 
497 				public void run()
498 				{
499 					try
500 					{
501 						Thread.sleep( 500 );
502 					}
503 					catch( InterruptedException e )
504 					{
505 						e.printStackTrace();
506 					}
507 					
508 					stop();
509 //					
510 //					try
511 //					{
512 //						Thread.sleep( 500 );
513 //					}
514 //					catch( InterruptedException e )
515 //					{
516 //						e.printStackTrace();
517 //					}
518 
519 					try
520 					{
521 						mockService.start();
522 					}
523 					catch( Exception e )
524 					{
525 						e.printStackTrace();
526 					}
527 					
528 				}} );
529 		}
530 	}
531 
532 	protected void dispatchWsdlRequest( HttpServletRequest request, HttpServletResponse response ) throws IOException
533 	{
534 		if( request.getQueryString().equalsIgnoreCase( "WSDL" ) )
535 		{
536 			printWsdl( response );
537 			return;
538 		}
539 
540 		String ifaceName = request.getParameter( "interface" );
541 		WsdlInterface iface = ( WsdlInterface )mockService.getProject().getInterfaceByName( ifaceName );
542 		if( iface == null )
543 		{
544 			printInterfaceList( response );
545 			return;
546 		}
547 
548 		StringToStringMap parts = wsdlCache.get( iface.getName() );
549 		String part = request.getParameter( "part" );
550 		String content = StringUtils.isNullOrEmpty( part ) ? null : parts.get( part );
551 
552 		if( content == null )
553 		{
554 			printPartList( iface, parts, response );
555 			return;
556 		}
557 
558 		if( content != null )
559 		{
560 			printOkXmlResult( response, content );
561 		}
562 	}
563 
564 	private void printOkXmlResult( HttpServletResponse response, String content ) throws IOException
565 	{
566 		response.setStatus( HttpServletResponse.SC_OK );
567 		response.setContentType( "text/xml" );
568 		response.setCharacterEncoding( "UTF-8" );
569 		response.getWriter().print( content );
570 	}
571 
572 	private void printWsdl( HttpServletResponse response ) throws IOException
573 	{
574 		WsdlInterface[] mockedInterfaces = mockService.getMockedInterfaces();
575 		if( mockedInterfaces.length == 1 )
576 		{
577 			StringToStringMap parts = wsdlCache.get( mockedInterfaces[0].getName() );
578 			printOkXmlResult( response, parts.get( parts.get( "#root#" ) ) );
579 		}
580 		else
581 		{
582 			try
583 			{
584 				WSDLFactory wsdlFactory = WSDLFactory.newInstance();
585 				Definition def = wsdlFactory.newDefinition();
586 				for( WsdlInterface iface : mockedInterfaces )
587 				{
588 					StringToStringMap parts = wsdlCache.get( iface.getName() );
589 					Import wsdlImport = def.createImport();
590 					wsdlImport.setLocationURI( getInterfacePrefix( iface ) + "&part=" + parts.get( "#root#" ) );
591 					wsdlImport.setNamespaceURI( iface.getWsdlContext().getDefinition().getTargetNamespace() );
592 
593 					def.addImport( wsdlImport );
594 				}
595 
596 				response.setStatus( HttpServletResponse.SC_OK );
597 				response.setContentType( "text/xml" );
598 				response.setCharacterEncoding( "UTF-8" );
599 
600 				WSDLWriter writer = wsdlFactory.newWSDLWriter();
601 				writer.writeWSDL( def, response.getWriter() );
602 			}
603 			catch( Exception e )
604 			{
605 				SoapUI.logError( e );
606 				throw new IOException( "Failed to create combined WSDL" );
607 			}
608 		}
609 	}
610 
611 	private void printPartList( WsdlInterface iface, StringToStringMap parts, HttpServletResponse response )
612 			throws IOException
613 	{
614 		response.setStatus( HttpServletResponse.SC_OK );
615 		response.setContentType( "text/html" );
616 
617 		PrintWriter out = response.getWriter();
618 		out.print( "<html><body><p>Parts in interface [" + iface.getName() + "]</p><ul>" );
619 
620 		for( String key : parts.keySet() )
621 		{
622 			if( key.equals( "#root#" ) )
623 				continue;
624 
625 			out.print( "<li><a href=\"" );
626 			out.print( getInterfacePrefix( iface ) + "&part=" + key );
627 			out.print( "\">" + key + "</a></li>" );
628 		}
629 
630 		out.print( "</ul></p></body></html>" );
631 	}
632 
633 	private void printInterfaceList( HttpServletResponse response ) throws IOException
634 	{
635 		response.setStatus( HttpServletResponse.SC_OK );
636 		response.setContentType( "text/html" );
637 
638 		PrintWriter out = response.getWriter();
639 		out.print( "<html><body><p>Mocked Interfaces in project [" + mockService.getProject().getName() + "]</p><ul>" );
640 
641 		for( Interface iface : ModelSupport.getChildren( mockService.getProject(), WsdlInterface.class ) )
642 		{
643 			out.print( "<li><a href=\"" );
644 			out.print( getInterfacePrefix( iface ) );
645 			out.print( "\">" + iface.getName() + "</a></li>" );
646 		}
647 
648 		out.print( "</ul></p></body></html>" );
649 	}
650 
651 	public String getOverviewUrl()
652 	{
653 		return mockService.getPath() + "?WSDL";
654 	}
655 
656 	public void setLogEnabled( boolean logEnabled )
657 	{
658 		this.logEnabled = logEnabled;
659 	}
660 }