View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.chainsaw;
19  
20  import java.beans.PropertyChangeEvent;
21  import java.beans.PropertyChangeListener;
22  import java.beans.PropertyChangeSupport;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Date;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.swing.ProgressMonitor;
33  import javax.swing.SwingUtilities;
34  import javax.swing.event.EventListenerList;
35  import javax.swing.table.AbstractTableModel;
36  
37  import org.apache.log4j.LogManager;
38  import org.apache.log4j.Logger;
39  import org.apache.log4j.helpers.Constants;
40  import org.apache.log4j.rule.Rule;
41  import org.apache.log4j.spi.LoggingEvent;
42  import org.apache.log4j.spi.LocationInfo;
43  
44  
45  /***
46   * A CyclicBuffer implementation of the EventContainer.
47   *
48   * NOTE:  This implementation prevents duplicate rows from being added to the model.
49   *
50   * Ignoring duplicates was added to support receivers which may attempt to deliver the same
51   * event more than once but can be safely ignored (for example, the database receiver
52   * when set to retrieve in a loop).
53   *
54   * @author Paul Smith <psmith@apache.org>
55   * @author Scott Deboy <sdeboy@apache.org>
56   * @author Stephen Pain
57   *
58   */
59  class ChainsawCyclicBufferTableModel extends AbstractTableModel
60    implements EventContainer, PropertyChangeListener {
61    private static final int DEFAULT_CAPACITY = 5000;
62    private boolean cyclic = true;
63    private int cyclicBufferSize = DEFAULT_CAPACITY;
64    List unfilteredList;
65    List filteredList;
66    Set idSet = new HashSet(cyclicBufferSize);
67    private boolean currentSortAscending;
68    private int currentSortColumn;
69    private EventListenerList eventListenerList = new EventListenerList();
70    private List columnNames = new ArrayList(ChainsawColumns.getColumnsNames());
71    private boolean sortEnabled = false;
72    private boolean reachedCapacity = false;
73    private final Logger logger = LogManager.getLogger(ChainsawCyclicBufferTableModel.class);
74  
75    //  protected final Object syncLock = new Object();
76    private LoggerNameModel loggerNameModelDelegate =
77      new LoggerNameModelSupport();
78  
79    //because we may be using a cyclic buffer, if an ID is not provided in the property, 
80    //use and increment this row counter as the ID for each received row
81    int uniqueRow;
82    private Set uniquePropertyKeys = new HashSet();
83    private Rule displayRule;
84    private PropertyChangeSupport propertySupport =
85      new PropertyChangeSupport(this);
86  
87    public ChainsawCyclicBufferTableModel(int cyclicBufferSize) {
88      propertySupport.addPropertyChangeListener("cyclic", new ModelChanger());
89      this.cyclicBufferSize = cyclicBufferSize;
90  
91      unfilteredList = new CyclicBufferList(cyclicBufferSize);
92      filteredList = new CyclicBufferList(cyclicBufferSize);
93    }
94  
95    /* (non-Javadoc)
96     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
97     */
98    public void propertyChange(PropertyChangeEvent evt) {
99      if (evt.getSource() instanceof Rule) {
100       reFilter();
101     }
102   }
103 
104   public List getMatchingEvents(Rule rule) {
105     List list = new ArrayList();
106 
107     synchronized (unfilteredList) {
108       Iterator iter = unfilteredList.iterator();
109 
110       while (iter.hasNext()) {
111         LoggingEvent event = (LoggingEvent) iter.next();
112 
113         if (rule.evaluate(event)) {
114           list.add(event);
115         }
116       }
117     }
118 
119     return list;
120   }
121 
122   private void reFilter() {
123     synchronized (unfilteredList) {
124       final int previousSize = filteredList.size();
125       try {
126         filteredList.clear();
127 
128         Iterator iter = unfilteredList.iterator();
129 
130         while (iter.hasNext()) {
131           LoggingEvent e = (LoggingEvent) iter.next();
132 
133           if ((displayRule == null) || (displayRule.evaluate(e))) {
134             filteredList.add(e);
135           }
136         }
137       } finally {
138       	SwingUtilities.invokeLater(new Runnable() {
139       		public void run() {
140       			if (filteredList.size() > 0) {
141 	      			if (previousSize == filteredList.size()) {
142 	      				//same - update all
143 	      				fireTableRowsUpdated(0, filteredList.size() - 1);
144 	      			} else if (previousSize > filteredList.size()) {
145 	      				//less now..update and delete difference
146 	      				fireTableRowsUpdated(0, filteredList.size() - 1);
147 	      				fireTableRowsDeleted(filteredList.size(), previousSize);
148 	      			} else if (previousSize < filteredList.size()) {
149 	      				//more now..update and insert difference
150 	      				fireTableRowsUpdated(0, Math.max(0, previousSize - 1));
151 	      				fireTableRowsInserted(previousSize, filteredList.size() - 1);
152 	      			}
153       			} else {
154       				//no rows to show
155       				fireTableDataChanged();
156       			}
157 	      	notifyCountListeners();
158       	}});
159       }
160     }
161   }
162 
163   public int find(Rule rule, int startLocation, boolean searchForward) {
164     synchronized (filteredList) {
165       if (searchForward) {
166         for (int i = startLocation; i < filteredList.size(); i++) {
167           if (rule.evaluate((LoggingEvent) filteredList.get(i))) {
168             return i;
169           }
170         }
171       } else {
172         for (int i = startLocation; i > -1; i--) {
173           if (rule.evaluate((LoggingEvent) filteredList.get(i))) {
174             return i;
175           }
176         }
177       }
178     }
179 
180     return -1;
181   }
182 
183   /***
184    * @param l
185    */
186   public void removeLoggerNameListener(LoggerNameListener l) {
187     loggerNameModelDelegate.removeLoggerNameListener(l);
188   }
189 
190   /***
191    * @param loggerName
192    * @return
193    */
194   public boolean addLoggerName(String loggerName) {
195     return loggerNameModelDelegate.addLoggerName(loggerName);
196   }
197 
198   /***
199    * @param l
200    */
201   public void addLoggerNameListener(LoggerNameListener l) {
202     loggerNameModelDelegate.addLoggerNameListener(l);
203   }
204 
205   /***
206    * @return
207    */
208   public Collection getLoggerNames() {
209     return loggerNameModelDelegate.getLoggerNames();
210   }
211 
212   public void addEventCountListener(EventCountListener listener) {
213     eventListenerList.add(EventCountListener.class, listener);
214   }
215 
216   public boolean isSortable(int col) {
217     return true;
218   }
219 
220   public void notifyCountListeners() {
221     EventCountListener[] listeners =
222       (EventCountListener[]) eventListenerList.getListeners(
223         EventCountListener.class);
224 
225     for (int i = 0; i < listeners.length; i++) {
226       listeners[i].eventCountChanged(
227         filteredList.size(), unfilteredList.size());
228     }
229   }
230 
231   /***
232    * Changes the underlying display rule in use.  If there was
233    * a previous Rule defined, this Model removes itself as a listener
234    * from the old rule, and adds itself to the new rule (if the new Rule is not Null).
235    *
236    * In any case, the model ensures the Filtered list is made up to date in a separate thread.
237    */
238   public void setDisplayRule(Rule displayRule) {
239     if (this.displayRule != null) {
240       this.displayRule.removePropertyChangeListener(this);
241     }
242 
243     this.displayRule = displayRule;
244 
245     if (this.displayRule != null) {
246       this.displayRule.addPropertyChangeListener(this);
247     }
248 
249     reFilter();
250   }
251 
252   /* (non-Javadoc)
253      * @see org.apache.log4j.chainsaw.EventContainer#sort()
254      */
255   public void sort() {
256     if (sortEnabled && filteredList.size() > 0) {
257       synchronized (filteredList) {
258         Collections.sort(
259           filteredList,
260           new ColumnComparator(
261             getColumnName(currentSortColumn), currentSortColumn,
262             currentSortAscending));
263       }
264 
265      	SwingUtilities.invokeLater(new Runnable() {
266      		public void run() {
267       			fireTableRowsUpdated(0, Math.max(filteredList.size() - 1, 0));
268       		}
269       	});
270     }
271   }
272 
273   public boolean isSortEnabled() {
274     return sortEnabled;
275   }
276 
277   public void sortColumn(int col, boolean ascending) {
278     logger.debug("request to sort col=" + col);
279     currentSortAscending = ascending;
280     currentSortColumn = col;
281     sortEnabled = true;
282     sort();
283   }
284 
285   /* (non-Javadoc)
286    * @see org.apache.log4j.chainsaw.EventContainer#clear()
287    */
288   public void clearModel() {
289     reachedCapacity = false;
290 
291     synchronized (unfilteredList) {
292       unfilteredList.clear();
293       filteredList.clear();
294       idSet.clear();
295       uniqueRow = 0;
296     }
297 
298     SwingUtilities.invokeLater(new Runnable() {
299     	public void run() {
300     	    fireTableDataChanged();
301     	}
302     });
303 
304     notifyCountListeners();
305   }
306 
307   public List getAllEvents() {
308     List list = new ArrayList(unfilteredList.size());
309 
310     synchronized (unfilteredList) {
311       list.addAll(unfilteredList);
312     }
313 
314     return list;
315   }
316   
317   
318   public List getFilteredEvents() {
319   	List list = new ArrayList(filteredList.size());
320   	
321   	synchronized (filteredList) {
322   		list.addAll(filteredList);
323   	}
324   	
325   	return list;
326   }
327   
328   public int getRowIndex(LoggingEvent e) {
329     synchronized (filteredList) {
330       return filteredList.indexOf(e);
331     }
332   }
333 
334   public int getColumnCount() {
335     return columnNames.size();
336   }
337 
338   public String getColumnName(int column) {
339     return columnNames.get(column).toString();
340   }
341 
342   public LoggingEvent getRow(int row) {
343     synchronized (filteredList) {
344       if (row < filteredList.size()) {
345         return (LoggingEvent) filteredList.get(row);
346       }
347     }
348 
349     return null;
350   }
351 
352   public int getRowCount() {
353     synchronized (filteredList) {
354       return filteredList.size();
355     }
356   }
357 
358   public Object getValueAt(int rowIndex, int columnIndex) {
359     LoggingEvent event = null;
360 
361     synchronized (filteredList) {
362       if (rowIndex < filteredList.size() && rowIndex > -1) {
363         event = (LoggingEvent) filteredList.get(rowIndex);
364       }
365     }
366 
367     if (event == null) {
368       return null;
369     }
370 
371     LocationInfo info = null;
372 
373     if (event.locationInformationExists()) {
374       info = event.getLocationInformation();
375     }
376 
377     if (event == null) {
378       logger.error("Invalid rowindex=" + rowIndex);
379       throw new NullPointerException("Invalid rowIndex=" + rowIndex);
380     }
381 
382     switch (columnIndex + 1) {
383     case ChainsawColumns.INDEX_ID_COL_NAME:
384 
385       Object id = event.getProperty(Constants.LOG4J_ID_KEY);
386 
387       if (id != null) {
388         return id;
389       }
390 
391       return new Integer(rowIndex);
392 
393     case ChainsawColumns.INDEX_LEVEL_COL_NAME:
394       return event.getLevel();
395 
396     case ChainsawColumns.INDEX_LOGGER_COL_NAME:
397       return event.getLoggerName();
398 
399     case ChainsawColumns.INDEX_TIMESTAMP_COL_NAME:
400       return new Date(event.getTimeStamp());
401 
402     case ChainsawColumns.INDEX_MESSAGE_COL_NAME:
403       return event.getRenderedMessage();
404 
405     case ChainsawColumns.INDEX_NDC_COL_NAME:
406       return event.getNDC();
407 
408     case ChainsawColumns.INDEX_THREAD_COL_NAME:
409       return event.getThreadName();
410 
411     case ChainsawColumns.INDEX_THROWABLE_COL_NAME:
412       return event.getThrowableStrRep();
413 
414     case ChainsawColumns.INDEX_CLASS_COL_NAME:
415       return ((info == null)
416       || ((info != null) && "?".equals(info.getClassName()))) ? ""
417                                                               : info
418       .getClassName();
419 
420     case ChainsawColumns.INDEX_FILE_COL_NAME:
421       return ((info == null)
422       || ((info != null) && "?".equals(info.getFileName()))) ? ""
423                                                              : info
424       .getFileName();
425 
426     case ChainsawColumns.INDEX_LINE_COL_NAME:
427       return ((info == null)
428       || ((info != null) && "?".equals(info.getLineNumber()))) ? ""
429                                                                : info
430       .getLineNumber();
431 
432     case ChainsawColumns.INDEX_METHOD_COL_NAME:
433       return ((info == null)
434       || ((info != null) && "?".equals(info.getMethodName()))) ? ""
435                                                                : info
436       .getMethodName();
437 
438     default:
439 
440       if (columnIndex < columnNames.size()) {
441         return event.getProperty(columnNames.get(columnIndex).toString());
442       }
443     }
444 
445     return "";
446   }
447 
448   public boolean isAddRow(LoggingEvent e, boolean valueIsAdjusting) {
449     boolean rowAdded = false;
450 
451     Object id = e.getProperty(Constants.LOG4J_ID_KEY);
452 
453     if (id == null) {
454       id = new Integer(++uniqueRow);
455       e.setProperty(Constants.LOG4J_ID_KEY, id.toString());
456     }
457 
458     //prevent duplicate rows
459     if (idSet.contains(id)) {
460       return false;
461     }
462 
463     idSet.add(id);
464     unfilteredList.add(e);
465 
466     if ((displayRule == null) || (displayRule.evaluate(e))) {
467       synchronized (filteredList) {
468         filteredList.add(e);
469         rowAdded = true;
470       }
471     }
472 
473     /***
474      * Is this a new Property key we haven't seen before?  Remember that now MDC has been merged
475      * into the Properties collection
476      */
477     boolean newColumn = uniquePropertyKeys.addAll(e.getPropertyKeySet());
478 
479     if (newColumn) {
480       /***
481        * If so, we should add them as columns and notify listeners.
482        */
483       for (Iterator iter = e.getPropertyKeySet().iterator(); iter.hasNext();) {
484         Object key = iter.next();
485 
486         //add all keys except the 'log4jid' key
487         if (!columnNames.contains(key) && !(Constants.LOG4J_ID_KEY.equalsIgnoreCase(key.toString()))) {
488           columnNames.add(key);
489           logger.debug("Adding col '" + key + "', columNames=" + columnNames);
490           fireNewKeyColumnAdded(
491             new NewKeyEvent(
492               this, columnNames.indexOf(key), key, e.getProperty(key.toString())));
493         }
494       }
495     }
496 
497     if (!valueIsAdjusting) {
498       int lastAdded = getLastAdded();
499       fireTableEvent(lastAdded, lastAdded, 1);
500     }
501 
502     return rowAdded;
503   }
504 
505   public int getLastAdded() {
506     int last = 0;
507 
508     if (cyclic) {
509       last = ((CyclicBufferList) filteredList).getLast();
510     } else {
511       last = filteredList.size();
512     }
513 
514     return last;
515   }
516 
517   public void fireTableEvent(final int begin, final int end, final int count) {
518   	SwingUtilities.invokeLater(new Runnable() {
519   		public void run() {
520     if (cyclic) {
521       if (!reachedCapacity) {
522         //if we didn't loop and it's the 1st time, insert
523         if ((begin + count) < cyclicBufferSize) {
524           fireTableRowsInserted(begin, end);
525         } else {
526           //we did loop - insert and then update rows
527           fireTableRowsInserted(begin, cyclicBufferSize);
528           fireTableRowsUpdated(0, cyclicBufferSize);
529           reachedCapacity = true;
530         }
531       } else {
532         fireTableRowsUpdated(0, cyclicBufferSize);
533       }
534     } else {
535       fireTableRowsInserted(begin, end);
536     }
537   }});
538   }
539 
540   /***
541   * @param key
542   */
543   private void fireNewKeyColumnAdded(NewKeyEvent e) {
544     NewKeyListener[] listeners =
545       (NewKeyListener[]) eventListenerList.getListeners(NewKeyListener.class);
546 
547     for (int i = 0; i < listeners.length; i++) {
548       NewKeyListener listener = listeners[i];
549       listener.newKeyAdded(e);
550     }
551   }
552 
553   /***
554      * Returns true if this model is Cyclic (bounded) or not
555      * @return true/false
556      */
557   public boolean isCyclic() {
558     return cyclic;
559   }
560 
561   /***
562    * @return
563    */
564   public int getMaxSize() {
565     return cyclicBufferSize;
566   }
567 
568   /* (non-Javadoc)
569    * @see org.apache.log4j.chainsaw.EventContainer#addNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener)
570    */
571   public void addNewKeyListener(NewKeyListener l) {
572     eventListenerList.add(NewKeyListener.class, l);
573   }
574 
575   /* (non-Javadoc)
576    * @see org.apache.log4j.chainsaw.EventContainer#removeNewKeyListener(org.apache.log4j.chainsaw.NewKeyListener)
577    */
578   public void removeNewKeyListener(NewKeyListener l) {
579     eventListenerList.remove(NewKeyListener.class, l);
580   }
581 
582   /* (non-Javadoc)
583    * @see javax.swing.table.TableModel#isCellEditable(int, int)
584    */
585   public boolean isCellEditable(int rowIndex, int columnIndex) {
586     switch (columnIndex + 1) {
587     case ChainsawColumns.INDEX_THROWABLE_COL_NAME:
588       return true;
589     }
590 
591     return super.isCellEditable(rowIndex, columnIndex);
592   }
593 
594   /* (non-Javadoc)
595    * @see org.apache.log4j.chainsaw.EventContainer#setCyclic(boolean)
596    */
597   public void setCyclic(final boolean cyclic) {
598     if (this.cyclic == cyclic) {
599       return;
600     }
601 
602     final boolean old = this.cyclic;
603     this.cyclic = cyclic;
604     propertySupport.firePropertyChange("cyclic", old, cyclic);
605   }
606 
607   /* (non-Javadoc)
608    * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.beans.PropertyChangeListener)
609    */
610   public void addPropertyChangeListener(PropertyChangeListener l) {
611     propertySupport.addPropertyChangeListener(l);
612   }
613 
614   /* (non-Javadoc)
615    * @see org.apache.log4j.chainsaw.EventContainer#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
616    */
617   public void addPropertyChangeListener(
618     String propertyName, PropertyChangeListener l) {
619     propertySupport.addPropertyChangeListener(propertyName, l);
620   }
621 
622   /* (non-Javadoc)
623    * @see org.apache.log4j.chainsaw.EventContainer#size()
624    */
625   public int size() {
626     return unfilteredList.size();
627   }
628 
629   private class ModelChanger implements PropertyChangeListener {
630     /* (non-Javadoc)
631      * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
632      */
633     public void propertyChange(PropertyChangeEvent arg0) {
634       Thread thread =
635         new Thread(
636           new Runnable() {
637             public void run() {
638               ProgressMonitor monitor = null;
639 
640               int index = 0;
641 
642               try {
643                 synchronized (unfilteredList) {
644                   monitor =
645                     new ProgressMonitor(
646                       null, "Switching models...",
647                       "Transferring between data structures, please wait...", 0,
648                       unfilteredList.size() + 1);
649                   monitor.setMillisToDecideToPopup(250);
650                   monitor.setMillisToPopup(100);
651                   logger.debug(
652                     "Changing Model, isCyclic is now " + isCyclic());
653 
654                   List newUnfilteredList = null;
655                   List newFilteredList = null;
656                   HashSet newIDSet = null;
657 
658                   newIDSet = new HashSet(cyclicBufferSize);
659 
660                   if (isCyclic()) {
661                     newUnfilteredList = new CyclicBufferList(cyclicBufferSize);
662                     newFilteredList = new CyclicBufferList(cyclicBufferSize);
663                   } else {
664                     newUnfilteredList = new ArrayList(cyclicBufferSize);
665                     newFilteredList = new ArrayList(cyclicBufferSize);
666                   }
667 
668                   int increment = 0;
669 
670                   for (Iterator iter = unfilteredList.iterator();
671                       iter.hasNext();) {
672                     LoggingEvent e = (LoggingEvent) iter.next();
673                     newUnfilteredList.add(e);
674 
675                     Object o =
676                       e.getProperty(
677                         e.getProperty(Constants.LOG4J_ID_KEY));
678 
679                     if (o != null) {
680                       newIDSet.add(o);
681                     } else {
682                       newIDSet.add(new Integer(increment++));
683                     }
684 
685                     monitor.setProgress(index++);
686                   }
687 
688                   unfilteredList = newUnfilteredList;
689                   filteredList = newFilteredList;
690                   idSet = newIDSet;
691                 }
692 
693                 monitor.setNote("Refiltering...");
694                 reFilter();
695 
696                 monitor.setProgress(index++);
697               } finally {
698                 monitor.close();
699               }
700 
701               logger.debug("Model Change completed");
702             }
703           });
704       thread.setPriority(Thread.MIN_PRIORITY + 1);
705       thread.start();
706     }
707   }
708 }