2
* Hibernate, Relational Persistence for Idiomatic Java
4
* Copyright (c) 2007, Red Hat Middleware LLC or third-party contributors as
5
* indicated by the @author tags or express copyright attribution
6
* statements applied by the authors. All third-party contributions are
7
* distributed under license by Red Hat Middleware LLC.
9
* This copyrighted material is made available to anyone wishing to use, modify,
10
* copy, or redistribute it subject to the terms and conditions of the GNU
11
* Lesser General Public License, as published by the Free Software Foundation.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
18
* You should have received a copy of the GNU Lesser General Public License
19
* along with this distribution; if not, write to:
20
* Free Software Foundation, Inc.
21
* 51 Franklin Street, Fifth Floor
22
* Boston, MA 02110-1301 USA
24
package org.hibernate.cache.jbc;
26
import java.util.Collections;
27
import java.util.HashMap;
28
import java.util.HashSet;
29
import java.util.List;
32
import java.util.concurrent.atomic.AtomicReference;
34
import javax.transaction.SystemException;
35
import javax.transaction.Transaction;
36
import javax.transaction.TransactionManager;
38
import org.hibernate.cache.CacheException;
39
import org.hibernate.cache.Region;
40
import org.hibernate.cache.jbc.util.CacheHelper;
41
import org.hibernate.cache.jbc.util.NonLockingDataVersion;
42
import org.jboss.cache.Cache;
43
import org.jboss.cache.Fqn;
44
import org.jboss.cache.Node;
45
import org.jboss.cache.NodeSPI;
46
import org.jboss.cache.config.Configuration;
47
import org.jboss.cache.config.Option;
48
import org.jboss.cache.config.Configuration.NodeLockingScheme;
49
import org.jboss.cache.notifications.annotation.NodeInvalidated;
50
import org.jboss.cache.notifications.annotation.NodeModified;
51
import org.jboss.cache.notifications.annotation.ViewChanged;
52
import org.jboss.cache.notifications.event.NodeInvalidatedEvent;
53
import org.jboss.cache.notifications.event.NodeModifiedEvent;
54
import org.jboss.cache.notifications.event.ViewChangedEvent;
55
import org.jboss.cache.optimistic.DataVersion;
56
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
60
* General support for writing {@link Region} implementations for JBoss Cache
63
* @author Steve Ebersole
65
public abstract class BasicRegionAdapter implements Region {
67
private enum InvalidateState { INVALID, CLEARING, VALID };
69
public static final String ITEM = CacheHelper.ITEM;
71
protected final Cache jbcCache;
72
protected final String regionName;
73
protected final Fqn regionFqn;
74
protected final Fqn internalFqn;
75
protected Node regionRoot;
76
protected final boolean optimistic;
77
protected final TransactionManager transactionManager;
78
protected final Logger log;
79
protected final Object regionRootMutex = new Object();
80
protected final Object memberId;
81
protected final boolean replication;
82
protected final Object invalidationMutex = new Object();
83
protected final AtomicReference<InvalidateState> invalidateState =
84
new AtomicReference<InvalidateState>(InvalidateState.VALID);
85
protected final Set<Object> currentView = new HashSet<Object>();
87
// protected RegionRootListener listener;
89
public BasicRegionAdapter(Cache jbcCache, String regionName, String regionPrefix) {
91
this.log = LoggerFactory.getLogger(getClass());
93
this.jbcCache = jbcCache;
94
this.transactionManager = jbcCache.getConfiguration().getRuntimeConfig().getTransactionManager();
95
this.regionName = regionName;
96
this.regionFqn = createRegionFqn(regionName, regionPrefix);
97
this.internalFqn = CacheHelper.getInternalFqn(regionFqn);
98
this.optimistic = jbcCache.getConfiguration().getNodeLockingScheme() == NodeLockingScheme.OPTIMISTIC;
99
this.memberId = jbcCache.getLocalAddress();
100
this.replication = CacheHelper.isClusteredReplication(jbcCache);
102
this.jbcCache.addCacheListener(this);
104
synchronized (currentView) {
105
List view = jbcCache.getMembers();
107
currentView.addAll(view);
111
activateLocalClusterNode();
113
log.debug("Created Region for " + regionName + " -- regionPrefix is " + regionPrefix);
116
protected abstract Fqn<String> createRegionFqn(String regionName, String regionPrefix);
118
protected void activateLocalClusterNode() {
120
// Regions can get instantiated in the course of normal work (e.g.
121
// a named query region will be created the first time the query is
122
// executed), so suspend any ongoing tx
123
Transaction tx = suspend();
125
Configuration cfg = jbcCache.getConfiguration();
126
if (cfg.isUseRegionBasedMarshalling()) {
127
org.jboss.cache.Region jbcRegion = jbcCache.getRegion(regionFqn, true);
128
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
129
if (classLoader == null) {
130
classLoader = getClass().getClassLoader();
132
jbcRegion.registerContextClassLoader(classLoader);
133
if ( !jbcRegion.isActive() ) {
134
jbcRegion.activate();
138
// // If we are using replication, we may remove the root node
139
// // and then need to re-add it. In that case, the fact
140
// // that it is resident will not replicate, so use a listener
141
// // to set it as resident
142
// if (CacheHelper.isClusteredReplication(cfg.getCacheMode())
143
// || CacheHelper.isClusteredInvalidation(cfg.getCacheMode())) {
144
// listener = new RegionRootListener();
145
// jbcCache.addCacheListener(listener);
148
regionRoot = jbcCache.getRoot().getChild( regionFqn );
149
if (regionRoot == null || !regionRoot.isValid()) {
150
// Establish the region root node with a non-locking data version
151
DataVersion version = optimistic ? NonLockingDataVersion.INSTANCE : null;
152
regionRoot = CacheHelper.addNode(jbcCache, regionFqn, true, true, version);
154
else if (optimistic && regionRoot instanceof NodeSPI) {
155
// FIXME Hacky workaround to JBCACHE-1202
156
if ( !( ( ( NodeSPI ) regionRoot ).getVersion() instanceof NonLockingDataVersion ) ) {
157
((NodeSPI) regionRoot).setVersion(NonLockingDataVersion.INSTANCE);
160
if (!regionRoot.isResident()) {
161
regionRoot.setResident(true);
163
establishInternalNodes();
165
catch (Exception e) {
166
throw new CacheException(e.getMessage(), e);
174
private void establishRegionRootNode()
176
synchronized (regionRootMutex) {
177
// If we've been blocking for the mutex, perhaps another
178
// thread has already reestablished the root.
179
// In case the node was reestablised via replication, confirm it's
180
// marked "resident" (a status which doesn't replicate)
181
if (regionRoot != null && regionRoot.isValid()) {
185
// For pessimistic locking, we just want to toss out our ref
186
// to any old invalid root node and get the latest (may be null)
188
establishInternalNodes();
189
regionRoot = jbcCache.getRoot().getChild( regionFqn );
193
// The rest only matters for optimistic locking, where we
194
// need to establish the proper data version on the region root
196
// Don't hold a transactional lock for this
197
Transaction tx = suspend();
200
// Make sure the root node for the region exists and
201
// has a DataVersion that never complains
202
newRoot = jbcCache.getRoot().getChild( regionFqn );
203
if (newRoot == null || !newRoot.isValid()) {
204
// Establish the region root node with a non-locking data version
205
DataVersion version = optimistic ? NonLockingDataVersion.INSTANCE : null;
206
newRoot = CacheHelper.addNode(jbcCache, regionFqn, true, true, version);
208
else if (newRoot instanceof NodeSPI) {
209
// FIXME Hacky workaround to JBCACHE-1202
210
if ( !( ( ( NodeSPI ) newRoot ).getVersion() instanceof NonLockingDataVersion ) ) {
211
((NodeSPI) newRoot).setVersion(NonLockingDataVersion.INSTANCE);
214
// Never evict this node
215
newRoot.setResident(true);
216
establishInternalNodes();
220
regionRoot = newRoot;
225
private void establishInternalNodes()
227
synchronized (currentView) {
228
Transaction tx = suspend();
230
for (Object member : currentView) {
231
DataVersion version = optimistic ? NonLockingDataVersion.INSTANCE : null;
232
Fqn f = Fqn.fromRelativeElements(internalFqn, member);
233
CacheHelper.addNode(jbcCache, f, true, false, version);
243
public String getName() {
247
public Cache getCacheInstance() {
251
public Fqn getRegionFqn() {
255
public Object getMemberId()
257
return this.memberId;
261
* Checks for the validity of the root cache node for this region,
262
* creating a new one if it does not exist or is invalid, and also
263
* ensuring that the root node is marked as resident. Suspends any
264
* transaction while doing this to ensure no transactional locks are held
265
* on the region root.
267
* TODO remove this once JBCACHE-1250 is resolved.
269
public void ensureRegionRootExists() {
271
if (regionRoot == null || !regionRoot.isValid())
272
establishRegionRootNode();
274
// Fix up the resident flag
275
if (regionRoot != null && regionRoot.isValid() && !regionRoot.isResident())
276
regionRoot.setResident(true);
279
public boolean checkValid()
281
boolean valid = invalidateState.get() == InvalidateState.VALID;
284
synchronized (invalidationMutex) {
285
if (invalidateState.compareAndSet(InvalidateState.INVALID, InvalidateState.CLEARING)) {
286
Transaction tx = suspend();
288
Option opt = new Option();
289
opt.setLockAcquisitionTimeout(1);
290
opt.setCacheModeLocal(true);
291
CacheHelper.removeAll(jbcCache, regionFqn, opt);
292
invalidateState.compareAndSet(InvalidateState.CLEARING, InvalidateState.VALID);
294
catch (Exception e) {
295
if (log.isTraceEnabled()) {
296
log.trace("Could not invalidate region: " + e.getLocalizedMessage());
304
valid = invalidateState.get() == InvalidateState.VALID;
310
public void destroy() throws CacheException {
312
// NOTE : this is being used from the process of shutting down a
313
// SessionFactory. Specific things to consider:
314
// (1) this clearing of the region should not propagate to
315
// other nodes on the cluster (if any); this is the
316
// cache-mode-local option bit...
317
// (2) really just trying a best effort to cleanup after
318
// ourselves; lock failures, etc are not critical here;
319
// this is the fail-silently option bit...
320
Option option = new Option();
321
option.setCacheModeLocal(true);
322
option.setFailSilently(true);
324
option.setDataVersion(NonLockingDataVersion.INSTANCE);
326
jbcCache.getInvocationContext().setOptionOverrides(option);
327
jbcCache.removeNode(regionFqn);
328
deactivateLocalNode();
329
} catch (Exception e) {
330
throw new CacheException(e);
333
jbcCache.removeCacheListener(this);
337
protected void deactivateLocalNode() {
338
org.jboss.cache.Region jbcRegion = jbcCache.getRegion(regionFqn, false);
339
if (jbcRegion != null && jbcRegion.isActive()) {
340
jbcRegion.deactivate();
341
jbcRegion.unregisterContextClassLoader();
345
public boolean contains(Object key) {
346
if ( !checkValid() ) {
351
Option opt = new Option();
352
opt.setLockAcquisitionTimeout(100);
353
CacheHelper.setInvocationOption( jbcCache, opt );
354
return CacheHelper.getAllowingTimeout( jbcCache, regionFqn, key ) != null;
356
catch ( CacheException ce ) {
359
catch ( Throwable t ) {
360
throw new CacheException( t );
364
public long getSizeInMemory() {
369
public long getElementCountInMemory() {
372
Set childrenNames = CacheHelper.getChildrenNames(jbcCache, regionFqn);
373
int size = childrenNames.size();
374
if (childrenNames.contains(CacheHelper.Internal.NODE)) {
379
catch ( CacheException ce ) {
382
catch (Exception e) {
383
throw new CacheException(e);
391
public long getElementCountOnDisk() {
398
Map result = new HashMap();
399
Set childrenNames = CacheHelper.getChildrenNames(jbcCache, regionFqn);
400
for (Object childName : childrenNames) {
401
if (CacheHelper.Internal.NODE != childName) {
402
result.put(childName, CacheHelper.get(jbcCache,regionFqn, childName));
406
} catch (CacheException e) {
408
} catch (Exception e) {
409
throw new CacheException(e);
413
return Collections.emptyMap();
417
public long nextTimestamp() {
418
return System.currentTimeMillis() / 100;
421
public int getTimeout() {
422
return 600; // 60 seconds
426
* Performs a JBoss Cache <code>get(Fqn, Object)</code> after first
427
* {@link #suspend suspending any ongoing transaction}. Wraps any exception
428
* in a {@link CacheException}. Ensures any ongoing transaction is resumed.
430
* @param key The key of the item to get
431
* @param opt any option to add to the get invocation. May be <code>null</code>
432
* @param suppressTimeout should any TimeoutException be suppressed?
433
* @return The retrieved object
434
* @throws CacheException issue managing transaction or talking to cache
436
protected Object suspendAndGet(Object key, Option opt, boolean suppressTimeout) throws CacheException {
437
Transaction tx = suspend();
439
CacheHelper.setInvocationOption(getCacheInstance(), opt);
441
return CacheHelper.getAllowingTimeout(getCacheInstance(), getRegionFqn(), key);
443
return CacheHelper.get(getCacheInstance(), getRegionFqn(), key);
450
* Tell the TransactionManager to suspend any ongoing transaction.
452
* @return the transaction that was suspended, or <code>null</code> if
455
public Transaction suspend() {
456
Transaction tx = null;
458
if (transactionManager != null) {
459
tx = transactionManager.suspend();
461
} catch (SystemException se) {
462
throw new CacheException("Could not suspend transaction", se);
468
* Tell the TransactionManager to resume the given transaction
471
* the transaction to suspend. May be <code>null</code>.
473
public void resume(Transaction tx) {
476
transactionManager.resume(tx);
477
} catch (Exception e) {
478
throw new CacheException("Could not resume transaction", e);
483
* Get an Option with a {@link Option#getDataVersion() data version}
484
* of {@link NonLockingDataVersion}. The data version will not be
485
* set if the cache is not configured for optimistic locking.
487
* @param allowNullReturn If <code>true</code>, return <code>null</code>
488
* if the cache is not using optimistic locking.
489
* If <code>false</code>, return a default
492
* @return the Option, or <code>null</code>.
494
protected Option getNonLockingDataVersionOption(boolean allowNullReturn) {
495
return optimistic ? NonLockingDataVersion.getInvocationOption()
496
: (allowNullReturn) ? null : new Option();
499
public static Fqn<String> getTypeFirstRegionFqn(String regionName, String regionPrefix, String regionType) {
500
Fqn<String> base = Fqn.fromString(regionType);
501
Fqn<String> added = Fqn.fromString(escapeRegionName(regionName, regionPrefix));
502
return new Fqn<String>(base, added);
505
public static Fqn<String> getTypeLastRegionFqn(String regionName, String regionPrefix, String regionType) {
506
Fqn<String> base = Fqn.fromString(escapeRegionName(regionName, regionPrefix));
507
return new Fqn<String>(base, regionType);
510
public static String escapeRegionName(String regionName, String regionPrefix) {
511
String escaped = null;
513
if (regionPrefix != null) {
514
idx = regionName.indexOf(regionPrefix);
518
int regionEnd = idx + regionPrefix.length();
519
String prefix = regionName.substring(0, regionEnd);
520
String suffix = regionName.substring(regionEnd);
521
suffix = suffix.replace('.', '/');
522
escaped = prefix + suffix;
524
escaped = regionName.replace('.', '/');
525
if (regionPrefix != null && regionPrefix.length() > 0) {
526
escaped = regionPrefix + "/" + escaped;
533
public void nodeModified(NodeModifiedEvent event)
535
handleEvictAllModification(event);
538
protected boolean handleEvictAllModification(NodeModifiedEvent event) {
540
if (!event.isPre() && (replication || event.isOriginLocal()) && event.getData().containsKey(ITEM))
542
if (event.getFqn().isChildOf(internalFqn))
544
invalidateState.set(InvalidateState.INVALID);
552
public void nodeInvalidated(NodeInvalidatedEvent event)
554
handleEvictAllInvalidation(event);
557
protected boolean handleEvictAllInvalidation(NodeInvalidatedEvent event)
559
if (!event.isPre() && event.getFqn().isChildOf(internalFqn))
561
invalidateState.set(InvalidateState.INVALID);
568
public void viewChanged(ViewChangedEvent event) {
570
synchronized (currentView) {
571
List view = event.getNewView().getMembers();
573
currentView.addAll(view);
574
establishInternalNodes();