1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
108 StyxServer server;
109
110
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;
120 private Hashtable rowTags;
121 private Hashtable fidNames;
122 private Hashtable createsOutstanding;
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
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
148 this.model = new StyxMonTableModel();
149 this.table = new JTable(model);
150
151
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
159
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
175 table.setEnabled(false);
176 this.nextFreeRow = 0;
177
178
179 JScrollPane scrollPane = new JScrollPane(table);
180
181
182 double size[][] =
183 {
184 { 10, TableLayout.FILL, 10, TableLayout.FILL, 10},
185 { 10, 20, 10, 20, 20, TableLayout.FILL, 10 }
186 };
187 contentPane.setLayout(new TableLayout(size));
188
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
194 this.popup = new StyxMonPopupMenu(this.model);
195
196
197 frame.addWindowListener
198 (
199 new WindowAdapter()
200 {
201 public void windowClosing(WindowEvent e)
202 {
203 exit();
204 }
205 }
206 );
207
208
209
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
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
231 String path = (String)this.fidNames.get(new Long(message.getFid()));
232 if (path == null)
233 {
234
235 path = NOT_APPLICABLE;
236 }
237
238
239 if (message instanceof TattachMessage)
240 {
241 TattachMessage tAttMsg = (TattachMessage)message;
242 this.fidNames.put(new Long(tAttMsg.getFid()), "");
243 }
244
245
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
257
258
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
276
277 if (message instanceof RcreateMessage)
278 {
279
280 TcreateMessage tCreateMsg =
281 (TcreateMessage)this.createsOutstanding.remove(new Integer(message.getTag()));
282 if (tCreateMsg != null)
283 {
284
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
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
306 String lastBit = tMessageName.substring(2);
307
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
320 Integer intRow = (Integer)rowTags.remove(new Integer(tag));
321 if (intRow == null)
322 {
323
324 model.addRow();
325
326 rowTags.put(new Integer(tag), new Integer(nextFreeRow));
327 int r = nextFreeRow;
328 nextFreeRow++;
329 return r;
330 }
331 else
332 {
333
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
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 }