package org.mobicents.slee.runtime.activity;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import javax.slee.Address;
import javax.slee.EventTypeID;
import javax.slee.SLEEException;
import javax.slee.ServiceID;
import javax.slee.UnrecognizedServiceException;
import javax.slee.facilities.TimerID;
import javax.slee.management.ServiceState;
import javax.slee.resource.ActivityFlags;
import javax.slee.resource.ActivityIsEndingException;
import javax.transaction.SystemException;
import org.apache.log4j.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.container.management.ServiceManagement;
import org.mobicents.slee.container.service.Service;
import org.mobicents.slee.container.service.ServiceActivityImpl;
import org.mobicents.slee.runtime.eventrouter.ActivityEventQueueManager;
import org.mobicents.slee.runtime.eventrouter.CommitDeferredEventAction;
import org.mobicents.slee.runtime.eventrouter.DeferredActivityEndEvent;
import org.mobicents.slee.runtime.eventrouter.DeferredEvent;
import org.mobicents.slee.runtime.eventrouter.EventRouterActivity;
import org.mobicents.slee.runtime.eventrouter.PendingAttachementsMonitor;
import org.mobicents.slee.runtime.eventrouter.RollbackDeferredEventAction;
import org.mobicents.slee.runtime.facilities.ActivityContextNamingFacilityImpl;
import org.mobicents.slee.runtime.facilities.TimerFacilityImpl;
import org.mobicents.slee.runtime.sbbentity.RootSbbEntitiesRemovalTask;
import org.mobicents.slee.runtime.sbbentity.SbbEntityComparator;
import org.mobicents.slee.runtime.transaction.SleeTransactionManager;
import org.mobicents.slee.runtime.transaction.TransactionalAction;
/**
* 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 ActivityContext {
private 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";
/**
* This map contains mappings between acId and Long Objects. Long object represnt timestamp of last access time to ac.
* Cash is not a good place to hold this data - it is modified frequently across tx causing rollbacks because of version errors. Values are first inserted when ac is created.
* There is posibility not to update or create this mapping by specifying <b>false</b> value to ActivityContext.createActivityContext(Object,boolean).
* This entry should be always updates if ac is used within core.
* Mapping are removed upon call of ActivityCOntext.markForRemoval function. However as this is static ap and we are
* acting in multithreaded env we cant be sure if this operation wasnt followed by some update (however tests didnt reveal this situation when trash is left),
* thus here is used WeakHashMap in which entries fade durign time. This is not a problem since ac should be accessed frequently, and if not we consider them old.
*/
private static final ConcurrentHashMap<ActivityContextHandle, Long> timeStamps = new ConcurrentHashMap<ActivityContextHandle, Long>(500);
/**
* the handle for this ac
*/
private final ActivityContextHandle activityContextHandle;
/**
* the data stored in cache for this ac
*/
protected final ActivityContextCacheData cacheData;
private static final SbbEntityComparator sbbEntityComparator = new SbbEntityComparator();
public ActivityContext(final ActivityContextHandle activityContextHandle, ActivityContextCacheData cacheData, boolean updateAccessTime, Integer activityFlags) {
this(activityContextHandle,cacheData,false);
// ac creation, create cache data and set activity flags
this.cacheData.create();
this.cacheData.putObject(NODE_MAP_KEY_ACTIVITY_FLAGS, activityFlags);
TransactionalAction action = new TransactionalAction() {
public void execute() {
updateLastAccessTime(activityContextHandle);
}
};
try {
sleeContainer.getTransactionManager().addAfterCommitAction(action);
// then we need to schedule check for it
if (ActivityFlags.hasRequestSleeActivityGCCallback(activityFlags) && activityContextHandle.getActivityType() == ActivityType.NULL) {
scheduleCheckForUnreferencedActivity();
}
} catch (SystemException e) {
throw new SLEEException(e.getMessage(),e);
}
}
public ActivityContext(ActivityContextHandle activityContextHandle, ActivityContextCacheData cacheData, boolean updateAccessTime) {
this.activityContextHandle = activityContextHandle;
this.cacheData = cacheData;
// update last acess time if needed
if (updateAccessTime) {
updateLastAccessTime(activityContextHandle);
}
}
/**
* Retrieves the handle of this ac
* @return
*/
public ActivityContextHandle getActivityContextHandle() {
return activityContextHandle;
}
/**
* Retrieve the {@link ActivityFlags} for this activity context
* @return
*/
public int getActivityFlags() {
Integer flags = (Integer) cacheData.getObject(NODE_MAP_KEY_ACTIVITY_FLAGS);
if (flags != null) {
return flags.intValue();
}
else {
return ActivityFlags.NO_FLAGS;
}
}
/**
* 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("ac "+getActivityContextHandle()+" cmp attribute set : attr name = " + key
+ " , attr value = " + newValue);
}
}
/**
* Get the shared data for the ACI.
*
* @param name --
* name we want to look up
* @return the shared data for the ACI
*
*/
public Object getDataAttribute(String key) {
return cacheData.getCmpAttribute(key);
}
public Map getDataAttributesCopy() {
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);
// cancel a possible check for unreferenced activity, no need to
// waste time in checkingif the flags requested such process
cacheData.setCheckingReferences(false);
}
/**
* This is called to release all the name bindings after
* the activity end event is delivered to the sbb.
*
*/
private void removeNamingBindings() {
ActivityContextNamingFacilityImpl acf = (ActivityContextNamingFacilityImpl) 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
*/
public Set<String> getNamingBindingCopy() {
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 && ActivityFlags.hasRequestSleeActivityGCCallback(getActivityFlags())) {
try {
scheduleCheckForUnreferencedActivity();
} catch (SystemException e) {
throw new SLEEException(e.getMessage(),e);
}
}
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)) {
// cancel a possible check for unreferenced activity, no need to
// waste time in checkingif the flags requested such process
cacheData.setCheckingReferences(false);
return true;
}
else {
return false;
}
}
/**
* Detach timer
*
* @param timerID
*
*/
public boolean detachTimer(TimerID timerID) {
boolean detached = cacheData.detachTimer(timerID);
if (detached && ActivityFlags.hasRequestSleeActivityGCCallback(getActivityFlags())) {
try {
scheduleCheckForUnreferencedActivity();
} catch (SystemException e) {
throw new SLEEException(e.getMessage(),e);
}
}
return detached;
}
/**
* Fetches set of attached timers.
*
* @return Set containing TimerID objects representing timers attached to
* this ac.
*/
public Set<TimerID> getAttachedTimersCopy() {
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() {
TimerFacilityImpl timerFacility = sleeContainer.getTimerFacility();
// Iterate through the attached timers, telling the timer facility to
// remove them
for (Object obj : cacheData.getAttachedTimers()) {
timerFacility.cancelTimer((TimerID)obj);
}
}
/**
* Mark this AC for garbage collection. It can no longer be used past this
* point.
*
*/
private void removeFromCache() {
cacheData.remove();
// Beyond here we dont stamp this one
TransactionalAction action = new TransactionalAction() {
public void execute() {
ActivityContext.timeStamps.remove(activityContextHandle);
}
};
try {
sleeContainer.getTransactionManager().addAfterCommitAction(action);
} catch (SystemException e) {
logger.error(e.getMessage(),e);
}
}
/**
* attach an sbb entity to this AC.
*
* @param sbbEntity --
* 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(String sbbEntityId) {
boolean attached = cacheData.attachSbbEntity(sbbEntityId);
if (attached) {
PendingAttachementsMonitor pendingAttachementsMonitor = getEventRouterActivity().getPendingAttachementsMonitor();
if (pendingAttachementsMonitor != null) {
try {
pendingAttachementsMonitor.txAttaching();
} catch (SystemException e) {
logger.error(e.getMessage(),e);
}
}
// cancel a possible check for unreferenced activity, no need to
// waste time in checkingif the flags requested such process
cacheData.setCheckingReferences(false);
}
if (logger.isDebugEnabled()) {
logger
.debug("attachement from sbb entity "+sbbEntityId+" to ac "+getActivityContextHandle()+" result: "+attached);
}
return attached;
}
/**
* Detach the sbb entity
*
* @param sbbEntityId
*/
public void detachSbbEntity(String sbbEntityId) throws javax.slee.TransactionRequiredLocalException {
boolean detached = cacheData.detachSbbEntity(sbbEntityId);
if (detached) {
if (ActivityFlags.hasRequestSleeActivityGCCallback(getActivityFlags())) {
try {
scheduleCheckForUnreferencedActivity();
} catch (SystemException e) {
throw new SLEEException(e.getMessage(),e);
}
}
PendingAttachementsMonitor pendingAttachementsMonitor = getEventRouterActivity().getPendingAttachementsMonitor();
if (pendingAttachementsMonitor != null) {
try {
pendingAttachementsMonitor.txDetaching();
} catch (SystemException e) {
logger.error(e.getMessage(),e);
}
}
if (logger.isDebugEnabled()) {
logger
.debug("detached sbb entity "+sbbEntityId+" from ac "+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 getSortedSbbAttachmentSet(Set excludeSet) {
final Set<String> sbbAttachementSet = cacheData.getSbbEntitiesAttached();
final SortedSet orderSbbSet = new TreeSet(sbbEntityComparator);
for (String sbbEntityId : sbbAttachementSet) {
if (!excludeSet.contains(sbbEntityId)) {
orderSbbSet.add(sbbEntityId);
}
}
return orderSbbSet;
}
public Set<String> getSbbAttachmentSet() {
return cacheData.getSbbEntitiesAttached();
}
// --- private helpers
/**
* Returns time stamp of last access to this ac. If timestamp is found it
* return its value, if not "0"- it means that ac was accessed not during
* last few cycles, thus its value has faded.
*
* @return
*/
public long getLastAccessTime() {
final Long l = timeStamps.get(activityContextHandle);
if (l != null)
return l.longValue();
else
return 0;
}
private void updateLastAccessTime(ActivityContextHandle activityContextHandle) {
timeStamps.put(activityContextHandle,Long.valueOf(System.currentTimeMillis()));
}
private static final Object MAP_VALUE = new Object();
public String toString() {
return "ActivityContext{ handle = "+activityContextHandle+" }";
}
// 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 ((ActivityContext)obj).activityContextHandle.equals(this.activityContextHandle);
}
else {
return false;
}
}
public int hashCode() {
return activityContextHandle.hashCode();
}
// FIXME add logic to remove refs in facilities when activity is explicitely
// ended. See example 7.3.4.1 in specs.
public static String dumpState() {
return "\n+-- AC Timestamps Map size: "+timeStamps.size();
}
/**
* Fires an event on this AC
* @param eventTypeId
* @param event
* @param address
* @param serviceID
* @param eventFlags
* @throws ActivityIsEndingException
* @throws SLEEException
*/
public void fireEvent(EventTypeID eventTypeId, Object event, Address address, ServiceID serviceID, int eventFlags) throws ActivityIsEndingException, SLEEException {
if (isEnding()) {
throw new ActivityIsEndingException(getActivityContextHandle().toString());
}
else {
// cancel a possible check for unreferenced activity, no need to
// waste time in checking if the flags requested such process
if (cacheData.setCheckingReferences(false)) {
try {
scheduleCheckForUnreferencedActivity();
} catch (SystemException e) {
throw new SLEEException(e.getMessage(),e);
}
}
// fire event
fireDeferredEvent(new DeferredEvent(eventTypeId,event,this,address,serviceID,eventFlags,getEventRouterActivity(),sleeContainer));
}
}
/**
* Ends the activity context.
*/
public void endActivity() {
if (logger.isDebugEnabled()) {
logger.debug("Ending ac "+this);
}
if (cacheData.setEnding(true)) {
fireDeferredEvent(new DeferredActivityEndEvent(this,getEventRouterActivity(),sleeContainer));
}
}
public void activityEnded() {
// remove references to this AC in timer and ac naming facility
removeNamingBindings();
removeFromTimers(); // Spec 7.3.4.1 Step 10
// check activity type
switch (activityContextHandle.getActivityType()) {
case RA:
// external activity, notify RA that the activity has ended
final int activityFlags = getActivityFlags();
TransactionalAction action = new TransactionalAction() {
public void execute() {
try {
sleeContainer.getResourceManagement().getResourceAdaptorEntity(activityContextHandle.getActivitySource()).activityEnded(activityContextHandle.getActivityHandle(),activityFlags);
}
catch (Throwable e) {
logger.error(e.getMessage(),e);
}
}
};
try {
sleeContainer.getTransactionManager().addAfterCommitAction(action);
} catch (Throwable e) {
logger.error("error adding tx action to tx manager, to inform ra entity of activity end, executing action now",e);
action.execute();
}
break;
case NULL:
// do nothing
break;
case PTABLE:
// do nothing
break;
case SERVICE:
ServiceActivityImpl serviceActivity = (ServiceActivityImpl) activityContextHandle.getActivity();
try {
// change service state to inactive if it is stopping
Service service = sleeContainer.getServiceManagement().getService(serviceActivity.getService());
if (service.getState().isStopping()) {
service.setState(ServiceState.INACTIVE);
// schedule task to remove outstanding root sbb entities of the service
new RootSbbEntitiesRemovalTask(serviceActivity.getService());
Logger.getLogger(ServiceManagement.class).info("Deactivated "+ serviceActivity.getService());
}
} catch (UnrecognizedServiceException e) {
logger.error("Unable to find "+serviceActivity.getService()+" to deactivate",e);
}
break;
default:
throw new SLEEException("Unknown activity type " + activityContextHandle.getActivityType());
}
removeFromCache();
}
private void fireDeferredEvent(DeferredEvent dE) {
// put event as pending in ac event queue manager
ActivityEventQueueManager aeqm = dE.getEventRouterActivity().getEventQueueManager();
if (aeqm != null) {
aeqm.pending(dE);
// add tx actions to commit or rollback
final SleeTransactionManager txManager = sleeContainer.getTransactionManager();
try {
txManager.addAfterCommitPriorityAction(
new CommitDeferredEventAction(dE, aeqm));
txManager.addAfterRollbackAction(
new RollbackDeferredEventAction(dE,
this.activityContextHandle));
} catch (SystemException e) {
aeqm.rollback(dE);
throw new SLEEException("failed to add tx actions to commit and rollback event firing",e);
}
} else {
throw new SLEEException("unable to find ACs event queue manager");
}
}
public EventRouterActivity getEventRouterActivity() {
return sleeContainer.getEventRouter().getEventRouterActivity(activityContextHandle);
}
// UNREF CHECK PROCESS
/*
* There are 3 "rounds" to check if an activity is unreferenced:
*
* 1st) in event handling if there is a sbb detach, cancel timer or name
* unbound and the activity flags require callback to activityUnreferenced then we add a tx action to check if the rules (except outstanding
* events) for an activity being unreferenced are satisfied.
*
* 2nd) When the action is executed and if the rules are satisfied a
* runnable task to do another rules check is enqueued in the activity event
* executor service, thus serialized with events
*
* 3rd a) If an event handling for this activity fires an event on it, attaches an sbb, sets a
* timer or binds a name then the whole process is canceled
*
* 3rd b) When the runnable is executed, if the process was not
* canceled then the activity unreferenced callback is invoked
*/
// keys related with the procedure to check of the ac is unreferenced
private static final String NODE_MAP_KEY_ActivityUnreferenced1stCheck = "unref-check-1";
private void scheduleCheckForUnreferencedActivity() throws SystemException {
if (!isEnding()) {
String activityUnreferenced1stCheckKey = activityContextHandle + NODE_MAP_KEY_ActivityUnreferenced1stCheck;
Map txLocalData = sleeContainer.getTransactionManager().getTransactionContext().getData();
// schedule check only once at time
if (txLocalData.get(activityUnreferenced1stCheckKey) != null) {
return;
}
else {
// raise the 1st check flag to ensure that the check is not scheduled more than once
txLocalData.put(activityUnreferenced1stCheckKey, MAP_VALUE);
}
if (logger.isDebugEnabled()) {
logger.debug("schedule checking for unreferenced activity on ac "+this.getActivityContextHandle());
}
TransactionalAction implicitEndCheck = new TransactionalAction() {
public void execute() {
unreferencedActivity1stCheck();
}
};
sleeContainer.getTransactionManager().addBeforeCommitAction(implicitEndCheck);
}
}
private void unreferencedActivity1stCheck() {
if (logger.isDebugEnabled()) {
logger.debug("1st check for unreferenced activity on ac "+this.getActivityContextHandle());
}
if (!isEnding()) {
if (this.isSbbAttachmentSetEmpty()
&& this.isAttachedTimersEmpty()
&& this.isNamingBindingEmpty()) {
// raise the 2nd check flag
cacheData.setCheckingReferences(true);
// lets submit final check task to activity event executor after commit
TransactionalAction action = new TransactionalAction() {
public void execute() {
getEventRouterActivity().getExecutorService()
.submit(new UnreferencedActivity2ndCheckTask(getActivityContextHandle()));
}
};
try {
sleeContainer.getTransactionManager().addAfterCommitAction(action);
} catch (SystemException e) {
logger.error(e.getMessage(),e);
}
}
}
}
protected void unreferencedActivity2ndCheck() {
// final verification
if (cacheData.isCheckingReferences()) {
if (logger.isDebugEnabled()) {
logger.debug(toString() + " is unreferenced");
}
switch (activityContextHandle.getActivityType()) {
case RA:
// external activity, notify RA that the activity is
// unreferenced
sleeContainer.getResourceManagement().getResourceAdaptorEntity(
activityContextHandle.getActivitySource())
.getResourceAdaptorObject().activityUnreferenced(
activityContextHandle.getActivityHandle());
break;
case NULL:
// null activity unreferenced, end it
endActivity();
break;
default:
// do nothing
break;
}
}
}
}