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