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  package org.apache.log4j.chainsaw.receivers;
18  
19  import java.awt.BorderLayout;
20  import java.awt.Component;
21  import java.awt.event.WindowAdapter;
22  import java.awt.event.WindowEvent;
23  import java.beans.BeanInfo;
24  import java.beans.IntrospectionException;
25  import java.beans.Introspector;
26  import java.beans.PropertyChangeEvent;
27  import java.beans.PropertyChangeListener;
28  import java.beans.PropertyDescriptor;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.Comparator;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  
37  import javax.swing.AbstractCellEditor;
38  import javax.swing.DefaultCellEditor;
39  import javax.swing.JFrame;
40  import javax.swing.JPanel;
41  import javax.swing.JScrollPane;
42  import javax.swing.JTable;
43  import javax.swing.JTextField;
44  import javax.swing.table.AbstractTableModel;
45  import javax.swing.table.DefaultTableModel;
46  import javax.swing.table.TableCellEditor;
47  import javax.swing.table.TableModel;
48  
49  import org.apache.log4j.Level;
50  import org.apache.log4j.LogManager;
51  import org.apache.log4j.Logger;
52  import org.apache.log4j.chainsaw.ChainsawConstants;
53  import org.apache.log4j.chainsaw.Generator;
54  import org.apache.log4j.chainsaw.helper.TableCellEditorFactory;
55  import org.apache.log4j.net.SocketHubReceiver;
56  import org.apache.log4j.plugins.Plugin;
57  
58  
59  /**
60   * A panel that allows the user to edit a particular Plugin, by using introspection
61   * this class discovers the modifiable properties of the Plugin
62   * @author Paul Smith <psmith@apache.org>
63   */
64  public class PluginPropertyEditorPanel extends JPanel {
65  
66      private final JScrollPane scrollPane = new JScrollPane();
67      private final JTable propertyTable = new JTable();
68  
69      private Plugin plugin;
70      private TableModel defaultModel = new DefaultTableModel(
71              new String[] { "Property", "Value" }, 1);
72  
73      private static final Logger logger = LogManager.getLogger(PluginPropertyEditorPanel.class);
74      /**
75       *
76       */
77      public PluginPropertyEditorPanel() {
78          super();
79          initComponents();
80          setupListeners();
81      }
82  
83      /**
84       *
85       */
86      private void initComponents() {
87          propertyTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
88          setLayout(new BorderLayout());
89          scrollPane.setViewportView(propertyTable);
90  
91          add(scrollPane, BorderLayout.CENTER);
92  
93          propertyTable.setModel(
94              defaultModel = new DefaultTableModel(
95                      new String[] { "Property", "Value" }, 1));
96  
97      }
98  
99      /**
100      *
101      */
102     private void setupListeners() {
103         addPropertyChangeListener("plugin", new PropertyChangeListener() {
104 
105                 public void propertyChange(PropertyChangeEvent evt) {
106 
107                     final Plugin p = (Plugin) evt.getNewValue();
108 
109                     if (p != null) {
110 
111                         try {
112 
113                             PluginPropertyTableModel model =
114                                 new PluginPropertyTableModel(p);
115                             propertyTable.setModel(model);
116                             propertyTable.getColumnModel().getColumn(1)
117                             .setCellEditor(new PluginTableCellEditor());
118                             propertyTable.setEnabled(true);
119                         } catch (Throwable e) {
120                             logger.error("Failed to introspect the Plugin", e);
121                         }
122                     } else {
123                         propertyTable.setModel(defaultModel);
124                         propertyTable.setEnabled(false);
125                     }
126 
127                 }
128             });
129     }
130 
131     public static void main(String[] args) {
132 
133         JFrame frame = new JFrame("Property Editor Test bed");
134         frame.addWindowListener(new WindowAdapter() {
135                 public void windowClosed(WindowEvent e) {
136                     System.exit(1);
137                 }
138             });
139 
140         PluginPropertyEditorPanel panel = new PluginPropertyEditorPanel();
141 
142 
143         frame.getContentPane().add(panel);
144         frame.pack();
145 
146         frame.setVisible(true);
147 
148         SocketHubReceiver r = new SocketHubReceiver();
149 
150         panel.setPlugin(r);
151 
152         try {
153             Thread.sleep(3000);
154 
155             panel.setPlugin(new Generator("MyPlugin"));
156         } catch (Exception e) {
157             // TODO: handle exception
158         }
159 
160 
161     }
162 
163     /**
164      * @return Returns the plugin.
165      */
166     public final Plugin getPlugin() {
167 
168         return plugin;
169     }
170 
171     /**
172      * @param plugin The plugin to set.
173      */
174     public final void setPlugin(Plugin plugin) {
175 
176         Plugin oldValue = this.plugin;
177         this.plugin = plugin;
178         firePropertyChange("plugin", oldValue, this.plugin);
179     }
180 
181     /**
182      * @author psmith
183      *
184      */
185     private class PluginTableCellEditor extends AbstractCellEditor
186         implements TableCellEditor {
187 
188         private Map editorMap = new HashMap();
189         private DefaultCellEditor defaultEditor = new DefaultCellEditor(
190                 new JTextField());
191         private DefaultCellEditor currentEditor = defaultEditor;
192 
193         private PluginTableCellEditor() {
194 
195             editorMap.put(Boolean.class,
196                 TableCellEditorFactory.createBooleanTableCellEditor());
197             editorMap.put(Level.class,
198                 TableCellEditorFactory.createLevelTableCellEditor());
199             //support primitive boolean parameters with the appropriate editor
200             editorMap.put(boolean.class, TableCellEditorFactory.createBooleanTableCellEditor());
201         }
202 
203         /* (non-Javadoc)
204          * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, java.lang.Object, boolean, int, int)
205          */
206         public Component getTableCellEditorComponent(JTable table, Object value,
207             boolean isSelected, int row, int column) {
208 
209            PluginPropertyTableModel model = (PluginPropertyTableModel) table.getModel();
210            PropertyDescriptor descriptor =  model.getDescriptors()[row];
211            Class valueClass = descriptor.getPropertyType();
212            
213             if (editorMap.containsKey(valueClass)) {
214 
215                 DefaultCellEditor editor =
216                     (DefaultCellEditor) editorMap.get(valueClass);
217                 logger.debug("Located CellEditor for " + valueClass);
218                 currentEditor = editor;
219 
220                 return currentEditor.getTableCellEditorComponent(table, value,
221                     isSelected, row, column);
222             }
223 
224             currentEditor = defaultEditor;
225             logger.debug("Cell value class " + valueClass +
226                 " not know, using default editor");
227 
228             return defaultEditor.getTableCellEditorComponent(table, value,
229                 isSelected, row, column);
230         }
231 
232         /* (non-Javadoc)
233          * @see javax.swing.CellEditor#getCellEditorValue()
234          */
235         public Object getCellEditorValue() {
236 
237             return currentEditor.getCellEditorValue();
238         }
239 
240     }
241 
242     private static class PluginPropertyTableModel extends AbstractTableModel {
243 
244         private final PropertyDescriptor[] descriptors;
245         private final Plugin plugin;
246 
247         private PluginPropertyTableModel(Plugin p)
248             throws IntrospectionException {
249             super();
250 
251             BeanInfo beanInfo = Introspector.getBeanInfo(p.getClass());
252 
253             List list = new ArrayList(Arrays.asList(
254                         beanInfo.getPropertyDescriptors()));
255 
256             Collections.sort(list, new Comparator() {
257 
258                     public int compare(Object o1, Object o2) {
259 
260                         PropertyDescriptor d1 = (PropertyDescriptor) o1;
261                         PropertyDescriptor d2 = (PropertyDescriptor) o2;
262 
263                         return d1.getDisplayName().compareToIgnoreCase(
264                             d2.getDisplayName());
265                     }
266                 });
267             this.plugin = p;
268             this.descriptors = (PropertyDescriptor[]) list.toArray(
269                     new PropertyDescriptor[0]);
270         }
271 
272         /* (non-Javadoc)
273          * @see javax.swing.table.AbstractTableModel#getValueAt(int, int)
274          */
275         public Object getValueAt(int row, int col) {
276 
277             PropertyDescriptor d = descriptors[row];
278 
279             switch (col) {
280 
281             case 1:
282 
283                 try {
284 
285                     Object object = d.getReadMethod().invoke(plugin,
286                             new Object[0]);
287 
288                     if (object != null) {
289 
290                         return object;
291                     }
292                 } catch (Exception e) {
293                     logger.error(
294                         "Error reading value for PropertyDescriptor " + d);
295                 }
296 
297                 return "";
298 
299             case 0:
300                 return d.getName();
301             }
302 
303             return null;
304         }
305 
306         /* (non-Javadoc)
307          * @see javax.swing.table.AbstractTableModel#getColumnCount()
308          */
309         public int getColumnCount() {
310 
311             return 2;
312         }
313 
314         /* (non-Javadoc)
315          * @see javax.swing.table.AbstractTableModel#getRowCount()
316          */
317         public int getRowCount() {
318 
319             return descriptors.length;
320         }
321 
322         /* (non-Javadoc)
323          * @see javax.swing.table.TableModel#isCellEditable(int, int)
324          */
325         public boolean isCellEditable(int rowIndex, int columnIndex) {
326 
327 //        TODO Determine if the property is one of the ones a User could edit
328             if (columnIndex == 1) {
329 
330                 return descriptors[rowIndex].getWriteMethod() != null;
331             }
332 
333             return false;
334         }
335 
336         /* (non-Javadoc)
337          * @see javax.swing.table.TableModel#getColumnName(int)
338          */
339         public String getColumnName(int column) {
340 
341             return (column == 0) ? "Property" : "Value";
342         }
343 
344         /* (non-Javadoc)
345          * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
346          */
347         public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
348 
349 
350             if (columnIndex == 1) {
351                 //ensure name is set
352                 if (descriptors[rowIndex].getName().equalsIgnoreCase("name") && (aValue == null || aValue.toString().trim().equals(""))) {
353                     logger.error("Name required");
354                     return;
355                 }
356                 aValue = translateValueIfNeeded(rowIndex, aValue);
357                 logger.debug(
358                     "setValueAt, " + rowIndex + ", " + columnIndex +
359                     ", value=" + aValue + ", valueClass" + aValue.getClass());
360 
361                 try {
362                     descriptors[rowIndex].getWriteMethod().invoke(plugin,
363                         new Object[] { aValue });
364                     fireTableCellUpdated(rowIndex, columnIndex);
365                 } catch (IllegalArgumentException e) {
366                     // ignore
367                 } catch (Exception e) {
368                     logger.error(
369                         "Failed to modify the Plugin because of Exception", e);
370                 }
371 
372             } else {
373                 super.setValueAt(aValue, rowIndex, columnIndex);
374             }
375         }
376 
377         /**
378          * @param row
379          * @param value
380          * @return
381          */
382         private Object translateValueIfNeeded(int row, Object value) {
383 
384             if ((descriptors[row].getPropertyType() == int.class) ||
385                     (descriptors[row].getPropertyType() == Integer.class)) {
386 
387                 try {
388 
389                     return Integer.valueOf(value.toString());
390                 } catch (Exception e) {
391                     logger.error("Failed to convert to Integer type");
392                 }
393             }
394 
395             return value;
396         }
397         /**
398          * @return Returns the descriptors.
399          */
400         public final PropertyDescriptor[] getDescriptors() {
401           return descriptors;
402         }
403     }
404 }