1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
76 private LoggerNameModel loggerNameModelDelegate =
77 new LoggerNameModelSupport();
78
79
80
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
96
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
143 fireTableRowsUpdated(0, filteredList.size() - 1);
144 } else if (previousSize > filteredList.size()) {
145
146 fireTableRowsUpdated(0, filteredList.size() - 1);
147 fireTableRowsDeleted(filteredList.size(), previousSize);
148 } else if (previousSize < filteredList.size()) {
149
150 fireTableRowsUpdated(0, Math.max(0, previousSize - 1));
151 fireTableRowsInserted(previousSize, filteredList.size() - 1);
152 }
153 } else {
154
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
253
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
286
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
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
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
523 if ((begin + count) < cyclicBufferSize) {
524 fireTableRowsInserted(begin, end);
525 } else {
526
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
569
570
571 public void addNewKeyListener(NewKeyListener l) {
572 eventListenerList.add(NewKeyListener.class, l);
573 }
574
575
576
577
578 public void removeNewKeyListener(NewKeyListener l) {
579 eventListenerList.remove(NewKeyListener.class, l);
580 }
581
582
583
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
595
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
608
609
610 public void addPropertyChangeListener(PropertyChangeListener l) {
611 propertySupport.addPropertyChangeListener(l);
612 }
613
614
615
616
617 public void addPropertyChangeListener(
618 String propertyName, PropertyChangeListener l) {
619 propertySupport.addPropertyChangeListener(propertyName, l);
620 }
621
622
623
624
625 public int size() {
626 return unfilteredList.size();
627 }
628
629 private class ModelChanger implements PropertyChangeListener {
630
631
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 }