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