/*
* JBoss, Home of Professional Open Source.
* Copyright 2009 Red Hat, Inc. 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.ha.jmx;
import java.io.Serializable;
import java.util.List;
import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.ObjectName;
import org.jboss.beans.metadata.api.annotations.Inject;
import org.jboss.beans.metadata.api.model.FromContext;
import org.jboss.ha.framework.interfaces.DistributedState;
import org.jboss.ha.framework.interfaces.HAPartition;
import org.jboss.ha.framework.interfaces.HAService;
import org.jboss.ha.framework.server.EventFacility;
import org.jboss.ha.framework.server.EventFactory;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.system.ServiceMBeanSupport;
/**
* Abstract implementation of HAServiceMBean for use by subclasses, e.g. {@link org.jboss.ha.singleton.HASingletonSupport}.
*
* @param S the class of the HAService delegate
* @author Paul Ferraro
*/
@SuppressWarnings("deprecation")
public abstract class AbstractHAServiceMBeanSupport<S extends HAService<Notification>>
extends ServiceMBeanSupport
implements HAServiceMBean, HAService<Notification>, EventFactory<Notification>, EventFacility<Notification>
{
private final S service = this.createHAService();
private volatile boolean sendLocalLifecycleNotifications = true;
private volatile boolean sendRemoteLifecycleNotifications = true;
/**
* Defines the object used to delegate service methods.
* @return an {@link HAService} delegate
*/
protected abstract S createHAService();
/**
* @return the object created by {@link #createHAService()}.
*/
protected final S getHAService()
{
return this.service;
}
// Public --------------------------------------------------------
public HAPartition getHAPartition()
{
return this.service.getHAPartition();
}
public void setHAPartition(HAPartition partition)
{
this.service.setHAPartition(partition);
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="The name of the HAPartition this service is associated with")
public String getPartitionName()
{
return this.service.getHAPartition().getPartitionName();
}
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether this service should register its classloader with the HAPartition for use in deserializing group RPC parameters and responses")
public boolean isRegisterThreadContextClassLoader()
{
return this.service.isRegisterThreadContextClassLoader();
}
public void setRegisterThreadContextClassLoader(boolean register)
{
this.service.setRegisterThreadContextClassLoader(register);
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="The name under which this service registers itself with the HAPartition")
public String getHAServiceKey()
{
return this.service.getHAServiceKey();
}
// Protected ------------------------------
/**
*
*
* Convenience method for sharing state across a cluster partition.
* Delegates to the DistributedStateService
*
* @param key key for the distributed object
* @param value the distributed object
*
*/
public void setDistributedState(String key, Serializable value) throws Exception
{
DistributedState ds = this.getHAPartition().getDistributedStateService();
ds.set(this.getHAServiceKey(), key, value);
}
/**
*
* Convenience method for sharing state across a cluster partition.
* Delegates to the DistributedStateService
*
* @param key key for the distributed object
* @return Serializable the distributed object
*
*/
public Serializable getDistributedState(String key)
{
DistributedState ds = this.getHAPartition().getDistributedStateService();
return ds.get(this.getHAServiceKey(), key);
}
/**
* @see org.jboss.system.ServiceMBeanSupport#createService()
*/
@Override
protected void createService() throws Exception
{
this.service.create();
}
/**
* <p>
* Implementors of this method should not
* code the singleton logic here.
* The MBean lifecycle create/start/stop are separate from
* the singleton logic.
* Singleton logic should originate in becomeMaster().
* </p>
*
* <p>
* <b>Attention</b>: Always call this method when you overwrite it in a subclass
* because it elects the master singleton node.
* </p>
*
*/
@Override
protected void startService() throws Exception
{
this.log.debug("start HAServiceMBeanSupport");
if (this.getHAPartition() == null)
{
throw new IllegalStateException("HAPartition property must be set before starting HAServiceMBeanSupport");
}
this.setupPartition();
// Default serviceHAName, if none defined
if (this.service.getServiceHAName() == null)
{
ObjectName name = this.getServiceName();
if (name != null)
{
this.service.setServiceHAName(name.getCanonicalName());
}
else
{
// This shouldn't occur as the service name is now injected by the microcontainer.
// If injection fails, the service name should then be used.
throw new IllegalStateException("Cannot determine ServiceHAName for " +
getClass().getName() + "; either set it explicitly " +
"or register this object in JMX before calling create");
}
}
this.service.start();
}
/**
* <p>
* <b>Attention</b>: Always call this method when you overwrite it in a subclass
* </p>
*
*/
@Override
protected void stopService() throws Exception
{
this.log.debug("stop HAServiceMBeanSupport");
this.service.stop();
}
/**
* @see org.jboss.system.ServiceMBeanSupport#destroyService()
*/
@Override
protected void destroyService() throws Exception
{
this.service.destroy();
}
protected void setupPartition() throws Exception
{
// method no longer used by base implementation
}
@SuppressWarnings("unchecked")
protected List callMethodOnPartition(String methodName, Object[] args, Class[] types) throws Exception
{
return this.getHAPartition().callMethodOnCluster(this.getHAServiceKey(), methodName, args, types, true);
}
@SuppressWarnings("unchecked")
protected void callAsyncMethodOnPartition(String methodName, Object[] args, Class[] types) throws Exception
{
this.getHAPartition().callAsynchMethodOnCluster(this.getHAServiceKey(), methodName, args, types, true);
}
/**
* Gets whether JMX Notifications should be sent to local (same JVM) listeners
* if the notification is for an attribute change to attribute "State".
* <p>
* Default is <code>true</code>.
* </p>
* @see #sendNotification(Notification)
*/
@ManagementProperty(use={ViewUse.STATISTIC}, description="Whether JMX Notifications should be sent to local (same JVM) listeners if the notification is for an attribute change to attribute \"State\"")
public boolean getSendLocalLifecycleNotifications()
{
return this.sendLocalLifecycleNotifications;
}
/**
* Sets whether JMX Notifications should be sent to local (same JVM) listeners
* if the notification is for an attribute change to attribute "State".
* <p>
* Default is <code>true</code>.
* </p>
* @see #sendNotification(Notification)
*/
public void setSendLocalLifecycleNotifications(boolean sendLocalLifecycleNotifications)
{
this.sendLocalLifecycleNotifications = sendLocalLifecycleNotifications;
}
/**
* Gets whether JMX Notifications should be sent to remote listeners
* if the notification is for an attribute change to attribute "State".
* <p>
* Default is <code>true</code>.
* </p>
* <p>
* See http://jira.jboss.com/jira/browse/JBAS-3194 for an example of a
* use case where this property should be set to false.
* </p>
*
* @see #sendNotification(Notification)
*/
@ManagementProperty(use={ViewUse.STATISTIC}, description="Whether JMX Notifications should be sent to remote listeners if the notification is for an attribute change to attribute \"State\"")
public boolean getSendRemoteLifecycleNotifications()
{
return this.sendRemoteLifecycleNotifications;
}
/**
* Sets whether JMX Notifications should be sent to remote listeners
* if the notification is for an attribute change to attribute "State".
* <p>
* Default is <code>true</code>.
* </p>
* <p>
* See http://jira.jboss.com/jira/browse/JBAS-3194 for an example of a
* use case where this property should be set to false.
* </p>
*
* @see #sendNotification(Notification)
*/
public void setSendRemoteLifecycleNotifications(boolean sendRemoteLifecycleNotifications)
{
this.sendRemoteLifecycleNotifications = sendRemoteLifecycleNotifications;
}
/**
* Broadcast the notification to the remote listener nodes (if any) and then
* invoke super.sendNotification() to notify local listeners.
*
* @param notification sent out to local listeners and other nodes. It should be serializable.
* It is recommended that the source of the notification is an ObjectName of an MBean that
* is is available on all nodes where the broadcaster MBean is registered.
*
* @see #getSendLocalLifecycleNotifications()
* @see #getSendRemoteLifecycleNotifications()
* @see javax.management.NotificationBroadcasterSupport#sendNotification(Notification)
* @see org.jboss.mx.util.JBossNotificationBroadcasterSupport#sendNotification(javax.management.Notification)
*/
@Override
public void sendNotification(Notification notification)
{
boolean stateChange = (notification instanceof AttributeChangeNotification) ? "State".equals(((AttributeChangeNotification) notification).getAttributeName()) : false;
if (!stateChange || this.sendRemoteLifecycleNotifications)
{
try
{
this.sendNotificationRemote(notification);
}
catch (Throwable e)
{
// even if broadcast failed, local notification should still be sent
this.log.warn("handleNotification( " + notification + " ) failed ", e);
}
}
if (!stateChange || this.sendLocalLifecycleNotifications)
{
this.sendNotificationToLocalListeners(notification);
}
}
/**
*
* Broadcast a notifcation remotely to the partition participants
*
* @param notification
*/
protected void sendNotificationRemote(Notification notification) throws Exception
{
// Overriding the source MBean with its ObjectName
// to ensure that it can be safely transferred over the wire
notification.setSource(this.getServiceName());
this.service.handleEvent(notification);
}
protected void sendNotificationToLocalListeners(Notification notification)
{
this.notifyListeners(notification);
}
/**
* @see org.jboss.ha.framework.server.EventFacility#notifyListeners(java.util.EventObject)
*/
public void notifyListeners(Notification notification)
{
super.sendNotification(notification);
}
/**
* @see org.jboss.ha.framework.interfaces.EventListener#handleEvent(java.util.EventObject)
*/
public void handleEvent(Notification notification) throws Exception
{
this.notifyListeners(notification);
}
/**
* @see org.jboss.ha.framework.server.EventFactory#createEvent(java.lang.Object, java.lang.String)
*/
public Notification createEvent(Object source, String type)
{
return new Notification(type, this, this.getNextNotificationSequenceNumber());
}
/**
*
* Override this method only if you need to provide a custom partition wide unique service name.
* The default implementation will usually work, provided that
* the getServiceName() method returns a unique canonical MBean name.
*
* @return partition wide unique service name
*/
public String getServiceHAName()
{
return this.service.getServiceHAName();
}
@Inject(fromContext = FromContext.NAME)
public void setServiceHAName(String haName)
{
this.service.setServiceHAName(haName);
}
}