View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2010 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.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 		// 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 			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 							// server.removeConnector( sslConnector );
256 							// sslConnector.stop();
257 							// sslConnector = null;
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 				// New request content available
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 			// buffer.mark( readlimit );
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 			// log.info( "Closing output stream, captured: " +
576 			// captureOutputStream.toString() );
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 			// log.info( "Closing input stream, captured: " +
633 			// captureOutputStream.toString() );
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 			// find mockService
658 			Map<String, List<MockRunner>> map = runners.get( request.getLocalPort() );
659 
660 			// ssl?
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 						// throw new ServletException( e );
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 }