KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > oddjob > jmx > client > ClientNode


1 package org.oddjob.jmx.client;
2
3 import java.io.IOException JavaDoc;
4 import java.lang.reflect.InvocationHandler JavaDoc;
5 import java.lang.reflect.Method JavaDoc;
6 import java.lang.reflect.Proxy JavaDoc;
7 import java.rmi.RemoteException JavaDoc;
8 import java.util.Arrays JavaDoc;
9 import java.util.HashMap JavaDoc;
10 import java.util.Map JavaDoc;
11 import java.util.Vector JavaDoc;
12
13 import javax.management.JMException JavaDoc;
14 import javax.management.MBeanServerConnection JavaDoc;
15 import javax.management.Notification JavaDoc;
16 import javax.management.NotificationListener JavaDoc;
17 import javax.management.ObjectName JavaDoc;
18
19 import org.apache.log4j.Logger;
20 import org.oddjob.Iconic;
21 import org.oddjob.OddjobException;
22 import org.oddjob.Stateful;
23 import org.oddjob.Structural;
24 import org.oddjob.arooa.Lifecycle;
25 import org.oddjob.arooa.registry.Address;
26 import org.oddjob.arooa.registry.ComponentRegistry;
27 import org.oddjob.arooa.registry.ServerId;
28 import org.oddjob.framework.BasePrimary;
29 import org.oddjob.framework.Destroyable;
30 import org.oddjob.framework.Exportable;
31 import org.oddjob.framework.Transportable;
32 import org.oddjob.images.IconEvent;
33 import org.oddjob.images.IconHelper;
34 import org.oddjob.images.IconListener;
35 import org.oddjob.jmx.SharedConstants;
36 import org.oddjob.jmx.Utils;
37 import org.oddjob.jmx.server.IconicInfo;
38 import org.oddjob.jmx.server.OddjobMBean;
39 import org.oddjob.jmx.server.ServerInfo;
40 import org.oddjob.jmx.server.StatefulInfo;
41 import org.oddjob.jmx.server.StructuralInfo;
42 import org.oddjob.logging.LogEvent;
43 import org.oddjob.state.JobStateEvent;
44 import org.oddjob.state.JobStateListener;
45 import org.oddjob.structural.ChildHelper;
46 import org.oddjob.structural.StructuralEvent;
47 import org.oddjob.structural.StructuralListener;
48
49 /**
50  * The client side representation of a remote node. A proxy is used to implement
51  * a mirror of the remote node. This class is the invocation handler for that
52  * proxy. This class is never accessed directly by client code.
53  * <p>
54  * On creation the client node will lookup up various things on the server
55  * on configure the proxy, register for notifications and start a resync.
56  * <p>
57  * It is possible that a serverside node has bean created and destroyed and
58  * that the client hasn't caught up. In this case dead placeholder nodes are
59  * put in the tree. They should be short lived, and removed when the client
60  * catches up with the notifications.
61  *
62  * @author Rob Gordon
63  */

