ReadOnlyBeanContainerpublic class ReadOnlyBeanContainer extends EntityContainer implements com.sun.ejb.spi.distributed.ReadOnlyBeanRefreshEventHandlerThe Container that manages instances of ReadOnly Beans. This container
blocks all calls to ejbStore() and selectively performs ejbLoad() |
Fields Summary |
---|
private static final Logger | _logger | private long | refreshPeriodInMillis | private int | beanLevelSequenceNum | private long | beanLevelLastRefreshRequestedAt | private volatile long | currentTimeInMillis | private TimerTask | refreshTask | private com.sun.ejb.containers.util.cache.EJBObjectCache | robCache | private com.sun.ejb.spi.distributed.DistributedReadOnlyBeanService | distributedReadOnlyBeanService | private volatile Map | finderResultsCache | private static final int | FINDER_LOCK_SIZE | private Object[] | finderLocks | private boolean | RELATIVE_TIME_CHECK_MODE |
Constructors Summary |
---|
protected ReadOnlyBeanContainer(EjbDescriptor desc, ClassLoader loader)
super(desc, loader);
containerFactory = (ContainerFactoryImpl)
theSwitch.getContainerFactory();
EjbEntityDescriptor ed = (EjbEntityDescriptor)desc;
refreshPeriodInMillis =
ed.getIASEjbExtraDescriptors().getRefreshPeriodInSeconds() * 1000;
if( refreshPeriodInMillis > 0 ) {
long timerFrequency = 1;
String refreshRateStr =
System.getProperty("com.sun.ejb.containers.readonly.timer.frequency", "1");
try {
timerFrequency = Integer.parseInt(refreshRateStr);
if (timerFrequency < 0) {
timerFrequency = 1;
}
} catch (Exception ex) {
_logger.log(Level.FINE, "Invalid timer frequency " + refreshRateStr);
}
try {
RELATIVE_TIME_CHECK_MODE = Boolean.valueOf(System.getProperty(
"com.sun.ejb.containers.readonly.relative.refresh.mode"));
_logger.log(Level.FINE, "RELATIVE_TIME_CHECK_MODE: " + RELATIVE_TIME_CHECK_MODE);
} catch (Exception ex) {
_logger.log(Level.FINE, "(Ignorable) Exception while initializing RELATIVE_TIME_CHECK_MODE", ex);
}
Timer timer =
ContainerFactoryImpl.getContainerService().getTimer();
if (RELATIVE_TIME_CHECK_MODE) {
refreshTask = new CurrentTimeRefreshTask ();
timer.scheduleAtFixedRate(refreshTask, timerFrequency*1000, timerFrequency*1000);
} else {
refreshTask = new RefreshTask();
timer.scheduleAtFixedRate(refreshTask, refreshPeriodInMillis,
refreshPeriodInMillis);
}
} else {
refreshPeriodInMillis = 0;
}
for (int i=0; i<FINDER_LOCK_SIZE; i++) {
finderLocks[i] = new Object();
}
// Create read-only bean cache
long idleTimeoutInMillis = (cacheProp.cacheIdleTimeoutInSeconds <= 0) ?
-1 : (cacheProp.cacheIdleTimeoutInSeconds * 1000);
if( (cacheProp.maxCacheSize <= 0) && (idleTimeoutInMillis <= 0) ) {
robCache = new UnboundedEJBObjectCache(ejbDescriptor.getName());
robCache.init(DEFAULT_CACHE_SIZE, cacheProp.numberOfVictimsToSelect,
0L, 1.0F, null);
} else {
int cacheSize = (cacheProp.maxCacheSize <= 0) ?
DEFAULT_CACHE_SIZE : cacheProp.maxCacheSize;
robCache = new FIFOEJBObjectCache(ejbDescriptor.getName());
robCache.init(cacheSize,
cacheProp.numberOfVictimsToSelect,
idleTimeoutInMillis, 1.0F, null);
// .setEJBObjectCacheListener(
// new EJBObjectCacheVictimHandler());
}
this.distributedReadOnlyBeanService =
DistributedEJBServiceFactory.getDistributedEJBService()
.getDistributedReadOnlyBeanService();
this.distributedReadOnlyBeanService.addReadOnlyBeanRefreshEventHandler(
getContainerId(), getClassLoader(), this);
|
Methods Summary |
---|
protected ComponentContext | _getContext(Invocation inv)
ComponentContext ctx = super._getContext(inv);
InvocationInfo info = inv.invocationInfo; // info cannot be null
if (info.isTxRequiredLocalCMPField) {
if (! inv.foundInTxCache) {
EntityContextImpl entityCtx = (EntityContextImpl) ctx;
super.afterBegin(entityCtx);
inv.foundInTxCache = true;
}
} else {
//TODO: We can still optimize NonTx access to CMP getters/setters
}
return ctx;
| protected void | addPooledEJB(EntityContextImpl ctx)
try {
ReadOnlyContextImpl readOnlyCtx = (ReadOnlyContextImpl)ctx;
if( readOnlyCtx.getReadOnlyBeanInfo() != null ) {
readOnlyCtx.setReadOnlyBeanInfo(null);
robCache.remove(ctx.getPrimaryKey(), true);
}
} catch (Exception ex) {
_logger.log(Level.SEVERE, "ejb.addPooledEJB", ex);
EJBException ejbEx = new EJBException();
ejbEx.initCause(ex);
throw ejbEx;
} finally {
super.addPooledEJB(ctx);
}
| private ReadOnlyBeanInfo | addToCache(java.lang.Object primaryKey, boolean incrementRefCount)
// Optimize for the cache where the cache item already
// exists and we have a 2nd, 3rd, 4th, etc. context for
// the same primary key. If the item exists, the ref count
// will be incremented.
ReadOnlyBeanInfo robInfo = (ReadOnlyBeanInfo)
robCache.get(primaryKey, incrementRefCount);
if( robInfo == null ) {
// If the item doesn't exist, create a new one. The cache
// ensures that the ref count is correct in the face of concurrent
// puts.
ReadOnlyBeanInfo newRobInfo = new ReadOnlyBeanInfo();
newRobInfo.primaryKey = primaryKey;
// Initialize bean level sequence num so that the first time an
// instance of this PK goes through callEJBLoad, it will force
// a refresh.
newRobInfo.beanLevelSequenceNum = -1;
newRobInfo.refreshNeeded = true;
newRobInfo.pkLevelSequenceNum = 1;
newRobInfo.lastRefreshRequestedAt = 0;
newRobInfo.lastRefreshedAt = 0;
// Cache ejbObject/ejbLocalObject within ROB info.
// This value is used by
// findByPrimaryKey to avoid a DB access. Caching here
// ensures that there will be one DB access for the PK
// regardless of the order in which findByPrimaryKey is called
// with respect to the business method call. This also covers
// the case where a business method is invoked through the
// local view and findByPrimaryKey is invoked through the
// Remote view (or vice versa).
if( ejbDescriptor.isLocalInterfacesSupported() ) {
newRobInfo.cachedEjbLocalObject =
getEJBLocalObjectForPrimaryKey(primaryKey);
}
if( ejbDescriptor.isRemoteInterfacesSupported() ) {
newRobInfo.cachedEjbObject =
getEJBObjectStub(primaryKey, null);
}
ReadOnlyBeanInfo otherRobInfo = (ReadOnlyBeanInfo)
robCache.put(primaryKey, newRobInfo, incrementRefCount);
// If someone else inserted robInfo for this pk before *our* put(),
// use that as the pk's robInfo. Otherwise, the new robInfo we
// created is the "truth" for this pk.
robInfo = (otherRobInfo == null) ? newRobInfo : otherRobInfo;
}
return robInfo;
| protected void | afterNewlyActivated(EntityContextImpl context)
// In the case of ReadOnlyBean store the Context into the list
ReadOnlyBeanInfo robInfo = addToCache(context.getPrimaryKey(), true);
// Set the read-only bean info on the context so we can access it
// without doing a cache lookup.
ReadOnlyContextImpl readOnlyContext = (ReadOnlyContextImpl) context;
readOnlyContext.setReadOnlyBeanInfo(robInfo);
| protected void | callEJBLoad(EntityBean ejb, EntityContextImpl entityCtx, boolean activeTx)
ReadOnlyContextImpl context = (ReadOnlyContextImpl) entityCtx;
ReadOnlyBeanInfo robInfo = context.getReadOnlyBeanInfo();
// Grab the pk-specific lock before doing the refresh comparisons.
// In the common-case, the lock will only be held for a very short
// amount of time. In the case where a pk-level refresh is needed,
// we want to ensure that no concurrent refreshes for the same
// pk can occur.
int pkLevelSequenceNum = 0;
long pkLastRefreshedAt = 0;
synchronized(robInfo) {
int currentBeanLevelSequenceNum = beanLevelSequenceNum;
if( robInfo.beanLevelSequenceNum != currentBeanLevelSequenceNum) {
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE, "REFRESH DUE TO BEAN-LEVEL UPDATE:"
+ " Bean-level sequence num = " +
beanLevelSequenceNum +
robInfo + " current time is " + new Date());
}
robInfo.refreshNeeded = true;
} else if (RELATIVE_TIME_CHECK_MODE && (refreshPeriodInMillis > 0)) { // 0 implies no time based refresh
if ((currentTimeInMillis - robInfo.lastRefreshedAt) > refreshPeriodInMillis) {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, "REFRESH DUE TO STALE PK:"
+ " robInfo.lastRefreshedAt: " + robInfo.lastRefreshedAt
+ "; current (approx) time is " + currentTimeInMillis);
}
robInfo.refreshNeeded = true;
}
}
// Refresh could be true EITHER because time-based refresh
// occurred or programmatic refresh of this PK.
if (robInfo.refreshNeeded) {
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE, " PK-LEVEL REFRESH : "
+ robInfo + " current time is " + new Date());
}
try {
if( isContainerManagedPers ) {
BeanStateSynchronization beanStateSynch =
(BeanStateSynchronization) ejb;
beanStateSynch.ejb__refresh(entityCtx.getPrimaryKey());
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE, " PK-LEVEL REFRESH DONE :"
+ robInfo + " current time is " + new Date());
}
} else {
if( ejb instanceof BeanStateSynchronization ) {
// For debugging purposes, call into ejb__refresh
// if it's present on a BMP bean class
BeanStateSynchronization beanStateSynch =
(BeanStateSynchronization) ejb;
beanStateSynch.ejb__refresh
(entityCtx.getPrimaryKey());
}
}
} finally {
// Always set refreshNeeded to false
robInfo.refreshNeeded = false;
}
// Rob info only updated if no errors so far.
updateAfterRefresh(robInfo);
}
pkLevelSequenceNum = robInfo.pkLevelSequenceNum;
pkLastRefreshedAt = robInfo.lastRefreshedAt;
} // releases lock for pk's read-only bean info
if ((entityCtx.isNewlyActivated())
|| (context.getPKLevelSequenceNum() != pkLevelSequenceNum)) {
// Now do instance-level refresh check to see if
// ejbLoad is warranted.
callLoad(ejb, context, pkLevelSequenceNum,
pkLastRefreshedAt, currentTimeInMillis);
}
| protected void | callEJBRemove(EntityBean ejb, EntityContextImpl context)
// This will only be called for BMP read-only beans since AS 7
// allowed the client to make this call. Calls to remove
// CMP read-only beans result in a runtime exception.
Object pk = context.getPrimaryKey();
robCache.removeAll(pk);
| protected void | callEJBStore(EntityBean ejb, EntityContextImpl context)
// this method in the ReadOnlyBean case should be a no-op
// and should not throw any exception.
| private void | callLoad(EntityBean ejb, EntityContextImpl entityCtx, int pkLevelSequenceNum, long pkLastRefreshedAt, long currentTime)
ReadOnlyContextImpl context = (ReadOnlyContextImpl) entityCtx;
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE,
"Calling ejbLoad for read-only bean " +
ejbDescriptor.getName() + " primary key " +
entityCtx.getPrimaryKey() + " at " +
new Date(currentTime));
}
try {
context.setInEjbLoad(true);
ejb.ejbLoad();
if( pkLevelSequenceNum > 0 ) {
// Synch up pk-level sequence num after successful load
context.setPKLevelSequenceNum(pkLevelSequenceNum);
}
// Set last refresh time after successful load
context.setLastRefreshedAt(pkLastRefreshedAt);
} finally {
context.setInEjbLoad(false);
}
| protected EntityContextImpl | createEntityContextInstance(EntityBean ejb, EntityContainer entityContainer)
return new ReadOnlyContextImpl(ejb, entityContainer);
| protected void | forceDestroyBean(EJBContextImpl context)
try {
ReadOnlyContextImpl readOnlyCtx = (ReadOnlyContextImpl) context;
if( readOnlyCtx.getReadOnlyBeanInfo() != null ) {
readOnlyCtx.setReadOnlyBeanInfo(null);
robCache.remove(readOnlyCtx.getPrimaryKey(), true);
}
} catch (Exception ex) {
_logger.log(Level.SEVERE, "ejb.forceDestroyBean", ex);
EJBException ejbEx = new EJBException();
ejbEx.initCause(ex);
throw ejbEx;
} finally {
super.forceDestroyBean(context);
}
| public void | handleRefreshAllRequest()
_logger.log(Level.FINE, "Received refreshAll request...");
updateBeanLevelRefresh();
| public void | handleRefreshRequest(java.lang.Object primaryKey)
// Lookup the read-only bean info for this pk.
// If there is no entry for this pk, do nothing.
// If there is a cache hit we *don't* want to increment the
// ref count.
ReadOnlyBeanInfo robInfo = (ReadOnlyBeanInfo)
robCache.get(primaryKey, false);
if( robInfo != null ) {
synchronized(robInfo) {
robInfo.refreshNeeded = true;
robInfo.lastRefreshRequestedAt = this.currentTimeInMillis;
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE,
"Updating refresh time for read-only bean " +
ejbDescriptor.getName() + " primary key " + primaryKey
+ " at " + new Date(robInfo.lastRefreshRequestedAt) +
" pkLevelSequenceNum = " + robInfo.pkLevelSequenceNum);
}
}
} else {
_logger.log(Level.FINE,
"Refresh event for unknown read-only bean PK = " +
primaryKey + " at " + new Date());
}
| protected void | initializeHome()
super.initializeHome();
if (isRemote) {
((ReadOnlyEJBHomeImpl) this.ejbHomeImpl).
setReadOnlyBeanContainer(this);
}
if (isLocal) {
ReadOnlyEJBLocalHomeImpl readOnlyLocalHomeImpl =
(ReadOnlyEJBLocalHomeImpl) ejbLocalHomeImpl;
readOnlyLocalHomeImpl.setReadOnlyBeanContainer(this);
}
| protected java.lang.Object | invokeFindByPrimaryKey(java.lang.reflect.Method method, Invocation inv, java.lang.Object[] args)
Object returnValue = null;
ReadOnlyBeanInfo robInfo = addToCache(args[0], false);
synchronized (robInfo) {
returnValue = inv.isLocal
? robInfo.cachedEjbLocalObject : robInfo.cachedEjbObject;
if ( robInfo.refreshNeeded ) {
_logger.log(Level.FINE, "ReadOnlyBeanContainer calling ejb.ejbFindByPK... for pk=" + args[0]);
returnValue = super.invokeFindByPrimaryKey(method, inv, args);
robInfo.refreshNeeded = false;
//set the seq numbers so that the subsequent business method calls
// (if within expiration time) do not have to call ejb__refresh!!
updateAfterRefresh(robInfo);
}
}
return returnValue;
| java.lang.Object | invokeTargetBeanMethod(java.lang.reflect.Method beanClassMethod, Invocation inv, java.lang.Object target, java.lang.Object[] params, com.sun.enterprise.SecurityManager mgr)
Object returnValue = null;
if( inv.invocationInfo.startsWithFind ) {
FinderResultsKey key = new FinderResultsKey(inv.method, params);
FinderResultsValue value = finderResultsCache.get(key);
if (value != null) {
if (RELATIVE_TIME_CHECK_MODE && (refreshPeriodInMillis > 0)) {
long timeLeft = currentTimeInMillis - value.lastRefreshedAt;
if (timeLeft >= refreshPeriodInMillis) {
returnValue = value.value;
}
} else {
//Use even if !RELATIVE_MODE or if refreshTime == 0
returnValue = value.value;
}
}
if (returnValue == null) {
int hashCode = key.getExtendedHC();
if (hashCode < 0) {
hashCode = -hashCode;
}
int index = hashCode & (FINDER_LOCK_SIZE - 1);
synchronized (finderLocks[index]) {
value = finderResultsCache.get(key);
if (value == null) {
returnValue = super.invokeTargetBeanMethod(
beanClassMethod, inv, target, params, mgr);
finderResultsCache.put(key, new FinderResultsValue(returnValue,
currentTimeInMillis));
} else {
returnValue = value.value;
}
}
}
} else {
returnValue = super.invokeTargetBeanMethod(beanClassMethod, inv,
target, params, mgr);
}
return returnValue;
| public java.lang.Object | postFind(Invocation inv, java.lang.Object primaryKeys, java.lang.Object[] findParams)
// Always call parent to convert pks to ejbobjects/ejblocalobjects.
Object returnValue = super.postFind(inv, primaryKeys, findParams);
// Only proceed if this is not a findByPK method. FindByPK
// processing is special since it's possible to actually
// skip the db access for the query itself. The caching requirements
// to actually skip nonFindByPK queries are extremely complex, but
// the next best thing to skipping the query is to populate the
// RobInfo cache with an entry for each pk in the result set. If
// a PK is part of the result set for a nonFindByPK query before
// it is accessed through some other means, no new refresh will be
// required. This will have the largest benefits for large result
// sets since it's possible for a query to return N beans from one
// db access, which would otherwise require N db accesses if the
// refresh were done upon business method invocation or findByPK.
// If a PK has been accessed before appearing in the result set of
// a nonFindByPK finder, there is no performance gain.
if( !inv.method.getName().equals("findByPrimaryKey") ) {
if ( primaryKeys instanceof Enumeration ) {
Enumeration e = (Enumeration) primaryKeys;
while ( e.hasMoreElements() ) {
Object primaryKey = e.nextElement();
if( primaryKey != null ) {
updateRobInfoAfterFinder(primaryKey);
}
}
} else if ( primaryKeys instanceof Collection ) {
Collection c = (Collection)primaryKeys;
Iterator it = c.iterator();
while ( it.hasNext() ) {
Object primaryKey = it.next();
if( primaryKey != null ) {
updateRobInfoAfterFinder(primaryKey);
}
}
} else {
if( primaryKeys != null ) {
updateRobInfoAfterFinder(primaryKeys);
}
}
}
return returnValue;
| public void | preInvoke(Invocation inv)
// Overriding preInvoke is the best way to interpose on the
// create early enough to throw an exception or eat the
// request before too much setup work is done by the container.
// It's better to keep this logic in the Read-Only Bean container
// than to put it in the InvocationHandlers. Note that
// interposition for the remove operation is handled below
// by overriding the removeBean method.
if( (inv.invocationInfo != null) &&
inv.invocationInfo.startsWithCreate ) {
String msg = "Error for ejb " + ejbDescriptor.getName() +
". create is not allowed for read-only entity beans";
if( isContainerManagedPers ) {
// EJB team decided that throwing a runtime exception was more
// appropriate in this case since creation is not a
// supported operation for read-only beans. If the application
// is coded this way, it's best to throw a system exception
// to signal that the application is broken. NOTE that this
// only applies to the CMP 1.x and 2.x read-only bean
// functionality added starting with AS 8.1.
throw new EJBException(msg);
} else {
// Preserve AS 7 BMP ROB create behavior
CreateException ce = new CreateException(msg);
throw new PreInvokeException(ce);
}
} else {
super.preInvoke(inv);
}
| void | preInvokeNoTx(Invocation inv)
EntityContextImpl context = (EntityContextImpl)inv.context;
if ( context.getState() == DESTROYED )
return;
if ( !inv.invocationInfo.isCreateHomeFinder ) {
// follow EJB2.0 section 12.1.6.1
EntityBean e = (EntityBean)context.getEJB();
try {
callEJBLoad(e, context, false);
} catch ( NoSuchEntityException ex ) {
_logger.log(Level.FINE, "Exception in preInvokeNoTx()", ex);
// Error during ejbLoad, so discard bean: EJB2.0 18.3.3
forceDestroyBean(context);
throw new NoSuchObjectLocalException(
"NoSuchEntityException thrown by ejbLoad, " +
"EJB instance discarded");
} catch ( Exception ex ) {
// Error during ejbLoad, so discard bean: EJB2.0 18.3.3
forceDestroyBean(context);
throw new EJBException(ex);
}
context.setNewlyActivated(false);
}
| void | refreshAll()invoked when application calls refreshAll()
try {
handleRefreshAllRequest();
} finally {
distributedReadOnlyBeanService.notifyRefreshAll(getContainerId());
}
| protected void | removeBean(EJBLocalRemoteObject ejbo, java.lang.reflect.Method removeMethod, boolean local)
String msg = "Error for ejb " + ejbDescriptor.getName() +
". remove is not allowed for read-only entity beans";
if( isContainerManagedPers ) {
// EJB team decided that throwing a runtime exception was more
// appropriate in this case since removal is not a
// supported operation for read-only beans. If the application
// is coded this way, it's best to throw a system exception
// to signal that the application is broken. NOTE that this
// only applies to the CMP 1.x and 2.x read-only bean
// functionality added starting with AS 8.1.
// There's no post-invoke logic to convert local exceptions
// to remote, so take care of that here.
if (local) {
throw new EJBException(msg);
} else {
throw new RemoteException(msg);
}
} else {
// Preserve AS 7 BMP ROB removal behavior.
// Calls to ejbRemove on BMP read-only beans in AS 7
// were silently "eaten" by the ejb container. The
// client didn't receive any exception, but ejbRemove
// was not called on the container.
}
| public void | setRefreshFlag(java.lang.Object primaryKey)
try {
handleRefreshRequest(primaryKey);
} finally {
distributedReadOnlyBeanService.notifyRefresh(
getContainerId(), primaryKey);
}
| public void | undeploy()
this.distributedReadOnlyBeanService.removeReadOnlyBeanRefreshEventHandler(
getContainerId());
super.undeploy();
if( refreshTask != null ) {
refreshTask.cancel();
}
robCache.clear();
| private void | updateAfterRefresh(ReadOnlyBeanInfo robInfo)
robInfo.beanLevelSequenceNum = beanLevelSequenceNum;
robInfo.pkLevelSequenceNum++;
robInfo.lastRefreshedAt = this.currentTimeInMillis;
| private void | updateBeanLevelRefresh()
beanLevelSequenceNum++;
beanLevelLastRefreshRequestedAt = this.currentTimeInMillis;
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE, "updating bean-level refresh for " +
" read-only bean " + ejbDescriptor.getName() +
" at " + new Date(beanLevelLastRefreshRequestedAt) +
" beanLevelSequenceNum = " + beanLevelSequenceNum);
}
// Clear out bean-level finder results cache.
if( _logger.isLoggable(Level.FINE) ) {
_logger.log(Level.FINE, "Clearing " +
finderResultsCache.size() + " items from " +
"finder results cache");
}
finderResultsCache =
new ConcurrentHashMap<FinderResultsKey, FinderResultsValue>();
| private void | updateRobInfoAfterFinder(java.lang.Object primaryKey)
ReadOnlyBeanInfo robInfo = addToCache(primaryKey, false);
/*
synchronized (robInfo) {
if( robInfo.refreshNeeded ) {
robInfo.refreshNeeded = false;
updateAfterRefresh(robInfo);
}
}
*/
|
|