1
2
3
4
5
6
7
8
9
10
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 }