64
65 public class ClientNode implements InvocationHandler JavaDoc, Exportable {
66     private static final Logger logger = Logger.getLogger(ClientNode.class);
67     
68     /** Set of local method names to method. */
69     private static final Map JavaDoc/*<String, Method>*/ methods = new HashMap JavaDoc();
70
71     /**
72      * create a list of unique method names so that we know what
73      * methods we invoke locally and what we need to pass accross
74      * to the remote MBean.
75      */

76     static {
77         Method JavaDoc[] ms = ClientNode.class.getMethods();
78         for (int i = 0; i < ms.length; ++i) {
79             methods.put(uniqueMethodName(ms[i]), ms[i]);
80         }
81     }
82
83     private final NotificationProcessor notificationProcessor;
84     
85     /** Helper class to handle state */
86     private final ClientStateHelper stateHelper;
87
88     /** Helper class to handle structure change */
89     private final ChildHelper structuralHelper;
90
91     /** Helper class to handle icon change */
92     private final ClientIconHelper iconHelper;
93
94     /** The name of the mbean this node represents. */
95     private final ObjectName JavaDoc objectName;
96
97     private final MBeanServerConnection JavaDoc serverConnection;
98
99     /** Save the proxy object created to shadow the remote node. */
100     private final Object JavaDoc proxy;
101
102     /** The current component registry. Required to transmit
103      * the proxy. */

104     private ComponentRegistry componentRegistry;
105
106     /** The listener that listens for all JMX notifications. */
107     private final ClientListener clientListener;
108
109     /** The server side server info. */
110     private ServerInfo serverInfo;
111     
112     /** Save the remote toString value */
113     private String JavaDoc toString;
114     
115     private final static int RESYNC_PENDING = 0;
116
117     private final static int RESYNC_STARTED = 1;
118
119     private final static int RESYNC_FINISHED = 2;
120
121     private final static int DESTROYED = 3;
122     
123     private volatile int phase = RESYNC_PENDING;
124
125     
126     
127     /**
128      * Constructor.
129      *
130      * @param objectName
131      * The name of the mbean were monitoring.
132      * @param serverConnection
133      * The connection to the remote server.
134      *
135      * @throws Exception
136      * if anything goes wrong.
137      */

138     private ClientNode(ObjectName JavaDoc objectName,
139             MBeanServerConnection JavaDoc serverConnection,
140             NotificationProcessor notificationProcessor)
141     throws JMException JavaDoc, IOException JavaDoc {
142
143         this.objectName = objectName;
144         this.serverConnection = serverConnection;
145         this.notificationProcessor = notificationProcessor;
146         
147         Vector JavaDoc interfaces = new Vector JavaDoc();
148         interfaces.add(Destroyable.class);
149         interfaces.add(Exportable.class);
150         interfaces.add(LogPollable.class);
151         
152         updateServerInfo();
153         // initialise the cached toString value
154
updateToString();
155         
156         Class JavaDoc[] serverSideInterfaces = serverInfo.getInterfaces();
157         interfaces.addAll(Arrays.asList(serverSideInterfaces));
158         
159         Class JavaDoc[] interfaceArray = (Class JavaDoc[]) interfaces.toArray(new Class JavaDoc[0]);
160         if (logger.isDebugEnabled()) {
161             String JavaDoc debug = "";
162             for (int i = 0; i < interfaceArray.length; ++i) {
163                 debug += "[" + interfaceArray[i].getName() + "]";
164             }
165             logger.debug("Creating proxy for [" + objectName + " interfaces " + debug + "]");
166         }
167         this.proxy = Proxy.newProxyInstance(ClientNode.class.getClassLoader(),
168                 interfaceArray, this);
169         
170         if (proxy instanceof Structural) {
171             structuralHelper = new ChildHelper(proxy);
172         } else {
173             structuralHelper = null;
174         }
175         if (proxy instanceof Stateful) {
176             stateHelper = new ClientStateHelper(proxy);
177         } else {
178             stateHelper = null;
179         }
180         if (proxy instanceof Iconic) {
181             iconHelper = new ClientIconHelper(proxy);
182         } else {
183             iconHelper = null;
184         }
185
186         clientListener = new ClientListener();
187         serverConnection.addNotificationListener(objectName,
188                     clientListener, null, null);
189         logger.debug("Registered for notifications. Client Node creation complete.");
190     }
191
192     /**
193      * Static factory method.
194      *
195      * @param objectName
196      * The remote node.
197      * @param serverConnection
198      * The server connection.
199      * @return A proxy oject that implements it's interfaces.
200      *
201      * @throws RemoteException
202      */

203     public static Object JavaDoc createProxyFor(ObjectName JavaDoc objectName,
204             MBeanServerConnection JavaDoc serverConnection,
205             Object JavaDoc linkNode,
206             ComponentRegistry parentRegistry,
207             NotificationProcessor notificationProcessor)
208     throws JMException JavaDoc, IOException JavaDoc {
209         ClientNode client = new ClientNode(objectName, serverConnection, notificationProcessor);
210         client.componentRegistry = new ComponentRegistry(
211                 new ServerId(client.serverInfo.getUrl()));
212         client.componentRegistry.register(client.serverInfo.getId(), client.proxy);
213         parentRegistry.addChild(client.componentRegistry, linkNode);
214         client.resync();
215         return client.proxy;
216     }
217
218     /**
219      *
220      * @param objectName
221      * @param parent
222      * @return
223      * @throws JMException
224      * @throws IOException
225      */

226     static protected Object JavaDoc createProxyFor(ObjectName JavaDoc objectName,
227             ClientNode parent)
228     throws JMException JavaDoc, IOException JavaDoc {
229         ClientNode client = new ClientNode(objectName,
230                 parent.serverConnection, parent.notificationProcessor);
231         parent.updateServerInfo();
232         if (parent.serverInfo.isRegistryOwner()) {
233             ComponentRegistry childRegistry
234                 = parent.componentRegistry.registryOwnedBy(parent.proxy);
235             if (childRegistry == null) {
236                 client.componentRegistry = new ComponentRegistry(
237                         new ServerId(client.serverInfo.getUrl()));
238                 parent.componentRegistry.addChild(client.componentRegistry, parent.proxy);
239                 client.componentRegistry.register(
240                         parent.serverInfo.getChildId(), parent.proxy);
241                 logger.debug("Created new registry for server ["
242                         + client.serverInfo.getUrl() + "]");
243             }
244             else {
245                 client.componentRegistry = childRegistry;
246             }
247         }
248         else {
249             client.componentRegistry = parent.componentRegistry;
250                 // can't think of a situation where adding a new child proxy
251
// would result in the parent no longer being a registry owner.
252
}
253         
254         client.componentRegistry.register(client.serverInfo.getId(), client.proxy);
255         
256         client.resync();
257         return client.proxy;
258     }
259     
260     /*
261      * (non-Javadoc)
262      * @see java.lang.Object#toString()
263      */

264     public String JavaDoc toString() {
265         return toString;
266     }
267
268     /**
269      * Update server info.
270      *
271      */

272     void updateServerInfo()
273     throws IOException JavaDoc, JMException JavaDoc {
274         this.serverInfo = (ServerInfo) serverConnection.invoke(
275                 objectName,
276                 SharedConstants.SERVER_INFO_METHOD,
277                 new Object JavaDoc[0],
278                 new String JavaDoc[0]);
279     }
280     
281     /**
282      * Update toString to get the remote nodes toString.
283      */

284     void updateToString()
285     throws IOException JavaDoc, JMException JavaDoc {
286         toString = (String JavaDoc) serverConnection.invoke(objectName,
287                 SharedConstants.TO_STRING_METHOD,
288                 new Object JavaDoc[0], new String JavaDoc[0]);
289         logger.debug("Retrieved [" + toString + "] as toString for remote node.");
290     }
291
292     /**
293      * Called by the proxy to invoke a method.
294      */

295     public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args)
296     throws Exception JavaDoc {
297
298         String JavaDoc umn = uniqueMethodName(method);
299         Method JavaDoc localMethod = (Method JavaDoc) methods.get(umn);
300         if (localMethod != null) {
301 // logger.debug("Invoking locally [" + umn + "] on [" + toString() + "]");
302
return localMethod.invoke(this, args);
303         }
304         else {
305             logger.debug("Invoking remotely [" + umn + "] on [" + toString() + "]");
306             Object JavaDoc[] exported = Utils.export(args);
307             String JavaDoc[] signature = Utils.classArray2StringArray(method.getParameterTypes());
308             
309             Object JavaDoc result = null;
310             result = serverConnection.invoke(objectName,
311                     method.getName(),
312                     exported,
313                     signature);
314             return Utils.importResolve(result, componentRegistry);
315         }
316     }
317
318     /**
319      * Start a resync between the client and server.
320      *
321      * @throws JMException
322      * @throws IOException
323      */

