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 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( modelThread == null )
152 {
153 released = false;
154 modelThread = new Thread( model, title + " LogListUpdater" );
155 modelThread.start();
156 }
157
158 if( line instanceof LoggingEvent )
159 {
160 LoggingEvent ev = ( LoggingEvent ) line;
161 linesToAdd.push( new LoggingEventWrapper( ev ) );
162
163 if( ev.getThrowableInformation() != null )
164 {
165 Throwable t = ev.getThrowableInformation().getThrowable();
166 StringWriter sw = new StringWriter();
167 PrintWriter pw = new PrintWriter( sw );
168 t.printStackTrace( pw );
169 StringTokenizer st = new StringTokenizer( sw.toString(), "\r\n" );
170 while( st.hasMoreElements() )
171 linesToAdd.push( " " + st.nextElement() );
172 }
173 }
174 else
175 {
176 linesToAdd.push( line );
177 }
178 }
179
180 public void setEnabled( boolean enabled )
181 {
182 super.setEnabled( enabled );
183 logList.setEnabled( enabled );
184 enableMenuItem.setSelected( enabled );
185 }
186
187 private static class LogAreaCellRenderer extends DefaultListCellRenderer
188 {
189 private Map<Level, Color> levelColors = new HashMap<Level, Color>();
190
191 private LogAreaCellRenderer()
192 {
193 levelColors.put( Level.ERROR, new Color( 192, 0, 0 ) );
194 levelColors.put( Level.INFO, new Color( 0, 92, 0 ) );
195 levelColors.put( Level.WARN, Color.ORANGE.darker().darker() );
196 levelColors.put( Level.DEBUG, new Color( 0, 0, 128 ) );
197 }
198
199 public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
200 boolean cellHasFocus )
201 {
202 JLabel component = ( JLabel ) super
203 .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 private final class LogListModel extends AbstractListModel implements Runnable
449 {
450 private List<Object> lines = new LinkedList<Object>();
451
452 public int getSize()
453 {
454 return lines.size();
455 }
456
457 public Object getElementAt( int index )
458 {
459 return lines.get( index );
460 }
461
462 public void clear()
463 {
464 int sz = lines.size();
465 if( sz == 0 )
466 return;
467
468 lines.clear();
469 fireIntervalRemoved( this, 0, sz - 1 );
470 }
471
472 public void run()
473 {
474 while( !released && !linesToAdd.isEmpty() )
475 {
476 try
477 {
478 if( !linesToAdd.isEmpty() )
479 {
480 SwingUtilities.invokeAndWait( new Runnable()
481 {
482 public void run()
483 {
484 while( !linesToAdd.isEmpty() )
485 {
486 int sz = lines.size();
487 lines.addAll( linesToAdd );
488 linesToAdd.clear();
489 fireIntervalAdded( LogListModel.this, sz, lines.size() - sz );
490 }
491
492 int cnt = 0;
493 while( lines.size() > maxRows )
494 {
495 lines.remove( 0 );
496 cnt++;
497 }
498
499 if( cnt > 0 )
500 fireIntervalRemoved( LogListModel.this, 0, cnt - 1 );
501
502 if( tailing )
503 {
504 logList.ensureIndexIsVisible( lines.size() - 1 );
505 }
506 }
507 } );
508 }
509
510 Thread.sleep( 500 );
511 }
512 catch( InterruptedException e )
513 {
514 SoapUI.logError( e );
515 }
516 catch( InvocationTargetException e )
517 {
518 SoapUI.logError( e );
519 }
520 }
521
522 modelThread = null;
523 }
524 }
525
526 public void release()
527 {
528 released = true;
529 }
530
531 public void saveToFile( File file )
532 {
533 try
534 {
535 PrintWriter writer = new PrintWriter( file );
536 for( int c = 0; c < model.getSize(); c++ )
537 {
538 writer.println( model.getElementAt( c ) );
539 }
540
541 writer.close();
542 }
543 catch( Exception e )
544 {
545 UISupport.showErrorMessage( e );
546 }
547 }
548 }