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 clusterBean) throws 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 = (JTextField) e.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(600, 400));
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[] args) throws 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 server) throws 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") && ((MBeanServerNotification) notification)
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 }
|