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.lang.reflect.InvocationTargetException;
23 import java.util.ArrayList;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Stack;
30
31 import javax.swing.AbstractAction;
32 import javax.swing.AbstractListModel;
33 import javax.swing.BorderFactory;
34 import javax.swing.DefaultListCellRenderer;
35 import javax.swing.JCheckBoxMenuItem;
36 import javax.swing.JLabel;
37 import javax.swing.JList;
38 import javax.swing.JPanel;
39 import javax.swing.JPopupMenu;
40 import javax.swing.JScrollPane;
41 import javax.swing.SwingUtilities;
42 import javax.swing.text.SimpleAttributeSet;
43 import javax.swing.text.StyleConstants;
44
45 import org.apache.log4j.AppenderSkeleton;
46 import org.apache.log4j.Level;
47 import org.apache.log4j.Logger;
48 import org.apache.log4j.spi.LoggingEvent;
49
50 import com.eviware.soapui.SoapUI;
51 import com.eviware.soapui.support.UISupport;
52
53 /***
54 * Component for displaying log entries
55 *
56 * @author Ole.Matzura
57 */
58
59 public class JLogList extends JPanel
60 {
61 private long maxRows = 1000;
62 private JList logList;
63 private SimpleAttributeSet requestAttributes;
64 private SimpleAttributeSet responseAttributes;
65 private LogListModel model;
66 private List<Logger> loggers = new ArrayList<Logger>();
67 private InternalLogAppender internalLogAppender = new InternalLogAppender();
68 private boolean tailing = true;
69 private Stack<Object> linesToAdd = new Stack<Object>();
70 private EnableAction enableAction;
71 private JCheckBoxMenuItem enableMenuItem;
72 private Thread modelThread;
73 private final String title;
74
75 public JLogList(String title)
76 {
77 super( new BorderLayout() );
78 this.title = title;
79
80 model = new LogListModel();
81 logList = new JList(model);
82 logList.setToolTipText( title );
83 logList.setCellRenderer( new LogAreaCellRenderer() );
84 logList.setPrototypeCellValue( "Testing 123" );
85 logList.setFixedCellWidth( -1 );
86
87 JPopupMenu listPopup = new JPopupMenu();
88 listPopup.add( new ClearAction() );
89 enableAction = new EnableAction();
90 enableMenuItem = new JCheckBoxMenuItem( enableAction );
91 enableMenuItem.setSelected( true );
92 listPopup.add( enableMenuItem );
93 listPopup.addSeparator();
94 listPopup.add( new CopyAction() );
95 listPopup.add( new SetMaxRowsAction() );
96
97 logList.setComponentPopupMenu( listPopup );
98
99 setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
100 add(new JScrollPane(logList), BorderLayout.CENTER);
101
102 requestAttributes = new SimpleAttributeSet();
103 StyleConstants.setForeground(requestAttributes, Color.BLUE );
104
105 responseAttributes = new SimpleAttributeSet();
106 StyleConstants.setForeground(responseAttributes, Color.GREEN );
107
108 try
109 {
110 maxRows = Long.parseLong( SoapUI.getSettings().getString( "JLogList#" + title, "1000"));
111 }
112 catch( NumberFormatException e )
113 {
114 }
115 }
116
117 public JList getLogList()
118 {
119 return logList;
120 }
121
122 public long getMaxRows()
123 {
124 return maxRows;
125 }
126
127 public void setMaxRows( long maxRows )
128 {
129 this.maxRows = maxRows;
130 }
131
132 public synchronized void addLine( Object line )
133 {
134 if( !isEnabled() )
135 return;
136
137 if( modelThread == null )
138 {
139 modelThread = new Thread( model );
140 modelThread.start();
141 }
142
143 if( line instanceof LoggingEvent )
144 {
145 linesToAdd.push( new LoggingEventWrapper( (LoggingEvent) line ));
146 }
147 else
148 {
149 linesToAdd.push( line );
150 }
151 }
152
153 public void setEnabled(boolean enabled)
154 {
155 super.setEnabled(enabled);
156 logList.setEnabled( enabled );
157 enableMenuItem.setSelected( enabled );
158 }
159
160 private static class LogAreaCellRenderer extends DefaultListCellRenderer
161 {
162 private Map<Level,Color> levelColors = new HashMap<Level,Color>();
163
164 private LogAreaCellRenderer()
165 {
166 levelColors.put( Level.ERROR, new Color( 192, 0, 0 ) );
167 levelColors.put( Level.INFO, new Color( 0, 92, 0 ) );
168 levelColors.put( Level.WARN, Color.ORANGE.darker().darker() );
169 levelColors.put( Level.DEBUG, new Color( 0, 0, 128 ) );
170 }
171
172 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
173 {
174 JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
175
176 if( value instanceof LoggingEventWrapper )
177 {
178 LoggingEventWrapper eventWrapper = (LoggingEventWrapper) value;
179
180 if( levelColors.containsKey( eventWrapper.getLevel() ))
181 component.setForeground( levelColors.get( eventWrapper.getLevel() ) );
182 }
183
184 component.setToolTipText( component.getText() );
185
186 return component;
187 }
188 }
189
190 private final static class LoggingEventWrapper
191 {
192 private final LoggingEvent loggingEvent;
193 private String str;
194
195 public LoggingEventWrapper( LoggingEvent loggingEvent )
196 {
197 this.loggingEvent = loggingEvent;
198 }
199
200 public Level getLevel()
201 {
202 return loggingEvent.getLevel();
203 }
204
205 public String toString()
206 {
207 if( str == null )
208 {
209 StringBuilder builder = new StringBuilder();
210 builder.append( new Date(loggingEvent.timeStamp) );
211 builder.append( ':' ).append( loggingEvent.getLevel() ).append( ':' ).append( loggingEvent.getMessage());
212 str = builder.toString();
213 }
214
215 return str;
216 }
217 }
218
219 public void addLogger(String loggerName, boolean addAppender)
220 {
221 Logger logger = Logger.getLogger( loggerName );
222 if( addAppender )
223 logger.addAppender( internalLogAppender);
224
225 loggers.add( logger );
226 }
227
228 public void setLevel( Level level )
229 {
230 for( Logger logger : loggers )
231 {
232 logger.setLevel( level );
233 }
234 }
235
236 private class InternalLogAppender extends AppenderSkeleton
237 {
238 protected void append(LoggingEvent event)
239 {
240 addLine( event );
241 }
242
243 public void close()
244 {
245 }
246
247 public boolean requiresLayout()
248 {
249 return false;
250 }
251 }
252
253 public boolean monitors(String loggerName)
254 {
255 for( Logger logger : loggers )
256 {
257 if( loggerName.startsWith( logger.getName() ) )
258 return true;
259 }
260
261 return false;
262 }
263
264 public void removeLogger(String loggerName)
265 {
266 for( Logger logger : loggers )
267 {
268 if( loggerName.equals( logger.getName() ) )
269 {
270 logger.removeAppender( internalLogAppender );
271 }
272 }
273 }
274
275 public boolean isTailing()
276 {
277 return tailing;
278 }
279
280 public void setTailing(boolean tail)
281 {
282 this.tailing = tail;
283 }
284
285 private class ClearAction extends AbstractAction
286 {
287 public ClearAction()
288 {
289 super( "Clear" );
290 }
291
292 public void actionPerformed(ActionEvent e)
293 {
294 model.clear();
295 }
296 }
297
298 private class SetMaxRowsAction extends AbstractAction
299 {
300 public SetMaxRowsAction()
301 {
302 super( "Set Max Rows" );
303 }
304
305 public void actionPerformed(ActionEvent e)
306 {
307 String val = UISupport.prompt( "Set maximum number of log rows to keep", "Set Max Rows", String.valueOf( maxRows ) );
308 if( val != null )
309 {
310 try
311 {
312 maxRows = Long.parseLong( val );
313 SoapUI.getSettings().setString( "JLogList#" + title, val );
314 }
315 catch( NumberFormatException e1 )
316 {
317 UISupport.beep();
318 }
319 }
320 }
321 }
322
323 private class CopyAction extends AbstractAction
324 {
325 public CopyAction()
326 {
327 super( "Copy to clipboard" );
328 }
329
330 public void actionPerformed(ActionEvent e)
331 {
332 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
333
334 StringBuffer buf = new StringBuffer();
335 int[] selectedIndices = logList.getSelectedIndices();
336 if( selectedIndices.length == 0 )
337 {
338 for( int c = 0; c < logList.getModel().getSize(); c++ )
339 {
340 buf.append( logList.getModel().getElementAt( c ).toString() );
341 buf.append( "\r\n" );
342 }
343 }
344 else
345 {
346 for( int c = 0; c < selectedIndices.length; c++ )
347 {
348 buf.append( logList.getModel().getElementAt( selectedIndices[c] ).toString() );
349 buf.append( "\r\n" );
350 }
351 }
352
353 StringSelection selection = new StringSelection( buf.toString() );
354 clipboard.setContents( selection, selection );
355 }
356 }
357
358
359 private class EnableAction extends AbstractAction
360 {
361 public EnableAction()
362 {
363 super( "Enable" );
364 }
365
366 public void actionPerformed(ActionEvent e)
367 {
368 JLogList.this.setEnabled( enableMenuItem.isSelected() );
369 }
370 }
371
372 /***
373 * Internal list model that for optimized storage and notifications
374 *
375 * @author Ole.Matzura
376 */
377
378 private final class LogListModel extends AbstractListModel implements Runnable
379 {
380 private List<Object> lines = new LinkedList<Object>();
381
382 public int getSize()
383 {
384 return lines.size();
385 }
386
387 public Object getElementAt(int index)
388 {
389 return lines.get( index );
390 }
391
392 public void clear()
393 {
394 int sz = lines.size();
395 if( sz == 0 )
396 return;
397
398 lines.clear();
399 fireIntervalRemoved( this, 0, sz-1 );
400 }
401
402 public void run()
403 {
404 while( true )
405 {
406 try
407 {
408 if( !linesToAdd.isEmpty() )
409 {
410 SwingUtilities.invokeAndWait( new Runnable()
411 {
412 public void run()
413 {
414 while( !linesToAdd.isEmpty() )
415 {
416 int sz = lines.size();
417 lines.addAll( linesToAdd );
418 linesToAdd.clear();
419 fireIntervalAdded( this, sz, lines.size()-sz );
420 }
421
422 int cnt = 0;
423 while( lines.size() > maxRows )
424 {
425 lines.remove( 0 );
426 cnt++;
427 }
428
429 if( cnt > 0 )
430 fireIntervalRemoved( this, 0, cnt-1 );
431
432 if( tailing )
433 {
434 logList.ensureIndexIsVisible( lines.size()-1 );
435 }
436 }
437 });
438 }
439
440 Thread.sleep( 500 );
441 }
442 catch (InterruptedException e)
443 {
444 e.printStackTrace();
445 }
446 catch (InvocationTargetException e)
447 {
448 e.printStackTrace();
449 }
450 }
451 }
452 }
453 }