1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.monitor;
14
15 import com.eviware.soapui.SoapUI;
16 import com.eviware.soapui.impl.wsdl.mock.DispatchException;
17 import com.eviware.soapui.impl.wsdl.mock.WsdlMockRunner;
18 import com.eviware.soapui.impl.wsdl.mock.WsdlMockService;
19 import com.eviware.soapui.impl.wsdl.support.soap.SoapMessageBuilder;
20 import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
21 import com.eviware.soapui.model.mock.MockResult;
22 import com.eviware.soapui.model.mock.MockRunner;
23 import com.eviware.soapui.model.mock.MockService;
24 import com.eviware.soapui.settings.HttpSettings;
25 import com.eviware.soapui.settings.SSLSettings;
26 import com.eviware.soapui.support.StringUtils;
27 import com.eviware.soapui.support.UISupport;
28 import com.eviware.soapui.support.log.JettyLogger;
29 import org.apache.log4j.Logger;
30 import org.mortbay.component.AbstractLifeCycle;
31 import org.mortbay.io.Connection;
32 import org.mortbay.io.EndPoint;
33 import org.mortbay.io.nio.SelectChannelEndPoint;
34 import org.mortbay.jetty.*;
35 import org.mortbay.jetty.handler.AbstractHandler;
36 import org.mortbay.jetty.handler.RequestLogHandler;
37 import org.mortbay.jetty.nio.SelectChannelConnector;
38 import org.mortbay.jetty.security.SslSocketConnector;
39 import org.mortbay.thread.BoundedThreadPool;
40
41 import javax.servlet.ServletException;
42 import javax.servlet.ServletInputStream;
43 import javax.servlet.ServletOutputStream;
44 import javax.servlet.http.HttpServletRequest;
45 import javax.servlet.http.HttpServletResponse;
46 import java.io.*;
47 import java.nio.channels.SocketChannel;
48 import java.util.*;
49
50 /***
51 * Core Mock-Engine hosting a Jetty web server
52 *
53 * @author ole.matzura
54 */
55
56 public class MockEngine
57 {
58 public final static Logger log = Logger.getLogger( MockEngine.class );
59
60 private Server server;
61 private Map<Integer, Map<String, List<MockRunner>>> runners = new HashMap<Integer, Map<String, List<MockRunner>>>();
62 private Map<Integer, SoapUIConnector> connectors = new HashMap<Integer, SoapUIConnector>();
63 private List<MockRunner> mockRunners = new ArrayList<MockRunner>();
64
65 private SslSocketConnector sslConnector;
66
67 public MockEngine()
68 {
69 System.setProperty( "org.mortbay.log.class", JettyLogger.class.getName() );
70 }
71
72 public boolean hasRunningMock( MockService mockService )
73 {
74 for( MockRunner runner : mockRunners )
75 if( runner.getMockService() == mockService )
76 return true;
77
78 return false;
79 }
80
81 public synchronized void startMockService( MockRunner runner ) throws Exception
82 {
83 if( server == null )
84 initServer();
85
86 WsdlMockService mockService = (WsdlMockService) runner.getMockService();
87 int port = mockService.getPort();
88
89 if( !runners.containsKey( port ) )
90 {
91 SoapUIConnector connector = new SoapUIConnector();
92
93 connector.setPort( port );
94 if( sslConnector != null )
95 connector.setConfidentialPort( sslConnector.getPort() );
96
97 if( mockService.getBindToHostOnly() )
98 {
99 String host = mockService.getHost();
100 if( StringUtils.hasContent( host ) )
101 {
102 connector.setHost( host );
103 }
104 }
105
106 boolean wasRunning = server.isRunning();
107
108 if( wasRunning )
109 {
110 server.stop();
111 }
112
113 server.addConnector( connector );
114 try
115 {
116 server.start();
117 }
118 catch( RuntimeException e )
119 {
120 UISupport.showErrorMessage( e );
121
122 server.removeConnector( connector );
123 if( wasRunning )
124 {
125 server.start();
126 return;
127 }
128 }
129
130 connectors.put( new Integer( port ), connector );
131 runners.put( new Integer( port ), new HashMap<String, List<MockRunner>>() );
132 }
133
134 Map<String, List<MockRunner>> map = runners.get( port );
135 String path = mockService.getPath();
136 if( !map.containsKey( path ) )
137 {
138 map.put( path, new ArrayList<MockRunner>() );
139 }
140 map.get( path ).add( runner );
141 mockRunners.add( runner );
142
143 log.info( "Started mockService [" + mockService.getName() + "] on port [" + port + "] at path [" + path + "]" );
144 }
145
146 private void initServer() throws Exception
147 {
148 server = new Server();
149 BoundedThreadPool threadPool = new BoundedThreadPool();
150 threadPool.setMaxThreads( 100 );
151 server.setThreadPool( threadPool );
152 server.setHandler( new ServerHandler() );
153
154 RequestLogHandler logHandler = new RequestLogHandler();
155 logHandler.setRequestLog( new MockRequestLog() );
156 server.addHandler( logHandler );
157
158 if( SoapUI.getSettings().getBoolean( SSLSettings.ENABLE_MOCK_SSL ) )
159 {
160 sslConnector = new SslSocketConnector();
161 sslConnector.setKeystore( SoapUI.getSettings().getString( SSLSettings.MOCK_KEYSTORE, null ) );
162 sslConnector.setPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_PASSWORD, null ) );
163 sslConnector.setKeyPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_KEYSTORE_PASSWORD, null ) );
164 sslConnector.setTruststore( SoapUI.getSettings().getString( SSLSettings.MOCK_TRUSTSTORE, null ) );
165 sslConnector.setTrustPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_TRUSTSTORE_PASSWORD, null ) );
166 sslConnector.setMaxIdleTime( 30000 );
167 sslConnector.setPort( (int) SoapUI.getSettings().getLong( SSLSettings.MOCK_PORT, 443 ) );
168 sslConnector.setNeedClientAuth( SoapUI.getSettings().getBoolean( SSLSettings.CLIENT_AUTHENTICATION ) );
169
170 server.addConnector( sslConnector );
171 }
172 }
173
174 public synchronized void stopMockService( WsdlMockRunner runner )
175 {
176 MockService mockService = runner.getMockService();
177 final Integer port = new Integer( mockService.getPort() );
178 Map<String, List<MockRunner>> map = runners.get( port );
179
180 map.get( mockService.getPath() ).remove( runner );
181 mockRunners.remove( runner );
182
183 log.info( "Stopped MockService [" + mockService.getName() + "] on port [" + port + "]" );
184
185 if( map.isEmpty() && !SoapUI.getSettings().getBoolean( HttpSettings.LEAVE_MOCKENGINE ) )
186 {
187 SoapUIConnector connector = connectors.get( port );
188 if( connector == null )
189 {
190 log.warn( "Missing connectors on port [" + port + "]" );
191 return;
192 }
193
194 try
195 {
196 log.info( "Stopping connector on port " + port );
197 if( !connector.waitUntilIdle( 5000 ) )
198 {
199 log.warn( "Failed to wait for idle.. stopping connector anyway.." );
200 }
201 connector.stop();
202 }
203 catch( Exception e )
204 {
205 SoapUI.logError( e );
206 }
207 server.removeConnector( connector );
208 runners.remove( port );
209 if( runners.isEmpty() )
210 {
211 try
212 {
213 log.info( "No more connectors.. stopping server" );
214 server.stop();
215 if( sslConnector != null )
216 {
217 server.removeConnector( sslConnector );
218 sslConnector.stop();
219 sslConnector = null;
220 }
221 }
222 catch( Exception e )
223 {
224 SoapUI.logError( e );
225 }
226 }
227 }
228 }
229
230 private class SoapUIConnector extends SelectChannelConnector
231 {
232 private Set<HttpConnection> connections = new HashSet<HttpConnection>();
233
234 @Override
235 protected void connectionClosed( HttpConnection arg0 )
236 {
237 super.connectionClosed( arg0 );
238 connections.remove( arg0 );
239 }
240
241 @Override
242 protected void connectionOpened( HttpConnection arg0 )
243 {
244 super.connectionOpened( arg0 );
245 connections.add( arg0 );
246 }
247
248 @Override
249 protected Connection newConnection( SocketChannel socketChannel, SelectChannelEndPoint selectChannelEndPoint )
250 {
251 if( SoapUI.getSettings().getBoolean( HttpSettings.ENABLE_MOCK_WIRE_LOG ) )
252 {
253 return new SoapUIHttpConnection( SoapUIConnector.this, selectChannelEndPoint, getServer() );
254 }
255 else
256 {
257 return super.newConnection( socketChannel, selectChannelEndPoint );
258 }
259 }
260
261 public boolean waitUntilIdle( long maxwait ) throws Exception
262 {
263 while( maxwait > 0 && hasActiveConnections() )
264 {
265 System.out.println( "Waiting for active connections to finish.." );
266 Thread.sleep( 500 );
267 maxwait -= 500;
268 }
269
270 return !hasActiveConnections();
271 }
272
273 private boolean hasActiveConnections()
274 {
275 for( HttpConnection connection : connections )
276 {
277 if( !connection.isIdle() )
278 return true;
279 }
280
281 return false;
282 }
283 }
284
285 private class SoapUIHttpConnection extends HttpConnection
286 {
287 private CapturingServletInputStream capturingServletInputStream;
288 private MockEngine.CapturingServletOutputStream capturingServletOutputStream;
289
290 public SoapUIHttpConnection( Connector connector, EndPoint endPoint, Server server )
291 {
292 super( connector, endPoint, server );
293 }
294
295 @Override
296 public ServletInputStream getInputStream()
297 {
298 if( capturingServletInputStream == null )
299 {
300 capturingServletInputStream = new CapturingServletInputStream( super.getInputStream() );
301 }
302
303 return capturingServletInputStream;
304 }
305
306 @Override
307 public ServletOutputStream getOutputStream()
308 {
309 if( capturingServletOutputStream == null )
310 {
311 capturingServletOutputStream = new CapturingServletOutputStream( super.getOutputStream() );
312 }
313
314 return capturingServletOutputStream;
315 }
316 }
317
318 private class CapturingServletOutputStream extends ServletOutputStream
319 {
320 private ServletOutputStream outputStream;
321 private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream();
322
323 public CapturingServletOutputStream( ServletOutputStream outputStream )
324 {
325 this.outputStream = outputStream;
326 }
327
328 public void print( String s )
329 throws IOException
330 {
331 outputStream.print( s );
332 }
333
334 public void print( boolean b )
335 throws IOException
336 {
337 outputStream.print( b );
338 }
339
340 public void print( char c )
341 throws IOException
342 {
343 outputStream.print( c );
344 }
345
346 public void print( int i )
347 throws IOException
348 {
349 outputStream.print( i );
350 }
351
352 public void print( long l )
353 throws IOException
354 {
355 outputStream.print( l );
356 }
357
358 public void print( float v )
359 throws IOException
360 {
361 outputStream.print( v );
362 }
363
364 public void print( double v )
365 throws IOException
366 {
367 outputStream.print( v );
368 }
369
370 public void println()
371 throws IOException
372 {
373 outputStream.println();
374 }
375
376 public void println( String s )
377 throws IOException
378 {
379 outputStream.println( s );
380 }
381
382 public void println( boolean b )
383 throws IOException
384 {
385 outputStream.println( b );
386 }
387
388 public void println( char c )
389 throws IOException
390 {
391 outputStream.println( c );
392 }
393
394 public void println( int i )
395 throws IOException
396 {
397 outputStream.println( i );
398 }
399
400 public void println( long l )
401 throws IOException
402 {
403 outputStream.println( l );
404 }
405
406 public void println( float v )
407 throws IOException
408 {
409 outputStream.println( v );
410 }
411
412 public void println( double v )
413 throws IOException
414 {
415 outputStream.println( v );
416 }
417
418 public void write( int b )
419 throws IOException
420 {
421 captureOutputStream.write( b );
422 outputStream.write( b );
423 }
424
425 public void write( byte[] b )
426 throws IOException
427 {
428 captureOutputStream.write( b );
429 outputStream.write( b );
430 }
431
432 public void write( byte[] b, int off, int len )
433 throws IOException
434 {
435 captureOutputStream.write( b, off, len );
436 outputStream.write( b, off, len );
437 }
438
439 public void flush()
440 throws IOException
441 {
442 outputStream.flush();
443 }
444
445 public void close()
446 throws IOException
447 {
448 outputStream.close();
449
450 }
451 }
452
453 private class CapturingServletInputStream extends ServletInputStream
454 {
455 private ServletInputStream inputStream;
456 private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream();
457
458 public CapturingServletInputStream( ServletInputStream inputStream )
459 {
460 this.inputStream = inputStream;
461 }
462
463 public int read()
464 throws IOException
465 {
466 int i = inputStream.read();
467 captureOutputStream.write( i );
468 return i;
469 }
470
471 public int readLine( byte[] bytes, int i, int i1 )
472 throws IOException
473 {
474 int result = inputStream.readLine( bytes, i, i1 );
475 captureOutputStream.write( bytes, i, i1 );
476 return result;
477 }
478
479 public int read( byte[] b )
480 throws IOException
481 {
482 int i = inputStream.read( b );
483 captureOutputStream.write( b );
484 return i;
485 }
486
487 public int read( byte[] b, int off, int len )
488 throws IOException
489 {
490 int result = inputStream.read( b, off, len );
491 if( result != -1 )
492 captureOutputStream.write( b, off, result );
493 return result;
494 }
495
496 public long skip( long n )
497 throws IOException
498 {
499 return inputStream.skip( n );
500 }
501
502 public int available()
503 throws IOException
504 {
505 return inputStream.available();
506 }
507
508 public void close()
509 throws IOException
510 {
511 inputStream.close();
512
513 }
514
515 public void mark( int readlimit )
516 {
517 inputStream.mark( readlimit );
518 }
519
520 public boolean markSupported()
521 {
522 return inputStream.markSupported();
523 }
524
525 public void reset()
526 throws IOException
527 {
528 inputStream.reset();
529 }
530 }
531
532 private class ServerHandler extends AbstractHandler
533 {
534 public void handle( String target, HttpServletRequest request, HttpServletResponse response, int dispatch )
535 throws IOException, ServletException
536 {
537
538 Map<String, List<MockRunner>> map = runners.get( request.getLocalPort() );
539
540
541 if( map == null && sslConnector != null && request.getLocalPort() == sslConnector.getPort() )
542 {
543 for( Map<String, List<MockRunner>> runnerMap : runners.values() )
544 {
545 if( runnerMap.containsKey( request.getPathInfo() ) )
546 {
547 map = runnerMap;
548 break;
549 }
550 }
551 }
552
553 if( map != null )
554 {
555 List<MockRunner> wsdlMockRunners = map.get( request.getPathInfo() );
556 if( wsdlMockRunners == null && request.getMethod().equals( "GET" ) )
557 {
558 for( String root : map.keySet() )
559 {
560 if( request.getPathInfo().startsWith( root ) )
561 {
562 wsdlMockRunners = map.get( root );
563 }
564 }
565 }
566
567 if( wsdlMockRunners != null )
568 {
569 synchronized( wsdlMockRunners )
570 {
571 try
572 {
573 DispatchException ex = null;
574
575 for( MockRunner wsdlMockRunner : wsdlMockRunners )
576 {
577 try
578 {
579 MockResult result = wsdlMockRunner.dispatchRequest( request, response );
580 if( result != null )
581 result.finish();
582
583
584 break;
585 }
586 catch( DispatchException e )
587 {
588 log.debug( wsdlMockRunner.getMockService().getName()
589 + " was unable to dispatch mock request ", e );
590
591 ex = e;
592 }
593 }
594
595 if( ex != null )
596 throw ex;
597 }
598 catch( Exception e )
599 {
600 SoapUI.logError( e );
601
602 response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
603 response.setContentType( "text/html" );
604 response.getWriter().print(
605 SoapMessageBuilder.buildFault( "Server", e.getMessage(), SoapVersion.Utils
606 .getSoapVersionForContentType( request.getContentType(), SoapVersion.Soap11 ) ) );
607
608 }
609 }
610 }
611 else
612 {
613 printMockServiceList( response );
614 }
615 }
616 else
617 {
618 printMockServiceList( response );
619 }
620
621 response.flushBuffer();
622 }
623
624 private void printMockServiceList( HttpServletResponse response ) throws IOException
625 {
626 response.setStatus( HttpServletResponse.SC_OK );
627 response.setContentType( "text/html" );
628
629 MockRunner[] mockRunners = getMockRunners();
630 PrintWriter out = response.getWriter();
631 out.print( "<html><body><p>There are currently " + mockRunners.length + " running soapUI MockServices</p><ul>" );
632
633 for( MockRunner mockRunner : mockRunners )
634 {
635 out.print( "<li><a href=\"" );
636 out.print( mockRunner.getMockService().getPath() + "?WSDL" );
637 out.print( "\">" + mockRunner.getMockService().getName() + "</a></li>" );
638 }
639
640 out.print( "</ul></p></body></html>" );
641 }
642 }
643
644 public MockRunner[] getMockRunners()
645 {
646 return mockRunners.toArray( new MockRunner[mockRunners.size()] );
647 }
648
649 private class MockRequestLog extends AbstractLifeCycle implements RequestLog
650 {
651 public void log( Request request, Response response )
652 {
653 if( SoapUI.getLogMonitor() == null || SoapUI.getLogMonitor().getLogArea( "jetty log" ) == null ||
654 SoapUI.getLogMonitor().getLogArea( "jetty log" ).getLoggers() == null )
655 return;
656
657 Logger logger = SoapUI.getLogMonitor().getLogArea( "jetty log" ).getLoggers()[0];
658
659 try
660 {
661 ServletInputStream inputStream = request.getInputStream();
662 if( inputStream instanceof CapturingServletInputStream )
663 {
664 ByteArrayOutputStream byteArrayOutputStream = ( (CapturingServletInputStream) inputStream ).captureOutputStream;
665 String str = request.toString() + byteArrayOutputStream.toString();
666 BufferedReader reader = new BufferedReader( new StringReader( str ) );
667 ( (CapturingServletInputStream) inputStream ).captureOutputStream = new ByteArrayOutputStream( );
668
669 String line = reader.readLine();
670 while( line != null )
671 {
672 logger.info( ">> \"" + line + "\"" );
673 line = reader.readLine();
674 }
675 }
676 }
677 catch( Throwable e )
678 {
679 log.error( "Failed to log request: " + e );
680 }
681
682 try
683 {
684 ServletOutputStream outputStream = response.getOutputStream();
685 if( outputStream instanceof CapturingServletOutputStream )
686 {
687 ByteArrayOutputStream byteArrayOutputStream = ( (CapturingServletOutputStream) outputStream ).captureOutputStream;
688 String str = request.toString() + byteArrayOutputStream.toString();
689 BufferedReader reader = new BufferedReader( new StringReader( str ) );
690 ( (CapturingServletOutputStream) outputStream ).captureOutputStream = new ByteArrayOutputStream( );
691
692 String line = reader.readLine();
693 while( line != null )
694 {
695 logger.info( "<< \"" + line + "\"" );
696 line = reader.readLine();
697 }
698 }
699 }
700 catch( Throwable e )
701 {
702 log.error( "Failed to log response: " + e );
703 }
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727 }
728 }
729 }