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