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 Logger [] getLoggers()
256 	{
257 		return loggers.toArray( new Logger[loggers.size()] );
258 	}
259 
260 	public void setLevel( Level level )
261 	{
262 		for( Logger logger : loggers )
263 		{
264 			logger.setLevel( level );
265 		}
266 	}
267 	
268 	public Logger getLogger( String loggerName )
269 	{
270 		for( Logger logger : loggers )
271 		{
272 			if( logger.getName().equals( loggerName ) )
273 				return logger;
274 		}
275 		
276 		return null;
277 	}
278 
279 	private class InternalLogAppender extends AppenderSkeleton
280 	{
281 		protected void append( LoggingEvent event )
282 		{
283 			addLine( event );
284 		}
285 
286 		public void close()
287 		{
288 		}
289 
290 		public boolean requiresLayout()
291 		{
292 			return false;
293 		}
294 	}
295 
296 	public boolean monitors( String loggerName )
297 	{
298 		for( Logger logger : loggers )
299 		{
300 			if( loggerName.startsWith( logger.getName() ) )
301 				return true;
302 		}
303 
304 		return false;
305 	}
306 
307 	public void removeLogger( String loggerName )
308 	{
309 		for( Logger logger : loggers )
310 		{
311 			if( loggerName.equals( logger.getName() ) )
312 			{
313 				logger.removeAppender( internalLogAppender );
314 			}
315 		}
316 	}
317 
318 	public boolean isTailing()
319 	{
320 		return tailing;
321 	}
322 
323 	public void setTailing( boolean tail )
324 	{
325 		this.tailing = tail;
326 	}
327 
328 	private class ClearAction extends AbstractAction
329 	{
330 		public ClearAction()
331 		{
332 			super( "Clear" );
333 		}
334 
335 		public void actionPerformed( ActionEvent e )
336 		{
337 			model.clear();
338 		}
339 	}
340 
341 	private class SetMaxRowsAction extends AbstractAction
342 	{
343 		public SetMaxRowsAction()
344 		{
345 			super( "Set Max Rows" );
346 		}
347 
348 		public void actionPerformed( ActionEvent e )
349 		{
350 			String val = UISupport.prompt( "Set maximum number of log rows to keep", "Set Max Rows", String
351 						.valueOf( maxRows ) );
352 			if( val != null )
353 			{
354 				try
355 				{
356 					maxRows = Long.parseLong( val );
357 					SoapUI.getSettings().setString( "JLogList#" + title, val );
358 				}
359 				catch( NumberFormatException e1 )
360 				{
361 					UISupport.beep();
362 				}
363 			}
364 		}
365 	}
366 
367 	private class ExportToFileAction extends AbstractAction
368 	{
369 		public ExportToFileAction()
370 		{
371 			super( "Export to File" );
372 		}
373 
374 		public void actionPerformed( ActionEvent e )
375 		{
376 			if( model.getSize() == 0 )
377 			{
378 				UISupport.showErrorMessage( "Log is empty; nothing to export" );
379 				return;
380 			}
381 
382 			File file = UISupport.getFileDialogs().saveAs( JLogList.this, "Save Log [] to File", "*.log", "*.log", null );
383 			if( file != null )
384 				saveToFile( file );
385 		}
386 	}
387 
388 	private class CopyAction extends AbstractAction
389 	{
390 		public CopyAction()
391 		{
392 			super( "Copy to clipboard" );
393 		}
394 
395 		public void actionPerformed( ActionEvent e )
396 		{
397 			Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
398 
399 			StringBuffer buf = new StringBuffer();
400 			int[] selectedIndices = logList.getSelectedIndices();
401 			if( selectedIndices.length == 0 )
402 			{
403 				for( int c = 0; c < logList.getModel().getSize(); c++ )
404 				{
405 					buf.append( logList.getModel().getElementAt( c ).toString() );
406 					buf.append( "\r\n" );
407 				}
408 			}
409 			else
410 			{
411 				for( int c = 0; c < selectedIndices.length; c++ )
412 				{
413 					buf.append( logList.getModel().getElementAt( selectedIndices[c] ).toString() );
414 					buf.append( "\r\n" );
415 				}
416 			}
417 
418 			StringSelection selection = new StringSelection( buf.toString() );
419 			clipboard.setContents( selection, selection );
420 		}
421 	}
422 
423 	private class EnableAction extends AbstractAction
424 	{
425 		public EnableAction()
426 		{
427 			super( "Enable" );
428 		}
429 
430 		public void actionPerformed( ActionEvent e )
431 		{
432 			JLogList.this.setEnabled( enableMenuItem.isSelected() );
433 		}
434 	}
435 
436 	/***
437 	 * Internal list model that for optimized storage and notifications
438 	 * 
439 	 * @author Ole.Matzura
440 	 */
441 
442 	private final class LogListModel extends AbstractListModel implements Runnable
443 	{
444 		private List<Object> lines = new LinkedList<Object>();
445 
446 		public int getSize()
447 		{
448 			return lines.size();
449 		}
450 
451 		public Object getElementAt( int index )
452 		{
453 			return lines.get( index );
454 		}
455 
456 		public void clear()
457 		{
458 			int sz = lines.size();
459 			if( sz == 0 )
460 				return;
461 
462 			lines.clear();
463 			fireIntervalRemoved( this, 0, sz - 1 );
464 		}
465 
466 		public void run()
467 		{
468 			while( !released && !linesToAdd.isEmpty() )
469 			{
470 				try
471 				{
472 					if( !linesToAdd.isEmpty() )
473 					{
474 						SwingUtilities.invokeAndWait( new Runnable()
475 						{
476 							public void run()
477 							{
478 								while( !linesToAdd.isEmpty() )
479 								{
480 									int sz = lines.size();
481 									lines.addAll( linesToAdd );
482 									linesToAdd.clear();
483 									fireIntervalAdded( LogListModel.this, sz, lines.size() - sz );
484 								}
485 
486 								int cnt = 0;
487 								while( lines.size() > maxRows )
488 								{
489 									lines.remove( 0 );
490 									cnt++;
491 								}
492 
493 								if( cnt > 0 )
494 									fireIntervalRemoved( LogListModel.this, 0, cnt - 1 );
495 
496 								if( tailing )
497 								{
498 									logList.ensureIndexIsVisible( lines.size() - 1 );
499 								}
500 							}
501 						} );
502 					}
503 
504 					Thread.sleep( 500 );
505 				}
506 				catch( InterruptedException e )
507 				{
508 					SoapUI.logError( e );
509 				}
510 				catch( InvocationTargetException e )
511 				{
512 					SoapUI.logError( e );
513 				}
514 			}
515 
516 			modelThread = null;
517 		}
518 	}
519 
520 	public void release()
521 	{
522 		released = true;
523 	}
524 
525 	public void saveToFile( File file )
526 	{
527 		try
528 		{
529 			PrintWriter writer = new PrintWriter( file );
530 			for( int c = 0; c < model.getSize(); c++ )
531 			{
532 				writer.println( model.getElementAt( c ) );
533 			}
534 
535 			writer.close();
536 		}
537 		catch( Exception e )
538 		{
539 			UISupport.showErrorMessage( e );
540 		}
541 	}
542 }