324     void resync() throws JMException JavaDoc, IOException JavaDoc {
325         if (phase != RESYNC_PENDING) {
326             return;
327         }
328         logger.debug("Starting resync invocation for [" + toString + "]");
329         serverConnection.invoke(objectName,
330             SharedConstants.RESYNC_METHOD, new Object JavaDoc[0],
331             new String JavaDoc[0]);
332         logger.debug("Completed resync invocation for [" + toString + "]");
333         notificationProcessor.check(this);
334     }
335
336     /**
337      * Add a job state listener.
338      *
339      * @param listener
340      * The job state listener.
341      */

342     public void addJobStateListener(JobStateListener listener) {
343         stateHelper.addJobStateListener(listener);
344     }
345
346     /**
347      * Remove a job state listener.
348      *
349      * @param listener
350      * The job state listener.
351      */

352     public void removeJobStateListener(JobStateListener listener) {
353         stateHelper.removeJobStateListener(listener);
354     }
355
356     
357     ///////////////////////////// Iconic Methods ///////////////////////////
358

359     public void addIconListener(IconListener listener) {
360         iconHelper.addIconListener(listener);
361     }
362
363     public void removeIconListener(IconListener listener) {
364         iconHelper.removeIconListener(listener);
365     }
366
367     ///////////////////////////// Structural Methods ///////////////////////////
368

