Main.java
001 /**
002  *
003  * All content copyright (c) 2003-2008 Terracotta, Inc.,
004  * except as may otherwise be noted in a separate copyright notice.
005  * All rights reserved.
006  *
007  */
008 package demo.chatter;
009 
010 import java.awt.BorderLayout;
011 import java.awt.Color;
012 import java.awt.Container;
013 import java.awt.Dimension;
014 import java.awt.Font;
015 import java.awt.event.ActionEvent;
016 import java.awt.event.ActionListener;
017 import java.lang.management.ManagementFactory;
018 import java.util.ArrayList;
019 import java.util.Collection;
020 import java.util.HashSet;
021 import java.util.List;
022 import java.util.Random;
023 import java.util.Set;
024 import java.util.concurrent.BlockingQueue;
025 import java.util.concurrent.LinkedBlockingQueue;
026 
027 import javax.management.InstanceNotFoundException;
028 import javax.management.MBeanServer;
029 import javax.management.MBeanServerNotification;
030 import javax.management.Notification;
031 import javax.management.NotificationFilter;
032 import javax.management.NotificationListener;
033 import javax.management.ObjectName;
034 import javax.swing.DefaultListModel;
035 import javax.swing.ImageIcon;
036 import javax.swing.JFrame;
037 import javax.swing.JLabel;
038 import javax.swing.JList;
039 import javax.swing.JPanel;
040 import javax.swing.JScrollPane;
041 import javax.swing.JTextField;
042 import javax.swing.JTextPane;
043 import javax.swing.ScrollPaneConstants;
044 import javax.swing.SwingConstants;
045 import javax.swing.SwingUtilities;
046 import javax.swing.text.BadLocationException;
047 import javax.swing.text.Document;
048 import javax.swing.text.Style;
049 import javax.swing.text.StyleConstants;
050 
051 /**
052  *  Description of the Class
053  *
054  *@author    Terracotta, Inc.
055  */
056 public class Main extends JFrame implements ActionListener, ChatListener, NodeListProvider {
057 
058    private final User localUser;
059    private ChatManager chatManager;
060    private MessageQueue messageQueue;
061    private boolean isServerDown = false;
062 
063    private final JTextPane display = new JTextPane();
064    private final JList buddyList = new JList();
065    private final JTextField input = new JTextField();
066 
067    private final Style systemStyle;
068    private final Style localUserStyle;
069    private final Style remoteUserStyle;
070 
071    private final Object lock = new Object();
072    private final MBeanServer mbeanServer;
073    private final ObjectName clusterBean;
074 
075    public Main(String nodeId, MBeanServer mbeanServer, ObjectName clusterBeanthrows Exception {
076       this.mbeanServer = mbeanServer;
077       this.clusterBean = clusterBean;
078       this.localUser = new User(nodeId);
079 
080       this.systemStyle = display.addStyle("systemStyle"null);
081       this.localUserStyle = display.addStyle("localUserStyle"null);
082       this.remoteUserStyle = display.addStyle("remoteUserStyle"null);
083    }
084 
085    public Collection<String> getNodeList() {
086       Collection<String> allNodes = new HashSet<String>();
087 
088       try {
089          String[] allNodeIDs = (String[]) mbeanServer.getAttribute(clusterBean, "NodesInCluster");
090          for (int i = 0; i < allNodeIDs.length; i++) {
091             allNodes.add(allNodeIDs[i]);
092          }
093       }
094       catch (Exception e) {
095          exit(e);
096       }
097 
098       return allNodes;
099    }
100 
101    public void actionPerformed(final ActionEvent e) {
102       final JTextField source = (JTextFielde.getSource();
103       final String message = source.getText();
104       source.setText("");
105 
106       synchronized (lock) {
107          Message msg = new Message(localUser, message, isServerDown);
108 
109          messageQueue.enqueue(msg);
110 
111          if (isServerDown) {
112             displayMessage(message, localUserStyle);
113          }
114       }
115    }
116 
117    public void newMessage(Message message) {
118       User source = message.getUser();
119       boolean local = source == localUser;
120 
121       if (local && message.wasAlreadyDisplayedLocally()) {
122          return;
123       }
124 
125       String displayMessage = (local ? "" : source.getName() ": "+ message.getText();
126       displayMessage(displayMessage, local ? localUserStyle : remoteUserStyle);
127    }
128 
129    public void newUser(String username) {
130       handleNewUser(username);
131    }
132 
133    private void setupUI() {
134       setDefaultLookAndFeelDecorated(true);
135       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
136       final Container content = getContentPane();
137 
138       display.setFont(new Font("Andale Mono", Font.PLAIN, 9));
139       display.setEditable(false);
140       display.setRequestFocusEnabled(false);
141 
142       StyleConstants.setItalic(localUserStyle, true);
143       StyleConstants.setForeground(localUserStyle, Color.LIGHT_GRAY);
144       StyleConstants.setFontSize(localUserStyle, 9);
145 
146       StyleConstants.setItalic(systemStyle, true);
147       StyleConstants.setForeground(systemStyle, Color.RED);
148 
149       input.setFont(new Font("Andale Mono", Font.PLAIN, 9));
150       input.addActionListener(this);
151       final JScrollPane scroll = new JScrollPane(display);
152       final Random r = new Random();
153       final JLabel avatar = new JLabel(localUser.getName() " (node id: " + localUser.getNodeId() ")",
154             new ImageIcon(getClass().getResource("/images/buddy" + r.nextInt(10".gif")),
155             SwingConstants.LEFT);
156       avatar.setForeground(Color.WHITE);
157       avatar.setFont(new Font("Georgia", Font.PLAIN, 16));
158       avatar.setVerticalTextPosition(SwingConstants.CENTER);
159       final JPanel buddypanel = new JPanel();
160       buddypanel.setBackground(Color.DARK_GRAY);
161       buddypanel.setLayout(new BorderLayout());
162       buddypanel.add(avatar, BorderLayout.CENTER);
163 
164       final JPanel buddyListPanel = new JPanel();
165       buddyListPanel.setBackground(Color.WHITE);
166       buddyListPanel.add(buddyList);
167       buddyList.setFont(new Font("Andale Mono", Font.BOLD, 9));
168 
169       content.setLayout(new BorderLayout());
170       content.add(buddypanel, BorderLayout.NORTH);
171       content.add(scroll, BorderLayout.CENTER);
172       content.add(input, BorderLayout.SOUTH);
173       JScrollPane scrollPane = new JScrollPane(buddyListPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
174             ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
175 
176       content.add(scrollPane, BorderLayout.EAST);
177       pack();
178 
179       setTitle("Chatter: " + localUser.getName());
180       setSize(new Dimension(600400));
181    }
182 
183    private void init() throws Exception {
184       setupUI();
185 
186       synchronized (lock) {
187          chatManager = new ChatManager();
188          messageQueue = new MessageQueue(chatManager);
189          messageQueue.start();
190 
191          chatManager.registerUser(localUser);
192          chatManager.setLocalListener(this);
193 
194          registerJMXNotifications();
195          retainNodes();
196          populateCurrentUsers();
197       }
198    }
199 
200    private void retainNodes() {
201       chatManager.retainNodes(this);
202    }
203 
204    private void registerJMXNotifications() throws InstanceNotFoundException {
205       // listener for clustered bean events
206       final NotificationListener clusterEventsListener =
207          new NotificationListener() {
208             public void handleNotification(Notification notification, Object handback) {
209                String nodeId = notification.getMessage();
210                if (notification.getType().endsWith("thisNodeConnected")) {
211                   handleConnectedServer();
212                }
213                else if (notification.getType().endsWith("thisNodeDisconnected")) {
214                   handleDisconnectedServer();
215                }
216                else if (notification.getType().endsWith("nodeDisconnected")) {
217                   handleDisconnectedUser(nodeId);
218                }
219             }
220          };
221 
222       // add listener for membership events
223       mbeanServer.addNotificationListener(clusterBean, clusterEventsListener, null, null);
224    }
225 
226    private void startup() {
227       setVisible(true);
228       input.requestFocus();
229    }
230 
231    private void toggleList(boolean on) {
232       this.buddyList.setVisible(on);
233       this.buddyList.setEnabled(on);
234    }
235 
236    private void handleConnectedServer() {
237       synchronized (lock) {
238          isServerDown = false;
239          systemMessage("The server is back up.");
240 
241          SwingUtilities.invokeLater(
242                   new Runnable() {
243                      public void run() {
244                         retainNodes();
245                         toggleList(true);
246                      }
247                   });
248       }
249 
250    }
251 
252    private void handleDisconnectedServer() {
253       synchronized (lock) {
254          isServerDown = true;
255          systemMessage("The server is down; all of your messages will be queued until the server comes back up again.");
256          SwingUtilities.invokeLater(
257                   new Runnable() {
258                      public void run() {
259                         toggleList(false);
260                      }
261                   });
262       }
263    }
264 
265    private void handleDisconnectedUser(final String nodeId) {
266       synchronized (lock) {
267          chatManager.removeUser(nodeId);
268          populateCurrentUsers();
269       }
270    }
271 
272    private void handleNewUser(final String username) {
273       synchronized (lock) {
274          populateCurrentUsers();
275       }
276    }
277 
278    private void displayMessage(final String message, final Style style) {
279       SwingUtilities.invokeLater(
280                new Runnable() {
281                   public void run() {
282                      Document doc = display.getDocument();
283                      try {
284                         doc.insertString(doc.getLength(), message + "\n", style);
285                      }
286                      catch (BadLocationException ble) {
287                         exit(ble);
288                      }
289                      display.setCaretPosition(doc.getLength());
290                   }
291                });
292    }
293 
294    private void populateCurrentUsers() {
295       final DefaultListModel list = new DefaultListModel();
296       User[] currentUsers = chatManager.getCurrentUsers();
297       for (int i = 0; i < currentUsers.length; i++) {
298          list.addElement(currentUsers[i].getName());
299       }
300 
301       Runnable setList =
302          new Runnable() {
303             public void run() {
304                buddyList.setModel(list);
305                buddyList.invalidate();
306                buddyList.repaint();
307             }
308          };
309 
310       if (SwingUtilities.isEventDispatchThread()) {
311          setList.run();
312       }
313       else {
314          SwingUtilities.invokeLater(setList);
315       }
316    }
317 
318    private void systemMessage(String message) {
319       displayMessage(message, systemStyle);
320    }
321 
322    public static void main(final String[] argsthrows Exception {
323       MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
324       ObjectName clusterBean = getClusterBean(mbeanServer);
325       String nodeId = mbeanServer.getAttribute(clusterBean, "NodeId").toString();
326 
327       final Main main = new Main(nodeId, mbeanServer, clusterBean);
328       main.init();
329 
330       javax.swing.SwingUtilities.invokeLater(
331                new Runnable() {
332                   public void run() {
333                      main.startup();
334                   }
335                });
336    }
337 
338    /**
339     *  Ensures (waiting if needed) that the terracotta cluster bean is
340     *  available
341     *
342     *@param  server         Description of Parameter
343     *@return                The ObjectName of the terracotta cluster bean
344     *@exception  Exception  Description of Exception
345     */
346    private static ObjectName getClusterBean(MBeanServer serverthrows Exception {
347       final ObjectName clusterBean = new ObjectName("org.terracotta:type=Terracotta Cluster,name=Terracotta Cluster Bean");
348       final ObjectName delegateName = ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");
349       final List<Object> clusterBeanBag = new ArrayList<Object>();
350 
351       // listener for newly registered MBeans
352       final NotificationListener clusterBeanListener =
353          new NotificationListener() {
354             public void handleNotification(Notification notification, Object handback) {
355                synchronized (clusterBeanBag) {
356                   clusterBeanBag.add(handback);
357                   clusterBeanBag.notifyAll();
358                }
359             }
360          };
361 
362       // filter to let only clusterBean passed through
363       final NotificationFilter cluserBeanFilter =
364          new NotificationFilter() {
365             public boolean isNotificationEnabled(Notification notification) {
366                return (notification.getType().equals("JMX.mbean.registered"&& ((MBeanServerNotificationnotification)
367                      .getMBeanName().equals(clusterBean));
368             }
369          };
370 
371       // add our listener for clusterBean's registration
372       server.addNotificationListener(delegateName, clusterBeanListener, cluserBeanFilter, clusterBean);
373 
374       // because of race condition, clusterBean might already have registered
375       // before we registered the listener
376       final Set<ObjectName> allObjectNames = server.queryNames(null, null);
377 
378       if (!allObjectNames.contains(clusterBean)) {
379          synchronized (clusterBeanBag) {
380             while (clusterBeanBag.isEmpty()) {
381                clusterBeanBag.wait();
382             }
383          }
384       }
385 
386       // clusterBean is now registered, no need to listen for it
387       server.removeNotificationListener(delegateName, clusterBeanListener);
388 
389       return clusterBean;
390    }
391 
392    private static void exit(Throwable t) {
393       t.printStackTrace();
394       System.exit(1);
395    }
396 
397    /**
398     *  Description of the Class
399     *
400     *@author    Terracotta, Inc.
401     */
402    private static class MessageQueue extends Thread {
403       private final BlockingQueue<Message> msgQueue = new LinkedBlockingQueue<Message>(Integer.MAX_VALUE);
404       private final ChatManager chatManager;
405 
406       MessageQueue(ChatManager chatManager) {
407          this.chatManager = chatManager;
408          setDaemon(true);
409          setName("Offline Message Queue");
410       }
411 
412       public void run() {
413          while (true) {
414             try {
415                Message msg = msgQueue.take();
416                chatManager.send(msg);
417             }
418             catch (InterruptedException e) {
419                exit(e);
420             }
421          }
422       }
423 
424       void enqueue(Message msg) {
425          try {
426             msgQueue.put(msg);
427          }
428          catch (InterruptedException e) {
429             exit(e);
430          }
431       }
432    }
433 
434 }