View Javadoc

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