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