/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, 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 java.util.concurrent.atomic.AtomicLong;
import javax.management.AttributeChangeNotification;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;
import org.jboss.logging.Logger;
import org.jboss.system.logging.ServiceMBeanLogger;
/**
* 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 <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
* @author Scott.Stark@jboss.org
* @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author Eduardo Martins (AS7)
*/
public class ServiceMBeanSupport extends NotificationBroadcasterSupport implements ServiceMBean, MBeanRegistration {
protected Logger log;
/** The MBeanServer which we have been register with. */
protected MBeanServer server;
/** The object name which we are registered under. */
protected ObjectName serviceName;
/** The current state this service is in. */
private int state = UNREGISTERED;
/** Sequence number for jmx notifications we send out */
private final AtomicLong sequenceNumber = new AtomicLong(0);
// on AS7 MBean lifecycle is CREATED -> STARTED -> REGISTERED -> UNREGISTERED -> STOP -> DESTROY,
// on previous AS versions is REGISTERED -> CREATED -> STARTED -> STOP -> DESTROYED -> UNREGISTERED
// to maintain compatibility with old ServiceMBeanSupport, we ignore some state changes, but redo
// these when proper state change happens
// the flags below are used to mark ignored lifecycle methods invocations
private boolean createIgnored = false;
private boolean startIgnored = false;
private boolean stopIgnored = false;
private boolean destroyIgnored = false;
private boolean unregisterIgnored = false;
/**
* Construct a <t>ServiceMBeanSupport</tt>.
*
* <p>
* 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 <t>ServiceMBeanSupport</tt>.
*
* <p>
* Sets up logging.
*
* @param type The class type to determine category name from.
*/
public ServiceMBeanSupport(final Class<?> type) {
this(type.getName());
}
/**
* Construct a <t>ServiceMBeanSupport</tt>.
*
* <p>
* Sets up logging.
*
* @param category The logger category name.
*/
public ServiceMBeanSupport(final String category) {
this(Logger.getLogger(category));
}
/**
* Construct a <t>ServiceMBeanSupport</tt>.
*
* @param log The logger to use.
*/
public ServiceMBeanSupport(final Logger log) {
this.log = log;
log.trace("Constructing");
}
/**
* Use the short class name as the default for the service name.
*
* @return a description of the mbean
*/
public String getName() {
final String s = log.getName();
final int i = s.lastIndexOf(".");
return i != -1 ? s.substring(i + 1, s.length()) : s;
}
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 //
// /////////////////////////////////////////////////////////////////////////
public void create() throws Exception {
jbossInternalCreate();
}
public void start() throws Exception {
jbossInternalStart();
}
public void stop() {
try {
jbossInternalStop();
} catch (Throwable t) {
log.warn(ServiceMBeanLogger.ROOT_LOGGER.errorInStop(jbossInternalDescription()), t);
}
}
public void destroy() {
try {
jbossInternalDestroy();
} catch (Throwable t) {
log.warn(ServiceMBeanLogger.ROOT_LOGGER.errorInDestroy(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 ServiceMBeanLogger.ROOT_LOGGER.nullMethodName();
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 ServiceMBeanLogger.ROOT_LOGGER.unknownLifecycleMethod(method);
}
protected void jbossInternalCreate() throws Exception {
// if (state == CREATED || state == STARTING || state == STARTED
// || state == STOPPING || state == STOPPED)
if (state != REGISTERED) {
createIgnored = true;
if (log.isDebugEnabled()) {
log.debug("Ignoring create call; current state is " + getStateString());
}
return;
}
createIgnored = false;
if (log.isDebugEnabled()) {
log.debug("Creating " + jbossInternalDescription());
}
try {
createService();
state = CREATED;
} catch (Exception e) {
log.warn(ServiceMBeanLogger.ROOT_LOGGER.initializationFailed(jbossInternalDescription()), e);
throw e;
}
if (log.isDebugEnabled()) {
log.debug("Created " + jbossInternalDescription());
}
if (startIgnored) {
start();
}
}
protected void jbossInternalStart() throws Exception {
if (state != CREATED && state != STOPPED) {
startIgnored = true;
if (log.isDebugEnabled()) {
log.debug("Ignoring start call; current state is " + getStateString());
}
return;
}
startIgnored = false;
state = STARTING;
sendStateChangeNotification(STOPPED, STARTING, getName() + " starting", null);
if (log.isDebugEnabled()) {
log.debug("Starting " + jbossInternalDescription());
}
try {
startService();
} catch (Exception e) {
state = FAILED;
sendStateChangeNotification(STARTING, FAILED, getName() + " failed", e);
log.warn(ServiceMBeanLogger.ROOT_LOGGER.startingFailed(jbossInternalDescription()), e);
throw e;
}
state = STARTED;
sendStateChangeNotification(STARTING, STARTED, getName() + " started", null);
if (log.isDebugEnabled()) {
log.debug("Started " + jbossInternalDescription());
}
if (stopIgnored) {
stop();
}
}
protected void jbossInternalStop() {
if (state != STARTED) {
stopIgnored = true;
if (log.isDebugEnabled()) {
log.debug("Ignoring stop call; current state is " + getStateString());
}
return;
}
stopIgnored = false;
state = STOPPING;
sendStateChangeNotification(STARTED, STOPPING, getName() + " stopping", null);
if (log.isDebugEnabled()) {
log.debug("Stopping " + jbossInternalDescription());
}
try {
stopService();
} catch (Throwable e) {
state = FAILED;
sendStateChangeNotification(STOPPING, FAILED, getName() + " failed", e);
log.warn(ServiceMBeanLogger.ROOT_LOGGER.stoppingFailed(jbossInternalDescription()), e);
return;
}
state = STOPPED;
sendStateChangeNotification(STOPPING, STOPPED, getName() + " stopped", null);
if (log.isDebugEnabled()) {
log.debug("Stopped " + jbossInternalDescription());
}
if (destroyIgnored) {
destroy();
}
}
protected void jbossInternalDestroy() {
if (state != STOPPED) {
destroyIgnored = true;
if (log.isDebugEnabled()) {
log.debug("Ignoring destroy call; current state is " + getStateString());
}
return;
}
destroyIgnored = false;
if (log.isDebugEnabled()) {
log.debug("Destroying " + jbossInternalDescription());
}
try {
destroyService();
} catch (Throwable t) {
log.warn(ServiceMBeanLogger.ROOT_LOGGER.destroyingFailed(jbossInternalDescription()), t);
}
state = DESTROYED;
if (log.isDebugEnabled()) {
log.debug("Destroyed " + jbossInternalDescription());
}
if (unregisterIgnored) {
postDeregister();
}
}
// /////////////////////////////////////////////////////////////////////////
// JMX Hooks //
// /////////////////////////////////////////////////////////////////////////
/**
* Callback method of {@link javax.management.MBeanRegistration} before the MBean is registered at the JMX Agent.
*
* <p>
* <b>Attention</b>: 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.debug("Registration is not done -> stop");
stop();
} else {
state = REGISTERED;
if (createIgnored) {
try {
create();
} catch (Exception e) {
log.error(ServiceMBeanLogger.ROOT_LOGGER.postRegisterInitializationFailed(), e);
}
}
}
}
public void preDeregister() throws Exception {}
public void postDeregister() {
if (state != DESTROYED) {
unregisterIgnored = true;
if (log.isDebugEnabled()) {
log.debug("Ignoring postDeregister call; current state is " + getStateString());
}
return;
}
unregisterIgnored = false;
server = null;
serviceName = null;
state = UNREGISTERED;
}
// /////////////////////////////////////////////////////////////////////////
// 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 javax.management.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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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 {}
/**
* The <code>nextNotificationSequenceNumber</code> method returns the next sequence number for use in notifications.
*
* @return a <code>long</code> value
*/
public long nextNotificationSequenceNumber() {
return sequenceNumber.incrementAndGet();
}
/**
* The <code>getNextNotificationSequenceNumber</code> method returns the next sequence number for use in notifications.
*
* @return a <code>long</code> value
*/
protected long getNextNotificationSequenceNumber() {
return nextNotificationSequenceNumber();
}
/**
* 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);
}
}