4 * Copyright 2015 TJ <hacker@iam.tj>.
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 package uk.ac.ntu.n0521366.wsyd.management;
26 import java.io.FileWriter;
27 import java.io.BufferedWriter;
29 import javax.swing.ImageIcon;
30 import java.awt.Desktop;
32 import java.io.IOException;
33 import java.awt.event.ActionEvent;
34 import java.awt.event.ActionListener;
35 import javax.swing.Timer;
36 import javax.swing.table.DefaultTableModel;
37 import java.net.UnknownHostException;
38 import java.util.logging.Logger;
39 import java.util.logging.Level;
40 import java.util.logging.LogRecord;
41 import java.util.logging.Filter;
42 import java.util.Date;
43 import java.text.SimpleDateFormat;
44 import java.text.MessageFormat;
45 import java.util.ArrayList;
46 import uk.ac.ntu.n0521366.wsyd.libs.logging.TableModelHandler;
47 import uk.ac.ntu.n0521366.wsyd.libs.message.MessageLogRecord;
48 import uk.ac.ntu.n0521366.wsyd.libs.message.MessagePresence;
49 import uk.ac.ntu.n0521366.wsyd.libs.net.WSYD_SocketAddress.Protocol;
50 import uk.ac.ntu.n0521366.wsyd.libs.net.*;
54 * @author TJ <hacker@iam.tj>
56 public class ServerManagement extends javax.swing.JFrame implements NetworkMessageEventListener, Filter {
60 static final Logger LOGGER = Logger.getLogger("ServerManagement");
63 * Location of resource bundles, images, icons
65 private static final String resourcePath = "/uk/ac/ntu/n0521366/wsyd/resources";
68 * The UDP listener address for incoming log messages
70 WSYD_SocketAddress _logServerSA = null;
73 * UDP multi-cast presence advertiser
75 WSYD_SocketAddress _multicastAdvertiserSA = null;
78 * Log service running in a SwingWorker thread.
80 NetworkServerUDP _logServer = null;
83 * Multi-cast neighbour advertise and discover service in a SwingWorker thread.
85 NetworkServerUDPMulticast _multicastServer = null;
89 * Enable or suspend logging
91 private boolean _doLogging;
94 * Enable or suspend table auto-scroll
96 private boolean _autoScroll;
99 * Regular presence announcements
101 Timer multicastAnnounce;
105 * Instantiating code <em>must</em> also call initListeners()
107 * @see #initListeners()
109 public ServerManagement() {
110 LOGGER.setLevel(Level.ALL);
114 setLocationRelativeTo(null); // center on screen
115 // XXX: workaround for bug in NetBeans that doesn't set the displayed background colour, only the component colour, from the Properties editor
116 gDialogAbout.getContentPane().setBackground(gDialogAbout.getBackground());
120 * Initialise listeners and other objects that require a reference to 'this'.
122 * Passing 'this' from within the constructor is unsafe since the object is not
123 * fully constructed, so do it here. The compiler and virtual machine are free to move 'final'
124 * properties outside the constructor which means they may not be correctly
125 * initialised before the constructor returns. This is especially problematic
126 * in multi-threading applications.
128 * @return a reference to 'this' so method calls can be chained (e.g. new ServerManagement().initListeners().setVisible(true) )
129 * @throws UnknownHostException
131 public ServerManagement initListeners() throws UnknownHostException {
132 LOGGER.addHandler(new TableModelHandler(this)); // send messages to the GUI log table
133 LOGGER.setUseParentHandlers(false); // don't send messages to the default error stream logger of the parent
134 LOGGER.log(Level.INFO, "Server Management starting");
136 _logServerSA = new WSYD_SocketAddress(Network.PORTS_SERVER_LOG, Protocol.UDP);
137 _logServer = new NetworkServerUDP(_logServerSA, "ServerLog", LOGGER);
138 _logServer.addNetworkMessageEventListener(this, "Log");
139 _logServer.setSimulate(false);
140 _logServer.execute();
141 _multicastAdvertiserSA = new WSYD_SocketAddress(Network.MULTICAST_IP, Network.PORTS_MULTICAST_DISCOVERY, Protocol.UDP);
142 _multicastServer = new NetworkServerUDPMulticast(_multicastAdvertiserSA, "ServerLogMC", LOGGER);
143 _multicastServer.addNetworkMessageEventListener(this, "Neighbour");
144 _multicastServer.execute();
146 ActionListener multicastAnnounceActionListener = new ActionListener() {
148 * Activated by timer events to send multi-cast neighbour announcements for the Log Service.
152 public void actionPerformed(ActionEvent e) {
153 // Create local log report first
154 LogRecord record = new LogRecord(Level.FINEST, "Multicast: Announcing Presence");
155 record.setSourceClassName("ServerLog");
156 record.setMillis(System.currentTimeMillis());
159 // Announce the Log Server service
160 MessagePresence mp = new MessagePresence("ServerLog", Network.PORTS_SERVER_LOG);
161 NetworkMessage nm = NetworkMessage.createNetworkMessage("Neighbour", "all", mp);
162 nm.setSender("ServerLog");
163 _multicastServer.queueMessage(nm);
165 // clean up the known hosts map and keep Server menu up-to-date
166 ArrayList<String> servicesRemoved = _multicastServer.cleanServiceToHostMap(5000);
167 for (String service: servicesRemoved) {
170 gMenuServerSocial.setEnabled(false);
173 gMenuServerChat.setEnabled(false);
180 multicastAnnounce = new Timer(1000, multicastAnnounceActionListener);
181 multicastAnnounce.setInitialDelay(100);
182 multicastAnnounce.start();
188 * Add a log record to the log table if logging is enabled.
190 * Abusing an already-existing logging interface. This method isn't filtering, it is called by
191 * the Logger's TableModelHandler to publish records.
193 * @see TableModelHandler
195 * @return true if logging is enabled
198 public boolean isLoggable(LogRecord record) {
200 if (record != null) {
201 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
202 // add the log record to the log table
203 ((DefaultTableModel)gLogTable.getModel()).addRow(
205 df.format(new Date(record.getMillis())),
206 record.getLevel().toString(),
207 record.getSourceClassName(),
211 if (_autoScroll) { // keep last record added in the view
212 int row = gLogTable.getModel().getRowCount();
213 gLogTable.scrollRectToVisible(gLogTable.getCellRect(row, 0, true));
221 * This method is called from within the constructor to initialize the form.
222 * WARNING: Do NOT modify this code. The content of this method is always
223 * regenerated by the Form Editor.
225 @SuppressWarnings("unchecked")
226 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
227 private void initComponents() {
229 buttonGroup1 = new javax.swing.ButtonGroup();
230 gDialogAbout = new javax.swing.JDialog();
231 gTextAreaAbout = new javax.swing.JTextArea();
232 gBtnAbout = new javax.swing.JButton();
233 gFileChooser = new javax.swing.JFileChooser();
234 gLogScroller = new javax.swing.JScrollPane();
235 gLogTable = new javax.swing.JTable();
236 gMenuBar = new javax.swing.JMenuBar();
237 gMenuFile = new javax.swing.JMenu();
238 gMenuFileSave = new javax.swing.JMenuItem();
239 gMenuLog = new javax.swing.JMenu();
240 gMenuLogClear = new javax.swing.JMenuItem();
241 gMenuLogControl = new javax.swing.JCheckBoxMenuItem();
242 gMenuLogAutoScroll = new javax.swing.JCheckBoxMenuItem();
243 gMenuServers = new javax.swing.JMenu();
244 gMenuServerSocial = new javax.swing.JMenu();
245 gMenuServerSocialRestart = new javax.swing.JMenuItem();
246 gMenuServerSocialStop = new javax.swing.JMenuItem();
247 gMenuServerChat = new javax.swing.JMenu();
248 gMenuServerChatRestart = new javax.swing.JMenuItem();
249 gMenuServerChatStop = new javax.swing.JMenuItem();
250 gMenuHelp = new javax.swing.JMenu();
251 gMenuHelpAbout = new javax.swing.JMenuItem();
253 gDialogAbout.setTitle("About");
254 gDialogAbout.setBackground(new java.awt.Color(255, 255, 255));
255 gDialogAbout.setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
256 gDialogAbout.setMinimumSize(new java.awt.Dimension(590, 340));
257 gDialogAbout.setResizable(false);
258 gDialogAbout.getContentPane().setLayout(null);
260 gTextAreaAbout.setEditable(false);
261 gTextAreaAbout.setColumns(20);
262 gTextAreaAbout.setRows(5);
263 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");
264 gTextAreaAbout.setBorder(null);
265 gDialogAbout.getContentPane().add(gTextAreaAbout);
266 gTextAreaAbout.setBounds(310, 30, 270, 200);
268 gBtnAbout.setBackground(new java.awt.Color(255, 255, 255));
269 gBtnAbout.setFont(new java.awt.Font("Dialog", 1, 18)); // NOI18N
270 gBtnAbout.setIcon(new javax.swing.ImageIcon(getClass().getResource("/uk/ac/ntu/n0521366/wsyd/resources/ScroogledKeepCalmMug.png"))); // NOI18N
271 gBtnAbout.setText("We Stealz Your Dataz");
272 gBtnAbout.setBorder(null);
273 gBtnAbout.setBorderPainted(false);
274 gBtnAbout.setContentAreaFilled(false);
275 gBtnAbout.setDefaultCapable(false);
276 gBtnAbout.setFocusPainted(false);
277 gBtnAbout.setRolloverEnabled(false);
278 gBtnAbout.setVerticalAlignment(javax.swing.SwingConstants.TOP);
279 gBtnAbout.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
280 gBtnAbout.addActionListener(new java.awt.event.ActionListener() {
281 public void actionPerformed(java.awt.event.ActionEvent evt) {
285 gDialogAbout.getContentPane().add(gBtnAbout);
286 gBtnAbout.setBounds(0, 0, 530, 324);
288 gFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);
289 gFileChooser.setCurrentDirectory(new File(System.getProperty("user.dir")));
290 gFileChooser.setDialogTitle("Save As");
292 setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
293 setTitle("We Stealz Your Dataz Servers Management");
294 setIconImage(new ImageIcon( getClass().getResource(resourcePath +"/ScroogledKeepCalmMug.icon.png")).getImage());
296 gLogScroller.setAutoscrolls(true);
298 gLogTable.setModel(new javax.swing.table.DefaultTableModel(
300 {null, null, null, null}
303 "Time", "Level", "Facility", "Message"
306 Class[] types = new Class [] {
307 java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
309 boolean[] canEdit = new boolean [] {
310 false, false, false, false
313 public Class getColumnClass(int columnIndex) {
314 return types [columnIndex];
317 public boolean isCellEditable(int rowIndex, int columnIndex) {
318 return canEdit [columnIndex];
321 gLogTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN);
322 gLogTable.setFillsViewportHeight(true);
323 gLogTable.getTableHeader().setReorderingAllowed(false);
324 gLogScroller.setViewportView(gLogTable);
325 if (gLogTable.getColumnModel().getColumnCount() > 0) {
326 gLogTable.getColumnModel().getColumn(0).setPreferredWidth(128);
327 gLogTable.getColumnModel().getColumn(1).setPreferredWidth(128);
328 gLogTable.getColumnModel().getColumn(2).setPreferredWidth(128);
331 getContentPane().add(gLogScroller, java.awt.BorderLayout.CENTER);
333 gMenuFile.setText("File");
335 gMenuFileSave.setText("Save...");
336 gMenuFileSave.setActionCommand("FileSave");
337 gMenuFileSave.addActionListener(new java.awt.event.ActionListener() {
338 public void actionPerformed(java.awt.event.ActionEvent evt) {
339 gMenuFileSaveActionPerformed(evt);
342 gMenuFile.add(gMenuFileSave);
344 gMenuBar.add(gMenuFile);
346 gMenuLog.setText("Log");
348 gMenuLogClear.setText("Clear");
349 gMenuLogClear.setActionCommand("LogTableClear");
350 gMenuLogClear.addActionListener(new java.awt.event.ActionListener() {
351 public void actionPerformed(java.awt.event.ActionEvent evt) {
352 gMenuLogClearActionPerformed(evt);
355 gMenuLog.add(gMenuLogClear);
357 gMenuLogControl.setSelected(true);
358 gMenuLogControl.setText("Running");
359 gMenuLogControl.setActionCommand("LogControl");
360 gMenuLogControl.addActionListener(new java.awt.event.ActionListener() {
361 public void actionPerformed(java.awt.event.ActionEvent evt) {
362 gMenuLogControlActionPerformed(evt);
365 gMenuLog.add(gMenuLogControl);
367 gMenuLogAutoScroll.setSelected(true);
368 gMenuLogAutoScroll.setText("Auto-scroll");
369 gMenuLogAutoScroll.setActionCommand("LogAutoScroll");
370 gMenuLogAutoScroll.addActionListener(new java.awt.event.ActionListener() {
371 public void actionPerformed(java.awt.event.ActionEvent evt) {
372 gMenuLogAutoScrollActionPerformed(evt);
375 gMenuLog.add(gMenuLogAutoScroll);
377 gMenuBar.add(gMenuLog);
379 gMenuServers.setText("Servers");
381 gMenuServerSocial.setText("Social");
382 gMenuServerSocial.setEnabled(false);
384 gMenuServerSocialRestart.setText("Restart");
385 gMenuServerSocialRestart.setActionCommand("SocialRestart");
386 gMenuServerSocialRestart.addActionListener(new java.awt.event.ActionListener() {
387 public void actionPerformed(java.awt.event.ActionEvent evt) {
388 gMenuServerSocialRestartActionPerformed(evt);
391 gMenuServerSocial.add(gMenuServerSocialRestart);
393 gMenuServerSocialStop.setText("Stop");
394 gMenuServerSocialStop.setActionCommand("SocialStop");
395 gMenuServerSocial.add(gMenuServerSocialStop);
397 gMenuServers.add(gMenuServerSocial);
399 gMenuServerChat.setText("Chat");
400 gMenuServerChat.setEnabled(false);
402 gMenuServerChatRestart.setText("Restart");
403 gMenuServerChatRestart.setActionCommand("ChatRestart");
404 gMenuServerChat.add(gMenuServerChatRestart);
406 gMenuServerChatStop.setText("Stop");
407 gMenuServerChatStop.setActionCommand("ChatStop");
408 gMenuServerChat.add(gMenuServerChatStop);
410 gMenuServers.add(gMenuServerChat);
412 gMenuBar.add(gMenuServers);
414 gMenuHelp.setText("Help");
416 gMenuHelpAbout.setText("About");
417 gMenuHelpAbout.addActionListener(new java.awt.event.ActionListener() {
418 public void actionPerformed(java.awt.event.ActionEvent evt) {
419 gMenuHelpAboutActionPerformed(evt);
422 gMenuHelp.add(gMenuHelpAbout);
424 gMenuBar.add(gMenuHelp);
426 setJMenuBar(gMenuBar);
429 }// </editor-fold>//GEN-END:initComponents
431 private void gMenuServerSocialRestartActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuServerSocialRestartActionPerformed
432 // TODO add your handling code here:
434 }//GEN-LAST:event_gMenuServerSocialRestartActionPerformed
437 * When the (disguised) mug-icon button is pressed load a web page in the system default browser.
439 * Displays a news story about Microsoft Store's 'Scroogle' mug which coincidentally has a tag line
440 * that is almost identical to the chosen name of this application.
444 private void AboutAction(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AboutAction
446 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"));
447 } catch(IOException e) {
449 }//GEN-LAST:event_AboutAction
452 * Show the Help>About dialog and auto-close it after 20 seconds.
456 private void gMenuHelpAboutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuHelpAboutActionPerformed
457 ActionListener autoCloseAboutDlg = new ActionListener() {
459 public void actionPerformed(ActionEvent e) {
460 gDialogAbout.setVisible(false);
464 gDialogAbout.setLocationRelativeTo(this); // center in application window
465 gDialogAbout.setVisible(true);
466 Timer autoClose = new Timer(20000, autoCloseAboutDlg);
468 }//GEN-LAST:event_gMenuHelpAboutActionPerformed
471 * Save log entries to a file.
475 private void gMenuFileSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuFileSaveActionPerformed
476 if (gFileChooser.showSaveDialog(this) == javax.swing.JFileChooser.APPROVE_OPTION) {
477 File saveAs = gFileChooser.getSelectedFile();
479 BufferedWriter writer = new BufferedWriter(new FileWriter(saveAs));
482 for (int row = 0; row < gLogTable.getModel().getRowCount(); row++) {
483 StringBuilder record = new StringBuilder();
484 int colMax = gLogTable.getModel().getColumnCount();
485 for (int col = 0; col < colMax; col++) {
486 record.append(gLogTable.getModel().getValueAt(row, col));
487 if (col < colMax - 1) {
491 writer.write(record.toString());
495 } catch (IOException e) {
496 System.err.println("Unable to write to file");
499 }//GEN-LAST:event_gMenuFileSaveActionPerformed
502 * Clear the log table.
505 private void gMenuLogClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogClearActionPerformed
506 ((DefaultTableModel)gLogTable.getModel()).setRowCount(0);
507 }//GEN-LAST:event_gMenuLogClearActionPerformed
510 * Enable or disable logging.
513 private void gMenuLogControlActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogControlActionPerformed
514 _doLogging = !_doLogging;
515 gMenuLogControl.setSelected(_doLogging);
516 }//GEN-LAST:event_gMenuLogControlActionPerformed
519 * Enable or disable automatic scrolling of the log table.
522 private void gMenuLogAutoScrollActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gMenuLogAutoScrollActionPerformed
523 _autoScroll = !_autoScroll;
524 gMenuLogAutoScroll.setSelected(_autoScroll);
525 }//GEN-LAST:event_gMenuLogAutoScrollActionPerformed
528 * Receive a NetworkMessageEvent and dispose of it
532 public void NetworkMessageReceived(NetworkMessageEvent event) {
533 NetworkMessage nm = event.getNetworkMessage();
534 if (nm == null || !_doLogging)
536 // is it a LogRecord?
537 switch (nm.getIntent()) {
539 MessageLogRecord m = (MessageLogRecord) nm.getMessage();
542 this.isLoggable(m.record);
545 // TODO: NetworkMessageReceived(): Test Multicast message received handler to enable menu items for recognised servers
546 String type = nm.getMessage().getMessageType();
547 if (type.equals(MessagePresence.getType())) { // Presence
548 MessagePresence mp = (MessagePresence)nm.getMessage();
549 switch (mp.serviceName) {
551 gMenuServerSocial.setEnabled(true);
554 gMenuServerChat.setEnabled(true);
560 default: // log all unhandled messages
561 LogRecord record = new LogRecord(Level.WARNING,
562 MessageFormat.format("Unhandled NetworkMessage received with intent: \"{0}\" sender: \"{1}\" target: \"{2}\"",
563 nm.getIntent(), nm.getSender(), nm.getTarget() )
565 record.setMillis(System.currentTimeMillis());
571 * @param args the command line arguments
573 public static void main(String args[]) {
574 /* Set the Nimbus look and feel */
575 //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
576 /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
577 * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
580 for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
581 if ("Nimbus".equals(info.getName())) {
582 javax.swing.UIManager.setLookAndFeel(info.getClassName());
586 } catch (ClassNotFoundException|InstantiationException|IllegalAccessException|javax.swing.UnsupportedLookAndFeelException ex) {
587 java.util.logging.Logger.getLogger(ServerManagement.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
594 /* Create and display the form */
595 java.awt.EventQueue.invokeLater(new Runnable() {
599 new ServerManagement().initListeners().setVisible(true);
601 catch(UnknownHostException e) {
602 System.err.println("Error: cannot create log server listener socket");
608 // Variables declaration - do not modify//GEN-BEGIN:variables
609 private javax.swing.ButtonGroup buttonGroup1;
610 private javax.swing.JButton gBtnAbout;
611 private javax.swing.JDialog gDialogAbout;
612 private javax.swing.JFileChooser gFileChooser;
613 private javax.swing.JScrollPane gLogScroller;
614 private javax.swing.JTable gLogTable;
615 private javax.swing.JMenuBar gMenuBar;
616 private javax.swing.JMenu gMenuFile;
617 private javax.swing.JMenuItem gMenuFileSave;
618 private javax.swing.JMenu gMenuHelp;
619 private javax.swing.JMenuItem gMenuHelpAbout;
620 private javax.swing.JMenu gMenuLog;
621 private javax.swing.JCheckBoxMenuItem gMenuLogAutoScroll;
622 private javax.swing.JMenuItem gMenuLogClear;
623 private javax.swing.JCheckBoxMenuItem gMenuLogControl;
624 private javax.swing.JMenu gMenuServerChat;
625 private javax.swing.JMenuItem gMenuServerChatRestart;
626 private javax.swing.JMenuItem gMenuServerChatStop;
627 private javax.swing.JMenu gMenuServerSocial;
628 private javax.swing.JMenuItem gMenuServerSocialRestart;
629 private javax.swing.JMenuItem gMenuServerSocialStop;
630 private javax.swing.JMenu gMenuServers;
631 private javax.swing.JTextArea gTextAreaAbout;
632 // End of variables declaration//GEN-END:variables