View Javadoc

1   /*
2    * $HeadURL: $
3    * $Date: $
4    * $Revision: $
5    * $Author: $
6    * 
7    * Copyright (c) 2005 MindTree Consulting Ltd. 
8    * 
9    * This file is part of Insight.
10   * 
11   * Insight is free software: you can redistribute it and/or modify it under the 
12   * terms of the GNU General Public License as published by the Free Software 
13   * Foundation, either version 3 of the License, or (at your option) any later 
14   * version.
15   * 
16   * Insight is distributed in the hope that it will be useful, but 
17   * WITHOUT ANY WARRANTY; without even the implied warranty of 
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
19   * Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public License along with 
22   * Insight.  If not, see <http://www.gnu.org/licenses/>.
23   */
24  
25  package com.mindtree.techworks.insight.gui;
26  
27  import java.awt.Dimension;
28  import java.awt.EventQueue;
29  import java.awt.Toolkit;
30  import java.awt.event.MouseAdapter;
31  import java.awt.event.MouseEvent;
32  import java.util.Enumeration;
33  
34  import javax.swing.BorderFactory;
35  import javax.swing.JComponent;
36  import javax.swing.JScrollBar;
37  import javax.swing.JScrollPane;
38  import javax.swing.JTable;
39  import javax.swing.ListSelectionModel;
40  import javax.swing.event.ListSelectionEvent;
41  import javax.swing.event.ListSelectionListener;
42  import javax.swing.table.JTableHeader;
43  import javax.swing.table.TableColumn;
44  import javax.swing.table.TableColumnModel;
45  
46  import com.mindtree.techworks.insight.Controller;
47  import com.mindtree.techworks.insight.InsightConstants;
48  import com.mindtree.techworks.insight.gui.action.IAction;
49  import com.mindtree.techworks.insight.gui.renderers.LogEventTableCellRenderer;
50  import com.mindtree.techworks.insight.gui.renderers.SortableHeaderRenderer;
51  import com.mindtree.techworks.insight.model.ILogEventModelMutator;
52  import com.mindtree.techworks.insight.model.LogEventTableModel;
53  import com.mindtree.techworks.insight.pagination.IPage;
54  import com.mindtree.techworks.insight.preferences.PreferenceChangeListener;
55  import com.mindtree.techworks.insight.preferences.PreferenceManager;
56  import com.mindtree.techworks.insight.preferences.util.PreferenceInterpreter;
57  import com.mindtree.techworks.insight.spi.LogEvent;
58  
59  /**
60  *
61  * The <code>EventListPresentation</code> class is a Presentation implementation
62  * that displays summary information of LoggingEvent instances received by this
63  * Presentation. This Presentation contains a JTable that displays the summary - one
64  * row per event.   
65  *
66  * @author  Regunath B
67  * @version 1.0, 04/10/25
68  * @see     com.mindtree.techworks.insight.gui.Presentation
69  * @see     org.apache.log4j.spi.LoggingEvent
70  */
71  
72  public class EventListPresentation implements Presentation, ListSelectionListener, IAction, PreferenceChangeListener {	
73  
74  	/*
75  	 * Constant that identifies the JTable widget
76  	 */
77  	private static final int TABLE_WIDGET = 1;
78  	
79  	/*
80  	 * Constants for the preferred size of this Presentation 
81  	 */
82  	private static final int WIDTH = 900;
83  	private static final int HEIGHT = 300;
84  	
85  	/**
86  	 * Constants for scroll lock state
87  	 */
88  	private static final int SCROLL_LOCK = 10;
89  	private static final int SCROLL_UNLOCK = 20;
90  	private static final int SCROLL_JUST_UNLOCKED = 30;
91  	
92  	/**
93  	 * Constant for the Event Column display Preference identifier
94  	 */
95  	private static final String EVENT_COLUMN_PREFERENCE_ID = "eventcolumnpreference";
96  
97  	/*
98  	 * The Controller instance for this Presentation
99  	 */
100 	private Controller controller;
101 
102 	/*
103 	 * The AbstractTableModel implementation that backs the JTable contained in 
104 	 * this Presentation
105 	 */
106 	private LogEventTableModel tableModel;
107 
108 	/*
109 	 * The JTable instance that renders the LoggingEvent instances
110 	 */
111 	private JTable table;
112 
113 	/*
114 	 * The JScrollPane that contains the JTable
115 	 */
116 	private JScrollPane scrollPane;
117 	
118 	/*
119 	 * The tableCellRenderer instance used for rendering the table columns
120 	 */
121 	private LogEventTableCellRenderer cellRenderer;
122 	
123 	/*
124 	 * The boolean indicator to enable/disable sorting
125 	 */
126 	private boolean enableSort;
127 	
128 	/**
129 	 * The scroll lock state indicator
130 	 */
131 	private volatile int scrollLockState = SCROLL_UNLOCK;
132 	
133 	/**
134 	 * Constructor for this class
135 	 * @param controller the Controller instance for this Presentation
136 	 */
137 	public EventListPresentation(Controller controller) {
138 		int[] displayColumns = PreferenceInterpreter.getConfiguredColumnsForDisplay();
139 		tableModel = new LogEventTableModel(displayColumns);
140 		table = new JTable(tableModel);
141 		initTable(displayColumns, false);
142 		
143         table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
144         table.getTableHeader().addMouseListener(new MouseHandler(this.tableModel));		
145 		
146         scrollPane = new JScrollPane(table);
147         scrollPane.setBorder(BorderFactory.createTitledBorder(InsightConstants.getLiteral("EVENTS")));
148         scrollPane.setPreferredSize(getIdealPreferredSize());
149         
150         ListSelectionModel rowSM = table.getSelectionModel();
151         rowSM.addListSelectionListener(this);
152         
153 		this.controller = controller;
154 		controller.registerPresentation(this);
155 		controller.registerAction(this);
156 		
157 		// add this PreferenceChangeListener for callback when Event column preference changes
158 		PreferenceManager.getInstance().registerListener(EVENT_COLUMN_PREFERENCE_ID, this);
159 		
160 	}	
161 
162 	/**
163 	 * Presentation interface method implementation. Returns the fully qualified class name
164 	 * of this Presentation
165 	 * @see com.mindtree.techworks.insight.gui.Presentation#getUID()
166 	 */
167 	public String getUID() {
168 		return this.getClass().getName();
169 	}
170 
171 	/**
172 	 * Presentation interface method implementation. Does nothing
173 	 * @see com.mindtree.techworks.insight.gui.Presentation#notifyWidgetStateChange(com.mindtree.techworks.insight.gui.Presentation, int, java.lang.Object)
174 	 */
175 	public void notifyWidgetStateChange(Presentation presentation, int identifier, Object data) {	
176 	}
177 	
178 	/**
179 	 * Presentation interface method implementation. Returns the contained JScrollPane instance
180 	 * @see com.mindtree.techworks.insight.gui.Presentation#getViewComponent()
181 	 */
182 	public JComponent getViewComponent() {
183 		return scrollPane;
184 	}
185 
186 	/**
187 	 * Presentation interface method implementation. Returns true.
188 	 * @see com.mindtree.techworks.insight.gui.Presentation#doesProcessRealTimeUpdates()
189 	 */
190 	public boolean doesProcessRealTimeUpdates() {
191 		return true;
192 	}
193 
194 	/**
195 	 * Presentation interface method implementation.Adds the event to the AbstractTableModel instance
196 	 * that backs the JTable instance contained in this Presentation. 
197 	 * @see com.mindtree.techworks.insight.gui.Presentation#processRealTimeUpdate(com.mindtree.techworks.insight.spi.LogEvent)
198 	 */
199 	public void processRealTimeUpdate(LogEvent event) {
200 		if (this.scrollLockState == SCROLL_JUST_UNLOCKED) {
201 			synchronized(this) {
202 				try {
203 					// Wait for the table model to be updated with the last set of
204 					// entries and return since the last event would have been added
205 					// anyway
206 					wait();
207 					return;
208 				} catch(InterruptedException ie) {
209 					// do nothing
210 				}
211 			}
212 		}
213 		int size = tableModel.getRowCount();
214 		if (size >= PreferenceInterpreter.getCachePageSize()) {
215 			if (!this.controller.isScrollLock() && this.controller.getMutatorType() == ILogEventModelMutator.TAILING_MUTATOR) {
216 				// Clear the model if tailing mutator in order to
217 				// continue to display the last received entries
218 				tableModel.clearModel();
219 				tableModel.addLogEvent(event);
220 			}
221 		} else {
222 			tableModel.addLogEvent(event);
223 		}
224 		// If tailing, set the scrollbar position to display the last received entry
225 		if (!this.controller.isScrollLock() && this.controller.getMutatorType() == ILogEventModelMutator.TAILING_MUTATOR) {
226 			JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
227 			scrollBar.setValue(scrollBar.getMaximum());
228 		}
229 	}
230 	
231 	/** 
232 	 * Interface methid implementation
233 	 * @see com.mindtree.techworks.insight.gui.Presentation#resetWidgets()
234 	 */
235 	public void resetWidgets() {
236 		tableModel.clearModel();
237 	}	
238 
239 	/**
240 	 * ListSelectionListener interface method implementation. Gets the selected
241 	 * LoggingEvent and informs the Controller of a widget change. 
242 	 * Note that the data Object passed by this Presentation is of type org.apache.log4j.spi.LoggingEvent
243 	 * @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
244 	 * @see org.apache.log4j.spi.LoggingEvent
245 	 */
246 	public void valueChanged(ListSelectionEvent event) {
247         if (event.getValueIsAdjusting()) {
248             return;
249         }
250         ListSelectionModel lsm = (ListSelectionModel) event.getSource();
251         if (!lsm.isSelectionEmpty()) {
252             Object data = tableModel.getLoggingEvent(lsm.getMinSelectionIndex());
253             controller.getCurrentLogEventModel().setLogEventIndex(tableModel.getLoggingEvent(lsm.getMinSelectionIndex()).getSequenceNumber());
254             // fire a widget change only when data is really available and the change has occurred from a manual
255             // selection. Helps to ignore events that fire when data is loaded asynchronously into the table model
256             if (data != null) {
257             	controller.fireWidgetChanged(this,TABLE_WIDGET, data);
258             }
259         }
260 	}
261 
262 	/**
263 	 * Interface method implementation
264 	 * @see Presentation#displayPage(IPage, long)
265 	 */
266 	public void displayPage(IPage page, long eventSequenceNumber) {
267 		this.tableModel.updateDataModel(page.getLogEventList());
268 		initTable(tableModel.getColumnList(), true);
269 		if (eventSequenceNumber >= 0) {
270 			// get the zero based index in the current page
271 			int selectedIndex = ((int)((eventSequenceNumber-1) % PreferenceInterpreter.getCachePageSize()));
272 			this.table.setRowSelectionInterval(selectedIndex,selectedIndex);
273 			this.table.setColumnSelectionInterval(0, 0);
274 			// Set the Thread to be invoked for Positioning the scrollbar after 
275 			// all other GUI events have completed
276 			EventQueue.invokeLater(new ScrollBarPositionLocator(page,selectedIndex));
277 		} else {
278 			// position the scrollbar to the start of the page
279 			scrollPane.getVerticalScrollBar().setValue(0);
280 		}
281 	}
282 	
283 	/**
284 	 * IAction interface method implementation
285 	 * @see com.mindtree.techworks.insight.gui.action.IAction#getType()
286 	 */
287 	public int getType() {
288 		return IAction.MUTATION;
289 	}
290 
291 	/**
292 	 * IAction interface method implementation
293 	 * @see com.mindtree.techworks.insight.gui.action.IAction#setEnabled(boolean)
294 	 */
295 	public void setEnabled(boolean enable) {
296 		this.enableSort = enable;
297 	}
298 	
299 	/**
300 	 * PreferenceChangeListener interface method implementation
301 	 * @see com.mindtree.techworks.insight.preferences.PreferenceChangeListener#preferenceChanged(java.lang.String)
302 	 */
303 	public void preferenceChanged(String preferenceId) {
304 		if (preferenceId.equals(EVENT_COLUMN_PREFERENCE_ID)) {
305 			initTable(PreferenceInterpreter.getConfiguredColumnsForDisplay(), true);
306 		}
307 	}	
308 
309 	/**
310 	 * Presentation Interface method implementation
311 	 * @see Presentation#setScrollLock(boolean)
312 	 */
313 	public void setScrollLock(boolean scrollLock) {
314 		this.scrollLockState = scrollLock ? SCROLL_LOCK : SCROLL_JUST_UNLOCKED;
315 		if (this.scrollLockState == SCROLL_JUST_UNLOCKED) {
316 			// The scroll lock is being disabled. Load the last page
317 			// entries if the mutator is a tailing mutator
318 			if (this.controller.getMutatorType() == ILogEventModelMutator.TAILING_MUTATOR) {
319 				IPage page = this.controller.
320 				getCurrentLogEventModel().getPageSet().getLastPage();
321 				this.tableModel.updateDataModelAndDisplay(page.getLogEventList());
322 				synchronized(this) {
323 					this.scrollLockState = SCROLL_UNLOCK;
324 					// wake up all waiting threads that may want to update
325 					// the table model
326 					notifyAll();
327 				}
328 			} else {
329 				// set to unlocked state immediately if the mutator is not tailing
330 				this.scrollLockState = SCROLL_UNLOCK;
331 			}
332 		}
333 	}
334 	
335 	/**
336 	 * Helper method to initialize the table display aspects
337 	 * @param displayColumns array containing the display column ids
338 	 * @param hasStructureChanged true if the display of table or structure has changed, false otherwise
339 	 */
340 	private void initTable(int[] displayColumns, boolean hasStructureChanged) {
341 		this.tableModel.setColumnList(displayColumns);
342 		if (hasStructureChanged) {
343 			this.tableModel.fireTableStructureChanged();
344 		}		
345         Enumeration columns = table.getColumnModel().getColumns();
346         cellRenderer = new LogEventTableCellRenderer();
347 		int index = 0;
348         while(columns.hasMoreElements()) {
349         	TableColumn tableColumn = (TableColumn)columns.nextElement();
350     		// set the identifiers and renderer for the TableColumn instances
351         	tableColumn.setIdentifier(new Integer(displayColumns[index]));
352         	tableColumn.setCellRenderer(cellRenderer);
353         	index += 1;
354         }        
355         JTableHeader tableHeader = table.getTableHeader();
356         tableHeader.setDefaultRenderer(
357                 new SortableHeaderRenderer(tableHeader.getDefaultRenderer()));
358 	}
359 	
360 	/**
361 	 * Helper method that returns the best size between this component's preferred size and that ideal 
362 	 * for the display size
363 	 * @return Dimension most appropriate for this Presentation
364 	 */
365 	private Dimension getIdealPreferredSize() {
366 		Dimension dimension = null;
367 		Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
368 		dimension = new Dimension((int)Math.min(screenDim.getWidth() - 50, WIDTH),
369 				(int)Math.min(screenDim.getHeight()/3, HEIGHT));
370 		return dimension;		
371 	}        	
372 	
373 	/**
374 	 * Helper class that sets the value/position of the scroll pane's the 
375 	 * vertical scroll bar depending on the log event that was located.
376 	 *  
377 	 */
378 	private class ScrollBarPositionLocator extends Thread {
379 		
380 		private IPage page;
381 		private int selectedIndex;
382 		public ScrollBarPositionLocator(IPage page, int selectedIndex) {
383 			this.page = page;
384 			this.selectedIndex = selectedIndex;
385 		  	// explicitly set the priority to normal. Otherwise
386 		  	// this thread will inherit the priority of the creator thread
387 		  	// i.e the AWT event dispatch thread that runs with max priority
388 			this.setPriority(Thread.NORM_PRIORITY);						
389 		}		
390 		public void run() {
391 			JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
392 			int widthPerRow = scrollBar.getMaximum()/page.getLogEventList().size();
393 			scrollBar.setValue(selectedIndex * widthPerRow);
394 		}
395 	}
396 	
397 	/**
398 	 * Helper class that handles mouse clicks on the table header
399 	 */
400 
401     private class MouseHandler extends MouseAdapter {
402 
403 		/*
404 		 * The table model of the JTable
405 		 */
406 		private LogEventTableModel tableModel;
407     	
408     	/**
409     	 * Constructor for this class
410     	 * @param tableModel The table model for the JTable
411     	 */    	
412     	public MouseHandler(LogEventTableModel tableModel){
413     		this.tableModel = tableModel;
414     	}
415     	
416     	/**
417     	 * Overriden super class method
418     	 * @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
419     	 */
420         public void mouseClicked(MouseEvent e) {
421         	if (table.getModel().getRowCount() < PreferenceInterpreter.getCachePageSize()) { 
422 	        	if (!enableSort) {
423 	        		// donot sort if current page is being updated or if no data found
424 	       			return;
425 	       		}
426         	}
427             JTableHeader h = (JTableHeader) e.getSource();
428             TableColumnModel columnModel = h.getColumnModel();
429             int viewColumn = columnModel.getColumnIndexAtX(e.getX());
430             int column = columnModel.getColumn(viewColumn).getModelIndex();
431             this.tableModel.sortColumn(this.tableModel.getColumnList()[column]);
432         }
433     }
434 
435 }