2fb29387b5df08998f064168be313aecaae9e147
[WeStealzYourDataz.git] / src / uk / ac / ntu / n0521366 / wsyd / management / ServerManagement.java
1 /*
2  * The MIT License
3  *
4  * Copyright 2015 TJ <hacker@iam.tj>.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 package uk.ac.ntu.n0521366.wsyd.management;
25
26 import java.io.FileWriter;
27 import java.io.BufferedWriter;
28 import java.io.File;
29 import javax.swing.ImageIcon;
30 import java.awt.Desktop;
31 import java.net.URI;
32 import java.net.InetAddress;
33 import java.io.IOException;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.net.DatagramPacket;
37 import java.net.DatagramSocket;
38 import java.net.InetSocketAddress;
39 import javax.swing.Timer;
40 import javax.swing.table.DefaultTableModel;
41 import java.net.UnknownHostException;
42 import java.util.logging.Logger;
43 import java.util.logging.Level;
44 import java.util.logging.LogRecord;
45 import java.util.logging.Filter;
46 import java.util.Date;
47 import java.text.SimpleDateFormat;
48 import java.text.MessageFormat;
49 import java.util.ArrayList;
50 import uk.ac.ntu.n0521366.wsyd.libs.logging.TableModelHandler;
51 import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
52 import uk.ac.ntu.n0521366.wsyd.libs.message.MessagePresence;
53 import uk.ac.ntu.n0521366.wsyd.libs.message.MessageServerControl;
54 import uk.ac.ntu.n0521366.wsyd.libs.net.WSYD_SocketAddress.Protocol;
55 import uk.ac.ntu.n0521366.wsyd.libs.net.*;
56 import uk.ac.ntu.n0521366.wsyd.libs.net.ServiceAddressMap.LastSeenHost;
57 /**
58  *
59  * @author TJ <hacker@iam.tj>
60  */
61 public class ServerManagement extends javax.swing.JFrame implements NetworkMessageEventListener, Filter {
62     /**
63      * class-wide logger
64      */
65     Logger LOGGER = null;
66     
67     /**
68      * Location of resource bundles, images, icons
69      */
70     private static final String resourcePath = "/uk/ac/ntu/n0521366/wsyd/resources";
71
72     /**
73      * Readable/displayable name of this application
74      */
75     final String _title = "ServerManagement";
76
77     /**
78      * Network services to address map.
79      */
80     ServiceAddressMap _serviceToAddressMap;
81
82     /**
83      * The UDP listener address for incoming log messages
84      */
85     WSYD_SocketAddress _udpLogServiceSA = null;
86
87     /**
88      * UDP multi-cast presence advertiser
89      */
90     WSYD_SocketAddress _multicastAdvertiserSA = null;
91     
92     /**
93      * Log service running in a SwingWorker thread.
94      */
95     NetworkServerUDP _udpLogService = null;
96
97     /**
98      * Multi-cast neighbour advertise and discover service in a SwingWorker thread.
99      */
100     NetworkServerUDPMulticast _multicastServer = null;
101  
102     boolean _multicastAnnouncements = false;
103
104     /**
105      * Enable or suspend logging
106      */
107     private boolean _doLogging;
108     
109     /**
110      * Enable or suspend table auto-scroll
111      */
112     private boolean _autoScroll;
113     
114     /**
115      * Regular presence announcements
116      */
117     Timer multicastAnnounce;
118     
119     /**
120      * Creates new GUI
121      * Instantiating code <em>must</em> also call initListeners()
122      * 
123      * @see #initListeners()
124      */
125     public ServerManagement() {
126         if (LOGGER == null) // single instance of the Logger shared by all class objects
127             LOGGER = Logger.getLogger(_title);
128         LOGGER.setLevel(Level.ALL);
129         _serviceToAddressMap = new ServiceAddressMap(_title, LOGGER);
130         initComponents();
131         _doLogging = true;
132         _autoScroll = true;
133         setLocationRelativeTo(null); // center on screen
134         // XXX: workaround for bug in NetBeans that doesn't set the displayed background colour, only the component colour, from the Properties editor
135         gDialogAbout.getContentPane().setBackground(gDialogAbout.getBackground());
136     }
137
138     /**
139      * Creates new Server Management GUI that also announces itself using Multicast Neighbour discovery.
140      * @param multicastAnnouncements true if it should announce itself
141      * @param serverSocial IP address of ServerSocial - if non-null prevents ServerSocial map entry being treated as stale
142      */
143     public ServerManagement(boolean multicastAnnouncements, InetAddress serverSocial) {
144         this();
145         this._multicastAnnouncements = multicastAnnouncements;
146         // TODO: implement constructor setting IP address of SocialServer from command-line value - needs ServiceToHostMap separating from NetworkServerAbstract and do not allow manually added service map element being treated as stale
147     }
148     /**
149      * Initialise listeners and other objects that require a reference to 'this'.
150      * 
151      * Passing 'this' from within the constructor is unsafe since the object is not
152      * fully constructed, so do it here. The compiler and virtual machine are free to move 'final'
153      * properties outside the constructor which means they may not be correctly
154      * initialised before the constructor returns. This is especially problematic
155      * in multi-threading applications.
156      * 
157      * @return a reference to 'this' so method calls can be chained (e.g. new ServerManagement().initListeners().setVisible(true) )
158      * @throws UnknownHostException
159      */
160     public ServerManagement initListeners() throws UnknownHostException {        
161         LOGGER.addHandler(new TableModelHandler(this)); // send messages to the GUI log table
162         LOGGER.setUseParentHandlers(false); // don't send messages to the default error stream logger of the parent
163         LOGGER.log(Level.INFO, "Server Management starting");
164         
165         _udpLogServiceSA = new WSYD_SocketAddress(Network.PORTS_SERVER_LOG, Protocol.UDP);
166         _udpLogService = new NetworkServerUDP(_udpLogServiceSA, _title + "Log", _serviceToAddressMap, LOGGER);
167         _udpLogService.addNetworkMessageEventListener(this, "Log");
168         _udpLogService.setSimulate(false);
169         _udpLogService.execute();
170
171         _multicastAdvertiserSA = new WSYD_SocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY, Protocol.UDP);
172         _multicastServer = new NetworkServerUDPMulticast(_multicastAdvertiserSA, _title + "MC", _serviceToAddressMap, LOGGER);
173         _multicastServer.addNetworkMessageEventListener(this, "Neighbour");
174         _multicastServer.execute();
175         // permit broadcasting to pseudo-host 'all' since this is multicast
176         _serviceToAddressMap.put("all", new LastSeenHost(new InetSocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY), LastSeenHost.STATE.STATIC));
177         
178         if (this._multicastAnnouncements) {
179             ActionListener multicastAnnounceActionListener = new ActionListener() {
180                 /**
181                  * Activated by timer events to send multi-cast neighbour
182                  * announcements for the Log Service.
183                  *
184                  * @param e
185                  */
186                 @Override
187                 public void actionPerformed(ActionEvent e) {
188                     // Create local log report first
189                     LogRecord record = new LogRecord(Level.FINEST, "Multicast: Announcing Presence");
190                     record.setSourceClassName("ServerLog");
191                     record.setMillis(System.currentTimeMillis());
192                     LOGGER.log(record);
193
194                     // Announce the Log Server service
195                     MessagePresence mp = new MessagePresence(_title + "Log", _udpLogService.getSocketAddress());
196                     NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
197                     nm.setSender(_title + "Log");
198                     _multicastServer.queueMessage(nm);
199
200                     // clean up the known hosts map and keep Server menu up-to-date
201                     ArrayList<String> servicesRemoved =  _serviceToAddressMap.cleanServiceAddressMap(5000);
202                     for (String service : servicesRemoved) {
203                         switch (service) {
204                             case "ServerSocial":
205                                 gMenuServerSocial.setEnabled(false);
206                                 break;
207                             case "ServerChat":
208                                 gMenuServerChat.setEnabled(false);
209                                 break;
210                         }
211                     }
212
213                 }
214             };
215             multicastAnnounce = new Timer(1000, multicastAnnounceActionListener);
216             multicastAnnounce.setInitialDelay(100);
217             multicastAnnounce.start();
218         }
219         
220         return this;
221     }
222
223     /**
224      * Add a log record to the log table if logging is enabled.
225      * 
226      * Abusing an already-existing logging interface. This method isn't filtering, it is called by
227      * the Logger's TableModelHandler to publish records.
228      * 
229      * @see TableModelHandler
230      * @param record
231      * @return true if logging is enabled
232      */
233     @Override
234     public boolean isLoggable(LogRecord record) {
235         if (_doLogging) {
236             if (record != null) {
237                 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
238                 // add the log record to the log table
239                 ((DefaultTableModel)gLogTable.getModel()).addRow(
240                         new Object[]{
241                             df.format(new Date(record.getMillis())),
242                             record.getLevel().toString(),
243                             record.getSourceClassName(),
244                             record.getMessage()
245                         }
246                 );
247                 if (_autoScroll) { // keep last record added in the view
248                     int row = gLogTable.getModel().getRowCount();
249                     gLogTable.scrollRectToVisible(gLogTable.getCellRect(row, 0, true));
250                 }
251             }
252         }
253         return _doLogging;
254     }
255
256     protected boolean UDPSend(NetworkMessage message) {
257         boolean result = false;
258
259         if (message != null) {
260             InetSocketAddress address = this._serviceToAddressMap.getServiceAddress(message.getTarget());
261             if (address != null) {
262                 message.setSender("ServerManagement");
263                 try {
264                     byte[] dataSend = NetworkMessage.serialize(message);
265                     DatagramPacket packetSend = new DatagramPacket(dataSend, dataSend.length);
266                     // set target's remote host address and port
267                     packetSend.setAddress(address.getAddress());
268                     packetSend.setPort(address.getPort());
269
270                     DatagramSocket socket = new DatagramSocket();
271                     // acknowledge receipt
272                     socket.send(packetSend);
273                     LOGGER.logp(Level.FINEST, _title, null, MessageFormat.format("Sending packet for {0} to {1} ({3}:{4,number,integer}) from {2}", message.getIntent(), message.getTarget(), message.getSender(), packetSend.getAddress().getHostAddress(), packetSend.getPort()));
274
275                     result = true; // successful
276                 } catch (IOException e) {
277                     // TODO: serverSend() add IOException handler
278                     e.printStackTrace();
279                 }
280             }
281         }
282         return result;
283     }
284
285     /**
286      * This method is called from within the constructor to initialize the form.
287      * WARNING: Do NOT modify this code. The content of this method is always
288      * regenerated by the Form Editor.
289      */
290     @SuppressWarnings("unchecked")
291     // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
292     private void initComponents() {
293
294         buttonGroup1 = new javax.swing.ButtonGroup();
295         gDialogAbout = new javax.swing.JDialog();
296         gTextAreaAbout = new javax.swing.JTextArea();
297         gBtnAbout = new javax.swing.JButton();
298         gFileChooser = new javax.swing.JFileChooser();
299         gLogScroller = new javax.swing.JScrollPane();
300         gLogTable = new javax.swing.JTable();
301         gMenuBar = new javax.swing.JMenuBar();
302         gMenuFile = new javax.swing.JMenu();
303         gMenuFileSave = new javax.swing.JMenuItem();
304         gMenuLog = new javax.swing.JMenu();
305         gMenuLogClear = new javax.swing.JMenuItem();
306         gMenuLogControl = new javax.swing.JCheckBoxMenuItem();
307         gMenuLogAutoScroll = new javax.swing.JCheckBoxMenuItem();
308         gMenuServers = new javax.swing.JMenu();
309         gMenuServerSocial = new javax.swing.JMenu();
310         gMenuServerSocialRestart = new javax.swing.JMenuItem();
311         gMenuServerSocialStop = new javax.swing.JMenuItem();
312         gMenuServerChat = new javax.swing.JMenu();
313         gMenuServerChatRestart = new javax.swing.JMenuItem();
314         gMenuServerChatStop = new javax.swing.JMenuItem();
315         gMenuHelp = new javax.swing.JMenu();
316         gMenuHelpAbout = new javax.swing.JMenuItem();
317
318         gDialogAbout.setTitle("About");
319         gDialogAbout.setBackground(new java.awt.Color(255, 255, 255));
320         gDialogAbout.setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
321         gDialogAbout.setMinimumSize(new java.awt.Dimension(590, 340));
322         gDialogAbout.setResizable(false);
323         gDialogAbout.getContentPane().setLayout(null);
324
325         gTextAreaAbout.setEditable(false);
326         gTextAreaAbout.setColumns(20);
327         gTextAreaAbout.setRows(5);
328         gTextAreaAbout.setText("Server Management client\n© Copyright 2015 TJ <hacker@iam.tj>\n\n<<< Click on the mug to learn more!\n\nI will disappear in 20 seconds.\n\n");
329         gTextAreaAbout.setBorder(null);
330         gDialogAbout.getContentPane().add(gTextAreaAbout);
331         gTextAreaAbout.setBounds(310, 30, 270, 200);
332
333         gBtnAbout.setBackground(new java.awt.Color(255, 255, 255));
334         gBtnAbout.setFont(new java.awt.Font("Dialog", 1, 18)); // NOI18N
335         gBtnAbout.setIcon(new javax.swing.ImageIcon(getClass().getResource("/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png"))); // NOI18N
336         gBtnAbout.setText("We Stealz Your Dataz");
337         gBtnAbout.setBorder(null);
338         gBtnAbout.setBorderPainted(false);
339         gBtnAbout.setContentAreaFilled(false);
340         gBtnAbout.setDefaultCapable(false);
341         gBtnAbout.setFocusPainted(false);
342         gBtnAbout.setRolloverEnabled(false);
343         gBtnAbout.setVerticalAlignment(javax.swing.SwingConstants.TOP);
344         gBtnAbout.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
345         gBtnAbout.addActionListener(new java.awt.event.ActionListener() {
346             public void actionPerformed(java.awt.event.ActionEvent evt) {
347                 AboutAction(evt);
348             }
349         });
350         gDialogAbout.getContentPane().add(gBtnAbout);
351         gBtnAbout.setBounds(0, 0, 530, 324);
352
353         gFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);
354         gFileChooser.setCurrentDirectory(new File(System.getProperty("user.dir")));
355         gFileChooser.setDialogTitle("Save As");
356
357         setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
358         setTitle("We Stealz Your Dataz Servers Management");
359         setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
360
361         gLogScroller.setAutoscrolls(true);
362
363         gLogTable.setModel(new javax.swing.table.DefaultTableModel(
364             new Object [][] {
365                 {null, null, null, null}
366             },
367             new String [] {
368                 "Time", "Level", "Facility", "Message"
369             }
370         ) {
371             Class[] types = new Class [] {
372                 java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
373             };
374             boolean[] canEdit = new boolean [] {
375                 false, false, false, false
376             };
377
378             public Class getColumnClass(int columnIndex) {
379                 return types [columnIndex];
380             }
381
382             public boolean isCellEditable(int rowIndex, int columnIndex) {
383                 return canEdit [columnIndex];
384             }
385         });
386         gLogTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN);
387         gLogTable.setFillsViewportHeight(true);
388         gLogTable.getTableHeader().setReorderingAllowed(false);
389         gLogScroller.setViewportView(gLogTable);
390         if (gLogTable.getColumnModel().getColumnCount() > 0) {
391             gLogTable.getColumnModel().getColumn(0).setPreferredWidth(128);
392             gLogTable.getColumnModel().getColumn(1).setPreferredWidth(128);
393             gLogTable.getColumnModel().getColumn(2).setPreferredWidth(128);
394         }
395
396         getContentPane().add(gLogScroller, java.awt.BorderLayout.CENTER);
397
398         gMenuFile.setText("File");
399
400         gMenuFileSave.setText("Save...");
401         gMenuFileSave.setActionCommand("FileSave");
402         gMenuFileSave.addActionListener(new java.awt.event.ActionListener() {
403             public void actionPerformed(java.awt.event.ActionEvent evt) {
404                 gMenuFileSaveActionPerformed(evt);
405             }
406         });
407         gMenuFile.add(gMenuFileSave);
408
409         gMenuBar.add(gMenuFile);
410
411         gMenuLog.setText("Log");
412
413         gMenuLogClear.setText("Clear");
414         gMenuLogClear.setActionCommand("LogTableClear");
415         gMenuLogClear.addActionListener(new java.awt.event.ActionListener() {
416             public void actionPerformed(java.awt.event.ActionEvent evt) {
417                 gMenuLogClearActionPerformed(evt);
418             }
419         });
420         gMenuLog.add(gMenuLogClear);
421
422         gMenuLogControl.setSelected(true);
423         gMenuLogControl.setText("Running");
424         gMenuLogControl.setActionCommand("LogControl");
425         gMenuLogControl.addActionListener(new java.awt.event.ActionListener() {
426             public void actionPerformed(java.awt.event.ActionEvent evt) {
427                 gMenuLogControlActionPerformed(evt);
428             }
429         });
430         gMenuLog.add(gMenuLogControl);
431
432         gMenuLogAutoScroll.setSelected(true);
433         gMenuLogAutoScroll.setText("Auto-scroll");
434         gMenuLogAutoScroll.setActionCommand("LogAutoScroll");
435         gMenuLogAutoScroll.addActionListener(new java.awt.event.ActionListener() {
436             public void actionPerformed(java.awt.event.ActionEvent evt) {
437                 gMenuLogAutoScrollActionPerformed(evt);
438             }
439         });
440         gMenuLog.add(gMenuLogAutoScroll);
441
442         gMenuBar.add(gMenuLog);
443
444         gMenuServers.setText("Servers");
445
446         gMenuServerSocial.setText("Social");
447         gMenuServerSocial.setEnabled(false);
448
449         gMenuServerSocialRestart.setText("Restart");
450         gMenuServerSocialRestart.setActionCommand("SocialRestart");
451         gMenuServerSocialRestart.addActionListener(new java.awt.event.ActionListener() {
452             public void actionPerformed(java.awt.event.ActionEvent evt) {
453                 gMenuServerSocialRestartActionPerformed(evt);
454             }
455         });
456         gMenuServerSocial.add(gMenuServerSocialRestart);
457
458         gMenuServerSocialStop.setText("Stop");
459         gMenuServerSocialStop.setActionCommand("SocialStop");
460         gMenuServerSocial.add(gMenuServerSocialStop);
461
462         gMenuServers.add(gMenuServerSocial);
463
464         gMenuServerChat.setText("Chat");
465         gMenuServerChat.setEnabled(false);
466
467         gMenuServerChatRestart.setText("Restart");
468         gMenuServerChatRestart.setActionCommand("ChatRestart");
469         gMenuServerChat.add(gMenuServerChatRestart);
470
471         gMenuServerChatStop.setText("Stop");
472         gMenuServerChatStop.setActionCommand("ChatStop");
473         gMenuServerChat.add(gMenuServerChatStop);
474
475         gMenuServers.add(gMenuServerChat);
476
477         gMenuBar.add(gMenuServers);
478
479         gMenuHelp.setText("Help");
480
481         gMenuHelpAbout.setText("About");
482         gMenuHelpAbout.addActionListener(new java.awt.event.ActionListener() {
483             public void actionPerformed(java.awt.event.ActionEvent evt) {
484                 gMenuHelpAboutActionPerformed(evt);
485             }
486         });
487         gMenuHelp.add(gMenuHelpAbout);
488
489         gMenuBar.add(gMenuHelp);
490
491         setJMenuBar(gMenuBar);
492
493         pack();
494     }// </editor-fold>//GEN-END:initComponents
495
496     private void gMenuServerSocialRestartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuServerSocialRestartActionPerformed
497         // TODO add your handling code here:
498         LOGGER.logp(Level.FINEST, _title, null, "Requesting ServerSocial Restart");
499         MessageServerControl mc = new MessageServerControl(MessageServerControl.EXIT.NO , MessageServerControl.RESTART.YES);
500         NetworkMessage nm = NetworkMessage.createNetworkMessage("Control", "ServerSocialControl", mc);
501         nm.setSender(_title);
502         UDPSend(nm);
503
504                 
505     }//GEN-LAST:event_gMenuServerSocialRestartActionPerformed
506
507     /**
508      * When the (disguised) mug-icon button is pressed load a web page in the system default browser.
509      * 
510      * Displays a news story about Microsoft Store's 'Scroogle' mug which coincidentally has a tag line
511      * that is almost identical to the chosen name of this application.
512      * 
513      * @param evt 
514      */
515     private void AboutAction(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AboutAction
516         try {
517             Desktop.getDesktop().browse(URI.create("http://www.slate.com/blogs/future_tense/2013/11/20/microsoft_scroogled_gear_anti_google_t_shirts_mugs_say_keep_calm_while_we.html"));
518         } catch(IOException e) {
519         }
520     }//GEN-LAST:event_AboutAction
521
522     /**
523      * Show the Help>About dialog and auto-close it after 20 seconds.
524      * 
525      * @param evt
526      */
527     private void gMenuHelpAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuHelpAboutActionPerformed
528         ActionListener autoCloseAboutDlg = new ActionListener() {
529             @Override
530             public void actionPerformed(ActionEvent e) {
531                 gDialogAbout.setVisible(false);
532             }
533         };
534
535         gDialogAbout.setLocationRelativeTo(this); // center in application window
536         gDialogAbout.setVisible(true);
537         Timer autoClose = new Timer(20000, autoCloseAboutDlg);
538         autoClose.start();
539     }//GEN-LAST:event_gMenuHelpAboutActionPerformed
540
541     /**
542      * Save log entries to a file.
543      * 
544      * @param evt 
545      */
546     private void gMenuFileSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuFileSaveActionPerformed
547         if (gFileChooser.showSaveDialog(this) == javax.swing.JFileChooser.APPROVE_OPTION) {
548             File saveAs = gFileChooser.getSelectedFile();
549             try (
550                 BufferedWriter writer = new BufferedWriter(new FileWriter(saveAs));
551             )
552             {
553                 for (int row = 0; row < gLogTable.getModel().getRowCount(); row++) {
554                     StringBuilder record = new StringBuilder();
555                     int colMax = gLogTable.getModel().getColumnCount();
556                     for (int col = 0; col < colMax; col++) {
557                         record.append(gLogTable.getModel().getValueAt(row, col));
558                         if (col < colMax - 1) {
559                             record.append(",");
560                         }
561                     }
562                     writer.write(record.toString());
563                     writer.newLine();
564                 }
565                 writer.close();
566             } catch (IOException e) {
567                 System.err.println("Unable to write to file");
568             }
569         }
570     }//GEN-LAST:event_gMenuFileSaveActionPerformed
571
572     /**
573      * Clear the log table.
574      * @param evt 
575      */
576     private void gMenuLogClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogClearActionPerformed
577         ((DefaultTableModel)gLogTable.getModel()).setRowCount(0);
578     }//GEN-LAST:event_gMenuLogClearActionPerformed
579
580     /**
581      * Enable or disable logging.
582      * @param evt 
583      */
584     private void gMenuLogControlActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogControlActionPerformed
585         _doLogging = !_doLogging;
586         gMenuLogControl.setSelected(_doLogging);
587     }//GEN-LAST:event_gMenuLogControlActionPerformed
588
589     /**
590      * Enable or disable automatic scrolling of the log table.
591      * @param evt 
592      */
593     private void gMenuLogAutoScrollActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogAutoScrollActionPerformed
594         _autoScroll = !_autoScroll;
595         gMenuLogAutoScroll.setSelected(_autoScroll);
596     }//GEN-LAST:event_gMenuLogAutoScrollActionPerformed
597
598     /**
599      * Receive a NetworkMessageEvent and dispose of it
600      * @param event
601      */
602     @Override
603     public void NetworkMessageReceived(NetworkMessageEvent event) {
604         NetworkMessage nm = event.getNetworkMessage();
605         if (nm == null || !_doLogging)
606             return;
607         // is it a LogRecord?
608         switch (nm.getIntent()) {
609             case "Log":
610                 MessageLogRecord m = (MessageLogRecord) nm.getMessage();
611                 if (m == null)
612                     return;
613                 this.isLoggable(m.record);
614                 break;
615             case "Neighbour":
616                 String type = nm.getMessage().getMessageType();
617                     if (type.equals(MessagePresence.getType())) { // Presence
618                         MessagePresence mp = (MessagePresence)nm.getMessage();
619                         switch (mp.serviceName) {
620                             case "ServerSocial":
621                                 gMenuServerSocial.setEnabled(true);
622                                 break;
623                             case "ServerChat":
624                                 gMenuServerChat.setEnabled(true);
625                                 break;                                
626                         }
627                     }
628
629
630             default: // log all unhandled messages
631                 LogRecord record = new LogRecord(Level.WARNING,
632                     MessageFormat.format("Unhandled NetworkMessage received with intent: \"{0}\" sender: \"{1}\" target: \"{2}\"",
633                         nm.getIntent(), nm.getSender(), nm.getTarget() )
634                 );
635                 record.setMillis(System.currentTimeMillis());
636                 LOGGER.log(record);
637         }
638     }
639
640     /**
641      * @param args the command line arguments
642      */
643     public static void main(String args[]) {
644         /* Set the Nimbus look and feel */
645         //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
646         /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
647          * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
648          */
649         try {
650             for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
651                 if ("Nimbus".equals(info.getName())) {
652                     javax.swing.UIManager.setLookAndFeel(info.getClassName());
653                     break;
654                 }
655             }
656         } catch (ClassNotFoundException|InstantiationException|IllegalAccessException|javax.swing.UnsupportedLookAndFeelException ex) {
657             java.util.logging.Logger.getLogger(ServerManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
658         }
659         //</editor-fold>
660         //</editor-fold>
661         //</editor-fold>
662         //</editor-fold>
663         /* Create and display the form */
664         class App implements Runnable {
665             public boolean multicastAnnouncements = false;
666             public InetAddress serverSocial = null;
667             @Override
668             public void run() {
669                 try {
670                 new ServerManagement(multicastAnnouncements, serverSocial).initListeners().setVisible(true);
671                 }
672                 catch(UnknownHostException e) {
673                     System.err.println("Error: cannot create log server listener socket");
674                 }
675             }
676         }
677         App app = new App();
678         
679         // process command line arguments        
680         for (int i = 0; i < args.length; i++) {
681             switch (args[i]) {
682                 case "-announce":
683                     app.multicastAnnouncements = true;
684                     break;
685                 case "-server":
686                     if (args.length >= i+1) {
687                         // read the next argument as an IP address
688                         InetAddress temp;
689                         try {
690                             temp = InetAddress.getByName(args[i+1]);
691                             app.serverSocial = temp;
692                         } catch (UnknownHostException e) {
693                             System.err.println(MessageFormat.format("Error: {0} is not a valid hostname or IP address", args[i+1]));
694                         }
695                     }
696                 break;
697             }
698         }
699         java.awt.EventQueue.invokeLater(app);
700     }
701
702     // Variables declaration - do not modify//GEN-BEGIN:variables
703     private javax.swing.ButtonGroup buttonGroup1;
704     private javax.swing.JButton gBtnAbout;
705     private javax.swing.JDialog gDialogAbout;
706     private javax.swing.JFileChooser gFileChooser;
707     private javax.swing.JScrollPane gLogScroller;
708     private javax.swing.JTable gLogTable;
709     private javax.swing.JMenuBar gMenuBar;
710     private javax.swing.JMenu gMenuFile;
711     private javax.swing.JMenuItem gMenuFileSave;
712     private javax.swing.JMenu gMenuHelp;
713     private javax.swing.JMenuItem gMenuHelpAbout;
714     private javax.swing.JMenu gMenuLog;
715     private javax.swing.JCheckBoxMenuItem gMenuLogAutoScroll;
716     private javax.swing.JMenuItem gMenuLogClear;
717     private javax.swing.JCheckBoxMenuItem gMenuLogControl;
718     private javax.swing.JMenu gMenuServerChat;
719     private javax.swing.JMenuItem gMenuServerChatRestart;
720     private javax.swing.JMenuItem gMenuServerChatStop;
721     private javax.swing.JMenu gMenuServerSocial;
722     private javax.swing.JMenuItem gMenuServerSocialRestart;
723     private javax.swing.JMenuItem gMenuServerSocialStop;
724     private javax.swing.JMenu gMenuServers;
725     private javax.swing.JTextArea gTextAreaAbout;
726     // End of variables declaration//GEN-END:variables
727 }