/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt 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.mobicents.slee.runtime.activity;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.slee.Address;
import javax.slee.EventTypeID;
import javax.slee.SLEEException;
import javax.slee.ServiceID;
import javax.slee.facilities.TimerID;
import javax.slee.resource.ActivityFlags;
import javax.slee.resource.ActivityIsEndingException;

import org.apache.log4j.Logger;
import org.infinispan.tree.Node;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.activity.ActivityContext;
import org.mobicents.slee.container.activity.ActivityContextHandle;
import org.mobicents.slee.container.activity.ActivityEventQueueManager;
import org.mobicents.slee.container.activity.ActivityType;
import org.mobicents.slee.container.event.EventContext;
import org.mobicents.slee.container.event.EventProcessingFailedCallback;
import org.mobicents.slee.container.event.EventProcessingSucceedCallback;
import org.mobicents.slee.container.event.EventUnreferencedCallback;
import org.mobicents.slee.container.facilities.ActivityContextNamingFacility;
import org.mobicents.slee.container.facilities.TimerFacility;
import org.mobicents.slee.container.resource.ResourceAdaptorActivityContextHandle;
import org.mobicents.slee.container.sbbentity.SbbEntityID;
import org.mobicents.slee.container.service.ServiceActivityHandle;
import org.mobicents.slee.container.transaction.TransactionContext;
import org.mobicents.slee.runtime.event.ActivityEndEventUnreferencedCallback;
import org.mobicents.slee.runtime.event.CommitEventContextAction;
import org.mobicents.slee.runtime.event.RollbackEventContextAction;

/**
 * Create one of these when a new SipTransaction is seen by the stack. Call the
 * Slee Endpoint. This is a cached object so it is created from a factory
 * interface.
 *
 * @author M. Ranganathan
 * @author F.Moggia
 * @author Tim - tx stuff
 * @author Ivelin Ivanov
 * @author eduardomartins
 *
 */

public class ActivityContextImpl implements ActivityContext {

	static final SleeContainer sleeContainer = SleeContainer.lookupFromJndi();

	private static final Logger logger = Logger
			.getLogger(ActivityContext.class);

	// --- map keys for attributes cached
	private static final String NODE_MAP_KEY_ACTIVITY_FLAGS = "flags";

	private static final String NODE_MAP_KEY_LAST_ACCESS = "time";

	/**
	 * the handle for this ac
	 */
	private final ActivityContextHandle activityContextHandle;

	/**
	 * the data stored in cache for this ac
	 */
	protected final ActivityContextCacheData cacheData;

	private final ActivityContextFactoryImpl factory;

	private Integer flags;

	private final ActivityContextReferencesHandler acReferencesHandler;

	public ActivityContextImpl(
			final ActivityContextHandle activityContextHandle,
			ActivityContextCacheData cacheData, boolean updateAccessTime,
			Integer activityFlags, ActivityContextFactoryImpl factory) {
		this.activityContextHandle = activityContextHandle;
		this.factory = factory;
		this.cacheData = cacheData;
		// ac creation, create cache data and set activity flags
		this.cacheData.create();
		this.cacheData.putObject(NODE_MAP_KEY_ACTIVITY_FLAGS, activityFlags);
		this.flags = activityFlags;
		// set access time if needed
		if (updateAccessTime) {
			updateLastAccessTime(true);
		}
		// setup references handler if needed
		if (ActivityFlags.hasRequestSleeActivityGCCallback(activityFlags)
				&& !isEnding()) {
			acReferencesHandler = new ActivityContextReferencesHandler(this);
		} else {
			acReferencesHandler = null;
		}
	}

	public ActivityContextImpl(ActivityContextHandle activityContextHandle,
			ActivityContextCacheData cacheData, boolean updateAccessTime,
			ActivityContextFactoryImpl factory) {
		this.activityContextHandle = activityContextHandle;
		this.factory = factory;
		this.cacheData = cacheData;
		// set access time if needed
		if (cacheData.exists() && updateAccessTime) {
			updateLastAccessTime(false);
		}
		// setup references handler if needed
		if (ActivityFlags.hasRequestSleeActivityGCCallback(getActivityFlags())
				&& !isEnding()) {
			acReferencesHandler = new ActivityContextReferencesHandler(this);
		} else {
			acReferencesHandler = null;
		}
	}

	/**
	 * Retrieves the handle of this ac
	 *
	 * @return
	 */
	public ActivityContextHandle getActivityContextHandle() {
		return activityContextHandle;
	}

	/**
	 * Retrieve the {@link ActivityFlags} for this activity context
	 *
	 * @return
	 */
	public int getActivityFlags() {
		if (flags == null) {
			// instance has no flags stored, check local ac
			if (localActivityContext != null) {
				flags = localActivityContext.getActivityFlags();
			} else {
				// local ac does not exists, get from cache
				flags = (Integer) cacheData
						.getObject(NODE_MAP_KEY_ACTIVITY_FLAGS);
			}
		}
		return flags != null ? flags : ActivityFlags.NO_FLAGS;
	}

	/**
	 * @return the factory
	 */
	public ActivityContextFactoryImpl getFactory() {
		return factory;
	}

	/**
	 * test if the activity context is ending.
	 *
	 * @return true if ending.
	 */
	public boolean isEnding() {
		return cacheData.isEnding();
	}

	/**
	 * Set a shared data item for the ACI
	 *
	 * @param key
	 *            -- name of the shared data item.
	 * @param newValue
	 *            -- value of the shared data item.
	 */
	public void setDataAttribute(String key, Object newValue) {
		cacheData.setCmpAttribute(key, newValue);
		if (logger.isDebugEnabled()) {
			logger.debug("Activity context with handle "
					+ getActivityContextHandle() + " set cmp attribute named "
					+ key + " to value " + newValue);
		}
	}

	/**
	 * Get the shared data for the ACI.
	 *
	 * @param key
	 *            -- name we want to look up
	 * @return the shared data for the ACI
	 *
	 */
	public Object getDataAttribute(String key) {
		return cacheData.getCmpAttribute(key);
	}

	@SuppressWarnings("rawtypes")
	public Map getDataAttributes() {
		return cacheData.getCmpAttributesCopy();
	}

	/**
	 * add a naming binding to this activity context.
	 *
	 * @param aciName
	 *            - new name binding to be added.
	 *
	 */
	public void addNameBinding(String aciName) {
		cacheData.nameBound(aciName);
		if (acReferencesHandler != null) {
			acReferencesHandler.nameReferenceCreated();
		}
	}

	/**
	 * This is called to release all the name bindings after the activity end
	 * event is delivered to the sbb.
	 *
	 */
	private void removeNamingBindings() {
		ActivityContextNamingFacility acf = sleeContainer
				.getActivityContextNamingFacility();
		for (Object obj : cacheData.getNamesBoundCopy()) {
			String aciName = (String) obj;
			try {
				acf.removeName(aciName);
			} catch (Exception e) {
				logger.warn("failed to unbind name: " + aciName + " from ac:"
						+ getActivityContextHandle(), e);
			}
		}
	}

	/**
	 * Fetches set of names given to this ac
	 *
	 * @return Set containing String objects that are names of this ac
	 */
	@SuppressWarnings("unchecked")
	public Set<String> getNamingBindings() {
		return cacheData.getNamesBoundCopy();
	}

	/**
	 * Add the given name to the set of activity context names that we are bound
	 * to. The AC Naming facility implicitly ends the activity after all names
	 * are unbound.
	 *
	 * @param aciName
	 *            -- name to which we are bound.
	 * @return true if name bind was removed; false otherwise
	 *
	 */
	public boolean removeNameBinding(String aciName) {
		boolean removed = cacheData.nameUnbound(aciName);
		if (removed && acReferencesHandler != null) {
			acReferencesHandler.nameReferenceRemoved();
		}
		return removed;
	}

