1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.system;
23
24 import javax.management.AttributeChangeNotification;
25 import javax.management.JMException;
26 import javax.management.MBeanInfo;
27 import javax.management.MBeanOperationInfo;
28 import javax.management.MBeanRegistration;
29 import javax.management.MBeanServer;
30 import javax.management.MalformedObjectNameException;
31 import javax.management.ObjectName;
32
33 import org.jboss.beans.metadata.api.annotations.Create;
34 import org.jboss.beans.metadata.api.annotations.Destroy;
35 import org.jboss.beans.metadata.api.annotations.Start;
36 import org.jboss.beans.metadata.api.annotations.Stop;
37 import org.jboss.dependency.spi.Controller;
38 import org.jboss.dependency.spi.ControllerState;
39 import org.jboss.deployment.DeploymentInfo;
40 import org.jboss.deployment.SARDeployerMBean;
41 import org.jboss.kernel.spi.dependency.KernelControllerContext;
42 import org.jboss.kernel.spi.dependency.KernelControllerContextAware;
43 import org.jboss.logging.Logger;
44 import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
45
46 /**
47 * An abstract base class JBoss services can subclass to implement a
48 * service that conforms to the ServiceMBean interface. Subclasses must
49 * override {@link #getName} method and should override
50 * {@link #startService}, and {@link #stopService} as approriate.
51 *
52 * @see ServiceMBean
53 *
54 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
55 * @author Scott.Stark@jboss.org
56 * @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>
57 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
58 * @version $Revision: 74097 $
59 */
60 public class ServiceMBeanSupport
61 extends JBossNotificationBroadcasterSupport
62 implements ServiceMBean, MBeanRegistration, KernelControllerContextAware
63 {
64 /** The signature for service controller operations */
65 public static final String[] SERVICE_CONTROLLER_SIG = new String[] { ObjectName.class.getName() };
66
67 /**
68 * The instance logger for the service. Not using a class logger
69 * because we want to dynamically obtain the logger name from
70 * concreate sub-classes.
71 */
72 protected Logger log;
73
74 /** The MBeanServer which we have been register with. */
75 protected MBeanServer server;
76
77 /** The object name which we are registsred under. */
78 protected ObjectName serviceName;
79
80 /** The current state this service is in. */
81 private int state = UNREGISTERED;
82
83 /** For backwards compatibility */
84 private boolean isJBossInternalLifecycleExposed = false;
85
86 /** The controller context */
87 private KernelControllerContext controllerContext;
88
89 /**
90 * Construct a <t>ServiceMBeanSupport</tt>.
91 *
92 * <p>Sets up logging.
93 */
94 public ServiceMBeanSupport()
95 {
96 // can not call this(Class) because we need to call getClass()
97 this.log = Logger.getLogger(getClass().getName());
98 log.trace("Constructing");
99 }
100
101 /**
102 * Construct a <t>ServiceMBeanSupport</tt>.
103 *
104 * <p>Sets up logging.
105 *
106 * @param type The class type to determine category name from.
107 */
108 @SuppressWarnings("unchecked")
109 public ServiceMBeanSupport(final Class type)
110 {
111 this(type.getName());
112 }
113
114 /**
115 * Construct a <t>ServiceMBeanSupport</tt>.
116 *
117 * <p>Sets up logging.
118 *
119 * @param category The logger category name.
120 */
121 public ServiceMBeanSupport(final String category)
122 {
123 this(Logger.getLogger(category));
124 }
125
126 /**
127 * Construct a <t>ServiceMBeanSupport</tt>.
128 *
129 * @param log The logger to use.
130 */
131 public ServiceMBeanSupport(final Logger log)
132 {
133 this.log = log;
134 log.trace("Constructing");
135 }
136
137 public void setKernelControllerContext(KernelControllerContext controllerContext) throws Exception
138 {
139 this.controllerContext = controllerContext;
140 }
141
142 public void unsetKernelControllerContext(KernelControllerContext controllerContext) throws Exception
143 {
144 this.controllerContext = null;
145 }
146
147 /**
148 * Use the short class name as the default for the service name.
149 *
150 * @return a description of the mbean
151 */
152 public String getName()
153 {
154 // TODO: Check if this gets called often, if so cache this or remove if not used
155 // use the logger so we can better be used as a delegate instead of sub-class
156 return org.jboss.util.Classes.stripPackageName(log.getName());
157 }
158
159 public ObjectName getServiceName()
160 {
161 return serviceName;
162 }
163
164 /**
165 * Provide access to the service DeploymentInfo. This is only available
166 * after the service has passed its create step.
167 *
168 * @return The service DeploymentInfo if found registered under the SARDeployer.
169 * @throws JMException - thrown on failure to invoke
170 * SARDeployer.getService(ObjectName)
171 */
172 public DeploymentInfo getDeploymentInfo()
173 throws JMException
174 {
175 Object[] args = {serviceName};
176 String[] sig = {serviceName.getClass().getName()};
177 DeploymentInfo sdi = (DeploymentInfo) server.invoke(SARDeployerMBean.OBJECT_NAME,
178 "getService", args, sig);
179 return sdi;
180 }
181
182 public MBeanServer getServer()
183 {
184 return server;
185 }
186
187 public int getState()
188 {
189 return state;
190 }
191
192 public String getStateString()
193 {
194 return states[state];
195 }
196
197 public Logger getLog()
198 {
199 return log;
200 }
201
202
203 ///////////////////////////////////////////////////////////////////////////
204 // State Mutators //
205 ///////////////////////////////////////////////////////////////////////////
206
207 @Create
208 public void pojoCreate() throws Exception
209 {
210 jbossInternalCreate();
211 }
212
213 @Start
214 public void pojoStart() throws Exception
215 {
216 jbossInternalStart();
217 }
218
219 @Stop
220 public void pojoStop() throws Exception
221 {
222 jbossInternalStop();
223 }
224
225 @Destroy
226 public void pojoDestroy() throws Exception
227 {
228 jbossInternalDestroy();
229 }
230
231 protected void pojoChange(ControllerState state)
232 {
233 Controller controller = controllerContext.getController();
234 try
235 {
236 controller.change(controllerContext, state);
237 }
238 catch (RuntimeException e)
239 {
240 throw e;
241 }
242 catch (Error e)
243 {
244 throw e;
245 }
246 catch (Throwable t)
247 {
248 throw new RuntimeException("Error changing state of " + controllerContext.getName() + " to " + state.getStateString(), t);
249 }
250 }
251
252 public void create() throws Exception
253 {
254 if (controllerContext != null)
255 pojoChange(ControllerState.CREATE);
256 else if (serviceName != null && isJBossInternalLifecycleExposed)
257 server.invoke(ServiceController.OBJECT_NAME, "create", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
258 else
259 jbossInternalCreate();
260 }
261
262 public void start() throws Exception
263 {
264 if (controllerContext != null)
265 pojoChange(ControllerState.START);
266 else if (serviceName != null && isJBossInternalLifecycleExposed)
267 server.invoke(ServiceController.OBJECT_NAME, "start", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
268 else
269 jbossInternalStart();
270 }
271
272 public void stop()
273 {
274 try
275 {
276 if (controllerContext != null)
277 pojoChange(ControllerState.CREATE);
278 else if (serviceName != null && isJBossInternalLifecycleExposed)
279 server.invoke(ServiceController.OBJECT_NAME, "stop", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
280 else
281 jbossInternalStop();
282 }
283 catch (Throwable t)
284 {
285 log.warn("Error in stop " + jbossInternalDescription(), t);
286 }
287 }
288
289 public void destroy()
290 {
291 try
292 {
293 if (controllerContext != null)
294 pojoChange(ControllerState.CONFIGURED);
295 else if (serviceName != null && isJBossInternalLifecycleExposed)
296 server.invoke(ServiceController.OBJECT_NAME, "destroy", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
297 else
298 jbossInternalDestroy();
299 }
300 catch (Throwable t)
301 {
302 log.warn("Error in destroy " + jbossInternalDescription(), t);
303 }
304 }
305
306 protected String jbossInternalDescription()
307 {
308 if (serviceName != null)
309 return serviceName.toString();
310 else
311 return getName();
312 }
313
314 public void jbossInternalLifecycle(String method) throws Exception
315 {
316 if (method == null)
317 throw new IllegalArgumentException("Null method name");
318
319 if (method.equals("create"))
320 jbossInternalCreate();
321 else if (method.equals("start"))
322 jbossInternalStart();
323 else if (method.equals("stop"))
324 jbossInternalStop();
325 else if (method.equals("destroy"))
326 jbossInternalDestroy();
327 else
328 throw new IllegalArgumentException("Unknown lifecyle method " + method);
329 }
330
331 protected void jbossInternalCreate() throws Exception
332 {
333 if (state == CREATED || state == STARTING || state == STARTED
334 || state == STOPPING || state == STOPPED)
335 {
336 log.debug("Ignoring create call; current state is " + getStateString());
337 return;
338 }
339
340 log.debug("Creating " + jbossInternalDescription());
341
342 try
343 {
344 createService();
345 state = CREATED;
346 }
347 catch (Exception e)
348 {
349 log.debug("Initialization failed " + jbossInternalDescription(), e);
350 throw e;
351 }
352
353 log.debug("Created " + jbossInternalDescription());
354 }
355
356 protected void jbossInternalStart() throws Exception
357 {
358 if (state == STARTING || state == STARTED || state == STOPPING)
359 {
360 log.debug("Ignoring start call; current state is " + getStateString());
361 return;
362 }
363
364 if (state != CREATED && state != STOPPED && state != FAILED)
365 {
366 log.debug("Start requested before create, calling create now");
367 create();
368 }
369
370 state = STARTING;
371 sendStateChangeNotification(STOPPED, STARTING, getName() + " starting", null);
372 log.debug("Starting " + jbossInternalDescription());
373
374 try
375 {
376 startService();
377 }
378 catch (Exception e)
379 {
380 state = FAILED;
381 sendStateChangeNotification(STARTING, FAILED, getName() + " failed", e);
382 log.debug("Starting failed " + jbossInternalDescription(), e);
383 throw e;
384 }
385
386 state = STARTED;
387 sendStateChangeNotification(STARTING, STARTED, getName() + " started", null);
388 log.debug("Started " + jbossInternalDescription());
389 }
390
391 protected void jbossInternalStop()
392 {
393 if (state != STARTED)
394 {
395 log.debug("Ignoring stop call; current state is " + getStateString());
396 return;
397 }
398
399 state = STOPPING;
400 sendStateChangeNotification(STARTED, STOPPING, getName() + " stopping", null);
401 log.debug("Stopping " + jbossInternalDescription());
402
403 try
404 {
405 stopService();
406 }
407 catch (Throwable e)
408 {
409 state = FAILED;
410 sendStateChangeNotification(STOPPING, FAILED, getName() + " failed", e);
411 log.warn("Stopping failed " + jbossInternalDescription(), e);
412 return;
413 }
414
415 state = STOPPED;
416 sendStateChangeNotification(STOPPING, STOPPED, getName() + " stopped", null);
417 log.debug("Stopped " + jbossInternalDescription());
418 }
419
420 protected void jbossInternalDestroy()
421 {
422 if (state == DESTROYED)
423 {
424 log.debug("Ignoring destroy call; current state is " + getStateString());
425 return;
426 }
427
428 if (state == STARTED)
429 {
430 log.debug("Destroy requested before stop, calling stop now");
431 stop();
432 }
433
434 log.debug("Destroying " + jbossInternalDescription());
435
436 try
437 {
438 destroyService();
439 }
440 catch (Throwable t)
441 {
442 log.warn("Destroying failed " + jbossInternalDescription(), t);
443 }
444 state = DESTROYED;
445 log.debug("Destroyed " + jbossInternalDescription());
446 }
447
448
449 ///////////////////////////////////////////////////////////////////////////
450 // JMX Hooks //
451 ///////////////////////////////////////////////////////////////////////////
452
453 /**
454 * Callback method of {@link MBeanRegistration}
455 * before the MBean is registered at the JMX Agent.
456 *
457 * <p>
458 * <b>Attention</b>: Always call this method when you overwrite it in a subclass
459 * because it saves the Object Name of the MBean.
460 *
461 * @param server Reference to the JMX Agent this MBean is registered on
462 * @param name Name specified by the creator of the MBean. Note that you can
463 * overwrite it when the given ObjectName is null otherwise the
464 * change is discarded (maybe a bug in JMX-RI).
465 * @return the ObjectName
466 * @throws Exception for any error
467 */
468 public ObjectName preRegister(MBeanServer server, ObjectName name)
469 throws Exception
470 {
471 this.server = server;
472
473 serviceName = getObjectName(server, name);
474
475 return serviceName;
476 }
477
478 public void postRegister(Boolean registrationDone)
479 {
480 if (!registrationDone.booleanValue())
481 {
482 log.info( "Registration is not done -> stop" );
483 stop();
484 }
485 else
486 {
487 state = REGISTERED;
488 // This is for backwards compatibility - see whether jbossInternalLifecycle is exposed
489 try
490 {
491 MBeanInfo info = server.getMBeanInfo(serviceName);
492 MBeanOperationInfo[] ops = info.getOperations();
493 for (int i = 0; i < ops.length; ++i)
494 {
495 if (ops[i] != null && ServiceController.JBOSS_INTERNAL_LIFECYCLE.equals(ops[i].getName()))
496 {
497 isJBossInternalLifecycleExposed = true;
498 break;
499 }
500 }
501 }
502 catch (Throwable t)
503 {
504 log.warn("Unexcepted error accessing MBeanInfo for " + serviceName, t);
505 }
506 }
507 }
508
509 public void preDeregister() throws Exception
510 {
511 }
512
513 public void postDeregister()
514 {
515 server = null;
516 serviceName = null;
517 state = UNREGISTERED;
518 }
519
520 /**
521 * The <code>getNextNotificationSequenceNumber</code> method returns
522 * the next sequence number for use in notifications.
523 *
524 * @return a <code>long</code> value
525 */
526 protected long getNextNotificationSequenceNumber()
527 {
528 return nextNotificationSequenceNumber();
529 }
530
531
532 ///////////////////////////////////////////////////////////////////////////
533 // Concrete Service Overrides //
534 ///////////////////////////////////////////////////////////////////////////
535
536 /**
537 * Sub-classes should override this method if they only need to set their
538 * object name during MBean pre-registration.
539 *
540 * @param server the mbeanserver
541 * @param name the suggested name, maybe null
542 * @return the object name
543 * @throws MalformedObjectNameException for a bad object name
544 */
545 protected ObjectName getObjectName(MBeanServer server, ObjectName name)
546 throws MalformedObjectNameException
547 {
548 return name;
549 }
550
551 /**
552 * Sub-classes should override this method to provide
553 * custum 'create' logic.
554 *
555 * <p>This method is empty, and is provided for convenience
556 * when concrete service classes do not need to perform
557 * anything specific for this state change.
558 *
559 * @throws Exception for any error
560 */
561 protected void createService() throws Exception {}
562
563 /**
564 * Sub-classes should override this method to provide
565 * custum 'start' logic.
566 *
567 * <p>This method is empty, and is provided for convenience
568 * when concrete service classes do not need to perform
569 * anything specific for this state change.
570 *
571 * @throws Exception for any error
572 */
573 protected void startService() throws Exception {}
574
575 /**
576 * Sub-classes should override this method to provide
577 * custum 'stop' logic.
578 *
579 * <p>This method is empty, and is provided for convenience
580 * when concrete service classes do not need to perform
581 * anything specific for this state change.
582 *
583 * @throws Exception for any error
584 */
585 protected void stopService() throws Exception {}
586
587 /**
588 * Sub-classes should override this method to provide
589 * custum 'destroy' logic.
590 *
591 * <p>This method is empty, and is provided for convenience
592 * when concrete service classes do not need to perform
593 * anything specific for this state change.
594 *
595 * @throws Exception for any error
596 */
597 protected void destroyService() throws Exception {}
598
599 // Private -------------------------------------------------------
600
601 /**
602 * Helper for sending out state change notifications
603 */
604 private void sendStateChangeNotification(int oldState, int newState, String msg, Throwable t)
605 {
606 long now = System.currentTimeMillis();
607
608 AttributeChangeNotification stateChangeNotification = new AttributeChangeNotification(
609 this,
610 getNextNotificationSequenceNumber(), now, msg,
611 "State", "java.lang.Integer",
612 new Integer(oldState), new Integer(newState)
613 );
614 stateChangeNotification.setUserData(t);
615
616 sendNotification(stateChangeNotification);
617 }
618 }