369     /*
370      * Add a structural listener. From the Structural interface.
371      */

372     public void addStructuralListener(StructuralListener listener) {
373         structuralHelper.addStructuralListener(listener);
374     }
375
376     /*
377      * Remove a structural listener. From the Structural interface.
378      */

379     public void removeStructuralListener(StructuralListener listener) {
380         structuralHelper.removeStructuralListener(listener);
381     }
382     
383     /////////////////////////// Log Methods ////////////////////////
384

385     public LogEvent[] retrieveLogEvents(long from, int max) {
386         try {
387             return (LogEvent[]) serverConnection.invoke(objectName,
388                     SharedConstants.RETRIEVE_LOG_EVENTS_METHOD,
389                     new Object JavaDoc[] {
390                         new Long JavaDoc(from),
391                         new Integer JavaDoc(max) },
392                     new String JavaDoc[] {
393                         Long JavaDoc.class.getName(), Integer JavaDoc.class.getName() });
394         } catch (Exception JavaDoc e) {
395             throw new OddjobException(e);
396         }
397     }
398     
399     public LogEvent[] retrieveConsoleEvents(long from, int max) {
400         try {
401             return (LogEvent[]) serverConnection.invoke(objectName,
402                     SharedConstants.RETRIEVE_CONSOLE_EVENTS_METHOD,
403                     new Object JavaDoc[] {
404                         new Long JavaDoc(from),
405                         new Integer JavaDoc(max) },
406                     new String JavaDoc[] {
407                         Long JavaDoc.class.getName(), Integer JavaDoc.class.getName() });
408         } catch (Exception JavaDoc e) {
409             throw new OddjobException(e);
410         }
411     }
412
413     /**
414      * Get the consoleId which has been saved from the remote OddjobMBean. The console
415      * identifies the console on a remote server. The console will frequently be
416      * shared between components in a single JVM and so we don't want to get the same
417      * messages on a component by component bases.
418      *
419      * @return The consoleId.
420      */

421     public String JavaDoc consoleId() {
422         // sanity check - this should only be the case in some tests.
423
if (serverInfo.getConsoleId() == null) {
424             throw new NullPointerException JavaDoc("No ConsoleArchiver availble on server.");
425         }
426         return serverInfo.getConsoleId();
427     }
428     
429     ///////////////////////////////////////////////////
430

