001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.support.log;
014:
015: import java.awt.BorderLayout;
016: import java.awt.Color;
017: import java.awt.Component;
018: import java.awt.Toolkit;
019: import java.awt.datatransfer.Clipboard;
020: import java.awt.datatransfer.StringSelection;
021: import java.awt.event.ActionEvent;
022: import java.io.File;
023: import java.io.PrintWriter;
024: import java.io.StringWriter;
025: import java.lang.reflect.InvocationTargetException;
026: import java.util.ArrayList;
027: import java.util.Date;
028: import java.util.HashMap;
029: import java.util.LinkedList;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.Stack;
033: import java.util.StringTokenizer;
034:
035: import javax.swing.AbstractAction;
036: import javax.swing.AbstractListModel;
037: import javax.swing.BorderFactory;
038: import javax.swing.DefaultListCellRenderer;
039: import javax.swing.JCheckBoxMenuItem;
040: import javax.swing.JLabel;
041: import javax.swing.JList;
042: import javax.swing.JPanel;
043: import javax.swing.JPopupMenu;
044: import javax.swing.JScrollPane;
045: import javax.swing.SwingUtilities;
046: import javax.swing.text.SimpleAttributeSet;
047: import javax.swing.text.StyleConstants;
048:
049: import org.apache.log4j.AppenderSkeleton;
050: import org.apache.log4j.Level;
051: import org.apache.log4j.Logger;
052: import org.apache.log4j.spi.LoggingEvent;
053:
054: import com.eviware.soapui.SoapUI;
055: import com.eviware.soapui.support.UISupport;
056:
057: /**
058: * Component for displaying log entries
059: *
060: * @author Ole.Matzura
061: */
062:
063: public class JLogList extends JPanel {
064: private long maxRows = 1000;
065: private JList logList;
066: private SimpleAttributeSet requestAttributes;
067: private SimpleAttributeSet responseAttributes;
068: private LogListModel model;
069: private List<Logger> loggers = new ArrayList<Logger>();
070: private InternalLogAppender internalLogAppender = new InternalLogAppender();
071: private boolean tailing = true;
072: private Stack<Object> linesToAdd = new Stack<Object>();
073: private EnableAction enableAction;
074: private JCheckBoxMenuItem enableMenuItem;
075: private Thread modelThread;
076: private final String title;
077: private boolean released;
078:
079: public JLogList(String title) {
080: super (new BorderLayout());
081: this .title = title;
082:
083: model = new LogListModel();
084: logList = new JList(model);
085: logList.setToolTipText(title);
086: logList.setCellRenderer(new LogAreaCellRenderer());
087: logList.setPrototypeCellValue("Testing 123");
088: logList.setFixedCellWidth(-1);
089:
090: JPopupMenu listPopup = new JPopupMenu();
091: listPopup.add(new ClearAction());
092: enableAction = new EnableAction();
093: enableMenuItem = new JCheckBoxMenuItem(enableAction);
094: enableMenuItem.setSelected(true);
095: listPopup.add(enableMenuItem);
096: listPopup.addSeparator();
097: listPopup.add(new CopyAction());
098: listPopup.add(new SetMaxRowsAction());
099: listPopup.addSeparator();
100: listPopup.add(new ExportToFileAction());
101:
102: logList.setComponentPopupMenu(listPopup);
103:
104: setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
105: add(new JScrollPane(logList), BorderLayout.CENTER);
106:
107: requestAttributes = new SimpleAttributeSet();
108: StyleConstants.setForeground(requestAttributes, Color.BLUE);
109:
110: responseAttributes = new SimpleAttributeSet();
111: StyleConstants.setForeground(responseAttributes, Color.GREEN);
112:
113: try {
114: maxRows = Long.parseLong(SoapUI.getSettings().getString(
115: "JLogList#" + title, "1000"));
116: } catch (NumberFormatException e) {
117: }
118: }
119:
120: public void clear() {
121: model.clear();
122: }
123:
124: public JList getLogList() {
125: return logList;
126: }
127:
128: public long getMaxRows() {
129: return maxRows;
130: }
131:
132: public void setMaxRows(long maxRows) {
133: this .maxRows = maxRows;
134: }
135:
136: public synchronized void addLine(Object line) {
137: if (!isEnabled())
138: return;
139:
140: if (modelThread == null) {
141: released = false;
142: modelThread = new Thread(model, title + " LogListUpdater");
143: modelThread.start();
144: }
145:
146: if (line instanceof LoggingEvent) {
147: LoggingEvent ev = (LoggingEvent) line;
148: linesToAdd.push(new LoggingEventWrapper(ev));
149:
150: if (ev.getThrowableInformation() != null) {
151: Throwable t = ev.getThrowableInformation()
152: .getThrowable();
153: StringWriter sw = new StringWriter();
154: PrintWriter pw = new PrintWriter(sw);
155: t.printStackTrace(pw);
156: StringTokenizer st = new StringTokenizer(sw.toString(),
157: "\r\n");
158: while (st.hasMoreElements())
159: linesToAdd.push(" " + st.nextElement());
160: }
161: } else {
162: linesToAdd.push(line);
163: }
164: }
165:
166: public void setEnabled(boolean enabled) {
167: super .setEnabled(enabled);
168: logList.setEnabled(enabled);
169: enableMenuItem.setSelected(enabled);
170: }
171:
172: private static class LogAreaCellRenderer extends
173: DefaultListCellRenderer {
174: private Map<Level, Color> levelColors = new HashMap<Level, Color>();
175:
176: private LogAreaCellRenderer() {
177: levelColors.put(Level.ERROR, new Color(192, 0, 0));
178: levelColors.put(Level.INFO, new Color(0, 92, 0));
179: levelColors.put(Level.WARN, Color.ORANGE.darker().darker());
180: levelColors.put(Level.DEBUG, new Color(0, 0, 128));
181: }
182:
183: public Component getListCellRendererComponent(JList list,
184: Object value, int index, boolean isSelected,
185: boolean cellHasFocus) {
186: JLabel component = (JLabel) super
187: .getListCellRendererComponent(list, value, index,
188: isSelected, cellHasFocus);
189:
190: if (value instanceof LoggingEventWrapper) {
191: LoggingEventWrapper eventWrapper = (LoggingEventWrapper) value;
192:
193: if (levelColors.containsKey(eventWrapper.getLevel()))
194: component.setForeground(levelColors
195: .get(eventWrapper.getLevel()));
196: }
197:
198: component.setToolTipText(component.getText());
199:
200: return component;
201: }
202: }
203:
204: private final static class LoggingEventWrapper {
205: private final LoggingEvent loggingEvent;
206: private String str;
207:
208: public LoggingEventWrapper(LoggingEvent loggingEvent) {
209: this .loggingEvent = loggingEvent;
210: }
211:
212: public Level getLevel() {
213: return loggingEvent.getLevel();
214: }
215:
216: public String toString() {
217: if (str == null) {
218: StringBuilder builder = new StringBuilder();
219: builder.append(new Date(loggingEvent.timeStamp));
220: builder.append(':').append(loggingEvent.getLevel())
221: .append(':').append(loggingEvent.getMessage());
222: str = builder.toString();
223: }
224:
225: return str;
226: }
227: }
228:
229: public void addLogger(String loggerName, boolean addAppender) {
230: Logger logger = Logger.getLogger(loggerName);
231: if (addAppender)
232: logger.addAppender(internalLogAppender);
233:
234: loggers.add(logger);
235: }
236:
237: public void setLevel(Level level) {
238: for (Logger logger : loggers) {
239: logger.setLevel(level);
240: }
241: }
242:
243: private class InternalLogAppender extends AppenderSkeleton {
244: protected void append(LoggingEvent event) {
245: addLine(event);
246: }
247:
248: public void close() {
249: }
250:
251: public boolean requiresLayout() {
252: return false;
253: }
254: }
255:
256: public boolean monitors(String loggerName) {
257: for (Logger logger : loggers) {
258: if (loggerName.startsWith(logger.getName()))
259: return true;
260: }
261:
262: return false;
263: }
264:
265: public void removeLogger(String loggerName) {
266: for (Logger logger : loggers) {
267: if (loggerName.equals(logger.getName())) {
268: logger.removeAppender(internalLogAppender);
269: }
270: }
271: }
272:
273: public boolean isTailing() {
274: return tailing;
275: }
276:
277: public void setTailing(boolean tail) {
278: this .tailing = tail;
279: }
280:
281: private class ClearAction extends AbstractAction {
282: public ClearAction() {
283: super ("Clear");
284: }
285:
286: public void actionPerformed(ActionEvent e) {
287: model.clear();
288: }
289: }
290:
291: private class SetMaxRowsAction extends AbstractAction {
292: public SetMaxRowsAction() {
293: super ("Set Max Rows");
294: }
295:
296: public void actionPerformed(ActionEvent e) {
297: String val = UISupport.prompt(
298: "Set maximum number of log rows to keep",
299: "Set Max Rows", String.valueOf(maxRows));
300: if (val != null) {
301: try {
302: maxRows = Long.parseLong(val);
303: SoapUI.getSettings().setString("JLogList#" + title,
304: val);
305: } catch (NumberFormatException e1) {
306: UISupport.beep();
307: }
308: }
309: }
310: }
311:
312: private class ExportToFileAction extends AbstractAction {
313: public ExportToFileAction() {
314: super ("Export to File");
315: }
316:
317: public void actionPerformed(ActionEvent e) {
318: if (model.getSize() == 0) {
319: UISupport
320: .showErrorMessage("Log is empty; nothing to export");
321: return;
322: }
323:
324: File file = UISupport.getFileDialogs().saveAs(
325: JLogList.this , "Save Log [] to File", "*.log",
326: "*.log", null);
327: if (file != null)
328: saveToFile(file);
329: }
330: }
331:
332: private class CopyAction extends AbstractAction {
333: public CopyAction() {
334: super ("Copy to clipboard");
335: }
336:
337: public void actionPerformed(ActionEvent e) {
338: Clipboard clipboard = Toolkit.getDefaultToolkit()
339: .getSystemClipboard();
340:
341: StringBuffer buf = new StringBuffer();
342: int[] selectedIndices = logList.getSelectedIndices();
343: if (selectedIndices.length == 0) {
344: for (int c = 0; c < logList.getModel().getSize(); c++) {
345: buf.append(logList.getModel().getElementAt(c)
346: .toString());
347: buf.append("\r\n");
348: }
349: } else {
350: for (int c = 0; c < selectedIndices.length; c++) {
351: buf.append(logList.getModel().getElementAt(
352: selectedIndices[c]).toString());
353: buf.append("\r\n");
354: }
355: }
356:
357: StringSelection selection = new StringSelection(buf
358: .toString());
359: clipboard.setContents(selection, selection);
360: }
361: }
362:
363: private class EnableAction extends AbstractAction {
364: public EnableAction() {
365: super ("Enable");
366: }
367:
368: public void actionPerformed(ActionEvent e) {
369: JLogList.this .setEnabled(enableMenuItem.isSelected());
370: }
371: }
372:
373: /**
374: * Internal list model that for optimized storage and notifications
375: *
376: * @author Ole.Matzura
377: */
378:
379: private final class LogListModel extends AbstractListModel
380: implements Runnable {
381: private List<Object> lines = new LinkedList<Object>();
382:
383: public int getSize() {
384: return lines.size();
385: }
386:
387: public Object getElementAt(int index) {
388: return lines.get(index);
389: }
390:
391: public void clear() {
392: int sz = lines.size();
393: if (sz == 0)
394: return;
395:
396: lines.clear();
397: fireIntervalRemoved(this , 0, sz - 1);
398: }
399:
400: public void run() {
401: while (!released && !linesToAdd.isEmpty()) {
402: try {
403: if (!linesToAdd.isEmpty()) {
404: SwingUtilities.invokeAndWait(new Runnable() {
405: public void run() {
406: while (!linesToAdd.isEmpty()) {
407: int sz = lines.size();
408: lines.addAll(linesToAdd);
409: linesToAdd.clear();
410: fireIntervalAdded(this , sz, lines
411: .size()
412: - sz);
413: }
414:
415: int cnt = 0;
416: while (lines.size() > maxRows) {
417: lines.remove(0);
418: cnt++;
419: }
420:
421: if (cnt > 0)
422: fireIntervalRemoved(this , 0,
423: cnt - 1);
424:
425: if (tailing) {
426: logList.ensureIndexIsVisible(lines
427: .size() - 1);
428: }
429: }
430: });
431: }
432:
433: Thread.sleep(500);
434: } catch (InterruptedException e) {
435: SoapUI.logError(e);
436: } catch (InvocationTargetException e) {
437: SoapUI.logError(e);
438: }
439: }
440:
441: modelThread = null;
442: }
443: }
444:
445: public void release() {
446: released = true;
447: }
448:
449: public void saveToFile(File file) {
450: try {
451: PrintWriter writer = new PrintWriter(file);
452: for (int c = 0; c < model.getSize(); c++) {
453: writer.println(model.getElementAt(c));
454: }
455:
456: writer.close();
457: } catch (Exception e) {
458: UISupport.showErrorMessage(e);
459: }
460: }
461: }
|