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