package org.hibernate.cache.infinispan.impl; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.Region; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cache.infinispan.util.AddressAdapter; import org.hibernate.cache.infinispan.util.AddressAdapterImpl; import org.hibernate.cache.infinispan.util.CacheAdapter; import org.hibernate.cache.infinispan.util.CacheHelper; import org.hibernate.cache.infinispan.util.FlagAdapter; import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated; import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent; import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent; import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged; import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * Support for Infinispan {@link Region}s. Handles common "utility" methods for an underlying named * Cache. In other words, this implementation doesn't actually read or write data. Subclasses are * expected to provide core cache interaction appropriate to the semantics needed. * * @author Chris Bredesen * @author Galder ZamarreƱo * @since 3.5 */ public abstract class BaseRegion implements Region { private enum InvalidateState { INVALID, CLEARING, VALID }; private static final Log log = LogFactory.getLog(BaseRegion.class); private final String name; protected final CacheAdapter cacheAdapter; protected final AddressAdapter address; protected final Set<AddressAdapter> currentView = new HashSet<AddressAdapter>(); protected final TransactionManager transactionManager; protected final boolean replication; protected final Object invalidationMutex = new Object(); protected final AtomicReference<InvalidateState> invalidateState = new AtomicReference<InvalidateState>(InvalidateState.VALID); private final RegionFactory factory; public BaseRegion(CacheAdapter cacheAdapter, String name, TransactionManager transactionManager, RegionFactory factory) { this.cacheAdapter = cacheAdapter; this.name = name; this.transactionManager = transactionManager; this.replication = cacheAdapter.isClusteredReplication(); this.address = this.cacheAdapter.getAddress(); this.cacheAdapter.addListener(this); this.factory = factory; } public void start() { if (address != null) { synchronized (currentView) { List<AddressAdapter> view = cacheAdapter.getMembers(); if (view != null) { currentView.addAll(view); establishInternalNodes(); } } } } /** * Calls to this method must be done from synchronized (currentView) blocks only!! */ private void establishInternalNodes() { Transaction tx = suspend(); try { for (AddressAdapter member : currentView) { CacheHelper.initInternalEvict(cacheAdapter, member); } } finally { resume(tx); } } public String getName() { return name; } public CacheAdapter getCacheAdapter() { return cacheAdapter; } public long getElementCountInMemory() { if (checkValid()) { Set keySet = cacheAdapter.keySet(); int size = cacheAdapter.size(); if (CacheHelper.containsEvictAllNotification(keySet, address)) size--; return size; } return 0; } /** * Not supported. * * @return -1 */ public long getElementCountOnDisk() { return -1; } /** * Not supported. * * @return -1 */ public long getSizeInMemory() { return -1; } public int getTimeout() { return 600; // 60 seconds } public long nextTimestamp() { return factory.nextTimestamp(); } public Map toMap() { if (checkValid()) { // If copying causes issues, provide a lazily loaded Map Map map = new HashMap(); Set<Map.Entry> entries = cacheAdapter.toMap().entrySet(); for (Map.Entry entry : entries) { Object key = entry.getKey(); if (!CacheHelper.isEvictAllNotification(key)) { map.put(key, entry.getValue()); } } return map; } return Collections.EMPTY_MAP; } public void destroy() throws CacheException { try { cacheAdapter.stop(); // cacheAdapter.clear(); } finally { cacheAdapter.removeListener(this); } } public boolean contains(Object key) { if (!checkValid()) return false; // Reads are non-blocking in Infinispan, so not sure of the necessity of passing ZERO_LOCK_ACQUISITION_TIMEOUT return cacheAdapter.withFlags(FlagAdapter.ZERO_LOCK_ACQUISITION_TIMEOUT).containsKey(key); } public AddressAdapter getAddress() { return address; } public boolean checkValid() { boolean valid = isValid(); if (!valid) { synchronized (invalidationMutex) { if (invalidateState.compareAndSet(InvalidateState.INVALID, InvalidateState.CLEARING)) { Transaction tx = suspend(); try { cacheAdapter.withFlags(FlagAdapter.CACHE_MODE_LOCAL, FlagAdapter.ZERO_LOCK_ACQUISITION_TIMEOUT).clear(); invalidateState.compareAndSet(InvalidateState.CLEARING, InvalidateState.VALID); } catch (Exception e) { if (log.isTraceEnabled()) { log.trace("Could not invalidate region: " + e.getLocalizedMessage()); } } finally { resume(tx); } } } valid = isValid(); } return valid; } protected boolean isValid() { return invalidateState.get() == InvalidateState.VALID; } /** * Performs a Infinispan <code>get(Fqn, Object)</code> * * @param key The key of the item to get * @param suppressTimeout should any TimeoutException be suppressed? * @param flagAdapters flags to add to the get invocation * @return The retrieved object * @throws CacheException issue managing transaction or talking to cache */ protected Object get(Object key, boolean suppressTimeout, FlagAdapter... flagAdapters) throws CacheException { CacheAdapter localCacheAdapter = cacheAdapter; if (flagAdapters != null && flagAdapters.length > 0) localCacheAdapter = cacheAdapter.withFlags(flagAdapters); if (suppressTimeout) return localCacheAdapter.getAllowingTimeout(key); else return localCacheAdapter.get(key); } public Object getOwnerForPut() { Transaction tx = null; try { if (transactionManager != null) { tx = transactionManager.getTransaction(); } } catch (SystemException se) { throw new CacheException("Could not obtain transaction", se); } return tx == null ? Thread.currentThread() : tx; } /** * Tell the TransactionManager to suspend any ongoing transaction. * * @return the transaction that was suspended, or <code>null</code> if * there wasn't one */ public Transaction suspend() { Transaction tx = null; try { if (transactionManager != null) { tx = transactionManager.suspend(); } } catch (SystemException se) { throw new CacheException("Could not suspend transaction", se); } return tx; } /** * Tell the TransactionManager to resume the given transaction * * @param tx * the transaction to suspend. May be <code>null</code>. */ public void resume(Transaction tx) { try { if (tx != null) transactionManager.resume(tx); } catch (Exception e) { throw new CacheException("Could not resume transaction", e); } } @CacheEntryModified public void entryModified(CacheEntryModifiedEvent event) { handleEvictAllModification(event); } protected boolean handleEvictAllModification(CacheEntryModifiedEvent event) { if (!event.isPre() && (replication || event.isOriginLocal()) && CacheHelper.isEvictAllNotification(event.getKey(), event.getValue())) { if (log.isTraceEnabled()) log.tracef("Set invalid state because marker cache entry was put: {0}", event); invalidateState.set(InvalidateState.INVALID); return true; } return false; } @CacheEntryInvalidated public void entryInvalidated(CacheEntryInvalidatedEvent event) { if (log.isTraceEnabled()) log.tracef("Cache entry invalidated: {0}", event); handleEvictAllInvalidation(event); } protected boolean handleEvictAllInvalidation(CacheEntryInvalidatedEvent event) { if (!event.isPre() && CacheHelper.isEvictAllNotification(event.getKey())) { if (log.isTraceEnabled()) log.tracef("Set invalid state because marker cache entry was invalidated: {0}", event); invalidateState.set(InvalidateState.INVALID); return true; } return false; } @ViewChanged public void viewChanged(ViewChangedEvent event) { synchronized (currentView) { List<AddressAdapter> view = AddressAdapterImpl.toAddressAdapter(event.getNewMembers()); if (view != null) { currentView.addAll(view); establishInternalNodes(); } } } }