431     public String JavaDoc url() {
432         return serverInfo.getConsoleId();
433     }
434     
435     ///////////////////////////////////////////////////
436

437     /**
438      * Used in place of a proxy if the remote node has dissappeared before
439      * all notifications have caught up.
440      */

441     public class DeadNode extends BasePrimary
442     implements Stateful {
443         private static final long serialVersionUID = 20051130;
444         private final ObjectName JavaDoc objectName;
445         public DeadNode(ObjectName JavaDoc objectName) {
446             this.objectName = objectName;
447         }
448         void exception(Exception JavaDoc e) {
449             stateHandler.setJobStateException(e);
450             iconHelper.changeIcon(IconHelper.EXCEPTION);
451         }
452         public String JavaDoc toString() {
453             return "Failed Accessing [" + objectName + "]";
454         }
455     }
456     
457     /**
458      * Member class which listens for notifications coming
459      * across the network.
460      *
461      */

462     public class ClientListener implements NotificationListener JavaDoc {
463         // do notifcations alway come on one thread? should we synchronze just in case they don't?
464
public void handleNotification(Notification JavaDoc notification, Object JavaDoc object) {
465             String JavaDoc type = notification.getType();
466             logger.debug("Handling notification [" + type + "] sequence ["
467                     + notification.getSequenceNumber() + "] for [" + toString
468                     + "]");
469
470             if (phase == DESTROYED) {
471                 logger.debug("Ignoring notification as client destroyed [" + toString + "]");
472                 return;
473             }
474             
475             if (phase == RESYNC_PENDING
476                     && OddjobMBean.RESYNC_STARTED_NOTIF_TYPE.equals(type)) {
477                 phase = RESYNC_STARTED;
478                 logger.debug("Beginning of Resync Notifcation Recieved for [" + toString + "]");
479                 // todo put in a filter to remove not resyncs notifications
480
return;
481             }
482             if (phase == RESYNC_STARTED
483                     && OddjobMBean.RESYNC_FINISHED_NOTIF_TYPE.equals(type)) {
484                 phase = RESYNC_FINISHED;
485                 logger.debug("End of Resync Notifcation Recieved for [" + toString + "]");
486                 // todo put in a filter to remove future rysncs
487
return;
488             }
489
490             // this should be a filter.
491
if (phase == RESYNC_PENDING) {
492                 logger.debug("Waiting to start resync - ignoring notification type [" + type + "]");
493                 return;
494             }
495             
496             if (phase == RESYNC_FINISHED) {
497                 String JavaDoc stem = type.substring(OddjobMBean.RESYNC_PREFIX.length());
498                 if (stem.equals(OddjobMBean.RESYNC_PREFIX)) {
499                     logger.debug("Ignoring resync [" + type + "]");
500                     return;
501                 }
502             }
503             
504             if (phase == RESYNC_STARTED) {
505                 type = type.substring(OddjobMBean.RESYNC_PREFIX.length(),
506                         type.length());
507             }
508
509             if (StatefulInfo.STATE_CHANGE_NOTIF_TYPE.equals(type)) {
510                 final JobStateEvent event = (JobStateEvent) notification
511                     .getUserData();
512                 Runnable JavaDoc r = new Runnable JavaDoc() {
513                     public void run() {
514                         stateHelper.jobStateChange(event);
515                     }
516                 };
517                 notificationProcessor.enqueue(r);
518             } else if (StructuralInfo.CHILD_ADDED_NOTIF_TYPE.equals(type)) {
519                 final StructuralEvent se = (StructuralEvent) notification
520                         .getUserData();
521                 Runnable JavaDoc r = new Runnable JavaDoc() {
522                     public void run() {
523                         int index = se.getIndex();
524                         ObjectName JavaDoc childName = (ObjectName JavaDoc) se.getChild();
525                         Object JavaDoc childProxy = null;
526                         try {
527                             childProxy = ClientNode.createProxyFor(childName,
528                                     ClientNode.this);
529                         } catch (Exception JavaDoc e) {
530                             DeadNode deadNode = new DeadNode(childName);
531                             deadNode.exception(e);
532                             childProxy = deadNode;
533                         }
534                         structuralHelper.insertChild(index, childProxy);
535                     }
536                 };
537                 notificationProcessor.enqueue(r);
538             } else if (StructuralInfo.CHILD_REMOVED_NOTIF_TYPE.equals(type)) {
539                 final StructuralEvent se = (StructuralEvent) notification
540                         .getUserData();
541                 Runnable JavaDoc r = new Runnable JavaDoc() {
542                     public void run() {
543                         int index = se.getIndex();
544                         try {
545                             // keep getting index out of bounds exceptions.
546
// I think this is because parent child removed notifications
547
// arrive before our own.
548
Object JavaDoc child = structuralHelper.removeChildAt(index);
549                             Lifecycle.destroy(child);
550                         } catch (Exception JavaDoc e) {
551                             logger.error("Exception removing child [" + index
552                                     + "] from [" + toString + "]", e);
553                         }
554                     }
555                 };
556                 notificationProcessor.enqueue(r);
557             } else if (IconicInfo.ICON_CHANGED_NOTIF_TYPE.equals(type)) {
558                 final IconEvent ie = (IconEvent) notification.getUserData();
559                 Runnable JavaDoc r = new Runnable JavaDoc() {
560                     public void run() {
561                         try {
562                             iconHelper.iconEvent(ie);
563                         } catch (Exception JavaDoc e) {
564                             // this will happen when the remote node disappears
565
logger.debug(e);
566                         }
567                     }
568                 };
569                 notificationProcessor.enqueue(r);
570             }
571         }
572     }
573
574     /*
575      * (non-Javadoc)
576      *
577      * @see java.lang.Object#equals(java.lang.Object)
578      *
579      * True when the proxy objects are the same.
580      */

