/**
  * Fetches a value from the shared cache. If values were wrapped, then they will be unwrapped
  * before being returned. If code requires direct access to the wrapper object as well, then this
  * call should not be used.
  *
  * <p>If a TransactionStats instance is passed in, then cache access stats are tracked, otherwise
  * - if null is passed in then stats are not tracked.
  *
  * @param key the key
  * @return Returns the value or <tt>null</tt>
  */
 @SuppressWarnings("unchecked")
 public static <KEY extends Serializable, VAL> VAL getSharedCacheValue(
     SimpleCache<KEY, ValueHolder<VAL>> sharedCache, KEY key, TransactionStats stats) {
   final long startNanos = stats != null ? System.nanoTime() : 0;
   Object possibleWrapper = sharedCache.get(key);
   final long endNanos = stats != null ? System.nanoTime() : 0;
   if (possibleWrapper == null) {
     if (stats != null) {
       stats.record(startNanos, endNanos, OpType.GET_MISS);
     }
     return null;
   } else if (possibleWrapper instanceof ValueHolder) {
     if (stats != null) {
       stats.record(startNanos, endNanos, OpType.GET_HIT);
     }
     ValueHolder<VAL> wrapper = (ValueHolder<VAL>) possibleWrapper;
     return wrapper.getValue();
   } else {
     if (stats != null) {
       stats.record(startNanos, endNanos, OpType.GET_MISS);
     }
     throw new IllegalStateException(
         "All entries for TransactionalCache must be put using TransactionalCache.putSharedCacheValue.");
   }
 }
  /** Merge the transactional caches into the shared cache */
  public void beforeCommit(boolean readOnly) {
    if (isDebugEnabled) {
      logger.debug("Processing before-commit");
    }

    TransactionData txnData = getTransactionData();
    try {
      if (txnData.isClearOn) {
        // clear shared cache
        final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0;
        sharedCache.clear();
        final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0;
        if (cacheStatsEnabled) {
          TransactionStats stats = txnData.stats;
          stats.record(startNanos, endNanos, OpType.CLEAR);
        }
        if (isDebugEnabled) {
          logger.debug("Clear notification recieved in commit - clearing shared cache");
        }
      } else {
        // transfer any removed items
        for (Serializable key : txnData.removedItemsCache) {
          final long startNanos = System.nanoTime();
          sharedCache.remove(key);
          final long endNanos = System.nanoTime();
          TransactionStats stats = txnData.stats;
          stats.record(startNanos, endNanos, OpType.REMOVE);
        }
        if (isDebugEnabled) {
          logger.debug(
              "Removed "
                  + txnData.removedItemsCache.size()
                  + " values from shared cache in commit");
        }
      }

      // transfer updates
      Set<Serializable> keys = (Set<Serializable>) txnData.updatedItemsCache.keySet();
      for (Map.Entry<Serializable, CacheBucket<V>> entry :
          (Set<Map.Entry<Serializable, CacheBucket<V>>>) txnData.updatedItemsCache.entrySet()) {
        Serializable key = entry.getKey();
        CacheBucket<V> bucket = entry.getValue();
        bucket.doPreCommit(
            sharedCache, key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly);
      }
      if (isDebugEnabled) {
        logger.debug("Pre-commit called for " + keys.size() + " values.");
      }
    } catch (Throwable e) {
      throw new AlfrescoRuntimeException("Failed to transfer updates to shared cache", e);
    } finally {
      // Block any further updates
      txnData.isClosed = true;
    }
  }
 /**
  * Values written to the backing cache need proper wrapping and unwrapping
  *
  * @param sharedCache the cache to operate on
  * @param key the key
  * @param value the value to wrap
  * @since 4.2.3
  */
 public static <KEY extends Serializable, VAL> void putSharedCacheValue(
     SimpleCache<KEY, ValueHolder<VAL>> sharedCache, KEY key, VAL value, TransactionStats stats) {
   ValueHolder<VAL> wrapper = new ValueHolder<VAL>(value);
   final long startNanos = System.nanoTime(); // TODO: enabled?
   sharedCache.put(key, wrapper);
   final long endNanos = System.nanoTime();
   if (stats != null) {
     stats.record(startNanos, endNanos, OpType.PUT);
   }
 }
 /**
  * Transfers cache removals or clears. This allows explicit cache cleanup to be propagated to the
  * shared cache even in the event of rollback - useful if the cause of a problem is the shared
  * cache value.
  */
 public void afterRollback() {
   TransactionData txnData = getTransactionData();
   try {
     if (txnData.isClearOn) {
       // clear shared cache
       final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0;
       sharedCache.clear();
       final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0;
       if (cacheStatsEnabled) {
         TransactionStats stats = txnData.stats;
         stats.record(startNanos, endNanos, OpType.CLEAR);
       }
       if (isDebugEnabled) {
         logger.debug("Clear notification recieved in rollback - clearing shared cache");
       }
     } else {
       // transfer any removed items
       for (Serializable key : txnData.removedItemsCache) {
         final long startNanos = System.nanoTime();
         sharedCache.remove(key);
         final long endNanos = System.nanoTime();
         TransactionStats stats = txnData.stats;
         stats.record(startNanos, endNanos, OpType.REMOVE);
       }
       if (isDebugEnabled) {
         logger.debug(
             "Removed "
                 + txnData.removedItemsCache.size()
                 + " values from shared cache in rollback");
       }
     }
   } catch (Throwable e) {
     throw new AlfrescoRuntimeException("Failed to transfer updates to shared cache", e);
   } finally {
     removeCaches(txnData);
     // Aggregate this transaction's stats with centralised cache stats.
     if (cacheStatsEnabled) {
       cacheStats.add(name, txnData.stats);
     }
   }
 }
  /** Merge the transactional caches into the shared cache */
  public void afterCommit() {
    if (isDebugEnabled) {
      logger.debug("Processing after-commit");
    }

    TransactionData txnData = getTransactionData();
    try {
      if (txnData.isClearOn) {
        // clear shared cache
        final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0;
        sharedCache.clear();
        final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0;
        if (cacheStatsEnabled) {
          TransactionStats stats = txnData.stats;
          stats.record(startNanos, endNanos, OpType.CLEAR);
        }
        if (isDebugEnabled) {
          logger.debug("Clear notification recieved in commit - clearing shared cache");
        }
      } else {
        // transfer any removed items
        for (Serializable key : txnData.removedItemsCache) {
          final long startNanos = System.nanoTime();
          sharedCache.remove(key);
          final long endNanos = System.nanoTime();
          TransactionStats stats = txnData.stats;
          stats.record(startNanos, endNanos, OpType.REMOVE);
        }
        if (isDebugEnabled) {
          logger.debug(
              "Removed "
                  + txnData.removedItemsCache.size()
                  + " values from shared cache in commit");
        }
      }

      // transfer updates
      Set<Serializable> keys = (Set<Serializable>) txnData.updatedItemsCache.keySet();
      for (Map.Entry<Serializable, CacheBucket<V>> entry :
          (Set<Map.Entry<Serializable, CacheBucket<V>>>) txnData.updatedItemsCache.entrySet()) {
        Serializable key = entry.getKey();
        CacheBucket<V> bucket = entry.getValue();
        try {
          bucket.doPostCommit(
              sharedCache,
              key,
              this.isMutable,
              this.allowEqualsChecks,
              txnData.isReadOnly,
              txnData.stats);
        } catch (Exception e) {
          // MNT-10486: NPE in NodeEntity during post-commit write through to shared cache
          //              This try-catch is diagnostic in nature.  We need to know the names of the
          // caches
          //              and details of the values involved.
          //              The causal exception will be rethrown.
          throw new AlfrescoRuntimeException(
              "CacheBucket postCommit transfer to shared cache failed: \n"
                  + "   Cache:      "
                  + sharedCache
                  + "\n"
                  + "   Key:        "
                  + key
                  + "\n"
                  + "   New Value:  "
                  + bucket.getValue()
                  + "\n"
                  + "   Cache Value:"
                  + sharedCache.get(key),
              e);
        }
      }
      if (isDebugEnabled) {
        logger.debug("Post-commit called for " + keys.size() + " values.");
      }
    } catch (Throwable e) {
      throw new AlfrescoRuntimeException("Failed to transfer updates to shared cache", e);
    } finally {
      removeCaches(txnData);
      // Aggregate this transaction's stats with centralised cache stats.
      if (cacheStatsEnabled) {
        cacheStats.add(name, txnData.stats);
      }
    }
  }