View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  /*
19   * Project: KernelConfigurator
20   * Created on 05-oct-2004
21   *
22   * Copyright (c)2004 Grid Systems
23   */
24  package com.gridsystems.config.modules.tomcat;
25  
26  import java.awt.Color;
27  import java.awt.Component;
28  import java.awt.Dimension;
29  import java.awt.EventQueue;
30  import java.awt.Font;
31  import java.awt.GridBagConstraints;
32  import java.awt.GridBagLayout;
33  import java.awt.Insets;
34  import java.awt.event.ActionEvent;
35  import java.awt.event.ActionListener;
36  import java.awt.event.FocusEvent;
37  import java.awt.event.FocusListener;
38  import java.util.ArrayList;
39  import java.util.Iterator;
40  import java.util.MissingResourceException;
41  import java.util.ResourceBundle;
42  
43  import javax.swing.DefaultCellEditor;
44  import javax.swing.JButton;
45  import javax.swing.JComboBox;
46  import javax.swing.JComponent;
47  import javax.swing.JLabel;
48  import javax.swing.JPanel;
49  import javax.swing.JScrollPane;
50  import javax.swing.JTable;
51  import javax.swing.JTextField;
52  import javax.swing.SwingUtilities;
53  import javax.swing.event.ListSelectionEvent;
54  import javax.swing.event.ListSelectionListener;
55  import javax.swing.table.AbstractTableModel;
56  import javax.swing.table.DefaultTableCellRenderer;
57  import javax.swing.table.DefaultTableColumnModel;
58  import javax.swing.table.TableCellRenderer;
59  import javax.swing.table.TableColumn;
60  import javax.swing.table.TableColumnModel;
61  
62  /**
63   * Swing Component for Connection list edition.
64   *
65   * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
66   * @version 1.0
67   */
68  public class ConnectorEditor extends JPanel {
69    /**
70     * Preferred component width.
71     */
72    private static final int COMPONENT_WIDTH = 200;
73  
74    /**
75     * Preferred component height.
76     */
77    private static final int COMPONENT_HEIGHT = 100;
78  
79    /**
80     * Width of fixed size columns in the table.
81     */
82    private static final int FIXED_COLUMN_WIDTH = 70;
83  
84    /**
85     * Font used for the 'add' and 'remove' buttons.
86     */
87    private static final Font BUTTON_FONT = Font.decode("Dialog-PLAIN-11");
88  
89    /**
90     * Resource bundle for i18n.
91     */
92    private static ResourceBundle bundle = ResourceBundle
93                                           .getBundle(TomcatConfigurator.BUNDLE);
94  
95    /**
96     * Table for displaying the connector list.
97     */
98    private JTable table;
99  
100   /**
101    * Scroller instance that contains the table.
102    */
103   private JScrollPane scroller;
104 
105   /**
106    * Button for "Add connector" action.
107    */
108   private JButton btnAdd;
109 
110   /**
111    * Button for "Remove connector" action.
112    */
113   private JButton btnRemove;
114 
115   /**
116    * Callback to be executed on data changes.
117    */
118   private Runnable changeCallback;
119 
120   /**
121    * Creates a new instance.
122    */
123   public ConnectorEditor() {
124     super();
125     init();
126   }
127 
128   /**
129    * Initializes the component layout.
130    */
131   private void init() {
132     this.setLayout(new GridBagLayout());
133 
134     table = new JTable();
135     table.getSelectionModel().addListSelectionListener(new ConnectorSelectionListener());
136     scroller = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
137                                       JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
138 
139     btnAdd = new JButton(getString("ConnectorEditor.btnAdd.text"));
140     btnRemove = new JButton(getString("ConnectorEditor.btnRemove.text"));
141 
142     btnAdd.setFont(BUTTON_FONT);
143     btnRemove.setFont(BUTTON_FONT);
144 
145     btnAdd.addActionListener(new ActionListener() {
146       public void actionPerformed(ActionEvent event) {
147         addConnector();
148       }
149     });
150 
151     btnRemove.addActionListener(new ActionListener() {
152       public void actionPerformed(ActionEvent event) {
153         removeConnector();
154       }
155     });
156 
157     this.add(scroller, new GridBagConstraints(0, 0, 2, 1, 1.0, 1.0,
158       GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 2, 0), 0, 0));
159     this.add(btnAdd, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0,
160       GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
161     this.add(btnRemove, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
162       GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0));
163 
164     this.setPreferredSize(new Dimension(COMPONENT_WIDTH, COMPONENT_HEIGHT));
165   }
166 
167   /**
168    * Adds a connector to the table.
169    */
170   public void addConnector() {
171     ConnectorModel model = (ConnectorModel)table.getModel();
172     model.newConnector();
173     runCallback();
174   }
175 
176   /**
177    * Removes the selected connector from the table.
178    */
179   public void removeConnector() {
180     int row = table.getSelectedRow();
181     if (row != -1) {
182       ConnectorModel model = (ConnectorModel)table.getModel();
183       model.deleteConnector(row);
184       runCallback();
185     }
186   }
187 
188   /**
189    * Sets the list of connectors.
190    *
191    * @param list  the new connector list.
192    */
193   public void setConnectors(Connector[] list) {
194     table.setModel(new ConnectorModel(list));
195     setupTable(table);
196   }
197 
198   /**
199    * Gets the list of connectors.
200    *
201    * @return  the connector list
202    */
203   public Connector[] getConnectors() {
204     ConnectorModel model = (ConnectorModel)table.getModel();
205     Connector[] c = new Connector[model.list.size()];
206     model.list.toArray(c);
207     return c;
208   }
209 
210   /**
211    * Configures the table for correct display of data.
212    *
213    * @param table  the table instance
214    */
215   private void setupTable(JTable table) {
216     TableColumnModel cmodel = new DefaultTableColumnModel();
217     TableCellRenderer renderer = new ConnectorCellRenderer();
218 
219     TableColumn c = new TableColumn(0);
220     c.setHeaderValue(getString("ConnectorEditor.table.colName"));
221     c.setResizable(true);
222     c.setCellRenderer(renderer);
223     c.setCellEditor(new NameCellEditor());
224     cmodel.addColumn(c);
225 
226     c = new TableColumn(1);
227     c.setHeaderValue(getString("ConnectorEditor.table.colScheme"));
228     c.setMinWidth(FIXED_COLUMN_WIDTH);
229     c.setMaxWidth(FIXED_COLUMN_WIDTH);
230     c.setResizable(false);
231     c.setCellRenderer(renderer);
232     c.setCellEditor(new SchemeCellEditor());
233     cmodel.addColumn(c);
234 
235     c = new TableColumn(2);
236     c.setHeaderValue(getString("ConnectorEditor.table.colPort"));
237     c.setMinWidth(FIXED_COLUMN_WIDTH);
238     c.setMaxWidth(FIXED_COLUMN_WIDTH);
239     c.setResizable(false);
240     c.setCellRenderer(renderer);
241     c.setCellEditor(new PortCellEditor());
242     cmodel.addColumn(c);
243 
244     table.setColumnModel(cmodel);
245     table.setRowHeight(20);
246   }
247 
248   /**
249    * Sets a runnable that will be invoked whenever a change is made in data.
250    *
251    * @param callback  the callback to invoke
252    */
253   public void setChangeCallback(Runnable callback) {
254     this.changeCallback = callback;
255   }
256 
257   /**
258    * Gets if any of the listed connectors uses SSL.
259    *
260    * @return  <code>true</code> if at least one of the connectors is secure
261    */
262   public boolean containSecureConnector() {
263     ConnectorModel model = (ConnectorModel)table.getModel();
264     for (Iterator it = model.list.iterator(); it.hasNext();) {
265       Connector c = (Connector)it.next();
266       if (Connector.HTTPS.equalsIgnoreCase(c.getProtocol())) {
267         return true;
268       }
269     }
270     return false;
271   }
272 
273   /**
274    * Invokes the change callback, if there is any.
275    */
276   private void runCallback() {
277     if (changeCallback != null) {
278       SwingUtilities.invokeLater(changeCallback);
279     }
280   }
281 
282   /**
283    * Gets a localized string.
284    *
285    * @param key The i18n key
286    * @return    The localized string, or null if none found for key
287    * @throws NullPointerException if the bundle is not set
288    */
289   public static String getString(String key) throws NullPointerException {
290     if (bundle == null) {
291       throw new NullPointerException("Bundle not set");
292     }
293 
294     try {
295       return bundle.getString(key);
296     } catch (MissingResourceException e) {
297       return null;
298     }
299   }
300 
301   /**
302    * Connector Table Data Model.
303    *
304    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
305    * @version 1.0
306    */
307   private class ConnectorModel extends AbstractTableModel {
308     /**
309      * Number of columns in the table.
310      */
311     private static final int COLUMN_COUNT = 4;
312 
313     /**
314      * The internal data list.
315      */
316     public ArrayList<Connector> list;
317 
318     /**
319      * Creates a new instance.
320      *
321      * @param connectors  the list of connectors
322      */
323     public ConnectorModel(Connector[] connectors) {
324       super();
325       this.list = new ArrayList<Connector>();
326 
327       int count = connectors == null ? 0 : connectors.length;
328       for (int i = 0; i < count; i++) {
329         this.list.add(connectors[i]);
330       }
331     }
332 
333     /**
334      * {@inheritDoc}
335      */
336     public int getColumnCount() {
337       return COLUMN_COUNT;
338     }
339 
340     /**
341      * {@inheritDoc}
342      */
343     public int getRowCount() {
344       return this.list.size();
345     }
346 
347     /**
348      * {@inheritDoc}
349      */
350     public Object getValueAt(int rowIndex, int columnIndex) {
351       Connector c = (Connector)list.get(rowIndex);
352       return c;
353     }
354 
355     /**
356      * {@inheritDoc}
357      */
358     public void setValueAt(Object value, int rowIndex, int columnIndex) {
359       Connector c = (Connector)list.get(rowIndex);
360       boolean modified = false;
361       switch (columnIndex) {
362         case 0:
363           modified = !value.equals(c.getName());
364           c.setName(value.toString());
365           break;
366         case 1:
367           modified = !c.getProtocol().equals(value);
368           c.setProtocol((String)value);
369 
370           if (Connector.HTTPS.equalsIgnoreCase(c.getProtocol())
371             && c.getPort() == TomcatConfigModel.DEFAULT_HTTP_PORT) {
372             c.setPort(TomcatConfigModel.DEFAULT_HTTPS_PORT);
373             fireTableCellUpdated(rowIndex, 2);
374           }
375           if (Connector.HTTP.equalsIgnoreCase(c.getProtocol())
376             && c.getPort() == TomcatConfigModel.DEFAULT_HTTPS_PORT) {
377             c.setPort(TomcatConfigModel.DEFAULT_HTTP_PORT);
378             fireTableCellUpdated(rowIndex, 2);
379           }
380           break;
381         case 2:
382           try {
383             int port = Integer.parseInt(value.toString());
384             modified = (port != c.getPort());
385             c.setPort(port);
386           } catch (Exception e) { }
387           break;
388         default:
389           break;
390       }
391 
392       // Invokes the change callback
393       if (modified) {
394         runCallback();
395       }
396     }
397 
398     /**
399      * {@inheritDoc}
400      */
401     public boolean isCellEditable(int row, int column) {
402       Connector c = (Connector)list.get(row);
403       return c.isUserCreated() || column == 2;
404     }
405 
406     /**
407      * Creates a new connector with default values and adds it to the list.
408      */
409     public void newConnector() {
410       Connector c = new Connector(getString("ConnectorEditor.unnamed.text"),
411         TomcatConfigModel.DEFAULT_HTTP_PORT, Connector.HTTP);
412       // Searches an unused port
413       boolean changed = true;
414       while (changed) {
415         changed = false;
416         for (Iterator it = list.iterator(); it.hasNext();) {
417           Connector item = (Connector)it.next();
418           if (item.getPort() == c.getPort()) {
419             changed = true;
420             c.setPort(c.getPort() + 1);
421           }
422         }
423       }
424 
425       int lastRow = list.size();
426       list.add(c);
427       fireTableRowsInserted(lastRow, lastRow);
428     }
429 
430     /**
431      * Deletes the connector at the specified row.
432      *
433      * @param row  the row index.
434      */
435     public void deleteConnector(int row) {
436       Connector c = (Connector)list.get(row);
437       if (c != null) {
438         list.remove(row);
439         fireTableRowsDeleted(row, row);
440       }
441     }
442   }
443 
444   /**
445    * Renders cells containing Connector instances.
446    *
447    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
448    * @version 1.0
449    */
450   private static class ConnectorCellRenderer extends DefaultTableCellRenderer {
451     /**
452      * Column alignments.
453      */
454     private static final int[] ALIGNS = { JLabel.LEFT, JLabel.CENTER, JLabel.RIGHT };
455 
456     /**
457      * Background colors.
458      */
459     private static final Color[] BACKGROUNDS = { Color.WHITE, Color.decode("0xFFFFCC") };
460 
461     /**
462      * Returns the table cell renderer.
463      *
464      * @param table       the <code>JTable</code>
465      * @param value       the value to assign to the cell at <code>[row, column]</code>
466      * @param isSelected  true if cell is selected
467      * @param hasFocus    true if cell has focus
468      * @param row         the row of the cell to render
469      * @param column      the column of the cell to render
470      * @return the table cell renderer
471      */
472     public Component getTableCellRendererComponent(JTable table, Object value,
473         boolean isSelected, boolean hasFocus, int row, int column) {
474 
475       Connector conn = (Connector)value;
476       switch (column) {
477         case 0:
478           value = conn.getName();
479           break;
480         case 1:
481           value = conn.getProtocol();
482           break;
483         case 2:
484           value = String.valueOf(conn.getPort());
485         default:
486           break;
487       }
488 
489       JLabel lbl = (JLabel)super.getTableCellRendererComponent(table, value, isSelected,
490           hasFocus, row, column);
491 
492       lbl.setHorizontalAlignment(ALIGNS[column]);
493       boolean editable = (conn.isUserCreated() || column == 2);
494       if (!isSelected) {
495         lbl.setBackground(BACKGROUNDS[editable ? 0 : 1]);
496       }
497 
498       // The following code ensures that a proper edit action is fired when a cell
499       // receives the focus.
500       if (hasFocus) {
501         final JTable theTable = table;
502         final int theRow = row;
503         final int theCol = editable ? column : 2;
504 
505         EventQueue.invokeLater(new Runnable() {
506           public void run() {
507             // Sets the table in edit mode
508             theTable.editCellAt(theRow, theCol);
509 
510             // Brings the focus to the editor
511             Component c = theTable.getEditorComponent();
512             if (c != null) {
513               c.requestFocusInWindow();
514             }
515           }
516         });
517       }
518       return lbl;
519     }
520   }
521 
522   /**
523    * Cell editor for Connector names
524    * Type description.
525    *
526    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
527    * @version 1.0
528    */
529   private static class NameCellEditor extends DefaultCellEditor {
530     /**
531      * Shared editor instance.
532      */
533     private static JTextField txt = new JTextField();
534 
535     /**
536      * Shared focus listener to add to the editor.
537      */
538     private static ConnectorFocusListener fl = new ConnectorFocusListener();
539 
540     static {
541       txt.addFocusListener(fl);
542     }
543 
544     /**
545      * Creates a new instance.
546      */
547     public NameCellEditor() {
548       super(txt);
549     }
550 
551     /**
552      * Gets the editor component.
553      *
554      * @param table      The table to edit
555      * @param value      The cell value
556      * @param isSelected Indicates if the cell is selected
557      * @param row        The table selection row
558      * @param column     The table selection column
559      * @return A configured editor
560      */
561     public Component getTableCellEditorComponent(JTable table, Object value,
562             boolean isSelected, int row, int column) {
563       Connector c = (Connector)value;
564 
565       super.getTableCellEditorComponent(table, c.getName(), isSelected, row, column);
566       editorComponent.putClientProperty("cellEditor", this);
567       return editorComponent;
568     }
569   }
570 
571   /**
572    * Cell editor for Connector schemes.
573    *
574    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
575    * @version 1.0
576    */
577   private static class SchemeCellEditor extends DefaultCellEditor {
578 
579     /**
580      * Shared combo-box instance.
581      */
582     private static JComboBox cbox = new JComboBox(Connector.getAvailableProtocols());
583 
584     static {
585       cbox.setFont(Font.decode("Dialog-PLAIN-12"));
586       cbox.setFocusable(false);
587     };
588 
589     /**
590      * Creates a new instance.
591      */
592     public SchemeCellEditor() {
593       super(cbox);
594     }
595 
596     /**
597      * Gets the editor component.
598      *
599      * @param table      The table to edit
600      * @param value      The cell value
601      * @param isSelected Indicates if the cell is selected
602      * @param row        The table selection row
603      * @param column     The table selection column
604      * @return A configured editor
605      */
606     public Component getTableCellEditorComponent(JTable table, Object value,
607             boolean isSelected, int row, int column) {
608       Connector c = (Connector)value;
609       value = c.getProtocol();
610       super.getTableCellEditorComponent(table, value, isSelected, row, column);
611       return cbox;
612     }
613   }
614 
615   /**
616    * Cell editor for Connector ports.
617    *
618    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
619    * @version 1.0
620    */
621   private static class PortCellEditor extends DefaultCellEditor {
622     /**
623      * The internal text editor component.
624      */
625     private static JTextField txt = new JTextField();
626 
627     /**
628      * Shared focus listener to add to the editor.
629      */
630     private static ConnectorFocusListener fl = new ConnectorFocusListener();
631 
632     static {
633       txt.addFocusListener(fl);
634     }
635 
636     /**
637      * Creates a new instance.
638      */
639     public PortCellEditor() {
640       super(txt);
641     }
642 
643     /**
644      * Gets the editor component.
645      *
646      * @param table      The table to edit
647      * @param value      The cell value
648      * @param isSelected Indicates if the cell is selected
649      * @param row        The table selection row
650      * @param column     The table selection column
651      * @return A configured editor
652      */
653     public Component getTableCellEditorComponent(JTable table, Object value,
654             boolean isSelected, int row, int column) {
655       Connector c = (Connector)value;
656       value = "" + c.getPort();
657       super.getTableCellEditorComponent(table, value, isSelected, row, column);
658       editorComponent.putClientProperty("cellEditor", this);
659       return editorComponent;
660     }
661   }
662 
663   /**
664    * Selection listener for button synchronization.
665    *
666    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
667    * @version 1.0
668    */
669   private class ConnectorSelectionListener implements ListSelectionListener {
670     /**
671      * Creates a new instance.
672      */
673     public ConnectorSelectionListener() { }
674 
675     /**
676      * {@inheritDoc}
677      */
678     public void valueChanged(ListSelectionEvent event) {
679       if (!event.getValueIsAdjusting()) {
680         try {
681           ConnectorModel model = (ConnectorModel)table.getModel();
682           //Connector con = (Connector)model.list.get(table.getSelectedRow());
683           if (model.list.size() == 1) {
684             btnRemove.setEnabled(false);
685           } else {
686             btnRemove.setEnabled(true);
687           }
688         } catch (Exception e) {
689           btnRemove.setEnabled(false);
690         }
691       }
692     }
693   }
694 
695   /**
696    * Focus listener to use on table cell editors. It forces the edition to stop on
697    * focus loss.
698    *
699    * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
700    * @version 1.0
701    */
702   private static class ConnectorFocusListener implements FocusListener {
703 
704     /**
705      * Creates a new instance.
706      */
707     public ConnectorFocusListener() { }
708 
709     /**
710      * {@inheritDoc}
711      */
712     public void focusGained(FocusEvent e) {
713       Component c = e.getComponent();
714       if (c instanceof JTextField) {
715         JTextField field = (JTextField)c;
716         field.selectAll();
717       }
718     }
719 
720     /**
721      * {@inheritDoc}
722      */
723     public void focusLost(FocusEvent e) {
724       JComponent c = (JComponent)e.getComponent();
725       DefaultCellEditor editor = (DefaultCellEditor)c.getClientProperty("cellEditor");
726       if (editor != null) {
727         editor.stopCellEditing();
728       }
729     }
730   }
731 }