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.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 //         log.info( "Closing output stream, captured: " + captureOutputStream.toString() );
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 //         log.info( "Closing input stream, captured: " + captureOutputStream.toString() );
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          // find mockService
538          Map<String, List<MockRunner>> map = runners.get( request.getLocalPort() );
539 
540          // ssl?
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                            // if we get here, we got dispatched..
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                      // throw new ServletException( e );
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 //         log.info( ">> " + request.getMethod() + " " + request.getUri().toString() + " " + request.getProtocol());
706 //         for( Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements(); )
707 //         {
708 //            String header = String.valueOf( headerNames.nextElement() );
709 //            for( Enumeration headers = request.getHeaders( header ); headers.hasMoreElements(); )
710 //            {
711 //               log.info( ">> " + header + " : " + headers.nextElement());
712 //            }
713 //         }
714 //
715 //         log.info( "<< " + request.getProtocol() + " " + response.getStatus() );
716 //         HttpFields httpFields = response.getHttpFields();
717 //         for( Enumeration headerNames = httpFields.getFieldNames(); headerNames.hasMoreElements(); )
718 //         {
719 //            String header = String.valueOf( headerNames.nextElement() );
720 //            for( Enumeration headers = httpFields.getValues( header ); headers.hasMoreElements(); )
721 //            {
722 //               log.info( "<< " + header + " : " + headers.nextElement());
723 //            }
724 //         }
725 
726 //         log.info( request.getSt)
727       }
728    }
729 }