581     public boolean equals(Object JavaDoc other) {
582         return (other == proxy);
583     }
584
585     /**
586      * Destroy this node. Clean up resources, remove remote connections.
587      */

588     public void destroy() {
589         phase = DESTROYED;
590         componentRegistry.remove(proxy);
591         // beware the order here.
592
// notifications removed first
593
try {
594             // will fail if destroy is due to the remote node being removed.
595
if (clientListener != null) {
596                 serverConnection.removeNotificationListener(objectName,
597                         clientListener);
598             }
599         } catch (JMException JavaDoc e) {
600             logger.debug(e);
601         } catch (IOException JavaDoc e) {
602             logger.debug(e);
603         }
604         // then children destroyed. Othewise conflicting notifications
605
// trip each other up.
606
if (structuralHelper != null) {
607             structuralHelper.destroyAll();
608         }
609         logger.debug("Destroyed client for [" + toString + "]");
610     }
611
612     /**
613      * Part of the implementation of the HostRelative interface. This is called when
614      * the proxy is just about to be sent over the network.
615      *
616      * @return The object for transit.
617      */

618     public Transportable exportTransportable() {
619         Address address = componentRegistry.addressForObject(proxy);
620         logger.debug("[" + proxy + "] exported with address [" + address + "]");
621         ComponentTransportable transportable = new ComponentTransportable(address);
622         return transportable;
623     }
624     
625     
626     /**
627      * Utility function to build a unique name that can reside in a
628      * map to identify local and remote methods.
629      *
630      * @param m The method.
631      * @return A unique string for the method name.
632      */

633     static String JavaDoc uniqueMethodName(Method JavaDoc m) {
634         String JavaDoc methodName = m.getName();
635         Class JavaDoc[] paramTypes = m.getParameterTypes();
636         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
637         buf.append(methodName);
638         for (int j = 0; j < paramTypes.length; ++j) {
639             buf.append('#');
640             buf.append(paramTypes[j].getName());
641         }
642         return buf.toString();
643     }
644 }
645
Popular Tags