org.jboss.system.ServiceMBeanSupport Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.system;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.jboss.beans.metadata.api.annotations.Create;
import org.jboss.beans.metadata.api.annotations.Destroy;
import org.jboss.beans.metadata.api.annotations.Start;
import org.jboss.beans.metadata.api.annotations.Stop;
import org.jboss.dependency.spi.Controller;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.kernel.spi.dependency.KernelControllerContextAware;
import org.jboss.logging.Logger;
import org.jboss.mx.util.JBossNotificationBroadcasterSupport;
/**
* An abstract base class JBoss services can subclass to implement a
* service that conforms to the ServiceMBean interface. Subclasses must
* override {@link #getName} method and should override
* {@link #startService}, and {@link #stopService} as approriate.
*
* @see ServiceMBean
*
* @author Rickard Öberg
* @author [email protected]
* @author Andreas Schaefer
* @author Jason Dillon
* @version $Revision: 84175 $
*/
public class ServiceMBeanSupport
extends JBossNotificationBroadcasterSupport
implements ServiceMBean, MBeanRegistration, KernelControllerContextAware
{
/** The signature for service controller operations */
public static final String[] SERVICE_CONTROLLER_SIG = new String[] { ObjectName.class.getName() };
/**
* The instance logger for the service. Not using a class logger
* because we want to dynamically obtain the logger name from
* concreate sub-classes.
*/
protected Logger log;
/** The MBeanServer which we have been register with. */
protected MBeanServer server;
/** The object name which we are registsred under. */
protected ObjectName serviceName;
/** The current state this service is in. */
private int state = UNREGISTERED;
/** For backwards compatibility */
private boolean isJBossInternalLifecycleExposed = false;
/** The controller context */
private KernelControllerContext controllerContext;
/**
* Construct a ServiceMBeanSupport.
*
* Sets up logging.
*/
public ServiceMBeanSupport()
{
// can not call this(Class) because we need to call getClass()
this.log = Logger.getLogger(getClass().getName());
log.trace("Constructing");
}
/**
* Construct a ServiceMBeanSupport.
*
* Sets up logging.
*
* @param type The class type to determine category name from.
*/
@SuppressWarnings("unchecked")
public ServiceMBeanSupport(final Class type)
{
this(type.getName());
}
/**
* Construct a ServiceMBeanSupport.
*
* Sets up logging.
*
* @param category The logger category name.
*/
public ServiceMBeanSupport(final String category)
{
this(Logger.getLogger(category));
}
/**
* Construct a ServiceMBeanSupport.
*
* @param log The logger to use.
*/
public ServiceMBeanSupport(final Logger log)
{
this.log = log;
log.trace("Constructing");
}
public void setKernelControllerContext(KernelControllerContext controllerContext) throws Exception
{
this.controllerContext = controllerContext;
}
public void unsetKernelControllerContext(KernelControllerContext controllerContext) throws Exception
{
this.controllerContext = null;
}
/**
* Use the short class name as the default for the service name.
*
* @return a description of the mbean
*/
public String getName()
{
// TODO: Check if this gets called often, if so cache this or remove if not used
// use the logger so we can better be used as a delegate instead of sub-class
return org.jboss.util.Classes.stripPackageName(log.getName());
}
public ObjectName getServiceName()
{
return serviceName;
}
public MBeanServer getServer()
{
return server;
}
public int getState()
{
return state;
}
public String getStateString()
{
return states[state];
}
public Logger getLog()
{
return log;
}
///////////////////////////////////////////////////////////////////////////
// State Mutators //
///////////////////////////////////////////////////////////////////////////
@Create
public void pojoCreate() throws Exception
{
jbossInternalCreate();
}
@Start
public void pojoStart() throws Exception
{
jbossInternalStart();
}
@Stop
public void pojoStop() throws Exception
{
jbossInternalStop();
}
@Destroy
public void pojoDestroy() throws Exception
{
jbossInternalDestroy();
}
protected void pojoChange(ControllerState state)
{
Controller controller = controllerContext.getController();
try
{
controller.change(controllerContext, state);
}
catch (RuntimeException e)
{
throw e;
}
catch (Error e)
{
throw e;
}
catch (Throwable t)
{
throw new RuntimeException("Error changing state of " + controllerContext.getName() + " to " + state.getStateString(), t);
}
}
public void create() throws Exception
{
if (controllerContext != null)
pojoChange(ControllerState.CREATE);
else if (serviceName != null && isJBossInternalLifecycleExposed)
server.invoke(ServiceController.OBJECT_NAME, "create", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
else
jbossInternalCreate();
}
public void start() throws Exception
{
if (controllerContext != null)
pojoChange(ControllerState.START);
else if (serviceName != null && isJBossInternalLifecycleExposed)
server.invoke(ServiceController.OBJECT_NAME, "start", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
else
jbossInternalStart();
}
public void stop()
{
try
{
if (controllerContext != null)
pojoChange(ControllerState.CREATE);
else if (serviceName != null && isJBossInternalLifecycleExposed)
server.invoke(ServiceController.OBJECT_NAME, "stop", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
else
jbossInternalStop();
}
catch (Throwable t)
{
log.warn("Error in stop " + jbossInternalDescription(), t);
}
}
public void destroy()
{
try
{
if (controllerContext != null)
pojoChange(ControllerState.CONFIGURED);
else if (serviceName != null && isJBossInternalLifecycleExposed)
server.invoke(ServiceController.OBJECT_NAME, "destroy", new Object[] { serviceName }, SERVICE_CONTROLLER_SIG);
else
jbossInternalDestroy();
}
catch (Throwable t)
{
log.warn("Error in destroy " + jbossInternalDescription(), t);
}
}
protected String jbossInternalDescription()
{
if (serviceName != null)
return serviceName.toString();
else
return getName();
}
public void jbossInternalLifecycle(String method) throws Exception
{
if (method == null)
throw new IllegalArgumentException("Null method name");
if (method.equals("create"))
jbossInternalCreate();
else if (method.equals("start"))
jbossInternalStart();
else if (method.equals("stop"))
jbossInternalStop();
else if (method.equals("destroy"))
jbossInternalDestroy();
else
throw new IllegalArgumentException("Unknown lifecyle method " + method);
}
protected void jbossInternalCreate() throws Exception
{
if (state == CREATED || state == STARTING || state == STARTED
|| state == STOPPING || state == STOPPED)
{
log.debug("Ignoring create call; current state is " + getStateString());
return;
}
log.debug("Creating " + jbossInternalDescription());
try
{
createService();
state = CREATED;
}
catch (Exception e)
{
log.debug("Initialization failed " + jbossInternalDescription(), e);
throw e;
}
log.debug("Created " + jbossInternalDescription());
}
protected void jbossInternalStart() throws Exception
{
if (state == STARTING || state == STARTED || state == STOPPING)
{
log.debug("Ignoring start call; current state is " + getStateString());
return;
}
if (state != CREATED && state != STOPPED && state != FAILED)
{
log.debug("Start requested before create, calling create now");
create();
}
state = STARTING;
sendStateChangeNotification(STOPPED, STARTING, getName() + " starting", null);
log.debug("Starting " + jbossInternalDescription());
try
{
startService();
}
catch (Exception e)
{
state = FAILED;
sendStateChangeNotification(STARTING, FAILED, getName() + " failed", e);
log.debug("Starting failed " + jbossInternalDescription(), e);
throw e;
}
state = STARTED;
sendStateChangeNotification(STARTING, STARTED, getName() + " started", null);
log.debug("Started " + jbossInternalDescription());
}
protected void jbossInternalStop()
{
if (state != STARTED)
{
log.debug("Ignoring stop call; current state is " + getStateString());
return;
}
state = STOPPING;
sendStateChangeNotification(STARTED, STOPPING, getName() + " stopping", null);
log.debug("Stopping " + jbossInternalDescription());
try
{
stopService();
}
catch (Throwable e)
{
state = FAILED;
sendStateChangeNotification(STOPPING, FAILED, getName() + " failed", e);
log.warn("Stopping failed " + jbossInternalDescription(), e);
return;
}
state = STOPPED;
sendStateChangeNotification(STOPPING, STOPPED, getName() + " stopped", null);
log.debug("Stopped " + jbossInternalDescription());
}
protected void jbossInternalDestroy()
{
if (state == DESTROYED)
{
log.debug("Ignoring destroy call; current state is " + getStateString());
return;
}
if (state == STARTED)
{
log.debug("Destroy requested before stop, calling stop now");
stop();
}
log.debug("Destroying " + jbossInternalDescription());
try
{
destroyService();
}
catch (Throwable t)
{
log.warn("Destroying failed " + jbossInternalDescription(), t);
}
state = DESTROYED;
log.debug("Destroyed " + jbossInternalDescription());
}
///////////////////////////////////////////////////////////////////////////
// JMX Hooks //
///////////////////////////////////////////////////////////////////////////
/**
* Callback method of {@link MBeanRegistration}
* before the MBean is registered at the JMX Agent.
*
*
* Attention: Always call this method when you overwrite it in a subclass
* because it saves the Object Name of the MBean.
*
* @param server Reference to the JMX Agent this MBean is registered on
* @param name Name specified by the creator of the MBean. Note that you can
* overwrite it when the given ObjectName is null otherwise the
* change is discarded (maybe a bug in JMX-RI).
* @return the ObjectName
* @throws Exception for any error
*/
public ObjectName preRegister(MBeanServer server, ObjectName name)
throws Exception
{
this.server = server;
serviceName = getObjectName(server, name);
return serviceName;
}
public void postRegister(Boolean registrationDone)
{
if (!registrationDone.booleanValue())
{
log.info( "Registration is not done -> stop" );
stop();
}
else
{
state = REGISTERED;
// This is for backwards compatibility - see whether jbossInternalLifecycle is exposed
try
{
MBeanInfo info = server.getMBeanInfo(serviceName);
MBeanOperationInfo[] ops = info.getOperations();
for (int i = 0; i < ops.length; ++i)
{
if (ops[i] != null && ServiceController.JBOSS_INTERNAL_LIFECYCLE.equals(ops[i].getName()))
{
isJBossInternalLifecycleExposed = true;
break;
}
}
}
catch (Throwable t)
{
log.warn("Unexcepted error accessing MBeanInfo for " + serviceName, t);
}
}
}
public void preDeregister() throws Exception
{
}
public void postDeregister()
{
server = null;
serviceName = null;
state = UNREGISTERED;
}
/**
* The getNextNotificationSequenceNumber
method returns
* the next sequence number for use in notifications.
*
* @return a long
value
*/
protected long getNextNotificationSequenceNumber()
{
return nextNotificationSequenceNumber();
}
///////////////////////////////////////////////////////////////////////////
// Concrete Service Overrides //
///////////////////////////////////////////////////////////////////////////
/**
* Sub-classes should override this method if they only need to set their
* object name during MBean pre-registration.
*
* @param server the mbeanserver
* @param name the suggested name, maybe null
* @return the object name
* @throws MalformedObjectNameException for a bad object name
*/
protected ObjectName getObjectName(MBeanServer server, ObjectName name)
throws MalformedObjectNameException
{
return name;
}
/**
* Sub-classes should override this method to provide
* custum 'create' logic.
*
*
This method is empty, and is provided for convenience
* when concrete service classes do not need to perform
* anything specific for this state change.
*
* @throws Exception for any error
*/
protected void createService() throws Exception {}
/**
* Sub-classes should override this method to provide
* custum 'start' logic.
*
*
This method is empty, and is provided for convenience
* when concrete service classes do not need to perform
* anything specific for this state change.
*
* @throws Exception for any error
*/
protected void startService() throws Exception {}
/**
* Sub-classes should override this method to provide
* custum 'stop' logic.
*
*
This method is empty, and is provided for convenience
* when concrete service classes do not need to perform
* anything specific for this state change.
*
* @throws Exception for any error
*/
protected void stopService() throws Exception {}
/**
* Sub-classes should override this method to provide
* custum 'destroy' logic.
*
*
This method is empty, and is provided for convenience
* when concrete service classes do not need to perform
* anything specific for this state change.
*
* @throws Exception for any error
*/
protected void destroyService() throws Exception {}
// Private -------------------------------------------------------
/**
* Helper for sending out state change notifications
*/
private void sendStateChangeNotification(int oldState, int newState, String msg, Throwable t)
{
long now = System.currentTimeMillis();
AttributeChangeNotification stateChangeNotification = new AttributeChangeNotification(
this,
getNextNotificationSequenceNumber(), now, msg,
"State", "java.lang.Integer",
new Integer(oldState), new Integer(newState)
);
stateChangeNotification.setUserData(t);
sendNotification(stateChangeNotification);
}
}