KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > oddjob > jmx > JMXClientJob


1 package org.oddjob.jmx;
2
3 import javax.management.MBeanServerConnection JavaDoc;
4 import javax.management.Notification JavaDoc;
5 import javax.management.NotificationListener JavaDoc;
6 import javax.management.ObjectName JavaDoc;
7 import javax.management.relation.MBeanServerNotificationFilter JavaDoc;
8 import javax.management.remote.JMXConnector JavaDoc;
9 import javax.management.remote.JMXConnectorFactory JavaDoc;
10 import javax.management.remote.JMXServiceURL JavaDoc;
11
12 import org.apache.log4j.Logger;
13 import org.apache.log4j.MDC;
14 import org.oddjob.Resetable;
15 import org.oddjob.Stateful;
16 import org.oddjob.Stoppable;
17 import org.oddjob.Structural;
18 import org.oddjob.arooa.ArooaConstants;
19 import org.oddjob.arooa.ArooaContext;
20 import org.oddjob.arooa.ArooaRuntime;
21 import org.oddjob.arooa.registry.ComponentRegistry;
22 import org.oddjob.arooa.registry.Path;
23 import org.oddjob.framework.BaseComponent;
24 import org.oddjob.framework.Destroyable;
25 import org.oddjob.images.IconHelper;
26 import org.oddjob.jmx.client.ClientNode;
27 import org.oddjob.jmx.client.NotificationProcessor;
28 import org.oddjob.jmx.client.RemoteLogPoller;
29 import org.oddjob.jmx.server.OddjobMBeanFactory;
30 import org.oddjob.logging.ConsoleArchiver;
31 import org.oddjob.logging.Log4jArchiver;
32 import org.oddjob.logging.LogArchiver;
33 import org.oddjob.logging.LogLevel;
34 import org.oddjob.logging.LogListener;
35 import org.oddjob.state.JobState;
36 import org.oddjob.structural.ChildHelper;
37 import org.oddjob.structural.StructuralListener;
38
39 /**
40  * @oddjob.description Connect to an Oddjob {@link org.oddjob.jmx.JMXServerJob}.
41  * This job allows remote jobs to be monitored and controlled from
42  * a local Oddjob.
43  * <p>
44  * This job will run until it is manually stopped or until the remote server is
45  * stopped. If this job is stopped it's state will be COMPLETE, if the server stops
46  * this job's state will be NOT COMPLETE.
47  * <p>
48  * This job is Chainable and can link to another job in a
49  * {@link org.oddjob.structural.ChainJob} but the jobs it chains to are run before
50  * log remote log polling and so should be quick.
51  * <p>
52  * To access and control jobs on a server from within a configuration file this
53  * client job must have an id. If the client has an id of 'freds-pc' and the job
54  * on the server has an id of 'freds-job' the.
55  * be accessed using the path freds-pc/freds-job.
56  * <p>
57  *
58  * @oddjob.example
59  *
60  * To create a connection to a remote server.
61  * <pre>
62  * &lt;client id=name="Connection to Freds PC"
63  * url="service:jmx:rmi:///jndi/rmi://pcfred/public-jobs"/&gt;
64  * </pre>
65  *
66  * @oddjob.example
67  *
68  * Connect, run a remote job, and disconnect.
69  * <pre>
70  * &lt;chain&gt;
71  * &lt;client id="freds-pc" name="Connection to Fred's PC"
72  * url="service:jmx:rmi:///jndi/rmi://pcfred/public-jobs" /&gt;
73  * &lt;sequential&gt;
74  * &lt;run job="${freds-pc/freds-job}" name="Run Fred's Job" /&gt;
75  * &lt;wait for=${freds-pc/freds-job}" &gt;
76  * &lt;stop job="${freds-pc} name="Disconnect" /gt;
77  * &lt;sequential&gt;
78  * &lt;/chain&gt;
79  * </pre>
80  *
81  * Note that the wait job is needed because it can take a few seconds after
82  * the client connects for the information about all the jobs to arrive
83  * from the server.
84  *
85  *
86  * @author Rob Gordon
87  */