	/**
	 * attach the given timer to the current activity context.
	 *
	 * @param timerID
	 *            -- timer id to attach.
	 *
	 */
	public boolean attachTimer(TimerID timerID) {
		if (cacheData.attachTimer(timerID)) {
			if (acReferencesHandler != null) {
				acReferencesHandler.timerReferenceCreated();
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Detach timer
	 *
	 * @param timerID
	 *
	 */
	public boolean detachTimer(TimerID timerID) {
		boolean detached = cacheData.detachTimer(timerID);
		if (detached && acReferencesHandler != null) {
			acReferencesHandler.timerReferenceRemoved();
		}
		return detached;
	}

	/**
	 * Fetches set of attached timers.
	 *
	 * @return Set containing TimerID objects representing timers attached to
	 *         this ac.
	 */
	@SuppressWarnings("unchecked")
	public Set<TimerID> getAttachedTimers() {
		return cacheData.getAttachedTimers();
	}

	// Spec Sec 7.3.4.1 Step 10. "The SLEE notifies the SLEE Facilities that
	// have references to the Activity Context that the Activ-ity
	// End Event has been delivered on the Activity Context.
	private void removeFromTimers() {
		TimerFacility timerFacility = sleeContainer.getTimerFacility();
		// Iterate through the attached timers, telling the timer facility to
		// remove them
		for (Object obj : cacheData.getAttachedTimers()) {
			timerFacility.cancelTimer((TimerID) obj, false);
		}
	}

	/**
	 * Mark this AC for garbage collection. It can no longer be used past this
	 * point.
	 *
	 */
	private void removeFromCache(TransactionContext txContext) {
		cacheData.remove();
	}

	/**
	 * attach an sbb entity to this AC.
	 *
	 * @param sbbEntityId
	 *            -- sbb entity to attach.
	 * @return true if the SBB Entity is attached successfully, otherwise when
	 *         the SBB Entitiy has already been attached before, return false
	 */

	public boolean attachSbbEntity(SbbEntityID sbbEntityId) {

		boolean attached = cacheData.attachSbbEntity(sbbEntityId);
		if (attached) {
			if (acReferencesHandler != null) {
				acReferencesHandler.sbbeReferenceCreated(false);
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Attachement from sbb entity " + sbbEntityId
					+ " to AC " + getActivityContextHandle() + " result: "
					+ attached);
		}
		return attached;
	}

	/**
	 * Detach the sbb entity
	 *
	 * @param sbbEntityId
	 */
	public void detachSbbEntity(SbbEntityID sbbEntityId)
			throws javax.slee.TransactionRequiredLocalException {

		boolean detached = cacheData.detachSbbEntity(sbbEntityId);

		if (detached && acReferencesHandler != null && !isEnding()) {
			acReferencesHandler.sbbeReferenceRemoved();
			if (logger.isTraceEnabled()) {
				logger.trace("Detached sbb entity " + sbbEntityId
						+ " from AC with handle " + getActivityContextHandle());
			}
		}
	}

	/**
	 * get an ordered copy of the set of SBBs attached to this ac. The ordering
	 * is by SBB priority.
	 *
	 * @return list of SbbEIDs
	 *
	 */
	public Set<SbbEntityID> getSortedSbbAttachmentSet(
			Set<SbbEntityID> excludeSet) {
		final Set<SbbEntityID> sbbAttachementSet = cacheData
				.getSbbEntitiesAttached();
		Set<SbbEntityID> result = new HashSet<SbbEntityID>();
		for (SbbEntityID sbbEntityId : sbbAttachementSet) {
			if (!excludeSet.contains(sbbEntityId)) {
				result.add(sbbEntityId);
			}
		}
		if (result.size() > 1) {
			result = sleeContainer.getSbbEntityFactory().sortByPriority(result);
		}
		return result;
	}

	public Set<SbbEntityID> getSbbAttachmentSet() {
		return cacheData.getSbbEntitiesAttached();
	}

	/**
	 *
	 * @return
	 */
	public long getLastAccessTime() {
		final Long time = (Long) cacheData.getObject(NODE_MAP_KEY_LAST_ACCESS);
		return time == null ? System.currentTimeMillis() : time.longValue();
	}

	// --- private helpers

	private void updateLastAccessTime(boolean creation) {
		if (creation) {
			cacheData.putObject(NODE_MAP_KEY_LAST_ACCESS,
					Long.valueOf(System.currentTimeMillis()));
		} else {
			ActivityManagementConfiguration configuration = factory
					.getConfiguration();
			Long lastUpdate = (Long) cacheData
					.getObject(NODE_MAP_KEY_LAST_ACCESS);
			if (lastUpdate != null) {
				final long now = System.currentTimeMillis();
				if ((now - configuration.getMinTimeBetweenUpdatesInMs()) > lastUpdate
						.longValue()) {
					// last update
					if (logger.isTraceEnabled()) {
						logger.trace("Updating access time for AC with handle "
								+ getActivityContextHandle());
					}
					cacheData.putObject(NODE_MAP_KEY_LAST_ACCESS,
							Long.valueOf(now));
				} else {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipping update of access time for AC with handle "
								+ getActivityContextHandle());
					}
				}
			} else {
				cacheData.putObject(NODE_MAP_KEY_LAST_ACCESS,
						Long.valueOf(System.currentTimeMillis()));
				if (logger.isTraceEnabled()) {
					logger.trace("Updating access time for AC with handle "
							+ getActivityContextHandle());
				}
			}
		}
	}

	public String toString() {
		return new StringBuilder("ActivityContext{ handle = ")
				.append(activityContextHandle).append(" }").toString();
	}

	// emmartins: added to split null activity end related logic

	public boolean isSbbAttachmentSetEmpty() {
		return cacheData.noSbbEntitiesAttached();
	}

	public boolean isAttachedTimersEmpty() {
		return cacheData.noTimersAttached();
	}

	public boolean isNamingBindingEmpty() {
		return cacheData.noNamesBound();
	}

	public boolean equals(Object obj) {
		if (obj != null && obj.getClass() == this.getClass()) {
			return ((ActivityContextImpl) obj).activityContextHandle
					.equals(this.activityContextHandle);
		} else {
			return false;
		}
	}

	public int hashCode() {
		return activityContextHandle.hashCode();
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.mobicents.slee.container.activity.ActivityContext#fireEvent(javax
	 * .slee.EventTypeID, java.lang.Object, javax.slee.Address,
	 * javax.slee.ServiceID,
	 * org.mobicents.slee.container.event.EventProcessingSucceedCallback,
	 * org.mobicents.slee.container.event.EventProcessingFailedCallback,
	 * org.mobicents.slee.container.event.EventUnreferencedCallback)
	 */
	public void fireEvent(EventTypeID eventTypeId, Object event,
			Address address, ServiceID serviceID,
			EventProcessingSucceedCallback succeedCallback,
			EventProcessingFailedCallback failedCallback,
			EventUnreferencedCallback unreferencedCallback)
			throws ActivityIsEndingException, SLEEException {

		if (isEnding()) {
			throw new ActivityIsEndingException(getActivityContextHandle()
					.toString());
		}

		if (acReferencesHandler != null) {
			acReferencesHandler.eventReferenceCreated();
		}

		fireEvent(
				sleeContainer.getEventContextFactory().createEventContext(
						eventTypeId, event, this, address, serviceID,
						succeedCallback, failedCallback, unreferencedCallback),
				sleeContainer.getTransactionManager().getTransactionContext());
	}

	public void fireEvent(EventTypeID eventTypeId, Object event,
			Address address, ServiceID serviceID,
			EventContext reference)
			throws ActivityIsEndingException, SLEEException {

		if (isEnding()) {
			throw new ActivityIsEndingException(getActivityContextHandle()
					.toString());
		}

		if (acReferencesHandler != null) {
			acReferencesHandler.eventReferenceCreated();
		}

		fireEvent(
				sleeContainer.getEventContextFactory().createEventContext(
						eventTypeId, event, this, address, serviceID,
						reference), sleeContainer
						.getTransactionManager().getTransactionContext());
	}

	/**
	 * Ends the activity context.
	 */
	public void endActivity() {
		if (logger.isDebugEnabled()) {
			logger.debug("Ending activity context with handle "
					+ getActivityContextHandle());
		}
		if (cacheData.setEnding(true)) {
			fireEvent(
					sleeContainer
							.getEventContextFactory()
							.createActivityEndEventContext(
									this,
									new ActivityEndEventUnreferencedCallback(
											getActivityContextHandle(), factory)),
					sleeContainer.getTransactionManager()
							.getTransactionContext());
		}
	}

	public void activityEnded() {

		// remove references to this AC in timer and ac naming facility
		removeNamingBindings();
		removeFromTimers(); // Spec 7.3.4.1 Step 10
		final int activityFlags = activityContextHandle.getActivityType() == ActivityType.RA ? getActivityFlags()
				: -1;

		TransactionContext txContext = sleeContainer.getTransactionManager()
				.getTransactionContext();
		removeFromCache(txContext);
		factory.removeActivityContext(this);

		if (activityContextHandle.getActivityType() == ActivityType.RA) {
			// external activity, notify RA that the activity has ended
			((ResourceAdaptorActivityContextHandle) activityContextHandle)
					.getResourceAdaptorEntity().activityEnded(
							activityContextHandle.getActivityHandle(),
							activityFlags);
		} else if (activityContextHandle.getActivityType() == ActivityType.SERVICE) {
			sleeContainer.getServiceManagement().activityEnded(
					(ServiceActivityHandle) activityContextHandle
							.getActivityHandle());
		}

	}

	private void fireEvent(EventContext event, TransactionContext txContext) {

		if (logger.isDebugEnabled()) {
			logger.debug("Firing " + event);
		}

		final ActivityEventQueueManager aeqm = event.getLocalActivityContext()
				.getEventQueueManager();
		if (aeqm != null) {
			if (txContext != null) {
				// put event as pending in ac event queue manager
				aeqm.pending(event);
				// add tx actions to commit or rollback
				txContext.getAfterCommitPriorityActions().add(
						new CommitEventContextAction(event, aeqm));
				txContext.getAfterRollbackActions().add(
						new RollbackEventContextAction(event, aeqm));
			} else {
				// commit event, there is no tx
				aeqm.fireNotTransacted(event);
			}
		} else {
			throw new SLEEException("unable to find ACs event queue manager");
		}
	}

	private LocalActivityContextImpl localActivityContext;

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.mobicents.slee.runtime.activity.ActivityContext#getLocalActivityContext
	 * ()
	 */
	public LocalActivityContextImpl getLocalActivityContext() {
		if (localActivityContext == null) {
			localActivityContext = factory.getLocalActivityContext(this);
		}
		return localActivityContext;
	}

	public void activityUnreferenced() {
		if (logger.isDebugEnabled()) {
			logger.debug("Activity Context with handle "
					+ activityContextHandle + " is now unreferenced");
		}
		switch (activityContextHandle.getActivityType()) {
		case RA:
			// external activity, notify RA that the activity is unreferenced
			((ResourceAdaptorActivityContextHandle) activityContextHandle)
					.getResourceAdaptorEntity()
					.getResourceAdaptorObject()
					.activityUnreferenced(
							activityContextHandle.getActivityHandle());
			break;
		case NULL:
			// null activity unreferenced, end it
			ActivityContext ac = sleeContainer.getActivityContextFactory()
					.getActivityContext(activityContextHandle);
			if (ac != null)
				ac.endActivity();
			break;
		default:
			// do nothing
			break;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.mobicents.slee.runtime.activity.ActivityContext#
	 * getActivityContextInterface()
	 */
	public ActivityContextInterfaceImpl getActivityContextInterface() {
		return new ActivityContextInterfaceImpl(this);
	}

	public ActivityContextReferencesHandler getAcReferencesHandler() {
		return acReferencesHandler;
	}

	@Override
	public void beforeDeliveringEvent(EventContext ec) {
		if (acReferencesHandler != null) {
			// this means the ac is attached, thus we can indicate there a sbb
			// ref
			// to the refs handler, which means that more detachs must exist
			// than
			// attachs, for a unref state must be possible
			// BUT only if there is no sbb refs yet, cause otherwise it means
			// there was an
			// attachment before delivering the event, yep the sbb entity
			// receiving the event
			// the flag is to signal if this is special sbb reference due to
			// event delivery
			acReferencesHandler.sbbeReferenceCreated(true);
		}
	}

	@Override
	public String getStringID() {
		return getStringID(true);
	}

	public String getStringID(boolean createIfNull) {
		String sid = cacheData.getStringID();
		if (sid == null && createIfNull) {
			sid = sleeContainer.getUuidGenerator().createUUID();
			cacheData.setStringID(sid);
		}
		return sid;
	}

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt 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.mobicents.slee.container.deployment.jboss;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.slee.ComponentID;

import org.jboss.logging.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.deployment.jboss.action.InstallDeployableUnitAction;
import org.mobicents.slee.container.deployment.jboss.action.ManagementAction;
import org.mobicents.slee.container.deployment.jboss.action.UninstallDeployableUnitAction;
import org.mobicents.slee.container.management.jmx.editors.ComponentIDPropertyEditor;

/**
 * This class represents a SLEE Deployable Unit, represented by a collection of
 * Deployable Components. Contains all the DU dependencies, install/uninstall
 * actions needed for the DU and post-install and pre-uninstall actions.
 *
 * @author Alexandre Mendonça
 * @version 1.0
 */
public class DeployableUnit {
  // The logger.
  private static Logger logger = Logger.getLogger(DeployableUnit.class);

  // The DeploymentInfo short name
  private String diShortName;

  // The DeploymentInfo URL object
  private URL diURL;

  // A collection of the Deployable Components in this DU
  private Collection<DeployableComponent> components = new ArrayList<DeployableComponent>();

  // A collection of the IDs of the components in this DU.
  private Collection<String> componentIDs = new ArrayList<String>();

  // A collection of the IDs of the components that this DU depends on.
  private Collection<String> dependencies = new ArrayList<String>();

  // The install actions needed to install/activate this DU components.
  private Collection<ManagementAction> installActions = new ArrayList<ManagementAction>();

  // The post-install actions needed to install/activate this DU components.
  private Map<String, Collection<ManagementAction>> postInstallActions = new HashMap<String, Collection<ManagementAction>>();

  // The pre-uninstall actions needed to deactivate/uninstall this DU components.
  private Map<String, Collection<ManagementAction>> preUninstallActions = new HashMap<String, Collection<ManagementAction>>();

  // The install actions needed to deactivate/uninstall this DU components.
  private Collection<ManagementAction> uninstallActions = new ArrayList<ManagementAction>();

  // A flag indicating wether this DU is installed
  private boolean isInstalled = false;

  private final SleeContainerDeployerImpl sleeContainerDeployer;

  /**
   * Constructor.
   * @param duDeploymentInfo this DU deployment info.
   * @param deploymentManager the DeploymentManager in charge of this DU.
   * @throws Exception
   */
  public DeployableUnit(DeployableUnitWrapper du,SleeContainerDeployerImpl sleeContainerDeployer) throws Exception
  {
	  this.sleeContainerDeployer = sleeContainerDeployer;

    this.diShortName = du.getFileName();
    this.diURL = du.getUrl();

    // First action for the DU is always install.
    installActions.add(new InstallDeployableUnitAction(diURL.toString(), sleeContainerDeployer.getDeploymentMBean()));

    // Parse the deploy-config.xml to obtain post-install/pre-uninstall actions
    parseDUDeployConfig();
  }

  /**
   * Adder method for a Deployable Component.
   * @param dc the deployable component object.
   */
  public void addComponent(DeployableComponent dc) {
    if (logger.isTraceEnabled())
      logger.trace("Adding Component " + dc.getComponentKey());

    // Add the component ..
    components.add(dc);

    // .. the key ..
    componentIDs.add(dc.getComponentKey());

    // .. the dependencies ..
    dependencies.addAll(dc.getDependencies());

    // .. the install actions to be taken ..
    installActions.addAll(dc.getInstallActions());

    // .. post-install actions (if any) ..
    Collection<ManagementAction> postInstallActionsStrings = postInstallActions
    .remove(dc.getComponentKey());

    if (postInstallActionsStrings != null
        && !postInstallActionsStrings.isEmpty()) {
      installActions.addAll(postInstallActionsStrings);
    }

    // .. pre-uninstall actions (if any) ..
    Collection<ManagementAction> preUninstallActionsStrings = preUninstallActions
    .remove(dc.getComponentKey());

    if (preUninstallActionsStrings != null)
      uninstallActions.addAll(preUninstallActionsStrings);

    // .. and finally the uninstall actions to the DU.
    uninstallActions.addAll(dc.getUninstallActions());
  }

  /**
   * Method for checking if DU is self-sufficient.
   * @return true if the DU has no external dependencies.
   */
  public boolean isSelfSufficient() {
    // All dependencies in the DU components?
    return componentIDs.containsAll(dependencies);
  }

  /**
   * Method for obtaining the external dependencies for this DU, if any.
   * @return a Collection of external dependencies identifiers.
   */
  public Collection<String> getExternalDependencies() {
    // Take all dependencies...
    Collection<String> externalDependencies = new HashSet<String>(dependencies);

    // Remove those which are contained in this DU
    externalDependencies.removeAll(componentIDs);

    // Return what's left.
    return externalDependencies;
  }

  /**
   * Method for checking if the DU has all the dependencies needed to be deployed.
   * @param showMissing param to set whether to show or not missing dependencies.
   * @return true if all the dependencies are satisfied.
   */
  public boolean hasDependenciesSatisfied(boolean showMissing) {
    // First of all check if it is self-sufficient
    if (isSelfSufficient())
      return true;

    // If not self-sufficient, get the remaining dependencies
    Collection<String> externalDependencies = getExternalDependencies();

    // Remove those that are already installed...
    externalDependencies.removeAll(sleeContainerDeployer.getDeploymentManager().getDeployedComponents());

    // Some remaining?
    if (!externalDependencies.isEmpty()) {
      if (showMissing) {
        // List them to the user...
        String missingDepList = "";

        for (String missingDep : externalDependencies)
          missingDepList += "\r\n +-- " + missingDep;

        logger.info("Missing dependencies for " + this.diShortName
            + ":" + missingDepList);
      }

      // Return dependencies not satified.
      return false;
    }

    // OK, dependencies satisfied!
    return true;
  }

  /**
   * Method for checking if this DU contains any component that is already deployed.
   * @return true if there's a component that is already deployed.
   */
  public boolean hasDuplicates() {
    ArrayList<String> duplicates = new ArrayList<String>();

    // For each component in the DU ..
    for (String componentId : componentIDs) {
      // Check if it is already deployed
      if (sleeContainerDeployer.getDeploymentManager().getDeployedComponents().contains(componentId)) {
        duplicates.add(componentId);
      }
    }

    if (!duplicates.isEmpty()) {
      logger.warn("The deployable unit '" + this.diShortName + "' contains components that are already deployed. The following are already installed:");

      for (String dupComponent : duplicates) {
        logger.warn(" - " + dupComponent);
      }

      return true;
    }

    // If we got here, there's no dups.
    return false;
  }

  /**
   * Method for doing all the checking to make sure it is ready to be installed.
   * @param showMissing param to set whether to show or not missing dependencies.
   * @return true if all the pre-reqs are met.
   */
  public boolean isReadyToInstall(boolean showMissing) {
    // Check if the deps are satisfied and there are no dups.
    return hasDependenciesSatisfied(showMissing) && !hasDuplicates();
  }

  /**
   * Getter for the Install Actions.
   * @return a Collection of actions.
   */
  public Collection<ManagementAction> getInstallActions() {

	  ArrayList<ManagementAction> iActions = new ArrayList<ManagementAction>();

	  // if we have some remaining post install actions it means it is actions related with components already installed
	  // thus should be executed first
	  if (!postInstallActions.values().isEmpty()) {
		  for (String componentId : postInstallActions.keySet()) {
			  iActions.addAll(postInstallActions.get(componentId));
		  }
	  }

	  iActions.addAll(installActions);

	  return iActions;
  }

  /**
   * Getter for the Uninstall Actions.
   * @return a Collection of actions.
   */
  public Collection<ManagementAction> getUninstallActions() {
	  Collection<ManagementAction> uActions = new ArrayList<ManagementAction>(uninstallActions);

	  // ensures uninstall is the last action related with DU components
	  uActions.add(new UninstallDeployableUnitAction(diURL.toString(), sleeContainerDeployer.getDeploymentMBean()));

	  // if we have some remaining uninstall actions it means it is actions related with components not in DU
	  // thus should be executed last
	  if (!preUninstallActions.values().isEmpty()) {
		  for (String componentId : preUninstallActions.keySet()) {
			  uActions.addAll(preUninstallActions.get(componentId));
		  }
	  }

	  return uActions;
  }

  /**
   * Getter for this DU components.
   * @return a Collection of component identifiers.
   */
  public Collection<String> getComponents() {
    return componentIDs;
  }

  /**
   * Method for doing all the checking to make sure it is ready to be uninstalled.
   * @return true if all the pre-reqs are met.
   * @throws Exception
   */
  public boolean isReadyToUninstall() throws Exception {
    // Check DU for readiness ..
    return isInstalled && !hasReferringDU();
  }

  /**
   * Method for checking if this DU components are referred by any others.
   * @return true if there are other DUs installed referring this.
   * @throws Exception
   */
  private boolean hasReferringDU() throws Exception {

    // Get SleeContainer instance from JNDI
    SleeContainer sC = SleeContainer.lookupFromJndi();

    for (String componentIdString : this.getComponents()) {
      ComponentIDPropertyEditor cidpe = new ComponentIDPropertyEditor();
      cidpe.setAsText( componentIdString );

      ComponentID componentId = (ComponentID) cidpe.getValue();

      for (ComponentID referringComponentId : sC.getComponentRepository().getReferringComponents(componentId)) {
        ComponentIDPropertyEditor rcidpe = new ComponentIDPropertyEditor();
        rcidpe.setValue( referringComponentId );

        String referringComponentIdString = rcidpe.getAsText();

        if (!this.getComponents().contains( referringComponentIdString )) {
          return true;
        }
      }
    }

    return false;
  }

  /**
   * Getter for the DeploymentInfo short name
   * @return a String containing the filename
   */
  public String getDeploymentInfoShortName() {
    return this.diShortName;
  }

  /**
   * Setter for the isInstalled flag.
   * @return a boolean indicating if the DU is already installed.
   */
  public boolean isInstalled() {
    return isInstalled;
  }

  /**
   * Setter for the isInstalled flag.
   * @param isInstalled the isInstalled flag indicating that the DU is already installed.
   */
  public void setInstalled(boolean isInstalled) {
    this.isInstalled = isInstalled;
  }

	/**
	 * Parser for the deployment config xml.
	 *
	 * @throws Exception
	 */
	private void parseDUDeployConfig() throws Exception  {
		JarFile componentJarFile = new JarFile(diURL.getFile());
		try {
			// Get the JarEntry for the deploy-config.xml
			JarEntry deployInfoXML = componentJarFile
					.getJarEntry("META-INF/deploy-config.xml");
			if (deployInfoXML != null) {
				DeployConfigParser deployConfigParser = new DeployConfigParser(
						componentJarFile.getInputStream(deployInfoXML),
						sleeContainerDeployer.getSleeContainer()
								.getResourceManagement());
				for (Entry<String, Collection<ManagementAction>> e : deployConfigParser.getPostInstallActions().entrySet()) {
					postInstallActions.put(e.getKey(), e.getValue());
				}
				for (Entry<String, Collection<ManagementAction>> e : deployConfigParser.getPreUninstallActions().entrySet()) {
					preUninstallActions.put(e.getKey(), e.getValue());
				}
			}
		} finally {
			componentJarFile.close();
		}
	}

  public URL getURL() {
    return diURL;
  }

  public boolean areComponentsStillPresent() {
    Collection<String> presentComponents = sleeContainerDeployer.getDeploymentManager().getDeployedComponents();

    for(String cId : this.componentIDs) {
      if(!presentComponents.contains(cId)) {
        return false;
      }
    }

    return true;
  }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt 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.mobicents.slee.container.profile;

import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.slee.InvalidStateException;
import javax.slee.SLEEException;
import javax.slee.management.ManagementException;
import javax.slee.profile.ProfileImplementationException;
import javax.slee.profile.ProfileMBean;
import javax.slee.profile.ProfileVerificationException;
import javax.slee.profile.ReadOnlyProfileException;
import javax.transaction.RollbackException;
import javax.transaction.Transaction;

import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.jndi.JndiManagement;
import org.mobicents.slee.container.profile.entity.ProfileEntity;
import org.mobicents.slee.container.security.Utility;
import org.mobicents.slee.container.transaction.SleeTransactionManager;

import java.util.Map;

/**
 * Start time:12:23:15 2009-03-18

 * Project: restcomm-jainslee-server-core

 *

 * This is stub class, that is extended and instrumented to allow access to
 * underlying Profile
 *
 * @author  Bartosz Baranowski
 * @author  Alexandre Mendonca
 * @author martins
 */
public abstract class AbstractProfileMBeanImpl extends StandardMBean implements AbstractProfileMBean {

    private enum State {read, write}

    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger(AbstractProfileMBeanImpl.class);

    protected final static SleeContainer sleeContainer = SleeContainer.lookupFromJndi();
    private JndiManagement jndiManagement;

    /**
     * the table related with the profile mbean
     */
    private final ProfileTableImpl profileTable;

    /**
     * the name of the profile assigned to the mbean
     */
    private final String profileName;

    /**
     * current state in the mbean
     */
    private State state = State.read;

    /**
     * the transaction curretly assigned to the mbean, if any
     */
    private Transaction transaction;

    /**
     * @param mbeanInterface
     * @param profileName
     * @param profileTable
     * @throws NotCompliantMBeanException
     */
    public AbstractProfileMBeanImpl(Class<?> mbeanInterface, String profileName, ProfileTableImpl profileTable) throws NotCompliantMBeanException, ManagementException {
        super(mbeanInterface);
        this.profileTable = profileTable;
        this.profileName = profileName;
    }

    /**
     * Retrieves the profile object currently bound to the mbean transaction
     *
     * @return
     */
    protected ProfileObjectImpl getProfileObject() {
        return profileTable.getProfile(profileName);
    }

    /**
     * Registers the mbean in the server
     *
     * @throws ManagementException
     */
    public void register() throws ManagementException {
//		  try {
//			  sleeContainer.getMBeanServer().registerMBean(this, getObjectName());
//		  } catch (Throwable e) {
//			  throw new ManagementException(e.getMessage(),e);
//		  }
        Utility.registerSafelyMBean(sleeContainer, getObjectName(), this);
    }

    /**
     * Unregisters the mbean in the server
     *
     * @throws ManagementException
     */
    public void unregister() throws ManagementException {
//		try {
//			sleeContainer.getMBeanServer().unregisterMBean(getObjectName());
//		} catch (Throwable e) {
//			throw new ManagementException(e.getMessage(),e);
//		}
        Utility.unregisterSafelyMBean(sleeContainer, getObjectName());
    }

    public void close() throws ManagementException {
        try {
            // close mbean
            if (state == State.write) {
                restoreProfile();
            }
            closeProfile();
        } catch (Throwable e) {
            throw new ManagementException(e.getMessage(), e);
        }
    }

    /**
     * Closes and unregisters the mbean for the specified profile, if exists
     *
     * @param profileTableName
     * @param profileName
     */
    public static void close(String profileTableName, String profileName) {
        final ObjectName objectName = getObjectName(profileTableName, profileName);
        if (sleeContainer.getMBeanServer().isRegistered(objectName)) {
            Runnable r = new Runnable() {
                public void run() {
                    try {
                        sleeContainer.getMBeanServer().invoke(objectName, "close", new Object[]{}, new String[]{});
                    } catch (Throwable e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            };
            Thread t = new Thread(r);
            t.start();
        }
    }

    /**
     * Retrieves the object name used to register this mbean.
     *
     * @return
     */
    public ObjectName getObjectName() {
        return getObjectName(profileTable.getProfileTableName(), profileName);
    }

    /**
     * Retrieves the JMX ObjectName for a profile, given its profile name and
     * profile table name
     *
     * @param profileTableName
     * @param profileName
     * @return
     * @throws MalformedObjectNameException
     */
    public static ObjectName getObjectName(
            String profileTableName, String profileName) {
        // FIXME use only the "quoted" version when issue is fully solved at the JMX Console side
        try {
            return new ObjectName(ProfileMBean.BASE_OBJECT_NAME + ','
                    + ProfileMBean.PROFILE_TABLE_NAME_KEY + '='
                    + profileTableName + ','
                    + ProfileMBean.PROFILE_NAME_KEY + '='
                    + (profileName != null ? profileName : ""));
        } catch (Throwable e) {
            try {
                return new ObjectName(ProfileMBean.BASE_OBJECT_NAME + ','
                        + ProfileMBean.PROFILE_TABLE_NAME_KEY + '='
                        + ObjectName.quote(profileTableName) + ','
                        + ProfileMBean.PROFILE_NAME_KEY + '='
                        + ObjectName.quote(profileName != null ? profileName : ""));
            } catch (Throwable f) {
                throw new SLEEException(e.getMessage(), e);
            }
        }
    }

    /**
     * Retrieves the profile name
     *
     * @return
     */
    protected String getProfileName() {
        return profileName;
    }

    /**
     * Retreives the profile tbale name
     *
     * @return
     */
    protected String getProfileTableName() {
        return profileTable.getProfileTableName();
    }

    /**
     * Moves to the write mode, using specified object. The current java transaction will be suspended.
     *
     * @throws ManagementException
     */
    private void writeMode() throws SLEEException, ManagementException {
        if (!isProfileWriteable()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Changing state to read-write, for profile mbean with name " + profileName + ", from table with name " + this.profileTable.getProfileTableName());
            }
            // get object & make it writable
            ProfileObjectImpl profileObject = profileTable.getProfile(profileName);
            profileObject.getProfileEntity().setReadOnly(false);
            // change state
            state = State.write;
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Already in write state, for profile mbean with name " + profileName + ", from table with name " + this.profileTable.getProfileTableName());
            }
        }
    }

    /**
     *
     */
    private void readMode() {

        if (logger.isDebugEnabled()) {
            logger.debug("Changing state to read-only, for profile mbean with name " + profileName + ", from table with name " + this.profileTable.getProfileTableName());
        }
        state = State.read;
    }

    // #################
    // # MBean methods #
    // #################

    /*
     * (non-Javadoc)
     * @see javax.slee.profile.ProfileMBean#closeProfile()
     */
    public void closeProfile() throws InvalidStateException, ManagementException {
        if (logger.isDebugEnabled()) {
            logger.debug("closeProfile() on: " + profileName + ", from table:" + profileTable.getProfileTableName());
        }

        // The closeProfile method must throw a javax.slee.InvalidStateException if the Profile MBean object is in the read-write state.
        if (this.isProfileWriteable())
            throw new InvalidStateException();
        // unregister mbean
        unregister();

    }

    /*
     * (non-Javadoc)
     * @see javax.slee.profile.ProfileMBean#commitProfile()
     *
     * This method commits profile. See descritpion - profile becomes visible for slee ONLY when its commited.
     */
    public void commitProfile() throws InvalidStateException, ProfileVerificationException, ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("commitProfile() on: " + profileName + ", from table:" + profileTable.getProfileTableName());
        }

        if (!this.isProfileWriteable())
            throw new InvalidStateException("not in write state");

        final SleeTransactionManager txManager = sleeContainer.getTransactionManager();

        ProfileEntity profileEntity = null;

        try {

            // resume tx
            txManager.resume(this.transaction);

            // verify state
            ProfileObjectImpl profileObject = getProfileObject();
            profileObject.profileVerify();

            // check if the tx is marked for rollback
            if (txManager.getRollbackOnly()) {
                // FIXME can't undertsand why the rollback and change of state, perhaps tests/profiles/lifecycle/Test1110227Test.xml is faulty??
                // FIXME: Do we need to close EntityManager here?
                EntityManager em = getEntityManager(txManager);
                txManager.rollback();
                if (em != null && em.isOpen()) {
                    em.close();
                }
                readMode();
                this.transaction = null;
                throw new RollbackException("the tx is marked for rollback, can't proceeed with commit");
            }

            // "save" profile entity, we may need to recover its current state
            profileEntity = profileObject.getProfileEntity();

            // commit tx
            this.transaction = null;
            txManager.commit();

            // change mode
            readMode();

        } catch (ProfileVerificationException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            throw e;
        } catch (RollbackException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            if (e.getCause() instanceof PersistenceException && e.getCause().getCause() instanceof ConstraintViolationException) {
                throw new ProfileVerificationException(e.getCause().getMessage(), e);
            } else if (e.getCause() != null && e.getCause().getClass() == Throwable.class && e.getCause().getMessage() != null && e.getCause().getMessage().equals("setRollbackOnly called from:")) {
                // workaround for issue with jboss AS 5.1.0 GA / Hibernate Entity Manager 3.4.0.GA / JBoss TS 4.6.1.GA, the persistentexception is not thrown
                throw new ProfileVerificationException(e.getCause().getMessage(), e);
            } else {
                throw new ManagementException(e.getMessage(), e);
            }
        } catch (Throwable e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
            throw new ManagementException(e.getMessage(), e);

        } finally {
            if (this.transaction == null) {
                if (isProfileWriteable()) {
                    // still in write mode
                    if (logger.isDebugEnabled()) {
                        logger.debug("The tx commit failed, recreating tx with current profile entity state");
                    }
                    try {
                        txManager.begin();
                        this.transaction = txManager.getTransaction();
                        ProfileEntity newTxProfileEntity = profileEntity.isCreate() ? profileTable.createProfile(profileName).getProfileEntity() : getProfileObject().getProfileEntity();
                        profileTable.getProfileSpecificationComponent().getProfileEntityFramework().getProfileEntityFactory().copyAttributes(profileEntity, newTxProfileEntity);
                        newTxProfileEntity.setReadOnly(false);
                        txManager.suspend();
                    } catch (Throwable e) {
                        throw new ManagementException(e.getMessage(), e);
                    }
                }
            } else {
                // tx still valid
                try {
                    txManager.suspend();
                } catch (Throwable f) {
                    logger.error(f.getMessage(), f);
                }
            }
        }
    }

    public void createProfile() throws ManagementException {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating profile with name " + profileName + ", from table with name " + this.profileTable.getProfileTableName());
        }

        if (!isProfileWriteable()) {
            final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
            try {
                writeMode();
                this.transaction = txManager.suspend();
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }

        }
    }

    public void editProfile() throws ManagementException {
        if (logger.isDebugEnabled()) {
            logger.debug("Editing profile with name " + profileName + ", from table with name " + this.profileTable.getProfileTableName());
        }

        if (!isProfileWriteable()) {
            final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
            try {
                txManager.begin();
                writeMode();
                this.transaction = txManager.suspend();
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }

        }
    }

    public boolean isProfileDirty() throws ManagementException {
        if (logger.isDebugEnabled()) {
            logger.debug("isProfileDirty() on: " + profileName + ", from table:" + profileTable.getProfileTableName());
        }
        /*
         * The isProfileDirty method returns true if the Profile MBean object is
		 * in the read-write state and the dirty flag of the Profile Management
		 * object that caches the persistent state of the Profile returns true.
		 * This method returns false under any other situation.
		 */

        if (isProfileWriteable()) {
            final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
            try {
                txManager.resume(this.transaction);
                return getProfileObject().getProfileEntity().isDirty();
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            } finally {
                try {
                    txManager.suspend();
                } catch (Throwable e) {
                    throw new ManagementException(e.getMessage(), e);
                }
            }
        } else {
            return false;
        }

    }

    public boolean isProfileWriteable() throws ManagementException {
        return state == State.write;
    }

    private EntityManager getEntityManager(SleeTransactionManager manager) {
        String txDataKey = new StringBuilder("jpapef.em.").append(profileTable.getProfileSpecificationComponent().getProfileSpecificationID()).toString();
        Map transactionContextData = manager.getTransactionContext().getData();
        EntityManager result = (EntityManager) transactionContextData.get(txDataKey);
        return result;
    }

    public void restoreProfile() throws InvalidStateException, ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("restoreProfile() on: " + profileName + ", from table:" + profileTable.getProfileTableName());
        }

        if (!isProfileWriteable()) {
            throw new InvalidStateException("The restoreProfile method must throw a javax.slee.InvalidStateException if the Profile MBean object is not in the read-write state.");
        }

        final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
        try {
            txManager.resume(transaction);

            // FIXME: Do we need to close EntityManager here?
            EntityManager em = getEntityManager(txManager);
            txManager.rollback();
            if (em != null && em.isOpen()) {
                em.close();
            }

            transaction = null;
            readMode();
        } catch (Throwable e) {
            throw new ManagementException(e.getMessage(), e);
        }
    }

