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