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