| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.log4j.chainsaw; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.swing.ProgressMonitor; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.EventListenerList; |
| import javax.swing.table.AbstractTableModel; |
| |
| import org.apache.log4j.LogManager; |
| import org.apache.log4j.Logger; |
| import org.apache.log4j.helpers.Constants; |
| import org.apache.log4j.rule.Rule; |
| import org.apache.log4j.spi.LoggingEvent; |
| import org.apache.log4j.spi.LocationInfo; |
| |
| |
| /** |
| * A CyclicBuffer implementation of the EventContainer. |
| * |
| * NOTE: This implementation prevents duplicate rows from being added to the model. |
| * |
| * Ignoring duplicates was added to support receivers which may attempt to deliver the same |
| * event more than once but can be safely ignored (for example, the database receiver |
| * when set to retrieve in a loop). |
| * |
| * @author Paul Smith <psmith@apache.org> |
| * @author Scott Deboy <sdeboy@apache.org> |
| * @author Stephen Pain |
| * |
| */ |
| class ChainsawCyclicBufferTableModel extends AbstractTableModel |
| implements EventContainer, PropertyChangeListener { |
| private static final int DEFAULT_CAPACITY = 5000; |
| private boolean cyclic = true; |
| private int cyclicBufferSize = DEFAULT_CAPACITY; |
| List unfilteredList; |
| List filteredList; |
| Set idSet = new HashSet(cyclicBufferSize); |
| private boolean currentSortAscending; |
| private int currentSortColumn; |
| private EventListenerList eventListenerList = new EventListenerList(); |
| private List columnNames = new ArrayList(ChainsawColumns.getColumnsNames()); |
| private boolean sortEnabled = false; |
| private boolean reachedCapacity = false; |
| private final Logger logger = LogManager.getLogger(ChainsawCyclicBufferTableModel.class); |
| |
| // protected final Object syncLock = new Object(); |
| private LoggerNameModel loggerNameModelDelegate = |
| new LoggerNameModelSupport(); |
| |
| //because we may be using a cyclic buffer, if an ID is not provided in the property, |
| //use and increment this row counter as the ID for each received row |
| int uniqueRow; |
| private Set uniquePropertyKeys = new HashSet(); |
| private Rule displayRule; |
| private PropertyChangeSupport propertySupport = |
| new PropertyChangeSupport(this); |
| |
| public ChainsawCyclicBufferTableModel(int cyclicBufferSize) { |
| propertySupport.addPropertyChangeListener("cyclic", new ModelChanger()); |
| this.cyclicBufferSize = cyclicBufferSize; |
| |
| unfilteredList = new CyclicBufferList(cyclicBufferSize); |
| filteredList = new CyclicBufferList(cyclicBufferSize); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) |
| */ |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (evt.getSource() instanceof Rule) { |
| reFilter(); |
| } |
| } |
| |
| public List getMatchingEvents(Rule rule) { |
| List list = new ArrayList(); |
| |
| synchronized (unfilteredList) { |
| Iterator iter = unfilteredList.iterator(); |
| |
| while (iter.hasNext()) { |
| LoggingEvent event = (LoggingEvent) iter.next(); |
| |
| if (rule.evaluate(event)) { |
| list.add(event); |
| } |
| } |
| } |
| |
| return list; |
| } |
| |
| private void reFilter() { |
| synchronized (unfilteredList) { |
| final int previousSize = filteredList.size(); |
| try { |
| filteredList.clear(); |
| |
| Iterator iter = unfilteredList.iterator(); |
| |
| while (iter.hasNext()) { |
| LoggingEvent e = (LoggingEvent) iter.next(); |
| |
| if ((displayRule == null) || (displayRule.evaluate(e))) { |
| filteredList.add(e); |
| } |
| } |
| } finally { |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| if (filteredList.size() > 0) { |
| if (previousSize == filteredList.size()) { |
| //same - update all |
| fireTableRowsUpdated(0, filteredList.size() - 1); |
| } else if (previousSize > filteredList.size()) { |
| //less now..update and delete difference |
| fireTableRowsUpdated(0, filteredList.size() - 1); |
| fireTableRowsDeleted(filteredList.size(), previousSize); |
| } else if (previousSize < filteredList.size()) { |
| //more now..update and insert difference |
| fireTableRowsUpdated(0, Math.max(0, previousSize - 1)); |
| fireTableRowsInserted(previousSize, filteredList.size() - 1); |
| } |
| } else { |
| //no rows to show |
| fireTableDataChanged(); |
| } |
| notifyCountListeners(); |
| }}); |
| } |
| } |
| } |
| |
| public int find(Rule rule, int startLocation, boolean searchForward) { |
| synchronized (filteredList) { |
| if (searchForward) { |
| for (int i = startLocation; i < filteredList.size(); i++) { |
| if (rule.evaluate((LoggingEvent) filteredList.get(i))) { |
| return i; |
| } |
| } |
| } else { |
| for (int i = startLocation; i > -1; i--) { |
| if (rule.evaluate((LoggingEvent) filteredList.get(i))) { |
| return i; |
| } |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * @param l |
| */ |
| public void removeLoggerNameListener(LoggerNameListener l) { |
| loggerNameModelDelegate.removeLoggerNameListener(l); |
| } |
| |
| /** |
| * @param loggerName |
| * @return |
| */ |
| public boolean addLoggerName(String loggerName) { |
| return loggerNameModelDelegate.addLoggerName(loggerName); |
| } |
| |
| /** |
| * @param l |
| */ |
| public void addLoggerNameListener(LoggerNameListener l) { |
| loggerNameModelDelegate.addLoggerNameListener(l); |
| } |
| |
| /** |
| * @return |
| */ |
| public Collection getLoggerNames() { |
| return loggerNameModelDelegate.getLoggerNames(); |
| } |
| |
| public void addEventCountListener(EventCountListener listener) { |
| eventListenerList.add(EventCountListener.class, listener); |
| } |
| |
| public boolean isSortable(int col) { |
| return true; |
| } |
| |
| public void notifyCountListeners() { |
| EventCountListener[] listeners = |
| (EventCountListener[]) eventListenerList.getListeners( |
| EventCountListener.class); |
| |
| for (int i = 0; i < listeners.length; i++) { |
| listeners[i].eventCountChanged( |
| filteredList.size(), unfilteredList.size()); |
| } |
| } |
| |
| /** |
| * Changes the underlying display rule in use. If there was |
| * a previous Rule defined, this Model removes itself as a listener |
| * from the old rule, and adds itself to the new rule (if the new Rule is not Null). |
| * |
| * In any case, the model ensures the Filtered list is made up to date in a separate thread. |
| */ |
| public void setDisplayRule(Rule displayRule) { |
| if (this.displayRule != null) { |
| this.displayRule.removePropertyChangeListener(this); |
| } |
| |
| this.displayRule = displayRule; |
| |
| if (this.displayRule != null) { |
| this.displayRule.addPropertyChangeListener(this); |
| } |
| |
| reFilter(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#sort() |
| */ |
| public void sort() { |
| if (sortEnabled && filteredList.size() > 0) { |
| synchronized (filteredList) { |
| Collections.sort( |
| filteredList, |
| new ColumnComparator( |
| getColumnName(currentSortColumn), currentSortColumn, |
| currentSortAscending)); |
| } |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| fireTableRowsUpdated(0, Math.max(filteredList.size() - 1, 0)); |
| } |
| }); |
| } |
| } |
| |
| public boolean isSortEnabled() { |
| return sortEnabled; |
| } |
| |
| public void sortColumn(int col, boolean ascending) { |
| logger.debug("request to sort col=" + col); |
| currentSortAscending = ascending; |
| currentSortColumn = col; |
| sortEnabled = true; |
| sort(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#clear() |
| */ |
| public void clearModel() { |
| reachedCapacity = false; |
| |
| synchronized (unfilteredList) { |
| unfilteredList.clear(); |
| filteredList.clear(); |
| idSet.clear(); |
| uniqueRow = 0; |
| } |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| fireTableDataChanged(); |
| } |
| }); |
| |
| notifyCountListeners(); |
| } |
| |
| public List getAllEvents() { |
| List list = new ArrayList(unfilteredList.size()); |
| |
| synchronized (unfilteredList) { |
| list.addAll(unfilteredList); |
| } |
| |
| return list; |
| } |
| |
| |
| public List getFilteredEvents() { |
| List list = new ArrayList(filteredList.size()); |
| |
| synchronized (filteredList) { |
| list.addAll(filteredList); |
| } |
| |
| return list; |
| } |
| |
| public int getRowIndex(LoggingEvent e) { |
| synchronized (filteredList) { |
| return filteredList.indexOf(e); |
| } |
| } |
| |
| public int getColumnCount() { |
| return columnNames.size(); |
| } |
| |
| public String getColumnName(int column) { |
| return columnNames.get(column).toString(); |
| } |
| |
| public LoggingEvent getRow(int row) { |
| synchronized (filteredList) { |
| if (row < filteredList.size()) { |
| return (LoggingEvent) filteredList.get(row); |
| } |
| } |
| |
| return null; |
| } |
| |
| public int getRowCount() { |
| synchronized (filteredList) { |
| return filteredList.size(); |
| } |
| } |
| |
| public Object getValueAt(int rowIndex, int columnIndex) { |
| LoggingEvent event = null; |
| |
| synchronized (filteredList) { |
| if (rowIndex < filteredList.size() && rowIndex > -1) { |
| event = (LoggingEvent) filteredList.get(rowIndex); |
| } |
| } |
| |
| if (event == null) { |
| return null; |
| } |
| |
| LocationInfo info = null; |
| |
| if (event.locationInformationExists()) { |
| info = event.getLocationInformation(); |
| } |
| |
| if (event == null) { |
| logger.error("Invalid rowindex=" + rowIndex); |
| throw new NullPointerException("Invalid rowIndex=" + rowIndex); |
| } |
| |
| switch (columnIndex + 1) { |
| case ChainsawColumns.INDEX_ID_COL_NAME: |
| |
| Object id = event.getProperty(Constants.LOG4J_ID_KEY); |
| |
| if (id != null) { |
| return id; |
| } |
| |
| return new Integer(rowIndex); |
| |
| case ChainsawColumns.INDEX_LEVEL_COL_NAME: |
| return event.getLevel(); |
| |
| case ChainsawColumns.INDEX_LOGGER_COL_NAME: |
| return event.getLoggerName(); |
| |
| case ChainsawColumns.INDEX_TIMESTAMP_COL_NAME: |
| return new Date(event.getTimeStamp()); |
| |
| case ChainsawColumns.INDEX_MESSAGE_COL_NAME: |
| return event.getRenderedMessage(); |
| |
| case ChainsawColumns.INDEX_NDC_COL_NAME: |
| return event.getNDC(); |
| |
| case ChainsawColumns.INDEX_THREAD_COL_NAME: |
| return event.getThreadName(); |
| |
| case ChainsawColumns.INDEX_THROWABLE_COL_NAME: |
| return event.getThrowableStrRep(); |
| |
| case ChainsawColumns.INDEX_CLASS_COL_NAME: |
| return ((info == null) |
| || ((info != null) && "?".equals(info.getClassName()))) ? "" |
| : info |
| .getClassName(); |
| |
| case ChainsawColumns.INDEX_FILE_COL_NAME: |
| return ((info == null) |
| || ((info != null) && "?".equals(info.getFileName()))) ? "" |
| : info |
| .getFileName(); |
| |
| case ChainsawColumns.INDEX_LINE_COL_NAME: |
| return ((info == null) |
| || ((info != null) && "?".equals(info.getLineNumber()))) ? "" |
| : info |
| .getLineNumber(); |
| |
| case ChainsawColumns.INDEX_METHOD_COL_NAME: |
| return ((info == null) |
| || ((info != null) && "?".equals(info.getMethodName()))) ? "" |
| : info |
| .getMethodName(); |
| |
| default: |
| |
| if (columnIndex <= columnNames.size()) { |
| return event.getProperty(columnNames.get(columnIndex).toString()); |
| } |
| } |
| |
| return ""; |
| } |
| |
| public boolean isAddRow(LoggingEvent e, boolean valueIsAdjusting) { |
| boolean rowAdded = false; |
| |
| Object id = e.getProperty(Constants.LOG4J_ID_KEY); |
| |
| if (id == null) { |
| id = new Integer(++uniqueRow); |
| e.setProperty(Constants.LOG4J_ID_KEY, id.toString()); |
| } |
| |
| //prevent duplicate rows |
| if (idSet.contains(id)) { |
| return false; |
| } |
| |
| idSet.add(id); |
| unfilteredList.add(e); |
| |
| if ((displayRule == null) || (displayRule.evaluate(e))) { |
| synchronized (filteredList) { |
| filteredList.add(e); |
| rowAdded = true; |
| } |
| } |
| |
| /** |
| * Is this a new Property key we haven't seen before? Remember that now MDC has been merged |
| * into the Properties collection |
| */ |
| boolean newColumn = uniquePropertyKeys.addAll(e.getPropertyKeySet()); |
| |
| if (newColumn) { |
| /** |
| * If so, we should add them as columns and notify listeners. |
| */ |
| for (Iterator iter = e.getPropertyKeySet().iterator(); iter.hasNext();) { |
| Object key = iter.next(); |
| |
| //add all keys except the 'log4jid' key |
| if (!columnNames.contains(key) && !(Constants.LOG4J_ID_KEY.equalsIgnoreCase(key.toString()))) { |
| columnNames.add(key); |
| logger.debug("Adding col '" + key + "', columNames=" + columnNames); |
| fireNewKeyColumnAdded( |
| new NewKeyEvent( |
| this, columnNames.indexOf(key), key, e.getProperty(key.toString()))); |
| } |
| } |
| } |
| |
| if (!valueIsAdjusting) { |
| int lastAdded = getLastAdded(); |
| fireTableEvent(lastAdded, lastAdded, 1); |
| } |
| |
| return rowAdded; |
| } |
| |
| public int getLastAdded() { |
| int last = 0; |
| |
| if (cyclic) { |
| last = ((CyclicBufferList) filteredList).getLast(); |
| } else { |
| last = filteredList.size(); |
| } |
| |
| return last; |
| } |
| |
| public void fireTableEvent(final int begin, final int end, final int count) { |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| if (cyclic) { |
| if (!reachedCapacity) { |
| //if we didn't loop and it's the 1st time, insert |
| if ((begin + count) < cyclicBufferSize) { |
| fireTableRowsInserted(begin, end); |
| } else { |
| //we did loop - insert and then update rows |
| fireTableRowsInserted(begin, cyclicBufferSize); |
| fireTableRowsUpdated(0, cyclicBufferSize); |
| reachedCapacity = true; |
| } |
| } else { |
| fireTableRowsUpdated(0, cyclicBufferSize); |
| } |
| } else { |
| fireTableRowsInserted(begin, end); |
| } |
| }}); |
| } |
| |
| /** |
| * @param key |
| */ |
| private void fireNewKeyColumnAdded(NewKeyEvent e) { |
| NewKeyListener[] listeners = |
| (NewKeyListener[]) eventListenerList.getListeners(NewKeyListener.class); |
| |
| for (int i = 0; i < listeners.length; i++) { |
| NewKeyListener listener = listeners[i]; |
| listener.newKeyAdded(e); |
| } |
| } |
| |
| /** |
| * Returns true if this model is Cyclic (bounded) or not |
| * @return true/false |
| */ |
| public boolean isCyclic() { |
| return cyclic; |
| } |
| |
| /** |
| * @return |
| */ |
| public int getMaxSize() { |
| return cyclicBufferSize; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#addNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener) |
| */ |
| public void addNewKeyListener(NewKeyListener l) { |
| eventListenerList.add(NewKeyListener.class, l); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#removeNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener) |
| */ |
| public void removeNewKeyListener(NewKeyListener l) { |
| eventListenerList.remove(NewKeyListener.class, l); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.swing.table.TableModel#isCellEditable(int, int) |
| */ |
| public boolean isCellEditable(int rowIndex, int columnIndex) { |
| switch (columnIndex + 1) { |
| case ChainsawColumns.INDEX_THROWABLE_COL_NAME: |
| return true; |
| } |
| |
| return super.isCellEditable(rowIndex, columnIndex); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#setCyclic(boolean) |
| */ |
| public void setCyclic(final boolean cyclic) { |
| if (this.cyclic == cyclic) { |
| return; |
| } |
| |
| final boolean old = this.cyclic; |
| this.cyclic = cyclic; |
| propertySupport.firePropertyChange("cyclic", old, cyclic); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.beans.PropertyChangeListener) |
| */ |
| public void addPropertyChangeListener(PropertyChangeListener l) { |
| propertySupport.addPropertyChangeListener(l); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) |
| */ |
| public void addPropertyChangeListener( |
| String propertyName, PropertyChangeListener l) { |
| propertySupport.addPropertyChangeListener(propertyName, l); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.log4j.chainsaw.EventContainer#size() |
| */ |
| public int size() { |
| return unfilteredList.size(); |
| } |
| |
| private class ModelChanger implements PropertyChangeListener { |
| /* (non-Javadoc) |
| * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) |
| */ |
| public void propertyChange(PropertyChangeEvent arg0) { |
| Thread thread = |
| new Thread( |
| new Runnable() { |
| public void run() { |
| ProgressMonitor monitor = null; |
| |
| int index = 0; |
| |
| try { |
| synchronized (unfilteredList) { |
| monitor = |
| new ProgressMonitor( |
| null, "Switching models...", |
| "Transferring between data structures, please wait...", 0, |
| unfilteredList.size() + 1); |
| monitor.setMillisToDecideToPopup(250); |
| monitor.setMillisToPopup(100); |
| logger.debug( |
| "Changing Model, isCyclic is now " + isCyclic()); |
| |
| List newUnfilteredList = null; |
| List newFilteredList = null; |
| HashSet newIDSet = null; |
| |
| newIDSet = new HashSet(cyclicBufferSize); |
| |
| if (isCyclic()) { |
| newUnfilteredList = new CyclicBufferList(cyclicBufferSize); |
| newFilteredList = new CyclicBufferList(cyclicBufferSize); |
| } else { |
| newUnfilteredList = new ArrayList(cyclicBufferSize); |
| newFilteredList = new ArrayList(cyclicBufferSize); |
| } |
| |
| int increment = 0; |
| |
| for (Iterator iter = unfilteredList.iterator(); |
| iter.hasNext();) { |
| LoggingEvent e = (LoggingEvent) iter.next(); |
| newUnfilteredList.add(e); |
| |
| Object o = |
| e.getProperty( |
| e.getProperty(Constants.LOG4J_ID_KEY)); |
| |
| if (o != null) { |
| newIDSet.add(o); |
| } else { |
| newIDSet.add(new Integer(increment++)); |
| } |
| |
| monitor.setProgress(index++); |
| } |
| |
| unfilteredList = newUnfilteredList; |
| filteredList = newFilteredList; |
| idSet = newIDSet; |
| } |
| |
| monitor.setNote("Refiltering..."); |
| reFilter(); |
| |
| monitor.setProgress(index++); |
| } finally { |
| monitor.close(); |
| } |
| |
| logger.debug("Model Change completed"); |
| } |
| }); |
| thread.setPriority(Thread.MIN_PRIORITY + 1); |
| thread.start(); |
| } |
| } |
| } |