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.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.util.ArrayList;
26  import java.util.Date;
27  import java.util.HashMap;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Stack;
32  import java.util.StringTokenizer;
33  import java.util.concurrent.Future;
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 final String title;
77  	private boolean released;
78  	private Future<?> future;
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 		JScrollPane scrollPane = new JScrollPane( logList );
108 		UISupport.addPreviewCorner( scrollPane, true );
109 		add( scrollPane, BorderLayout.CENTER );
110 
111 		requestAttributes = new SimpleAttributeSet();
112 		StyleConstants.setForeground( requestAttributes, Color.BLUE );
113 
114 		responseAttributes = new SimpleAttributeSet();
115 		StyleConstants.setForeground( responseAttributes, Color.GREEN );
116 
117 		try
118 		{
119 			maxRows = Long.parseLong( SoapUI.getSettings().getString( "JLogList#" + title, "1000" ) );
120 		}
121 		catch( NumberFormatException e )
122 		{
123 		}
124 	}
125 
126 	public void clear()
127 	{
128 		model.clear();
129 	}
130 
131 	public JList getLogList()
132 	{
133 		return logList;
134 	}
135 
136 	public long getMaxRows()
137 	{
138 		return maxRows;
139 	}
140 
141 	public void setMaxRows( long maxRows )
142 	{
143 		this.maxRows = maxRows;
144 	}
145 
146 	public synchronized void addLine( Object line )
147 	{
148 		if( !isEnabled() )
149 			return;
150 
151 		if( line instanceof LoggingEvent )
152 		{
153 			LoggingEvent ev = ( LoggingEvent )line;
154 			linesToAdd.push( new LoggingEventWrapper( ev ) );
155 
156 			if( ev.getThrowableInformation() != null )
157 			{
158 				Throwable t = ev.getThrowableInformation().getThrowable();
159 				StringWriter sw = new StringWriter();
160 				PrintWriter pw = new PrintWriter( sw );
161 				t.printStackTrace( pw );
162 				StringTokenizer st = new StringTokenizer( sw.toString(), "\r\n" );
163 				while( st.hasMoreElements() )
164 					linesToAdd.push( "   " + st.nextElement() );
165 			}
166 		}
167 		else
168 		{
169 			linesToAdd.push( line );
170 		}
171 
172 		if( future == null )
173 		{
174 			released = false;
175 			future = SoapUI.getThreadPool().submit( model );
176 		}
177 	}
178 
179 	public void setEnabled( boolean enabled )
180 	{
181 		super.setEnabled( enabled );
182 		logList.setEnabled( enabled );
183 		enableMenuItem.setSelected( enabled );
184 	}
185 
186 	private static class LogAreaCellRenderer extends DefaultListCellRenderer
187 	{
188 		private Map<Level, Color> levelColors = new HashMap<Level, Color>();
189 
190 		private LogAreaCellRenderer()
191 		{
192 			levelColors.put( Level.ERROR, new Color( 192, 0, 0 ) );
193 			levelColors.put( Level.INFO, new Color( 0, 92, 0 ) );
194 			levelColors.put( Level.WARN, Color.ORANGE.darker().darker() );
195 			levelColors.put( Level.DEBUG, new Color( 0, 0, 128 ) );
196 		}
197 
198 		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
199 				boolean cellHasFocus )
200 		{
201 			JLabel component = ( JLabel )super.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 			// Limit the length of the tool tip, to prevent long delays.
212 			String toolTip = component.getText();
213 			if( toolTip != null && toolTip.length() > 1000 )
214 				toolTip = toolTip.substring( 0, 1000 );
215 			component.setToolTipText( toolTip );
216 
217 			return component;
218 		}
219 	}
220 
221 	private final static class LoggingEventWrapper
222 	{
223 		private final LoggingEvent loggingEvent;
224 		private String str;
225 
226 		public LoggingEventWrapper( LoggingEvent loggingEvent )
227 		{
228 			this.loggingEvent = loggingEvent;
229 		}
230 
231 		public Level getLevel()
232 		{
233 			return loggingEvent.getLevel();
234 		}
235 
236 		public String toString()
237 		{
238 			if( str == null )
239 			{
240 				StringBuilder builder = new StringBuilder();
241 				builder.append( new Date( loggingEvent.timeStamp ) );
242 				builder.append( ':' ).append( loggingEvent.getLevel() ).append( ':' ).append( loggingEvent.getMessage() );
243 				str = builder.toString();
244 			}
245 
246 			return str;
247 		}
248 	}
249 
250 	public void addLogger( String loggerName, boolean addAppender )
251 	{
252 		Logger logger = Logger.getLogger( loggerName );
253 		if( addAppender )
254 			logger.addAppender( internalLogAppender );
255 
256 		loggers.add( logger );
257 	}
258 
259 	public Logger[] getLoggers()
260 	{
261 		return loggers.toArray( new Logger[loggers.size()] );
262 	}
263 
264 	public void setLevel( Level level )
265 	{
266 		for( Logger logger : loggers )
267 		{
268 			logger.setLevel( level );
269 		}
270 	}
271 
272 	public Logger getLogger( String loggerName )
273 	{
274 		for( Logger logger : loggers )
275 		{
276 			if( logger.getName().equals( loggerName ) )
277 				return logger;
278 		}
279 
280 		return null;
281 	}
282 
283 	private class InternalLogAppender extends AppenderSkeleton
284 	{
285 		protected void append( LoggingEvent event )
286 		{
287 			addLine( event );
288 		}
289 
290 		public void close()
291 		{
292 		}
293 
294 		public boolean requiresLayout()
295 		{
296 			return false;
297 		}
298 	}
299 
300 	public boolean monitors( String loggerName )
301 	{
302 		for( Logger logger : loggers )
303 		{
304 			if( loggerName.startsWith( logger.getName() ) )
305 				return true;
306 		}
307 
308 		return false;
309 	}
310 
311 	public void removeLogger( String loggerName )
312 	{
313 		for( Logger logger : loggers )
314 		{
315 			if( loggerName.equals( logger.getName() ) )
316 			{
317 				logger.removeAppender( internalLogAppender );
318 			}
319 		}
320 	}
321 
322 	public boolean isTailing()
323 	{
324 		return tailing;
325 	}
326 
327 	public void setTailing( boolean tail )
328 	{
329 		this.tailing = tail;
330 	}
331 
332 	private class ClearAction extends AbstractAction
333 	{
334 		public ClearAction()
335 		{
336 			super( "Clear" );
337 		}
338 
339 		public void actionPerformed( ActionEvent e )
340 		{
341 			model.clear();
342 		}
343 	}
344 
345 	private class SetMaxRowsAction extends AbstractAction
346 	{
347 		public SetMaxRowsAction()
348 		{
349 			super( "Set Max Rows" );
350 		}
351 
352 		public void actionPerformed( ActionEvent e )
353 		{
354 			String val = UISupport.prompt( "Set maximum number of log rows to keep", "Set Max Rows", String
355 					.valueOf( maxRows ) );
356 			if( val != null )
357 			{
358 				try
359 				{
360 					maxRows = Long.parseLong( val );
361 					SoapUI.getSettings().setString( "JLogList#" + title, val );
362 				}
363 				catch( NumberFormatException e1 )
364 				{
365 					UISupport.beep();
366 				}
367 			}
368 		}
369 	}
370 
371 	private class ExportToFileAction extends AbstractAction
372 	{
373 		public ExportToFileAction()
374 		{
375 			super( "Export to File" );
376 		}
377 
378 		public void actionPerformed( ActionEvent e )
379 		{
380 			if( model.getSize() == 0 )
381 			{
382 				UISupport.showErrorMessage( "Log is empty; nothing to export" );
383 				return;
384 			}
385 
386 			File file = UISupport.getFileDialogs().saveAs( JLogList.this, "Save Log [] to File", "*.log", "*.log", null );
387 			if( file != null )
388 				saveToFile( file );
389 		}
390 	}
391 
392 	private class CopyAction extends AbstractAction
393 	{
394 		public CopyAction()
395 		{
396 			super( "Copy to clipboard" );
397 		}
398 
399 		public void actionPerformed( ActionEvent e )
400 		{
401 			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
402 
403 			StringBuffer buf = new StringBuffer();
404 			int[] selectedIndices = logList.getSelectedIndices();
405 			if( selectedIndices.length == 0 )
406 			{
407 				for( int c = 0; c < logList.getModel().getSize(); c++ )
408 				{
409 					buf.append( logList.getModel().getElementAt( c ).toString() );
410 					buf.append( "\r\n" );
411 				}
412 			}
413 			else
414 			{
415 				for( int c = 0; c < selectedIndices.length; c++ )
416 				{
417 					buf.append( logList.getModel().getElementAt( selectedIndices[c] ).toString() );
418 					buf.append( "\r\n" );
419 				}
420 			}
421 
422 			StringSelection selection = new StringSelection( buf.toString() );
423 			clipboard.setContents( selection, selection );
424 		}
425 	}
426 
427 	private class EnableAction extends AbstractAction
428 	{
429 		public EnableAction()
430 		{
431 			super( "Enable" );
432 		}
433 
434 		public void actionPerformed( ActionEvent e )
435 		{
436 			JLogList.this.setEnabled( enableMenuItem.isSelected() );
437 		}
438 	}
439 
440 	/***
441 	 * Internal list model that for optimized storage and notifications
442 	 * 
443 	 * @author Ole.Matzura
444 	 */
445 
446 	private final class LogListModel extends AbstractListModel implements Runnable
447 	{
448 		private List<Object> lines = new LinkedList<Object>();
449 
450 		public int getSize()
451 		{
452 			return lines.size();
453 		}
454 
455 		public Object getElementAt( int index )
456 		{
457 			return lines.get( index );
458 		}
459 
460 		public void clear()
461 		{
462 			int sz = lines.size();
463 			if( sz == 0 )
464 				return;
465 
466 			lines.clear();
467 			fireIntervalRemoved( this, 0, sz - 1 );
468 		}
469 
470 		public void run()
471 		{
472 			Thread.currentThread().setName( "LogList Updater for " + title );
473 
474 			try
475 			{
476 				while( !released && !linesToAdd.isEmpty() )
477 				{
478 					try
479 					{
480 						if( !linesToAdd.isEmpty() )
481 						{
482 							SwingUtilities.invokeAndWait( new Runnable()
483 							{
484 								public void run()
485 								{
486 									try
487 									{
488 										while( !linesToAdd.isEmpty() )
489 										{
490 											int sz = lines.size();
491 											lines.addAll( linesToAdd );
492 											linesToAdd.clear();
493 											fireIntervalAdded( LogListModel.this, sz, lines.size() - sz );
494 										}
495 
496 										int cnt = 0;
497 										while( lines.size() > maxRows )
498 										{
499 											lines.remove( 0 );
500 											cnt++ ;
501 										}
502 
503 										if( cnt > 0 )
504 											fireIntervalRemoved( LogListModel.this, 0, cnt - 1 );
505 
506 										if( tailing )
507 										{
508 											logList.ensureIndexIsVisible( lines.size() - 1 );
509 										}
510 									}
511 									catch( Throwable e )
512 									{
513 										SoapUI.logError( e );
514 									}
515 								}
516 							} );
517 						}
518 
519 						Thread.sleep( 500 );
520 					}
521 					catch( Throwable e )
522 					{
523 						SoapUI.logError( e );
524 					}
525 				}
526 			}
527 			finally
528 			{
529 				future = null;
530 			}
531 		}
532 	}
533 
534 	public void release()
535 	{
536 		released = true;
537 	}
538 
539 	public void saveToFile( File file )
540 	{
541 		try
542 		{
543 			PrintWriter writer = new PrintWriter( file );
544 			for( int c = 0; c < model.getSize(); c++ )
545 			{
546 				writer.println( model.getElementAt( c ) );
547 			}
548 
549 			writer.close();
550 		}
551 		catch( Exception e )
552 		{
553 			UISupport.showErrorMessage( e );
554 		}
555 	}
556 }