View Javadoc

1   /*
2    *  soapUI, copyright (C) 2006 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
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  		
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 }