View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2007 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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 }