/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.monitor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.mortbay.component.AbstractLifeCycle;
import org.mortbay.io.Connection;
import org.mortbay.io.EndPoint;
import org.mortbay.io.nio.SelectChannelEndPoint;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.RequestLog;
import org.mortbay.jetty.Response;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.RequestLogHandler;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.security.SslSocketConnector;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.mock.DispatchException;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockService;
import com.eviware.soapui.impl.wsdl.support.soap.SoapMessageBuilder;
import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
import com.eviware.soapui.model.mock.MockResult;
import com.eviware.soapui.model.mock.MockRunner;
import com.eviware.soapui.model.mock.MockService;
import com.eviware.soapui.settings.HttpSettings;
import com.eviware.soapui.settings.SSLSettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.log.JettyLogger;
/**
* Core Mock-Engine hosting a Jetty web server
*
* @author ole.matzura
*/
public class JettyMockEngine implements MockEngine
{
public final static Logger log = Logger.getLogger( JettyMockEngine.class );
private Server server;
private Map<Integer, Map<String, List<MockRunner>>> runners = new HashMap<Integer, Map<String, List<MockRunner>>>();
private Map<Integer, SoapUIConnector> connectors = new HashMap<Integer, SoapUIConnector>();
private List<MockRunner> mockRunners = new ArrayList<MockRunner>();
private SslSocketConnector sslConnector;
private boolean addedSslConnector;
public JettyMockEngine()
{
System.setProperty( "org.mortbay.log.class", JettyLogger.class.getName() );
}
public boolean hasRunningMock( MockService mockService )
{
for( MockRunner runner : mockRunners )
if( runner.getMockService() == mockService )
return true;
return false;
}
public synchronized void startMockService( MockRunner runner ) throws Exception
{
if( server == null )
initServer();
synchronized( server )
{
WsdlMockService mockService = ( WsdlMockService )runner.getMockService();
int port = mockService.getPort();
if( SoapUI.getSettings().getBoolean( SSLSettings.ENABLE_MOCK_SSL ) && !addedSslConnector )
{
updateSslConnectorSettings();
server.addConnector( sslConnector );
addedSslConnector = true;
}
else
{
if( addedSslConnector )
server.removeConnector( sslConnector );
addedSslConnector = false;
}
if( !runners.containsKey( port ) )
{
SoapUIConnector connector = new SoapUIConnector();
PropertySupport.applySystemProperties( connector, "soapui.mock.connector", runner.getMockService() );
connector.setPort( port );
if( sslConnector != null )
{
connector.setConfidentialPort( sslConnector.getPort() );
}
if( mockService.getBindToHostOnly() )
{
String host = mockService.getHost();
if( StringUtils.hasContent( host ) )
{
connector.setHost( host );
}
}
boolean wasRunning = server.isRunning();
if( wasRunning )
{
server.stop();
}
server.addConnector( connector );
try
{
server.start();
}
catch( RuntimeException e )
{
UISupport.showErrorMessage( e );
server.removeConnector( connector );
if( wasRunning )
{
server.start();
return;
}
}
connectors.put( new Integer( port ), connector );
runners.put( new Integer( port ), new HashMap<String, List<MockRunner>>() );
}
Map<String, List<MockRunner>> map = runners.get( port );
String path = mockService.getPath();
if( !map.containsKey( path ) )
{
map.put( path, new ArrayList<MockRunner>() );
}
map.get( path ).add( runner );
mockRunners.add( runner );
log.info( "Started mockService [" + mockService.getName() + "] on port [" + port + "] at path [" + path + "]" );
}
}
private void initServer() throws Exception
{
server = new Server();
server.setThreadPool( new SoapUIJettyThreadPool() );
server.setHandler( new ServerHandler() );
RequestLogHandler logHandler = new RequestLogHandler();
logHandler.setRequestLog( new MockRequestLog() );
server.addHandler( logHandler );
sslConnector = new SslSocketConnector();
sslConnector.setMaxIdleTime( 30000 );
}
private void updateSslConnectorSettings()
{
sslConnector.setKeystore( SoapUI.getSettings().getString( SSLSettings.MOCK_KEYSTORE, null ) );
sslConnector.setPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_PASSWORD, null ) );
sslConnector.setKeyPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_KEYSTORE_PASSWORD, null ) );
String truststore = SoapUI.getSettings().getString( SSLSettings.MOCK_TRUSTSTORE, null );
if( StringUtils.hasContent( truststore ) )
{
sslConnector.setTruststore( truststore );
sslConnector.setTrustPassword( SoapUI.getSettings().getString( SSLSettings.MOCK_TRUSTSTORE_PASSWORD, null ) );
}
sslConnector.setPort( ( int )SoapUI.getSettings().getLong( SSLSettings.MOCK_PORT, 443 ) );
sslConnector.setNeedClientAuth( SoapUI.getSettings().getBoolean( SSLSettings.CLIENT_AUTHENTICATION ) );
}
public void stopMockService( MockRunner runner )
{
synchronized( server )
{
MockService mockService = runner.getMockService();
final Integer port = new Integer( mockService.getPort() );
Map<String, List<MockRunner>> map = runners.get( port );
if( map == null || !map.containsKey( mockService.getPath() ) )
return;
map.get( mockService.getPath() ).remove( runner );
if( map.get( mockService.getPath() ).isEmpty() )
{
map.remove( mockService.getPath() );
}
mockRunners.remove( runner );
log.info( "Stopped MockService [" + mockService.getName() + "] on port [" + port + "]" );
if( map.isEmpty() && !SoapUI.getSettings().getBoolean( HttpSettings.LEAVE_MOCKENGINE ) )
{
SoapUIConnector connector = connectors.get( port );
if( connector == null )
{
log.warn( "Missing connectors on port [" + port + "]" );
return;
}
try
{
log.info( "Stopping connector on port " + port );
if( !connector.waitUntilIdle( 5000 ) )
{
log.warn( "Failed to wait for idle.. stopping connector anyway.." );
}
connector.stop();
}
catch( Exception e )
{
SoapUI.logError( e );
}
server.removeConnector( connector );
runners.remove( port );
if( runners.isEmpty() )
{
try
{
log.info( "No more connectors.. stopping server" );
server.stop();
if( sslConnector != null )
{
// server.removeConnector( sslConnector );
// sslConnector.stop();
// sslConnector = null;
}
}
catch( Exception e )
{
SoapUI.logError( e );
}
}
}
}
}
private class SoapUIConnector extends SelectChannelConnector
{
private Set<HttpConnection> connections = new HashSet<HttpConnection>();
@Override
protected void connectionClosed( HttpConnection arg0 )
{
super.connectionClosed( arg0 );
connections.remove( arg0 );
}
@Override
protected void connectionOpened( HttpConnection arg0 )
{
super.connectionOpened( arg0 );
connections.add( arg0 );
}
@Override
protected Connection newConnection( SocketChannel socketChannel, SelectChannelEndPoint selectChannelEndPoint )
{
return new SoapUIHttpConnection( SoapUIConnector.this, selectChannelEndPoint, getServer() );
}
public boolean waitUntilIdle( long maxwait ) throws Exception
{
while( maxwait > 0 && hasActiveConnections() )
{
System.out.println( "Waiting for active connections to finish.." );
Thread.sleep( 500 );
maxwait -= 500;
}
return !hasActiveConnections();
}
private boolean hasActiveConnections()
{
for( HttpConnection connection : connections )
{
if( !connection.isIdle() )
return true;
}
return false;
}
}
private class SoapUIHttpConnection extends HttpConnection
{
private CapturingServletInputStream capturingServletInputStream;
private BufferedServletInputStream bufferedServletInputStream;
private CapturingServletOutputStream capturingServletOutputStream;
public SoapUIHttpConnection( Connector connector, EndPoint endPoint, Server server )
{
super( connector, endPoint, server );
}
@Override
public ServletInputStream getInputStream()
{
if( SoapUI.getSettings().getBoolean( HttpSettings.ENABLE_MOCK_WIRE_LOG ) )
{
if( capturingServletInputStream == null )
{
capturingServletInputStream = new CapturingServletInputStream( super.getInputStream() );
bufferedServletInputStream = new BufferedServletInputStream( capturingServletInputStream );
}
}
else
{
bufferedServletInputStream = new BufferedServletInputStream( super.getInputStream() );
}
return bufferedServletInputStream;
}
@Override
public ServletOutputStream getOutputStream()
{
if( SoapUI.getSettings().getBoolean( HttpSettings.ENABLE_MOCK_WIRE_LOG ) )
{
if( capturingServletOutputStream == null )
{
capturingServletOutputStream = new CapturingServletOutputStream( super.getOutputStream() );
}
return capturingServletOutputStream;
}
else
return super.getOutputStream();
}
}
private class BufferedServletInputStream extends ServletInputStream
{
private InputStream source = null;
private byte[] data = null;
private InputStream buffer1 = null;
public BufferedServletInputStream( InputStream is )
{
super();
source = is;
}
public InputStream getBuffer() throws IOException
{
if( source.available() > 0 )
{
// New request content available
data = null;
}
if( data == null )
{
ByteArrayOutputStream out = Tools.readAll( source, Tools.READ_ALL );
data = out.toByteArray();
}
if( buffer1 == null )
{
buffer1 = new ByteArrayInputStream( data );
}
return buffer1;
}
public int read() throws IOException
{
int i = getBuffer().read();
return i;
}
public int readLine( byte[] b, int off, int len ) throws IOException
{
if( len <= 0 )
{
return 0;
}
int count = 0, c;
while( ( c = read() ) != -1 )
{
b[off++ ] = ( byte )c;
count++ ;
if( c == '\n' || count == len )
{
break;
}
}
return count > 0 ? count : -1;
}
public int read( byte[] b ) throws IOException
{
int i = getBuffer().read( b );
return i;
}
public int read( byte[] b, int off, int len ) throws IOException
{
int result = getBuffer().read( b, off, len );
return result;
}
public long skip( long n ) throws IOException
{
return getBuffer().skip( n );
}
public int available() throws IOException
{
return getBuffer().available();
}
public void close() throws IOException
{
getBuffer().close();
}
public void mark( int readlimit )
{
// buffer.mark( readlimit );
}
public boolean markSupported()
{
return false;
}
public void reset() throws IOException
{
buffer1 = null;
}
}
private class CapturingServletOutputStream extends ServletOutputStream
{
private ServletOutputStream outputStream;
private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream();
public CapturingServletOutputStream( ServletOutputStream outputStream )
{
this.outputStream = outputStream;
}
public void print( String s ) throws IOException
{
outputStream.print( s );
}
public void print( boolean b ) throws IOException
{
outputStream.print( b );
}
public void print( char c ) throws IOException
{
outputStream.print( c );
}
public void print( int i ) throws IOException
{
outputStream.print( i );
}
public void print( long l ) throws IOException
{
outputStream.print( l );
}
public void print( float v ) throws IOException
{
outputStream.print( v );
}
public void print( double v ) throws IOException
{
outputStream.print( v );
}
public void println() throws IOException
{
outputStream.println();
}
public void println( String s ) throws IOException
{
outputStream.println( s );
}
public void println( boolean b ) throws IOException
{
outputStream.println( b );
}
public void println( char c ) throws IOException
{
outputStream.println( c );
}
public void println( int i ) throws IOException
{
outputStream.println( i );
}
public void println( long l ) throws IOException
{
outputStream.println( l );
}
public void println( float v ) throws IOException
{
outputStream.println( v );
}
public void println( double v ) throws IOException
{
outputStream.println( v );
}
public void write( int b ) throws IOException
{
captureOutputStream.write( b );
outputStream.write( b );
}
public void write( byte[] b ) throws IOException
{
captureOutputStream.write( b );
outputStream.write( b );
}
public void write( byte[] b, int off, int len ) throws IOException
{
captureOutputStream.write( b, off, len );
outputStream.write( b, off, len );
}
public void flush() throws IOException
{
outputStream.flush();
}
public void close() throws IOException
{
outputStream.close();
// log.info( "Closing output stream, captured: " +
// captureOutputStream.toString() );
}
}
private class CapturingServletInputStream extends ServletInputStream
{
private ServletInputStream inputStream;
private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream();
public CapturingServletInputStream( ServletInputStream inputStream )
{
this.inputStream = inputStream;
}
public int read() throws IOException
{
int i = inputStream.read();
captureOutputStream.write( i );
return i;
}
public int readLine( byte[] bytes, int i, int i1 ) throws IOException
{
int result = inputStream.readLine( bytes, i, i1 );
captureOutputStream.write( bytes, i, i1 );
return result;
}
public int read( byte[] b ) throws IOException
{
int i = inputStream.read( b );
captureOutputStream.write( b );
return i;
}
public int read( byte[] b, int off, int len ) throws IOException
{
int result = inputStream.read( b, off, len );
if( result != -1 )
captureOutputStream.write( b, off, result );
return result;
}
public long skip( long n ) throws IOException
{
return inputStream.skip( n );
}
public int available() throws IOException
{
return inputStream.available();
}
public void close() throws IOException
{
inputStream.close();
// log.info( "Closing input stream, captured: " +
// captureOutputStream.toString() );
}
public void mark( int readlimit )
{
inputStream.mark( readlimit );
}
public boolean markSupported()
{
return inputStream.markSupported();
}
public void reset() throws IOException
{
inputStream.reset();
}
}
private class ServerHandler extends AbstractHandler
{
public void handle( String target, HttpServletRequest request, HttpServletResponse response, int dispatch )
throws IOException, ServletException
{
// find mockService
Map<String, List<MockRunner>> map = runners.get( request.getLocalPort() );
// ssl?
if( map == null && sslConnector != null && request.getLocalPort() == sslConnector.getPort() )
{
for( Map<String, List<MockRunner>> runnerMap : runners.values() )
{
if( runnerMap.containsKey( request.getPathInfo() ) )
{
map = runnerMap;
break;
}
}
}
if( map != null )
{
List<MockRunner> wsdlMockRunners = map.get( request.getPathInfo() );
if( wsdlMockRunners == null )
{
for( String root : map.keySet() )
{
if( request.getPathInfo().startsWith( root ) )
{
wsdlMockRunners = map.get( root );
}
}
}
if( wsdlMockRunners != null )
{
try
{
DispatchException ex = null;
MockResult result = null;
for( MockRunner wsdlMockRunner : wsdlMockRunners )
{
if( !wsdlMockRunner.isRunning() )
continue;
try
{
result = wsdlMockRunner.dispatchRequest( request, response );
if( result != null )
{
result.finish();
break;
}
}
catch( DispatchException e )
{
// log.debug( wsdlMockRunner.getMockService().getName()
// + " was unable to dispatch mock request ",
// e );
ex = e;
}
}
if( ex != null && result == null )
throw ex;
}
catch( Exception e )
{
SoapUI.logError( e );
response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
response.setContentType( "text/html" );
response.getWriter().print(
SoapMessageBuilder.buildFault( "Server", e.getMessage(), SoapVersion.Utils
.getSoapVersionForContentType( request.getContentType(), SoapVersion.Soap11 ) ) );
// throw new ServletException( e );
}
}
else
{
printMockServiceList( response );
}
}
else
{
printMockServiceList( response );
}
response.flushBuffer();
}
private void printMockServiceList( HttpServletResponse response ) throws IOException
{
response.setStatus( HttpServletResponse.SC_OK );
response.setContentType( "text/html" );
MockRunner[] mockRunners = getMockRunners();
PrintWriter out = response.getWriter();
out.print( "<html><body><p>There are currently " + mockRunners.length + " running soapUI MockServices</p><ul>" );
for( MockRunner mockRunner : mockRunners )
{
out.print( "<li><a href=\"" );
out.print( mockRunner.getMockService().getPath() + "?WSDL" );
out.print( "\">" + mockRunner.getMockService().getName() + "</a></li>" );
}
out.print( "</ul></p></body></html>" );
}
}
public MockRunner[] getMockRunners()
{
return mockRunners.toArray( new MockRunner[mockRunners.size()] );
}
private class MockRequestLog extends AbstractLifeCycle implements RequestLog
{
public void log( Request request, Response response )
{
if( !SoapUI.getSettings().getBoolean( HttpSettings.ENABLE_MOCK_WIRE_LOG ) )
return;
if( SoapUI.getLogMonitor() == null || SoapUI.getLogMonitor().getLogArea( "jetty log" ) == null
|| SoapUI.getLogMonitor().getLogArea( "jetty log" ).getLoggers() == null )
return;
Logger logger = SoapUI.getLogMonitor().getLogArea( "jetty log" ).getLoggers()[0];
try
{
ServletInputStream inputStream = request.getInputStream();
if( inputStream instanceof CapturingServletInputStream )
{
ByteArrayOutputStream byteArrayOutputStream = ( ( CapturingServletInputStream )inputStream ).captureOutputStream;
String str = request.toString() + byteArrayOutputStream.toString();
BufferedReader reader = new BufferedReader( new StringReader( str ) );
( ( CapturingServletInputStream )inputStream ).captureOutputStream = new ByteArrayOutputStream();
String line = reader.readLine();
while( line != null )
{
logger.info( ">> \"" + line + "\"" );
line = reader.readLine();
}
}
}
catch( Throwable e )
{
SoapUI.logError( e );
}
try
{
ServletOutputStream outputStream = response.getOutputStream();
if( outputStream instanceof CapturingServletOutputStream )
{
ByteArrayOutputStream byteArrayOutputStream = ( ( CapturingServletOutputStream )outputStream ).captureOutputStream;
String str = request.toString() + byteArrayOutputStream.toString();
BufferedReader reader = new BufferedReader( new StringReader( str ) );
( ( CapturingServletOutputStream )outputStream ).captureOutputStream = new ByteArrayOutputStream();
String line = reader.readLine();
while( line != null )
{
logger.info( "<< \"" + line + "\"" );
line = reader.readLine();
}
}
}
catch( Throwable e )
{
SoapUI.logError( e );
}
}
}
}