    // cmp accessor helpers

    /**
     * Logic to execute before invoking a cmp setter method on the mbean
     *
     * @return true if the method resumed a transaction
     * @throws ManagementException
     */
    protected void beforeSetCmpField() throws ManagementException, InvalidStateException {

        if (logger.isDebugEnabled()) {
            logger.debug("beforeSetCmpField() on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        if (isProfileWriteable()) {
            try {
                sleeContainer.getTransactionManager().resume(transaction);
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }
        } else {
            throw new InvalidStateException();
        }
    }

    /**
     * Logic to execute after invoking a cmp setter method on the mbean
     *
     * @throws ManagementException
     */
    protected void afterSetCmpField() throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("afterSetCmpField() on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        try {
            sleeContainer.getTransactionManager().suspend();
        } catch (Throwable e) {
            throw new ManagementException(e.getMessage(), e);
        }
    }

    /**
     * Logic to execute before invoking a cmp getter method on the mbean
     *
     * @return true if the method initiated or resumed a transaction
     * @throws ManagementException
     */
    protected boolean beforeGetCmpField() throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("beforeGetCmpField() on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        return beforeNonSetCmpField();
    }

    /**
     * Logic to execute after invoking a cmp getter method on the mbean
     *
     * @param activatedTransaction if the method beforeGetCmpField() initiated or resumed a transaction
     * @throws ManagementException
     */
    protected void afterGetCmpField(boolean activatedTransaction) throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("afterGetCmpField( activatedTransaction = " + activatedTransaction + " ) on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        afterNonSetCmpField(activatedTransaction);
    }