88
89 public class JMXClientJob extends BaseComponent
90 implements Runnable JavaDoc, Stateful, Resetable,
91         Stoppable, Structural, NotificationListener JavaDoc,
92         LogArchiver, ConsoleArchiver {
93         
94     public static final long DEFAULT_LOG_POLLING_INTERVAL = 5000;
95     
96     /**
97      * @oddjob.property
98      * @oddjob.description A name, can be any text.
99      * @oddjob.required No.
100      */

101     private String JavaDoc name;
102     
103     /** Destroyed flag for when the hierarchy had been destroyed by the peer */
104     private volatile boolean childDestroyed;
105     
106     /**
107      * @oddjob.property
108      * @oddjob.description The JMX service URL.
109      * @oddjob.required Yes.
110      */

111     private String JavaDoc url;
112     
113     /** The log poller thread */
114     private RemoteLogPoller logPoller;
115     
116     /** The notification processor thread */
117     private NotificationProcessor notificationProcessor;
118     
119     /** Child helper */
120     private ChildHelper childHelper = new ChildHelper(this);
121     
122     /** Registry */
123     private ComponentRegistry parentRegistry;
124         
125     /** The connector */
126     private JMXConnector JavaDoc cntor;
127     
128     
129     /**
130      * @oddjob.property
131      * @oddjob.description The maximum number of log lines to retrieve for any
132      * component.
133      * @oddjob.required No.
134      */

135     private int maxLoggerLines;
136     
137     /**
138      * @oddjob.property
139      * @oddjob.description The maximum number of console lines to retrieve for any
140      * component.
141      * @oddjob.required No.
142      */

143     private int maxConsoleLines;
144     
145     /**
146      * @oddjob.property
147      * @oddjob.description The number of milliseconds between polling for new
148      * log events.
149      * @oddjob.required No.
150      */

151     private long logPollingInterval;
152     
153     private static int instance;
154     private Logger theLogger;
155     
156     /**
157      * Get the name.
158      *
159      * @return The name.
160      */

161     public String JavaDoc getName() {
162         return name;
163     }
164     
165     /**
166      * Set the name
167      *
168      * @param name The name.
169      */

170     public void setName(String JavaDoc name) {
171         this.name = name;
172     }
173     
174     protected Logger logger() {
175         if (theLogger == null) {
176             synchronized (JMXClientJob.class) {
177                 theLogger = Logger.getLogger(JMXClientJob.class.getName()
178                         + "." + String.valueOf(instance++));
179             }
180         }
181         return theLogger;
182     }
183     
184     public String JavaDoc getLogger() {
185         return logger().getName();
186     }
187     
188     /**
189      * Set naming service url.
190      *
191      * @param url The name of the remote node in the naming service.
192      */

193     public void setUrl(String JavaDoc lookup) {
194         this.url = lookup;
195     }
196     
197     /**
198      * Get the JMX service URL.
199      *
200      * @return The name of the remote node in the naming service.
201      */

202     public String JavaDoc getUrl() {
203         return url;
204     }
205
206     /* (non-Javadoc)
207      * @see org.oddjob.logging.LogArchiver#addLogListener(org.oddjob.logging.LogListener, java.lang.String, org.oddjob.logging.LogLevel, long, long)
208      */

209     public void addLogListener(LogListener l, Object JavaDoc component, LogLevel level,
210             long last, int history) {
211         if (logPoller == null) {
212             throw new NullPointerException JavaDoc("logPoller not available");
213         }
214         logPoller.addLogListener(l, component, level, last, history);
215         // force main thread to poll.
216
synchronized (this) {
217             notifyAll();
218         }
219     }
220
221     /* (non-Javadoc)
222      * @see org.oddjob.logging.LogArchiver#removeLogListener(org.oddjob.logging.LogListener)
223      */

224     public void removeLogListener(LogListener l, Object JavaDoc component) {
225         if (logPoller == null) {
226             // must have been shut down.
227
return;
228         }
229         logPoller.removeLogListener(l, component);
230     }
231     
232     /* (non-Javadoc)
233      * @see org.oddjob.logging.ConsoleArchiver#addConsoleListener(org.oddjob.logging.LogListener, java.lang.Object, long, int)
234      */

235     public void addConsoleListener(LogListener l, Object JavaDoc component, long last,
236             int max) {
237         if (logPoller == null) {
238             throw new NullPointerException JavaDoc("logPoller not available");
239         }
240         logPoller.addConsoleListener(l, component, last, max);
241         // force main thread to poll.
242
synchronized (this) {
243             notifyAll();
244         }
245     }
246     
247     /* (non-Javadoc)
248      * @see org.oddjob.logging.ConsoleArchiver#removeConsoleListener(org.oddjob.logging.LogListener, java.lang.Object)
249      */

250     public void removeConsoleListener(LogListener l, Object JavaDoc component) {
251         if (logPoller == null) {
252             // must have been shut down.
253
return;
254         }
255         logPoller.removeConsoleListener(l, component);
256     }
257     
258     /* (non-Javadoc)
259      * @see org.oddjob.logging.ConsoleArchiver#consoleIdFor(java.lang.Object)
260      */

261     public String JavaDoc consoleIdFor(Object JavaDoc component) {
262         return logPoller.consoleIdFor(component);
263     }
264
265     public void init() {
266         if (maxConsoleLines == 0) {
267             maxConsoleLines = LogArchiver.MAX_HISTORY;
268         }
269         if (maxLoggerLines == 0) {
270             maxLoggerLines = LogArchiver.MAX_HISTORY;
271         }
272         if (logPollingInterval == 0) {
273             logPollingInterval = DEFAULT_LOG_POLLING_INTERVAL;
274         }
275
276     }
277     
278     /*
279      * (non-Javadoc)
280      * @see org.oddjob.framework.BaseComponent#setContext(org.oddjob.arooa.ArooaXMLContext)
281      */

282     public boolean setContext(ArooaContext context) {
283         parentRegistry = (ComponentRegistry) context.get(
284                 ArooaConstants.COMPONENT_REGISTRY);
285         arooaRuntime((ArooaRuntime)
286                 context.get(ArooaConstants.CURRENTLY_CONFIGURING));
287         return true;
288     }
289     
290     public void run() {
291         lock.accquire("Job executing.");
292         String JavaDoc oldMDC = (String JavaDoc)MDC.get(Log4jArchiver.MDC);
293         try {
294             MDC.put(Log4jArchiver.MDC, logger().getName());
295             if (!stateHandler.requestJobStateExecuting()) {
296                 logger().debug("Can't execute job [" + this + "] because state is ["
297                         + stateHandler.getJobState() + "]");
298                 return;
299             }
300             iconHelper.changeIcon(IconHelper.EXECUTING);
301     
302             // runtime configuration.
303
if (!configure()) {
304                 return;
305             }
306             logger().info("Starting [" + this + "]");
307         
308             onStart();
309         }
310         catch (Throwable JavaDoc e) {
311             logger().warn("Exception starting [" + this + "]", e);
312             setJobStateException(e);
313             return;
314         }
315         finally {
316             if (oldMDC != null) {
317                 MDC.put(Log4jArchiver.MDC, oldMDC);
318             }
319             else {
320                 MDC.remove(Log4jArchiver.MDC);
321             }
322             lock.release();
323         }
324     }
325     
326     /**
327      *
328      * @throws Exception
329      */

330     private void onStart() throws Exception JavaDoc {
331         if (url == null) {
332             throw new IllegalStateException JavaDoc("url must be provided.");
333         }
334         childDestroyed = false;
335         
336         JMXServiceURL JavaDoc address = new JMXServiceURL JavaDoc(url);
337
338         logger().debug("Connecting to [" + url + "] ...");
339         cntor = JMXConnectorFactory.connect(address);
340
341         MBeanServerConnection JavaDoc mbsc = cntor.getMBeanServerConnection();
342         MBeanServerNotificationFilter JavaDoc serverFilter = new MBeanServerNotificationFilter JavaDoc();
343         serverFilter.disableAllObjectNames();
344         serverFilter.enableObjectName(OddjobMBeanFactory.rootName());
345         mbsc.addNotificationListener(new ObjectName JavaDoc("JMImplementation:type=MBeanServerDelegate"),
346                 this, serverFilter, null);
347
348         // when used from code so that lookup still works on server objects.
349
if (parentRegistry == null) {
350             parentRegistry = new ComponentRegistry();
351         }
352         
353         notificationProcessor = new NotificationProcessor();
354         notificationProcessor.start();
355         
356         // create the client node
357
Object JavaDoc client = ClientNode.createProxyFor(
358                 OddjobMBeanFactory.rootName(),
359                 mbsc,
360                 this,
361                 parentRegistry,
362                 notificationProcessor);
363
364         this.logPoller = new RemoteLogPoller(client,
365                 maxConsoleLines, maxLoggerLines);
366         childHelper.addChild(client);
367         logPoller.start();
368     }
369
370     public void stop() {
371         if (destroyed) {
372             throw new IllegalStateException JavaDoc("[" + this + "] destroyed");
373         }
374         if (stateHandler.getJobState() != JobState.EXECUTING) {
375             return;
376         }
377         lock.accquire("Stopping.");
378         try {
379             logger().debug("Thread [" + Thread.currentThread().getName()
380                     + "] requested [" + this + "] stop.");
381             iconHelper.changeIcon(IconHelper.STOPPING);
382             onStop();
383         }
384         catch (Throwable JavaDoc t) {
385             logger().warn("Exception stopping [" + this + "]", t);
386             setJobStateException(t);
387         }
388         finally {
389             lock.release();
390         }
391     }
392     
393     private void onStop() throws Exception JavaDoc {
394         parentRegistry.removeChild(this);
395         
396         logPoller.interrupt();
397         logPoller.destroy();
398         
399         // if not destroyed by remote peer
400
if (!childDestroyed) {
401             ((Destroyable) childHelper.getChild()).destroy();
402             cntor.close();
403         }
404         childHelper.removeAllChildren();
405         
406         notificationProcessor.interrupt();
407         
408         logPoller.join();
409         notificationProcessor.join();
410         
411         childHelper.removeAllChildren();
412         
413         cntor = null;
414         logPoller = null;
415         notificationProcessor = null;
416         
417         if (childDestroyed) {
418             setJobStateNotComplete();
419         }
420         else {
421             setJobStateComplete();
422         }
423     }
424
425     
426     /* (non-Javadoc)
427      * @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
428      */

429     public void handleNotification(Notification JavaDoc notification, Object JavaDoc handback) {
430         if ("JMX.mbean.unregistered".equals(notification.getType())) {
431             logger().debug("Child unregestered in server.");
432             childDestroyed = true;
433             stop();
434         }
435     }
436
437     /**
438      * Lookup a component in the client registry. If in an oddjob file a
439      * remote node as an id of 'fred' and this has an id of 'client' other
440      * jobs would access fred using '${client.fred}', then the equivelant
441      * code lookup can be achieved with
442      * <p>
443      * <code>client.lookup("fred");</code>
444      * <p>
445      * Where client is an instance of this class.
446      *
447      * @param path The path
448      * @return The object or null if none is found.
449      * @throws IllegalStateException If the client is not running.
450      */

451     public Object JavaDoc lookup(String JavaDoc propertyName) throws IllegalStateException JavaDoc {
452         if (parentRegistry == null) {
453             throw new IllegalStateException JavaDoc("Client must be run before a lookup is possible");
454         }
455         ComponentRegistry componentRegistry = parentRegistry.registryOwnedBy(this);
456         if (componentRegistry == null) {
457             throw new IllegalStateException JavaDoc("Client not running.");
458         }
459         Object JavaDoc found = componentRegistry.objectForPath(new Path(propertyName));
460         logger().debug("lookup [" + propertyName + "] found [" + found + "]");
461         return found;
462     }
463     
464     /* (non-Javadoc)
465      * @see org.oddjob.Structural#addStructuralListener(org.oddjob.structural.StructuralListener)
466      */

467     public void addStructuralListener(StructuralListener listener) {
468         childHelper.addStructuralListener(listener);
469     }
470     
471     /* (non-Javadoc)
472      * @see org.oddjob.Structural#removeStructuralListener(org.oddjob.structural.StructuralListener)
473      */

474     public void removeStructuralListener(StructuralListener listener) {
475         childHelper.removeStructuralListener(listener);
476     }
477     
478     /**
479      * Perform a soft reset on the job.
480      */

481     public void softReset() {
482         lock.accquire("Soft reset in progress.");
483         try {
484             logger().debug("Thread [" + Thread.currentThread().getName()
485                     + "] soft reset for [" + this + "].");
486             if (canSoftReset()) {
487                 setJobStateReady();
488                 logger().info("Reset job [" + this + "]");
489             }
490         }
491         finally {
492             lock.release();
493         }
494     }
495     
496     /**
497      * Perform a hard reset on the job.
498      */

499     public void hardReset() {
500         lock.accquire("Hard reset in progress.");
501         try {
502             logger().debug("Thread [" + Thread.currentThread().getName()
503                     + "] hard reset for [" + this + "].");
504             if (canHardReset()) {
505                 setJobStateReady();
506                 logger().info("Reset job [" + this + "]");
507             }
508         }
509         finally {
510             lock.release();
511         }
512     }
513
514     public int getMaxConsoleLines() {
515         return maxConsoleLines;
516     }
517     public void setMaxConsoleLines(int maxConsoleLines) {
518         this.maxConsoleLines = maxConsoleLines;
519     }
520     public int getMaxLoggerLines() {
521         return maxLoggerLines;
522     }
523     public void setMaxLoggerLines(int maxLoggerLines) {
524         this.maxLoggerLines = maxLoggerLines;
525     }
526     public long getLogPollingInterval() {
527         return logPollingInterval;
528     }
529     public void setLogPollingInterval(long logPollingInterval) {
530         this.logPollingInterval = logPollingInterval;
531     }
532     
533     public String JavaDoc toString() {
534         if (name == null) {
535             return "Oddjob Client";
536         }
537         else {
538             return name;
539         }
540     }
541 }
542
Popular Tags