View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 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.support.log;
    14
    15  import java.awt.BorderLayout;
    16  import java.awt.Color;
    17  import java.awt.Component;
    18  import java.awt.Toolkit;
    19  import java.awt.datatransfer.Clipboard;
    20  import java.awt.datatransfer.StringSelection;
    21  import java.awt.event.ActionEvent;
    22  import java.io.File;
    23  import java.io.PrintWriter;
    24  import java.io.StringWriter;
    25  import java.lang.reflect.InvocationTargetException;
    26  import java.util.ArrayList;
    27  import java.util.Date;
    28  import java.util.HashMap;
    29  import java.util.LinkedList;
    30  import java.util.List;
    31  import java.util.Map;
    32  import java.util.Stack;
    33  import java.util.StringTokenizer;
    34
    35  import javax.swing.AbstractAction;
    36  import javax.swing.AbstractListModel;
    37  import javax.swing.BorderFactory;
    38  import javax.swing.DefaultListCellRenderer;
    39  import javax.swing.JCheckBoxMenuItem;
    40  import javax.swing.JLabel;
    41  import javax.swing.JList;
    42  import javax.swing.JPanel;
    43  import javax.swing.JPopupMenu;
    44  import javax.swing.JScrollPane;
    45  import javax.swing.SwingUtilities;
    46  import javax.swing.text.SimpleAttributeSet;
    47  import javax.swing.text.StyleConstants;
    48
    49  import org.apache.log4j.AppenderSkeleton;
    50  import org.apache.log4j.Level;
    51  import org.apache.log4j.Logger;
    52  import org.apache.log4j.spi.LoggingEvent;
    53
    54  import com.eviware.soapui.SoapUI;
    55  import com.eviware.soapui.support.UISupport;
    56
    57  /***
    58   * Component for displaying log entries
    59   *
    60   * @author Ole.Matzura
    61   */
    62
    63  public class JLogList extends JPanel
    64  {
    65  	private long maxRows = 1000;
    66  	private JList logList;
    67  	private SimpleAttributeSet requestAttributes;
    68  	private SimpleAttributeSet responseAttributes;
    69  	private LogListModel model;
    70  	private List<Logger> loggers = new ArrayList<Logger>();
    71  	private InternalLogAppender internalLogAppender = new InternalLogAppender();
    72  	private boolean tailing = true;
    73  	private Stack<Object> linesToAdd = new Stack<Object>();
    74  	private EnableAction enableAction;
    75  	private JCheckBoxMenuItem enableMenuItem;
    76  	private Thread modelThread;
    77  	private final String title;
    78  	private boolean released;
    79
    80  	public JLogList( String title )
    81  	{
    82  		super( new BorderLayout() );
    83  		this.title = title;
    84
    85  		model = new LogListModel();
    86  		logList = new JList( model );
    87  		logList.setToolTipText( title );
    88  		logList.setCellRenderer( new LogAreaCellRenderer() );
    89  		logList.setPrototypeCellValue( "Testing 123" );
    90  		logList.setFixedCellWidth( -1 );
    91
    92  		JPopupMenu listPopup = new JPopupMenu();
    93  		listPopup.add( new ClearAction() );
    94  		enableAction = new EnableAction();
    95  		enableMenuItem = new JCheckBoxMenuItem( enableAction );
    96  		enableMenuItem.setSelected( true );
    97  		listPopup.add( enableMenuItem );
    98  		listPopup.addSeparator();
    99  		listPopup.add( new CopyAction() );
    100 		listPopup.add( new SetMaxRowsAction() );
    101 		listPopup.addSeparator();
    102 		listPopup.add( new ExportToFileAction() );
    103
    104 		logList.setComponentPopupMenu( listPopup );
    105
    106 		setBorder( BorderFactory.createEmptyBorder( 3, 3, 3, 3 ) );
    107 		add( new JScrollPane( logList ), BorderLayout.CENTER );
    108
    109 		requestAttributes = new SimpleAttributeSet();
    110 		StyleConstants.setForeground( requestAttributes, Color.BLUE );
    111
    112 		responseAttributes = new SimpleAttributeSet();
    113 		StyleConstants.setForeground( responseAttributes, Color.GREEN );
    114
    115 		try
    116 		{
    117 			maxRows = Long.parseLong( SoapUI.getSettings().getString( "JLogList#" + title, "1000" ) );
    118 		}
    119 		catch( NumberFormatException e )
    120 		{
    121 		}
    122 	}
    123
    124 	public void clear()
    125 	{
    126 		model.clear();
    127 	}
    128
    129 	public JList getLogList()
    130 	{
    131 		return logList;
    132 	}
    133
    134 	public long getMaxRows()
    135 	{
    136 		return maxRows;
    137 	}
    138
    139 	public void setMaxRows( long maxRows )
    140 	{
    141 		this.maxRows = maxRows;
    142 	}
    143
    144 	public synchronized void addLine( Object line )
    145 	{
    146 		if( !isEnabled() )
    147 			return;
    148
    149 		if( modelThread == null )
    150 		{
    151 			released = false;
    152 			modelThread = new Thread( model, title + " LogListUpdater" );
    153 			modelThread.start();
    154 		}
    155
    156 		if( line instanceof LoggingEvent )
    157 		{
    158 			LoggingEvent ev = ( LoggingEvent ) line;
    159 			linesToAdd.push( new LoggingEventWrapper( ev ) );
    160
    161 			if( ev.getThrowableInformation() != null )
    162 			{
    163 				Throwable t = ev.getThrowableInformation().getThrowable();
    164 				StringWriter sw = new StringWriter();
    165 				PrintWriter pw = new PrintWriter( sw );
    166 				t.printStackTrace( pw );
    167 				StringTokenizer st = new StringTokenizer( sw.toString(), "\r\n" );
    168 				while( st.hasMoreElements() )
    169 					linesToAdd.push( "   " +  st.nextElement() );
    170 			}
    171 		}
    172 		else
    173 		{
    174 			linesToAdd.push( line );
    175 		}
    176 	}
    177
    178 	public void setEnabled( boolean enabled )
    179 	{
    180 		super.setEnabled( enabled );
    181 		logList.setEnabled( enabled );
    182 		enableMenuItem.setSelected( enabled );
    183 	}
    184
    185 	private static class LogAreaCellRenderer extends DefaultListCellRenderer
    186 	{
    187 		private Map<Level, Color> levelColors = new HashMap<Level, Color>();
    188
    189 		private LogAreaCellRenderer()
    190 		{
    191 			levelColors.put( Level.ERROR, new Color( 192, 0, 0 ) );
    192 			levelColors.put( Level.INFO, new Color( 0, 92, 0 ) );
    193 			levelColors.put( Level.WARN, Color.ORANGE.darker().darker() );
    194 			levelColors.put( Level.DEBUG, new Color( 0, 0, 128 ) );
    195 		}
    196
    197 		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
    198 					boolean cellHasFocus )
    199 		{
    200 			JLabel component = ( JLabel ) super
    201 						.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
    202
    203 			if( value instanceof LoggingEventWrapper )
    204 			{
    205 				LoggingEventWrapper eventWrapper = ( LoggingEventWrapper ) value;
    206
    207 				if( levelColors.containsKey( eventWrapper.getLevel() ) )
    208 					component.setForeground( levelColors.get( eventWrapper.getLevel() ) );
    209 			}
    210
    211 			component.setToolTipText( component.getText() );
    212
    213 			return component;
    214 		}
    215 	}
    216
    217 	private final static class LoggingEventWrapper
    218 	{
    219 		private final LoggingEvent loggingEvent;
    220 		private String str;
    221
    222 		public LoggingEventWrapper( LoggingEvent loggingEvent )
    223 		{
    224 			this.loggingEvent = loggingEvent;
    225 		}
    226
    227 		public Level getLevel()
    228 		{
    229 			return loggingEvent.getLevel();
    230 		}
    231
    232 		public String toString()
    233 		{
    234 			if( str == null )
    235 			{
    236 				StringBuilder builder = new StringBuilder();
    237 				builder.append( new Date( loggingEvent.timeStamp ) );
    238 				builder.append( ':' ).append( loggingEvent.getLevel() ).append( ':' ).append( loggingEvent.getMessage() );
    239 				str = builder.toString();
    240 			}
    241
    242 			return str;
    243 		}
    244 	}
    245
    246 	public void addLogger( String loggerName, boolean addAppender )
    247 	{
    248 		Logger logger = Logger.getLogger( loggerName );
    249 		if( addAppender )
    250 			logger.addAppender( internalLogAppender );
    251
    252 		loggers.add( logger );
    253 	}
    254
    255 	public void setLevel( Level level )
    256 	{
    257 		for( Logger logger : loggers )
    258 		{
    259 			logger.setLevel( level );
    260 		}
    261 	}
    262
    263 	private class InternalLogAppender extends AppenderSkeleton
    264 	{
    265 		protected void append( LoggingEvent event )
    266 		{
    267 			addLine( event );
    268 		}
    269
    270 		public void close()
    271 		{
    272 		}
    273
    274 		public boolean requiresLayout()
    275 		{
    276 			return false;
    277 		}
    278 	}
    279
    280 	public boolean monitors( String loggerName )
    281 	{
    282 		for( Logger logger : loggers )
    283 		{
    284 			if( loggerName.startsWith( logger.getName() ) )
    285 				return true;
    286 		}
    287
    288 		return false;
    289 	}
    290
    291 	public void removeLogger( String loggerName )
    292 	{
    293 		for( Logger logger : loggers )
    294 		{
    295 			if( loggerName.equals( logger.getName() ) )
    296 			{
    297 				logger.removeAppender( internalLogAppender );
    298 			}
    299 		}
    300 	}
    301
    302 	public boolean isTailing()
    303 	{
    304 		return tailing;
    305 	}
    306
    307 	public void setTailing( boolean tail )
    308 	{
    309 		this.tailing = tail;
    310 	}
    311
    312 	private class ClearAction extends AbstractAction
    313 	{
    314 		public ClearAction()
    315 		{
    316 			super( "Clear" );
    317 		}
    318
    319 		public void actionPerformed( ActionEvent e )
    320 		{
    321 			model.clear();
    322 		}
    323 	}
    324
    325 	private class SetMaxRowsAction extends AbstractAction
    326 	{
    327 		public SetMaxRowsAction()
    328 		{
    329 			super( "Set Max Rows" );
    330 		}
    331
    332 		public void actionPerformed( ActionEvent e )
    333 		{
    334 			String val = UISupport.prompt( "Set maximum number of log rows to keep", "Set Max Rows", String
    335 						.valueOf( maxRows ) );
    336 			if( val != null )
    337 			{
    338 				try
    339 				{
    340 					maxRows = Long.parseLong( val );
    341 					SoapUI.getSettings().setString( "JLogList#" + title, val );
    342 				}
    343 				catch( NumberFormatException e1 )
    344 				{
    345 					UISupport.beep();
    346 				}
    347 			}
    348 		}
    349 	}
    350
    351 	private class ExportToFileAction extends AbstractAction
    352 	{
    353 		public ExportToFileAction()
    354 		{
    355 			super( "Export to File" );
    356 		}
    357
    358 		public void actionPerformed( ActionEvent e )
    359 		{
    360 			if( model.getSize() == 0 )
    361 			{
    362 				UISupport.showErrorMessage( "Log is empty; nothing to export" );
    363 				return;
    364 			}
    365
    366 			File file = UISupport.getFileDialogs().saveAs( JLogList.this, "Save Log [] to File", "*.log", "*.log", null );
    367 			if( file != null )
    368 				saveToFile( file );
    369 		}
    370 	}
    371
    372 	private class CopyAction extends AbstractAction
    373 	{
    374 		public CopyAction()
    375 		{
    376 			super( "Copy to clipboard" );
    377 		}
    378
    379 		public void actionPerformed( ActionEvent e )
    380 		{
    381 			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    382
    383 			StringBuffer buf = new StringBuffer();
    384 			int[] selectedIndices = logList.getSelectedIndices();
    385 			if( selectedIndices.length == 0 )
    386 			{
    387 				for( int c = 0; c < logList.getModel().getSize(); c++ )
    388 				{
    389 					buf.append( logList.getModel().getElementAt( c ).toString() );
    390 					buf.append( "\r\n" );
    391 				}
    392 			}
    393 			else
    394 			{
    395 				for( int c = 0; c < selectedIndices.length; c++ )
    396 				{
    397 					buf.append( logList.getModel().getElementAt( selectedIndices[c] ).toString() );
    398 					buf.append( "\r\n" );
    399 				}
    400 			}
    401
    402 			StringSelection selection = new StringSelection( buf.toString() );
    403 			clipboard.setContents( selection, selection );
    404 		}
    405 	}
    406
    407 	private class EnableAction extends AbstractAction
    408 	{
    409 		public EnableAction()
    410 		{
    411 			super( "Enable" );
    412 		}
    413
    414 		public void actionPerformed( ActionEvent e )
    415 		{
    416 			JLogList.this.setEnabled( enableMenuItem.isSelected() );
    417 		}
    418 	}
    419
    420 	/***
    421 	 * Internal list model that for optimized storage and notifications
    422 	 *
    423 	 * @author Ole.Matzura
    424 	 */
    425
    426 	private final class LogListModel extends AbstractListModel implements Runnable
    427 	{
    428 		private List<Object> lines = new LinkedList<Object>();
    429
    430 		public int getSize()
    431 		{
    432 			return lines.size();
    433 		}
    434
    435 		public Object getElementAt( int index )
    436 		{
    437 			return lines.get( index );
    438 		}
    439
    440 		public void clear()
    441 		{
    442 			int sz = lines.size();
    443 			if( sz == 0 )
    444 				return;
    445
    446 			lines.clear();
    447 			fireIntervalRemoved( this, 0, sz - 1 );
    448 		}
    449
    450 		public void run()
    451 		{
    452 			while( !released && !linesToAdd.isEmpty() )
    453 			{
    454 				try
    455 				{
    456 					if( !linesToAdd.isEmpty() )
    457 					{
    458 						SwingUtilities.invokeAndWait( new Runnable()
    459 						{
    460 							public void run()
    461 							{
    462 								while( !linesToAdd.isEmpty() )
    463 								{
    464 									int sz = lines.size();
    465 									lines.addAll( linesToAdd );
    466 									linesToAdd.clear();
    467 									fireIntervalAdded( this, sz, lines.size() - sz );
    468 								}
    469
    470 								int cnt = 0;
    471 								while( lines.size() > maxRows )
    472 								{
    473 									lines.remove( 0 );
    474 									cnt++;
    475 								}
    476
    477 								if( cnt > 0 )
    478 									fireIntervalRemoved( this, 0, cnt - 1 );
    479
    480 								if( tailing )
    481 								{
    482 									logList.ensureIndexIsVisible( lines.size() - 1 );
    483 								}
    484 							}
    485 						} );
    486 					}
    487
    488 					Thread.sleep( 500 );
    489 				}
    490 				catch( InterruptedException e )
    491 				{
    492 					SoapUI.logError( e );
    493 				}
    494 				catch( InvocationTargetException e )
    495 				{
    496 					SoapUI.logError( e );
    497 				}
    498 			}
    499
    500 			modelThread = null;
    501 		}
    502 	}
    503
    504 	public void release()
    505 	{
    506 		released = true;
    507 	}
    508
    509 	public void saveToFile( File file )
    510 	{
    511 		try
    512 		{
    513 			PrintWriter writer = new PrintWriter( file );
    514 			for( int c = 0; c < model.getSize(); c++ )
    515 			{
    516 				writer.println( model.getElementAt( c ) );
    517 			}
    518
    519 			writer.close();
    520 		}
    521 		catch( Exception e )
    522 		{
    523 			UISupport.showErrorMessage( e );
    524 		}
    525 	}
    526 }