    private boolean beforeNonSetCmpField() throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("beforeNonSetCmpField() on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        if (isProfileWriteable()) {
            try {
                sleeContainer.getTransactionManager().resume(transaction);
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }
            return true;
        } else {
            // not in write mode, create transaction just for this invocation
            try {
                sleeContainer.getTransactionManager().begin();
                return true;
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }
        }
    }

    private void afterNonSetCmpField(boolean activatedTransaction) throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("afterNonSetCmpField( activatedTransaction = " + activatedTransaction + " ) on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        if (activatedTransaction) {
            if (isProfileWriteable()) {
                try {
                    sleeContainer.getTransactionManager().suspend();
                } catch (Throwable e) {
                    throw new ManagementException(e.getMessage(), e);
                }
            } else {
                try {
                    final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
                    if (txManager.getRollbackOnly()) {
                        // FIXME: Do we need to close EntityManager here?
                        EntityManager em = getEntityManager(txManager);
                        txManager.rollback();
                        if (em != null && em.isOpen()) {
                            em.close();
                        }
                    } else {
                        txManager.commit();
                    }
                } catch (Throwable e) {
                    throw new ManagementException(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Logic to execute before invoking a management method on the mbean
     *
     * @return true if the method initiated or resumed a transaction
     * @throws ManagementException
     */
    protected boolean beforeManagementMethodInvocation() throws ManagementException {

        if (logger.isDebugEnabled()) {
            logger.debug("beforeManagementMethodInvocation() on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        jndiManagement = sleeContainer.getJndiManagement();
        jndiManagement.pushJndiContext(profileTable.getProfileSpecificationComponent());

        return beforeNonSetCmpField();
    }

    /**
     * Logic to execute after invoking a management method on the mbean
     *
     * @param activatedTransaction if the method beforeManagementMethodInvocation() initiated or resumed a transaction
     * @throws ManagementException
     */
    protected void afterManagementMethodInvocation(boolean activatedTransaction) throws ManagementException {
        if (logger.isDebugEnabled()) {
            logger.debug("afterManagementMethodInvocation( activatedTransaction = " + activatedTransaction + " ) on profile with name " + profileName + " of table " + profileTable.getProfileTableName());
        }

        afterNonSetCmpField(activatedTransaction);

        jndiManagement.popJndiContext();
    }

    /**
     * Handles a {@link Throwable}, which was the result of a management method invocation
     *
     * @param t
     * @throws ProfileImplementationException
     * @throws InvalidStateException
     * @throws ManagementException
     */
    protected void throwableOnManagementMethodInvocation(Throwable t) throws ProfileImplementationException, InvalidStateException, ManagementException {
        if (t instanceof ProfileImplementationException) {
            throw (ProfileImplementationException) t;
        } else if (t instanceof InvalidStateException) {
            throw (InvalidStateException) t;
        } else if (t instanceof ReadOnlyProfileException) {
            throw new InvalidStateException(t.getMessage());
        } else if (t instanceof ManagementException) {
            throw (ManagementException) t;
        } else if (t instanceof RuntimeException) {
            try {
                getProfileObject().invalidateObject();
                sleeContainer.getTransactionManager().setRollbackOnly();
            } catch (Throwable e) {
                throw new ManagementException(e.getMessage(), e);
            }
            throw new ProfileImplementationException(t);
        } else {
            // checked exception
            throw new ProfileImplementationException(t);
        }
    }

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt 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.mobicents.slee.container.event;

import java.util.LinkedList;
import java.util.Set;

import javax.slee.ActivityContextInterface;
import javax.slee.Address;
import javax.slee.EventTypeID;
import javax.slee.SLEEException;
import javax.slee.ServiceID;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.resource.FailureReason;

import org.mobicents.slee.container.activity.ActivityContextHandle;
import org.mobicents.slee.container.activity.LocalActivityContext;
import org.mobicents.slee.container.component.service.ServiceComponent;
import org.mobicents.slee.container.sbbentity.SbbEntityID;

/**
 * A differed event. When an SBB posts an event, it winds up as one of these.
 * When the tx commits, it actually makes it into the event queue.
 *
 * @author M. Ranganathan
 * @author Ivelin Ivanov (refactoring)
 * @author eduardomartins
 *
 */
public class EventContextImpl extends LazyStoredEventContext implements EventContext {

	private final EventContextData data;
	private EventContextSuspensionHandler suspensionHandler;

	public EventContextImpl(EventContextData data, EventContextFactoryImpl factory) {
		super(factory);
		this.data = data;
	}

	private void suspensionHandlerLazyInit() {
		if (suspensionHandler == null) {
			suspensionHandler = new EventContextSuspensionHandler(this);
		}
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#barrierEvent(org.mobicents.slee.container.event.EventContext)
	 */
	public void barrierEvent(EventContext event) {
		suspensionHandlerLazyInit();
		suspensionHandler.barrierEvent(event);
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#getActiveServicesToProcessEventAsInitial()
	 */
	public LinkedList<ServiceComponent> getActiveServicesToProcessEventAsInitial() {
		return data.getActiveServicesToProcessEventAsInitial();
	}

	/*
	 * (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#getActivityContextHandle()
	 */
	public ActivityContextHandle getActivityContextHandle() {
		return data.getLocalActivityContext().getActivityContextHandle();
	}

	/* (non-Javadoc)
	 * @see javax.slee.EventContext#getActivityContextInterface()
	 */
	public ActivityContextInterface getActivityContextInterface() {
		return factory.getSleeContainer().getActivityContextFactory().getActivityContext(getActivityContextHandle()).getActivityContextInterface();
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#getReferencesHandler()
	 */
	public ReferencesHandler getReferencesHandler() {
		return data.getReferencesHandler();
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#getSbbEntitiesThatHandledEvent()
	 */
	public Set<SbbEntityID> getSbbEntitiesThatHandledEvent() {
		return data.getSbbEntitiesThatHandledEvent();
	}

	/* (non-Javadoc)
	 * @see javax.slee.EventContext#isSuspended()
	 */
	public boolean isSuspended() throws TransactionRequiredLocalException,
			SLEEException {
		suspensionHandlerLazyInit();
		return suspensionHandler.isSuspended();
	}
	 /* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#isSuspendedNotTransacted()
	 */
	public boolean isSuspendedNotTransacted() {
		suspensionHandlerLazyInit();
		return suspensionHandler.isSuspendedNotTransacted();
	}

	/* (non-Javadoc)
	 * @see javax.slee.EventContext#resumeDelivery()
	 */
	public void resumeDelivery() throws IllegalStateException,
			TransactionRequiredLocalException, SLEEException {
		suspensionHandlerLazyInit();
		suspensionHandler.resumeDelivery();
	}

	/* (non-Javadoc)
	 * @see javax.slee.EventContext#suspendDelivery()
	 */
	public void suspendDelivery() throws IllegalStateException,
			TransactionRequiredLocalException, SLEEException {
		suspendDelivery(factory.getConfiguration().getDefaultEventContextSuspensionTimeout());
	}

	/* (non-Javadoc)
	 * @see javax.slee.EventContext#suspendDelivery(int)
	 */
	public void suspendDelivery(int arg0) throws IllegalArgumentException,
			IllegalStateException, TransactionRequiredLocalException,
			SLEEException {
		suspensionHandlerLazyInit();
		suspensionHandler.suspendDelivery(arg0);
	}

	public Address getAddress() {
		return data.getAddress();
	}

	public Object getEvent() {
		return data.getEventObject();
	}

	public EventTypeID getEventTypeId() {
		return data.getEventTypeId();
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#eventProcessingFailed(javax.slee.resource.FailureReason)
	 */
	public void eventProcessingFailed(FailureReason reason) {
		if (data.getFailedCallback() != null) {
			try {
				data.getFailedCallback().eventProcessingFailed(reason);
			}
			catch (Throwable e) {
				// ignore
			}
		}
		canceled();
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#eventProcessingSucceed()
	 */
	public void eventProcessingSucceed(boolean sbbProcessedEvent) {
		if (data.getSucceedCallback() != null) {
			data.getSucceedCallback().eventProcessingSucceed(sbbProcessedEvent);
			// ensure failed never gets called, even if event is refired
			data.unsetFailedCallback();
		}
	}

	public LocalActivityContext getLocalActivityContext() {
		return data.getLocalActivityContext();
	}

	public ServiceID getService() {
		return data.getService();
	}

	public EventProcessingSucceedCallback getSucceedCallback() {
		return data.getSucceedCallback();
	}

	public EventUnreferencedCallback getUnreferencedCallback() {
		return data.getUnreferencedCallback();
	}

	@Override
	public String toString() {
		return new StringBuilder("EventContext[").append(
				data).append(']').toString();
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.core.event.SleeEvent#isActivityEndEvent()
	 */
	public boolean isActivityEndEvent() {
		return false;
	}

	/**
	 *
	 */
	protected void eventUnreferenced() {
		if (data.getUnreferencedCallback() != null) {
			data.getUnreferencedCallback().eventUnreferenced();
			data.unsetUnreferencedCallback();
		}
	}

	/* (non-Javadoc)
	 * @see org.mobicents.slee.container.event.EventContext#unreferencedCallbackRequiresTransaction()
	 */
	public boolean routedRequiresTransaction() {
		final EventUnreferencedCallback unreferencedCallback = data.getUnreferencedCallback();
		if (unreferencedCallback == null) {
			return false;
		}
		else {
			return unreferencedCallback.requiresTransaction();
		}
	}

	@Override
	public void fired() {
		final ReferencesHandler handler = data.getReferencesHandler();
		if (handler != null) {
			handler.add(data.getLocalActivityContext().getActivityContextHandle());
		}
	}

	@Override
	public void canceled() {
		routed();
	}

	@Override
	public void routed() {
		final ReferencesHandler handler = data.getReferencesHandler();
		if (handler != null) {
			handler.remove(data.getLocalActivityContext().getActivityContextHandle());
		}
		remove();
	}

}


package javax.slee.management;

import javax.management.ObjectName;

/**
 * This class identifies a notification such as an {@link AlarmNotification alarm} or
 * {@link TraceNotification trace} notification as being generated in response to
 * some action performed by a resource adaptor entity.  For example, if a resource
 * adaptor entity raises an alarm using the {@link javax.slee.facilities.AlarmFacility
 * alarm facility}, an alarm notification will be generated containing a
 * ResourceAdaptorEntityNotification object that identifies the resource
 * adaptor entity that raised the alarm.
 * @since SLEE 1.1
 */
public final class ResourceAdaptorEntityNotification extends AbstractNotificationSource implements NotificationSource {
    /**
     * The JMX notification type of alarm notifications that are generated in response
     * to a resource adaptor entity interacting with the {@link javax.slee.facilities.AlarmFacility}.
     *

     * The notification type is equal to the string "javax.slee.management.alarm.raentity".
     */
    public static final String ALARM_NOTIFICATION_TYPE = "javax.slee.management.alarm.raentity";

    /**
     * The JMX notification type of trace notifications that are generated in response
     * to a resource adaptor entity interacting with a {@link javax.slee.facilities.Tracer}
     * object.
     *

     * The notification type is equal to the string "javax.slee.management.trace.raentity".
     */
    public static final String TRACE_NOTIFICATION_TYPE = "javax.slee.management.trace.raentity";

    /**
     * The JMX notification type of usage notifications that are generated by a
     * {@link javax.slee.usage.UsageMBean} containing a ResourceAdaptorEntityNotification
     * as a notification source.
     *

     * The notification type is equal to the string "javax.slee.management.usage.raentity".
     */
    public static final String USAGE_NOTIFICATION_TYPE = "javax.slee.management.usage.raentity";

    /**
     * The JMX Object Name property key that identifies the name of the resource adaptor
     * entity in a Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "raEntityName".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @since SLEE 1.1
     */
    public static final String RESOURCE_ADAPTOR_ENTITY_NAME_KEY = "raEntityName";

    /**
     * Create a new ResourceAdaptorEntityNotification object that uniquely
     * identifies a resource adaptor entity.
     * @param entityName the name of the resource adaptor entity.
     * @throws NullPointerException if entityName is null.
     */
    public ResourceAdaptorEntityNotification(String entityName) {
        if (entityName == null) throw new NullPointerException("entityName is null");
        this.entityName = entityName;
    }

    /**
     * Get the name of the resource adaptor entity of this notification source.
     * @return the name of the resource adaptor entity.
     */
    public String getEntityName() {
        return entityName;
    }

    /**
     * Get the JMX notification type of alarm notifications generated in response
     * to a resource adaptor entity interacting with the Alarm Facility.
     * @return the string defined by {@link #ALARM_NOTIFICATION_TYPE}.
     */
    public String getAlarmNotificationType() {
        return ALARM_NOTIFICATION_TYPE;
    }

    /**
     * Get the JMX notification type of trace notifications generated in response
     * to a resource adaptor entity interacting with the Trace Facility.
     * @return the string defined by {@link #TRACE_NOTIFICATION_TYPE}.
     */
    public String getTraceNotificationType() {
        return TRACE_NOTIFICATION_TYPE;
    }

    /**
     * Get the JMX notification type of usage notifications generated in response
     * to a resource adaptor entity interacting with its usage parameters.
     * @return the string defined by {@link #USAGE_NOTIFICATION_TYPE}.
     */
    public String getUsageNotificationType() {
        return USAGE_NOTIFICATION_TYPE;
    }

    /**
     * Get a JMX Object Name property string that uniquely identifies the specified
     * resource adaptor entity, suitable for inclusion in the Object Name of a Usage
     * MBean.  This method makes use of the {@link ObjectName#quote} method
     * to ensure that the resource adaptor entity name is valid for inclusion as property
     * values in an Object Name.
     *

     * This method can be used as follows to manually construct a complete Object Name
     * for a Usage MBean:
     *

     *     ObjectName name = new ObjectName(

     *         {@link javax.slee.usage.UsageMBean#BASE_OBJECT_NAME} + "," +

     *         {@link javax.slee.usage.UsageMBean#USAGE_PARAMETER_SET_NAME_KEY} + "=" + ObjectName.quote(paramSetName) + ","   // optional

     *         {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY} + "=" + {@link #USAGE_NOTIFICATION_TYPE ResourceAdaptorEntityNotification.USAGE_NOTIFICATION_TYPE} + "," +

     *         {@link #getUsageMBeanProperties(String) ResourceAdaptorEntityNotification.getUsageMBeanProperties(entityName)}

     *     );
     *

     * @param entityName the name of the resource adaptor entity.
     * @return an Object Name property string that uniquely identifies the resource
     *        adaptor entity.
     * @throws NullPointerException if entityName is null.
     */
    public static String getUsageMBeanProperties(String entityName) {
        if (entityName == null) throw new NullPointerException("entityName is null");
        return RESOURCE_ADAPTOR_ENTITY_NAME_KEY + '=' + ObjectName.quote(entityName);
    }

    /**
     * Get a JMX Object Name property string that uniquely identifies the resource
     * adaptor entity of this notification source, suitable for inclusion in the Object Name
     * of a Usage MBean.
     *

     * This method is equivalent to {@link #getUsageMBeanProperties(String) getUsageMBeanProperties(getEntityName())}.
     * @return an Object Name property string that uniquely identifies the resource
     *        adaptor entity of this notification source.
     */
    public String getUsageMBeanProperties() {
        return getUsageMBeanProperties(entityName);
    }

    /**
     * Compare this notification source for equality with another object.
     * @param obj the object to compare this with.
     * @return true if obj is an instance of this class and
     *        contains the same resource adaptor entity name as this, false
     *        otherwise.
     */
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof ResourceAdaptorEntityNotification)) return false;

        return this.entityName.equals(((ResourceAdaptorEntityNotification)obj).entityName);
    }

    /**
     * Get a hash code value for this notification source.
     * @return a hash code value for this notification source.
     */
    public int hashCode() {
        return entityName.hashCode();
    }

    /**
     * Get a string representation for this notification source.
     * @return a string representation for this notification source.
     * @see Object#toString()
     */
    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("RAEntityNotification[entity=").append(entityName).append(']');
        return buf.toString();
    }

    /**
     * Compare this notification source with the specified object for order.
     * Returns a negative integer, zero, or a positive integer if this object
     * is less than, equal to, or greater than the specified object.
     *

     * If obj is a ResourceAdaptorEntityNotification,
     * order is determined by comparing the encapsulated resource adaptor entity
     * name.  Otherwise, if obj is a NotificationSource,
     * ordering is determined by comparing the class name of this class with the
     * class name of obj.
     * @param obj the object to compare this with.
     * @return a negative integer, zero, or a positive integer if this notification
     *        source is considered less than, equal to, or greater than the
     *        specified object.
     * @throws ClassCastException if obj does not implement the
     *        {@link NotificationSource} interface.
     * @see Comparable#compareTo(Object)
     */
    public int compareTo(Object obj) {
        // can't compare with null
        if (obj == null) throw new NullPointerException("obj is null");
        if (obj == this) return 0;

        if (obj instanceof ResourceAdaptorEntityNotification) {
            // compare the entity name
            ResourceAdaptorEntityNotification that = (ResourceAdaptorEntityNotification)obj;
            return this.entityName.compareTo(that.entityName);
        }
        else {
            return super.compareTo(TYPE, obj);
        }
    }

    // protected

    protected String getClassName() {
        return TYPE;
    }

    private final String entityName;

    // constant to avoid expensive getClass() invocations at runtime
    private static final String TYPE = ResourceAdaptorEntityNotification.class.getName();
}


package javax.slee.management;

import javax.management.ObjectName;
import javax.slee.SbbID;
import javax.slee.ServiceID;

/**
 * This class identifies a notification such as an {@link AlarmNotification alarm} or
 * {@link TraceNotification trace} notification as being generated in response to
 * some action performed by an SBB.  For example, if an SBB raises an alarm using the
 * {@link javax.slee.facilities.AlarmFacility alarm facility}, an alarm notification
 * will be generated containing an SbbNotification object that identifies
 * the SBB that raised the alarm.
 * @since SLEE 1.1
 */
public final class SbbNotification extends AbstractNotificationSource implements NotificationSource {
    /**
     * The JMX notification type of alarm notifications that are generated in response
     * to an SBB interacting with the {@link javax.slee.facilities.AlarmFacility}.
     *

     * The notification type is equal to the string "javax.slee.management.alarm.sbb".
     */
    public static final String ALARM_NOTIFICATION_TYPE = "javax.slee.management.alarm.sbb";

    /**
     * The JMX notification type of trace notifications that are generated in response
     * to an SBB interacting with the Trace Facility via a {@link javax.slee.facilities.Tracer}
     * object.
     *

     * The notification type is equal to the string "javax.slee.management.trace.sbb".
     */
    public static final String TRACE_NOTIFICATION_TYPE = "javax.slee.management.trace.sbb";

    /**
     * The JMX notification type of usage notifications that are generated by a
     * {@link javax.slee.usage.UsageMBean} containing an SbbNotification as
     * a notification source.
     *

     * The notification type is equal to the string "javax.slee.management.usage.sbb".
     */
    public static final String USAGE_NOTIFICATION_TYPE = "javax.slee.management.usage.sbb";

    /**
     * The JMX Object Name property key that identifies the name of the service in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "serviceName".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_VENDOR_KEY
     * @see #SERVICE_VERSION_KEY
     * @see #SBB_NAME_KEY
     * @see #SBB_VENDOR_KEY
     * @see #SBB_VERSION_KEY
     * @since SLEE 1.1
     */
    public static final String SERVICE_NAME_KEY = "serviceName";

    /**
     * The JMX Object Name property key that identifies the vendor of the service in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "serviceVendor".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_NAME_KEY
     * @see #SERVICE_VERSION_KEY
     * @see #SBB_NAME_KEY
     * @see #SBB_VENDOR_KEY
     * @see #SBB_VERSION_KEY
     * @since SLEE 1.1
     */
    public static final String SERVICE_VENDOR_KEY = "serviceVendor";

    /**
     * The JMX Object Name property key that identifies the version of the service in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "serviceVersion".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_NAME_KEY
     * @see #SERVICE_VENDOR_KEY
     * @see #SBB_NAME_KEY
     * @see #SBB_VENDOR_KEY
     * @see #SBB_VERSION_KEY
     * @since SLEE 1.1
     */
    public static final String SERVICE_VERSION_KEY = "serviceVersion";

    /**
     * The JMX Object Name property key that identifies the name of the SBB in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "sbbName".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_NAME_KEY
     * @see #SERVICE_VENDOR_KEY
     * @see #SERVICE_VERSION_KEY
     * @see #SBB_VENDOR_KEY
     * @see #SBB_VERSION_KEY
     * @since SLEE 1.1
     */
    public static final String SBB_NAME_KEY = "sbbName";

    /**
     * The JMX Object Name property key that identifies the vendor of the SBB in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "sbbVendor".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_NAME_KEY
     * @see #SERVICE_VENDOR_KEY
     * @see #SERVICE_VERSION_KEY
     * @see #SBB_NAME_KEY
     * @see #SBB_VERSION_KEY
     * @since SLEE 1.1
     */
    public static final String SBB_VENDOR_KEY = "sbbVendor";

    /**
     * The JMX Object Name property key that identifies the version of the SBB in a
     * Usage MBean whose {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY}
     * property has a value equal to {@link #USAGE_NOTIFICATION_TYPE}.  This key is
     * equal to the string "sbbVersion".
     * @see javax.slee.usage.UsageMBean#BASE_OBJECT_NAME
     * @see javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY
     * @see #SERVICE_NAME_KEY
     * @see #SERVICE_VENDOR_KEY
     * @see #SERVICE_VERSION_KEY
     * @see #SBB_NAME_KEY
     * @see #SBB_VENDOR_KEY
     * @since SLEE 1.1
     */
    public static final String SBB_VERSION_KEY = "sbbVersion";

    /**
     * Create a new SbbNotification object that uniquely identifies an SBB
     * component within a service.
     * @param service the component identifier of the service.
     * @param sbb the component identifier of the SBB.
     * @throws NullPointerException if either argument is null.
     */
    public SbbNotification(ServiceID service, SbbID sbb) {
        if (service == null) throw new NullPointerException("service is null");
        if (sbb == null) throw new NullPointerException("sbb is null");
        this.service = service;
        this.sbb = sbb;
    }

    /**
     * Get the service component identifier of this notification source.
     * @return the service component identifier.
     */
    public ServiceID getService() {
        return service;
    }

    /**
     * Get the SBB component identifier of this notification source.
     * @return the SBB component identifier.
     */
    public SbbID getSbb() {
        return sbb;
    }

    /**
     * Get the JMX notification type of alarm notifications generated in response
     * to an SBB interacting with the Alarm Facility.
     * @return the string defined by {@link #ALARM_NOTIFICATION_TYPE}.
     */
    public String getAlarmNotificationType() {
        return ALARM_NOTIFICATION_TYPE;
    }

    /**
     * Get the JMX notification type of trace notifications generated in response
     * to an SBB interacting with the Trace Facility.
     * @return the string defined by {@link #TRACE_NOTIFICATION_TYPE}.
     */
    public String getTraceNotificationType() {
        return TRACE_NOTIFICATION_TYPE;
    }

    /**
     * Get the JMX notification type of usage notifications generated in response
     * to an SBB interacting with its usage parameters.
     * @return the string defined by {@link #USAGE_NOTIFICATION_TYPE}.
     */
    public String getUsageNotificationType() {
        return USAGE_NOTIFICATION_TYPE;
    }

    /**
     * Get a JMX Object Name property string that uniquely identifies the specified
     * service and SBB, suitable for inclusion in the Object Name of a Usage MBean.
     * This method makes use of the {@link ObjectName#quote} method to ensure
     * that the component identifier attributes are valid for inclusion as property
     * values in an Object Name.
     *

     * This method can be used as follows to manually construct a complete Object Name
     * for a Usage MBean:
     *

     *     ObjectName name = new ObjectName(

     *         {@link javax.slee.usage.UsageMBean#BASE_OBJECT_NAME} + "," +

     *         {@link javax.slee.usage.UsageMBean#USAGE_PARAMETER_SET_NAME_KEY} + "=" + ObjectName.quote(paramSetName) + ","   // optional

     *         {@link javax.slee.usage.UsageMBean#NOTIFICATION_SOURCE_KEY} + "=" + {@link #USAGE_NOTIFICATION_TYPE SbbNotification.USAGE_NOTIFICATION_TYPE} + "," +

     *         {@link #getUsageMBeanProperties(ServiceID, SbbID) SbbNotification.getUsageMBeanProperties(service, sbb)}

     *     );
     *

     * @param service the component identifier of the service.
     * @param sbb the component identifier of the SBB.
     * @return an Object Name property string that uniquely identifies the specified
     *        service and SBB components.
     * @throws NullPointerException if either argument is null.
     */
    public static String getUsageMBeanProperties(ServiceID service, SbbID sbb) {
        if (service == null) throw new NullPointerException("service is null");
        if (sbb == null) throw new NullPointerException("sbb is null");
        StringBuffer buf = new StringBuffer();
        buf.append(SERVICE_NAME_KEY).append('=').append(ObjectName.quote(service.getName())).append(',');
        buf.append(SERVICE_VENDOR_KEY).append('=').append(ObjectName.quote(service.getVendor())).append(',');
        buf.append(SERVICE_VERSION_KEY).append('=').append(ObjectName.quote(service.getVersion())).append(',');
        buf.append(SBB_NAME_KEY).append('=').append(ObjectName.quote(sbb.getName())).append(',');
        buf.append(SBB_VENDOR_KEY).append('=').append(ObjectName.quote(sbb.getVendor())).append(',');
        buf.append(SBB_VERSION_KEY).append('=').append(ObjectName.quote(sbb.getVersion()));
        return buf.toString();
    }

    /**
     * Get a JMX Object Name property string that uniquely identifies the service
     * and SBB of this notification source, suitable for inclusion in the Object Name
     * of a Usage MBean.
     *

     * This method is equivalent to {@link #getUsageMBeanProperties(ServiceID, SbbID)
     * getUsageMBeanProperties(getService(), getSbb())}.
     * @return an Object Name property string that uniquely identifies the service
     *        and SBB components of this notification source.
     */
    public String getUsageMBeanProperties() {
        return getUsageMBeanProperties(service, sbb);
    }

    /**
     * Compare this notification source for equality with another object.
     * @param obj the object to compare this with.
     * @return true if obj is an instance of this class and
     *        references the same SBB and service component identifiers as this,
     *        false otherwise.
     */
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof SbbNotification)) return false;

        SbbNotification that = (SbbNotification)obj;
        return this.service.equals(that.service)
            && this.sbb.equals(that.sbb);
    }

    /**
     * Get a hash code value for this notification source.
     * @return a hash code value for this notification source.
     */
    public int hashCode() {
        return service.hashCode() ^ sbb.hashCode();
    }

    /**
     * Get a string representation for this notification source.
     * @return a string representation for this notification source.
     * @see Object#toString()
     */
    public String toString() {
        StringBuffer buf = new StringBuffer();
        buf.append("SbbNotification[service=").append(service).
            append(",sbb=").append(sbb).
            append(']');
        return buf.toString();
    }

    /**
     * Compare this notification source with the specified object for order.
     * Returns a negative integer, zero, or a positive integer if this object
     * is less than, equal to, or greater than the specified object.
     *

     * If obj is an SbbNotification, order is determined
     * by comparing first the encapsulated service component identifier and then,
     * if the service component identifiers are equal, the encapsulated SBB
     * component identifier.  Otherwise, if obj is a
     * NotificationSource, ordering is determined by comparing the
     * class name of this class with the class name of obj.
     * @param obj the object to compare this with.
     * @return a negative integer, zero, or a positive integer if this notification
     *        source is considered less than, equal to, or greater than the
     *        specified object.
     * @throws ClassCastException if obj does not implement the
     *        {@link NotificationSource} interface.
     * @see Comparable#compareTo(Object)
     */
    public int compareTo(Object obj) {
        // can't compare with null
        if (obj == null) throw new NullPointerException("obj is null");
        if (obj == this) return 0;

        if (obj instanceof SbbNotification) {
            // compare the service id then the sbb id
            SbbNotification that = (SbbNotification)obj;
            int serviceComparison = this.service.compareTo(that.service);
            return serviceComparison != 0 ? serviceComparison : this.sbb.compareTo(that.sbb);
        }
        else {
            return super.compareTo(TYPE, obj);
        }
    }

    // protected

    protected String getClassName() {
        return TYPE;
    }

    private final ServiceID service;
    private final SbbID sbb;

    // constant to avoid expensive getClass() invocations at runtime
    private static final String TYPE = SbbNotification.class.getName();
}