/* * #%L * Nazgul Project: nazgul-core-jmx-api * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% * */ package se.jguru.nazgul.core.jmx.api; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.jguru.nazgul.core.algorithms.api.Validate; import javax.management.AttributeChangeNotification; import javax.management.AttributeChangeNotificationFilter; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationBroadcaster; import javax.management.NotificationEmitter; import javax.management.ObjectName; import javax.management.StandardEmitterMBean; /** * Abstract StandardEmitterMBean implementation adhering to the StandardLifecycle. * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid, jGuru Europe AB</a> */ public abstract class AbstractMBean extends StandardEmitterMBean implements LifecycleStateful { // Our log private static final Logger log = LoggerFactory.getLogger(AbstractMBean.class); /** * The name of the state attribute. */ public static final String STATE_CHANGE_ATTRIBUTENAME = "state"; // Internal state private LifecycleState state = LifecycleState.UNINITIALIZED; private int eventSequenceId = 1; private final Object lock = new Object(); /** * <p>Convenience constructor, creating a new AbstractMBean wrapping the supplied data. * All NotificationEmitter operations executed in this AbstractMBean are delegated to * the provided NotificationEmitter object. Thus, all JMX notifications fired by this * AbstractMBean are simply delegated to the supplied NotificationEmitter.</p> * <p>This constructor must be called from a subclass that implements the provided {@code mbeanInterface}.</p> * * @param mbeanInterface a StandardMBean interface. * @param delegate A non-null NotificationEmitter to which this AbstractMBean will delegate all * NotificationEmitter operations. * @throws IllegalArgumentException if the {@code mbeanInterface} does not follow JMX design patterns for * Management Interfaces, or if {@code this} does not implement the specified * interface, or if {@code delegate} is null. * @see javax.management.StandardEmitterMBean#StandardEmitterMBean(Class, boolean, * javax.management.NotificationEmitter) */ public AbstractMBean(final Class<?> mbeanInterface, final NotificationEmitter delegate) { this(mbeanInterface, true, delegate); } /** * <p>Creates an AbstractMBean with the management interface {@code mbeanInterface}, and where notifications are * handled by the given {@code NotificationEmitter}. This constructor can be used to make either Standard MBeans * or MXBeans. The resultant MBean implements the {@code NotificationEmitter} interface by forwarding its methods * to {@code delegate}.</p> * <p>If {@code delegate} is an instance of {@code NotificationBroadcasterSupport} then the MBean's {@link * #sendNotification sendNotification} method will delegate its invocation to {@code delegate.} * {@link javax.management.NotificationBroadcasterSupport#sendNotification(Notification)}.</p> * <p>The array returned by {@link #getNotificationInfo()} on the new MBean is a copy of the array returned by * {@code emitter.}{@link NotificationBroadcaster#getNotificationInfo()} at the time of * construction. If the array returned by {@code emitter.getNotificationInfo()} later changes, that will have no * effect on this object's {@code getNotificationInfo()}.</p> * <p>This constructor must be called from a subclass that implements the given {@code mbeanInterface}.</p> * * @param mbeanInterface a StandardMBean interface. * @param isMXBean If true, the {@code mbeanInterface} parameter * names an MXBean interface and the resultant MBean is an MXBean. * @param delegate A non-null NotificationEmitter to which this AbstractMBean will delegate all * NotificationEmitter operations. * @throws IllegalArgumentException if the {@code mbeanInterface} * does not follow JMX design patterns for Management Interfaces, or * if {@code this} does not implement the specified interface, or * if {@code delegate} is null. */ public AbstractMBean(final Class<?> mbeanInterface, final boolean isMXBean, final NotificationEmitter delegate) { super(mbeanInterface, isMXBean, delegate); } /** * {@inheritDoc} */ @Override public final LifecycleState getState() { return state; } /** * <p>MBeanRegistration lifecycle method that delegates execution to (in order):</p> * <ol> * <li>{@code customPreregister()}, where subclasses may manipulate the JMX ObjectName.</li> * <li>{@code super.preRegister()}, performing the actual registration of this AbstractMBean * in the MBeanServer.</li> * </ol> * {@inheritDoc} * * @see #customPreregister(javax.management.MBeanServer, javax.management.ObjectName) */ @Override public final ObjectName preRegister(final MBeanServer server, final ObjectName name) throws Exception { // Check sanity if (name == null) { throw new IllegalArgumentException("You should define an ObjectName for the MBean [" + getClass().getName() + "]. Nameless AbstractMBeans are not permitted."); } // Perform any customizations on the ObjectName to be retrieved. ObjectName objectName = customPreregister(server, name); ObjectName toReturn = null; if (log.isDebugEnabled()) { log.debug("Preregistering object of type [" + getClass().getName() + "] under name [" + objectName.getCanonicalName() + "]"); } try { // Perform normal preregistration. toReturn = super.preRegister(server, objectName); // Preregistration successful; adjust the state. performStateTransition(true, LifecycleState.UNINITIALIZED, LifecycleState.STARTING); } catch (Exception e) { // Complain performStateTransition(false, LifecycleState.UNINITIALIZED, LifecycleState.ERROR); // Re-throw throw e; } if (log.isDebugEnabled()) { log.debug("PreRegister complete for [" + getClass().getName() + "]"); } // All done return toReturn; } /** * Delegates to the {@code #customPostregister} method after invoking {@code #postRegister} method * in the superclass. * {@inheritDoc} * * @see #customPostregister() */ @Override public final void postRegister(final Boolean successfulRegistration) { // Delegate to superclass super.postRegister(successfulRegistration); // Perform custom postRegistering? if (successfulRegistration) { try { customPostregister(); // Indicate that we are started performStateTransition(true, LifecycleState.STARTING, LifecycleState.STARTED); } catch (Exception e) { log.error("Custom postRegistration failed in [" + getClass().getName() + "]", e); performStateTransition(false, LifecycleState.STARTING, LifecycleState.ERROR); } } else { // Indicate that registration failed. if (getState() != LifecycleState.ERROR) { performStateTransition(false, LifecycleState.STARTING, LifecycleState.ERROR); } } } /** * Delegates any custom execution to the {@code #customPreDeregister} method before calling the * {@code #preDeregister} method in the superclass. * {@inheritDoc} * * @see #customPreDeregister() */ @Override public final void preDeregister() throws Exception { // Adjust state performStateTransition(true, LifecycleState.STARTED, LifecycleState.STOPPING); // Delegate to custom logic if (getState() != LifecycleState.ERROR) { try { customPreDeregister(); } catch (Exception e) { log.error("Custom preDeRegistration failed in [" + getClass().getName() + "]", e); performStateTransition(false, LifecycleState.STARTED, LifecycleState.ERROR); } } else if (log.isDebugEnabled()) { log.debug("" + LifecycleState.ERROR + " state, so customPreDeregister not called."); } // Delegate to superclass try { super.preDeregister(); } catch (Exception e) { // Change the state if (getState() != LifecycleState.ERROR) { performStateTransition(false, LifecycleState.STARTED, LifecycleState.ERROR); } // Re-throw throw e; } } /** * {@inheritDoc} */ @Override public final void postDeregister() { // Delegate to the custom postDeregister method. try { customPostDeregister(); } catch (Exception e) { log.error("customPostDeregister failed.", e); performStateTransition(false, LifecycleState.STOPPING, LifecycleState.ERROR); } try { super.postDeregister(); // Adjust state, if possible final LifecycleState targetState = state == LifecycleState.ERROR ? LifecycleState.ERROR : LifecycleState.STOPPED; performStateTransition(false, LifecycleState.STOPPING, targetState); } catch (Exception e) { log.error("super.postDeregister() failed.", e); performStateTransition(false, LifecycleState.STOPPING, LifecycleState.ERROR); } } /** * Override this method to perform customization of the ObjectName under which this AbstractMBean should * be registered within the supplied MBeanServer. <strong>Note!</strong> This method is invoked from within the * {@code preRegister} method, implying that this method may not register this AbstractMBean in the MBean server. * * @param server The MBeanServer instance. * @param objectName The object name of the MBean, which is {@code null} if the name parameter to one of the * <code>createMBean</code> or <code>registerMBean</code> methods in the {@link javax.management.MBeanServer} * interface is {@code null}. In that case, this method must return a non-null ObjectName for * the new MBean. * @return A non-null ObjectName under which this AbstractMBean will be registered in the MBeanServer. */ protected ObjectName customPreregister(final MBeanServer server, final ObjectName objectName) { return objectName; } /** * Override this method to perform any customized actions after this AbstractMBean has been * successfully registered in the MBeanServer. This method will only be invoked if the registration * in the MBeanServer was successful (i.e. if the {@code registrationDone} parameter received by the * postRegister method was {@code true}. This method should not throw Exceptions. * * @see #postRegister(Boolean) */ protected void customPostregister() { // By default, do nothing. } /** * Override this method to perform any customized actions before this AbstractMBean will be de-registered * in the MBeanServer. This method will be invoked immediately before the {@code super.preDeregister()} method * is called - but only if this AbstractMBean is not in {@code LifecycleState.ERROR} state. * (The customPostDeregister() method is always called, irrespective of state). */ protected void customPreDeregister() { // By default, do nothing. } /** * Override this mthod to perform any customized actions before this AbstractMBean will be de-registered * in the MBeanServer. This method will be invoked immediately before the {@code super.preDeregister()} method * is called. This method should not throw Exceptions. */ protected void customPostDeregister() { // By default, do nothing. } /** * Helper method which creates a JMX AttributeChangeNotification instance from the supplied * data, and notifies all registered NotificationListener instances by invoking {@code sendNotification()} * with the newly constructed AttributeChangeNotification as an argument. * * @param attributeName The name of the attribute which changed, such as "state". Cannot be null or empty. * @param eventMessage A human-readable AttributeChangeNotification eventMessage, * such as "LifecycleState changed". Cannot be null or empty. * @param objectType The attribute Class. Cannot be null. * @param oldValue The value before the change. * @param newValue The value after the change. * @param <T> The attribute Class. */ protected final <T> void sendAttributeChangeEvent(final String attributeName, final String eventMessage, final Class<T> objectType, final T oldValue, final T newValue) { // Check sanity Validate.notEmpty(attributeName, "attributeName"); Validate.notEmpty(eventMessage, "eventMessage"); Validate.notNull(objectType, "objectType"); // Notify our listeners about the state change. final Notification notification = new AttributeChangeNotification( this, eventSequenceId++, System.currentTimeMillis(), eventMessage, attributeName, objectType.getName(), oldValue, newValue); if (log.isDebugEnabled()) { log.debug("Sending AttributeChangeEvent [" + eventSequenceId + ", " + "newValue: " + newValue + "]: " + notification); } sendNotification(notification); } /** * Performs a state transition in this AbstractMBean which may be validated (in the sense that the * from state must match the result from {@code getState()} to permit the state transition). * If the stat transition was actually made, an AttributeChangeNotification is sent by this AbstractMBean to * permit any state listeners to react to the event. * * @param validateFromState if {@code true}, the result of {@code getState()} must be equal to from to permit the * state transition. Otherwise, the state transition will not be performed. * @param from The expected state of this AbstractMBean. * @param to The state to transition to. * @return {@code true} if the transition was successfully made, and {@code false} otherwise. */ protected final boolean performStateTransition(final boolean validateFromState, final LifecycleState from, final LifecycleState to) { if (validateFromState && getState() != from) { if (log.isWarnEnabled()) { log.warn("Aborting illegal state transition [" + getState() + " --> " + to + "]. (Expected " + from + ") in AbstractMBean [" + getClass().getName() + "]"); } // State update not performed. return false; } synchronized (lock) { // Perform the state transition itself. state = to; // Notify our listeners about the state change. sendAttributeChangeEvent(STATE_CHANGE_ATTRIBUTENAME, "LifecycleState changed", LifecycleState.class, from, to); } // All done. return true; } /** * @return An AttributeChangeNotificationFilter which will listen only to state change events emitted by * an AbstractMBean subclass (e.g. AttributeChangeEvent with attributeName set to the value of constant * {@code STATE_CHANGE_ATTRIBUTENAME}). */ public static AttributeChangeNotificationFilter getStateTransitionFilter() { final AttributeChangeNotificationFilter toReturn = new AttributeChangeNotificationFilter(); toReturn.enableAttribute(AbstractMBean.STATE_CHANGE_ATTRIBUTENAME); // All done. return toReturn; } }