View Javadoc

1   /*
2    * Copyright (c) 2005 The University of Reading
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    * 1. Redistributions of source code must retain the above copyright
9    *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. Neither the name of the University of Reading, nor the names of the
14   *    authors or contributors may be used to endorse or promote products
15   *    derived from this software without specific prior written permission.
16   * 
17   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20   * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   */
28  
29  package uk.ac.rdg.resc.jstyx.interloper;
30  
31  import javax.swing.*;
32  import javax.swing.table.TableColumn;
33  import javax.swing.table.TableModel;
34  import javax.swing.table.DefaultTableModel;
35  import javax.swing.table.TableCellRenderer;
36  
37  import java.awt.Container;
38  import java.awt.Point;
39  import java.awt.event.*;
40  import java.net.InetSocketAddress;
41  import java.util.Hashtable;
42  
43  import java.io.IOException;
44  
45  import uk.ac.rdg.resc.jstyx.server.StyxServer;
46  import uk.ac.rdg.resc.jstyx.messages.StyxMessage;
47  import uk.ac.rdg.resc.jstyx.messages.TattachMessage;
48  import uk.ac.rdg.resc.jstyx.messages.TwalkMessage;
49  import uk.ac.rdg.resc.jstyx.messages.TcreateMessage;
50  import uk.ac.rdg.resc.jstyx.messages.RcreateMessage;
51  
52  import info.clearthought.layout.TableLayout;
53  
54  /***
55   * TCPMon-like GUI application that displays all the messages going between
56   * a Styx client and server.  (i.e. a GUI version of the StyxInterloper)
57   * @todo Allow filtering by hierarchy (e.g. show all files rooted at /md5sum/1)
58   * @todo Make sure all connections are closed properly on exit, and that
59   * all threads are stopped.
60   * @todo Allow messages to be saved as a .csv file or plain ASCII text
61   * @todo Handle more events such as clients connecting, disconnecting,
62   * server connection going down, etc (feed back to GUI)
63   * @todo Filter messages by type, whether they have been replied to, errors
64   * @todo Add a "hint" column containing a description of what the message is doing
65   *
66   * @author Jon Blower
67   * $Revision: 272 $
68   * $Date: 2005-05-26 17:49:45 +0100 (Thu, 26 May 2005) $
69   * $Log$
70   * Revision 1.12  2005/05/26 16:49:45  jonblower
71   * Minor bug fix (ensures Java 1.4 compatibility)
72   *
73   * Revision 1.11  2005/05/25 16:37:09  jonblower
74   * Deals with change of filename associated with a fid when creating a file
75   *
76   * Revision 1.10  2005/05/05 16:57:38  jonblower
77   * Updated MINA library to revision 168337 and changed code accordingly
78   *
79   * Revision 1.9  2005/02/28 12:53:47  jonblower
80   * Improved message filtering
81   *
82   * Revision 1.8  2005/02/28 12:08:18  jonblower
83   * Tidied up interaction between StyxInterloper and StyxMon
84   *
85   * Revision 1.7  2005/02/26 10:00:24  jonblower
86   * Removed redundant methods
87   *
88   * Revision 1.6  2005/02/25 07:48:05  jonblower
89   * Fixed bug with displaying filename in table
90   *
91   * Revision 1.5  2005/02/24 17:53:13  jonblower
92   * Added code to read args from command line
93   *
94   * Revision 1.4  2005/02/24 11:23:32  jonblower
95   * Handles filtering by filename correctly
96   *
97   * Revision 1.3  2005/02/24 09:07:12  jonblower
98   * Added code to support filtering by pop-up menu
99   *
100  * Revision 1.1  2005/02/21 18:08:52  jonblower
101  * Initial import
102  *
103  */
104 public class StyxMon extends StyxInterloper
105 {
106     
107     // Styx components
108     StyxServer server;   // The server that listens for incoming messages
109     
110     // GUI components
111     private JFrame frame;
112     private Container contentPane;
113     private JTable table;
114     private StyxMonTableModel model;
115     private JLabel lblPort;
116     private JLabel lblRemoteServer;
117     private StyxMonPopupMenu popup;
118     
119     private int nextFreeRow;    // The next free row in the table
120     private Hashtable rowTags;  // Links tags to their row numbers
121     private Hashtable fidNames; // Links fids to the file name
122     private Hashtable createsOutstanding; // Links tags to new file names for Tcreate messages
123     
124     private static final String NOT_APPLICABLE = "N/A";
125     
126     /***
127      * Creates a new instance of StyxMon
128      * @param port The port on which this application will listen
129      * @param serverHost The host of the remote Styx server
130      * @param serverPort The port of the remote Styx server
131      * @throws IOException if the server process could not be started.
132      */
133     public StyxMon(int port, String serverHost, int serverPort)
134         throws IOException
135     {
136         // The superclass (StyxInterloper) creates the StyxServer and starts it
137         super(port, serverHost, serverPort);
138         
139         frame = new JFrame("Styx Monitor");
140         frame.setBounds (100, 100, 550, 500);
141         contentPane = frame.getContentPane();
142         
143         lblPort = new JLabel("Listening on port " + port);
144         lblRemoteServer = new JLabel("Remote server: " + serverHost + ":"
145             + serverPort);
146         
147         // Create the table model
148         this.model = new StyxMonTableModel();
149         this.table = new JTable(model);
150         // All the cells in the table are Strings, so the StyxMonTableCellRenderer
151         // will be used by all cells
152         this.table.setDefaultRenderer(String.class, new StyxMonTableCellRenderer());
153         
154         this.rowTags = new Hashtable();
155         this.fidNames = new Hashtable();
156         this.createsOutstanding = new Hashtable();
157         
158         // Set the table column widths (the Tag column should be much narrower
159         // than the rest)
160         TableColumn column = null;
161         for (int i = 0; i < this.model.getColumnCount(); i++)
162         {
163             column = table.getColumnModel().getColumn(i);
164             if (i == 0 || i == 1)
165             {
166                 column.setPreferredWidth(50);
167                 column.setMaxWidth(50);
168             }
169             else
170             {
171                 column.setPreferredWidth(100);
172             }
173         }
174         // Prevent users from editing the table
175         table.setEnabled(false);
176         this.nextFreeRow = 0; // The first free row in the table
177         
178         // Create the scroll pane and add the table to it
179         JScrollPane scrollPane = new JScrollPane(table);
180         
181         // Create the table layout
182         double size[][] =
183         {
184             { 10, TableLayout.FILL, 10, TableLayout.FILL, 10}, // Columns
185             { 10, 20, 10, 20, 20, TableLayout.FILL, 10 }  // Rows
186         };
187         contentPane.setLayout(new TableLayout(size));
188         // Add the components to the table layout
189         contentPane.add(lblPort, "1, 1, l, f");
190         contentPane.add(lblRemoteServer, "1, 3, l, f");
191         contentPane.add(scrollPane, "1, 5, 3, 5, f, f");
192         
193         // Create the popup menu
194         this.popup = new StyxMonPopupMenu(this.model);
195         
196         // Allow user to close the window to terminate the program
197         frame.addWindowListener
198         (
199             new WindowAdapter()
200             {
201                 public void windowClosing(WindowEvent e)
202                 {
203                     exit();
204                 }
205             }
206         );
207         
208         // Add the mouse listener that will listen for right-click events
209         // on the table
210         this.table.addMouseListener(ml);
211         
212         frame.setVisible(true);
213     }
214     
215     /***
216      * Closes the connections and exits the application
217      */
218     private void exit()
219     {
220         // TODO: close the connection
221         System.exit(0);
222     }
223     
224     /***
225      * Required by the InterloperListener interface. Called when a Tmessage
226      * arrives from a client
227      */
228     public synchronized void tMessageReceived(StyxMessage message)
229     {
230         // Get the full path of the file represented by this fid
231         String path = (String)this.fidNames.get(new Long(message.getFid()));
232         if (path == null)
233         {
234             // This happens for messages that aren't associated with a fid
235             path = NOT_APPLICABLE;
236         }
237         // If this is a TattachMessage we know what fid is associated with the root
238         // of the remote server
239         if (message instanceof TattachMessage)
240         {
241             TattachMessage tAttMsg = (TattachMessage)message;
242             this.fidNames.put(new Long(tAttMsg.getFid()), "");
243         }
244         // If this is a TwalkMessage we can associate the new fid with the
245         // path of the file in question
246         else if (message instanceof TwalkMessage)
247         {
248             TwalkMessage tWalkMsg = (TwalkMessage)message;
249             String fullPath = path;
250             if (tWalkMsg.getNumPathElements() > 0)
251             {
252                 fullPath = path + "/" + tWalkMsg.getPath();
253             }
254             this.fidNames.put(new Long(tWalkMsg.getNewFid()), fullPath);
255         }
256         // If this is a TcreateMessage we make a note of the name of the file
257         // that's being created as this will in future be associated with the fid
258         // if the create is successful
259         else if (message instanceof TcreateMessage)
260         {
261             this.createsOutstanding.put(new Integer(message.getTag()), message);
262         }
263         int theRow = this.getRow(message.getTag());
264         model.addTMessageData(theRow, getMessageName(message.getName()),
265             message.getTag(), path.equals("") ? "/" : path, message.toFriendlyString());
266         table.repaint();
267     }
268     
269     /***
270      * Required by the InterloperListener interface. Called when an Rmessage
271      * has been sent back to the client
272      */
273     public synchronized void rMessageSent(StyxMessage message)
274     {
275         // If this was a reply to a successful Create, the file that the fid
276         // refers to has changed
277         if (message instanceof RcreateMessage)
278         {
279             // Get the original TcreateMessage
280             TcreateMessage tCreateMsg =
281                 (TcreateMessage)this.createsOutstanding.remove(new Integer(message.getTag()));
282             if (tCreateMsg != null)
283             {
284                 // Get the filename that is currently associated with this fid
285                 String filename = (String)this.fidNames.get(new Long(tCreateMsg.getFid()));
286                 if (filename != null)
287                 {
288                     filename += "/" + tCreateMsg.getFileName();
289                     this.fidNames.put(new Long(tCreateMsg.getFid()), filename);
290                 }
291             }
292         }
293         // Get the row number for this message
294         int theRow = this.getRow(message.getTag());
295         model.setValueAt(message.toFriendlyString(), theRow, 4);
296         table.repaint();
297     }
298         
299     /***
300      * Converts the name of a Tmessage (e.g. "Tread") into the generic name
301      * of the message (e.g. "Read")
302      */
303     private String getMessageName(String tMessageName)
304     {
305         // Strip off first two letters
306         String lastBit = tMessageName.substring(2);
307         // Capitalise the second letter of the original string
308         String firstLetter = tMessageName.substring(1, 2).toUpperCase();
309         return firstLetter + lastBit;
310     }
311     
312     /***
313      * @return the row corresponding to the given tag. If the tag does not exist
314      * in the cache, the index of the next free row in the data model will be
315      * returned (creating a new row if necessary).
316      */
317     private synchronized int getRow(int tag)
318     {
319         // Find out if we have a row for this tag
320         Integer intRow = (Integer)rowTags.remove(new Integer(tag));
321         if (intRow == null)
322         {
323             // We don't have a row for this tag. Create a new row in the data model
324             model.addRow();
325             // Associate the new row with the tag
326             rowTags.put(new Integer(tag), new Integer(nextFreeRow));
327             int r = nextFreeRow;
328             nextFreeRow++;
329             return r;
330         }
331         else
332         {
333             // We already have a row for this tag
334             return intRow.intValue();
335         }
336     }
337     
338     /***
339      * Listens for right clicks (i.e. requests for the pop-up menu)
340      * Note that for platform independence we must check for the popup trigger
341      * in both mousePressed and mouseReleased
342      */
343     private MouseListener ml = new MouseAdapter()
344     {
345         public void mousePressed(MouseEvent e)
346         {
347             maybeShowPopup(e);
348         }
349         public void mouseReleased(MouseEvent e)
350         {
351             maybeShowPopup(e);
352         }
353         private void maybeShowPopup(MouseEvent e)
354         {
355             if (e.isPopupTrigger())
356             {
357                 int row = table.rowAtPoint(new Point(e.getX(), e.getY()));
358                 if (row != -1)
359                 {
360                     // Get the filename at this row
361                     String filename = (String)model.getValueAt(row, 2);
362                     if (filename != null && !filename.equals("") &&
363                         !filename.equals(NOT_APPLICABLE))
364                     {
365                         popup.showContext(filename, table, e.getX(), e.getY());
366                     }
367                 }
368             }
369         }
370     };
371     
372     public static void main(String[] args)
373     {
374         try
375         {
376             checkArgs(args);
377             new StyxMon(Integer.parseInt(args[0]), args[1],
378                 Integer.parseInt(args[2]));
379         }
380         catch(Exception e)
381         {
382             System.err.println(e.getMessage());
383         }
384     }
385     
386 }