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