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 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 }