All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ehcache.jsr107.Eh107Cache Maven / Gradle / Ivy

There is a newer version: 3.10.8
Show newest version
/*
 * Copyright Terracotta, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ehcache.jsr107;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.configuration.Configuration;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriterException;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import javax.cache.processor.EntryProcessorResult;

import org.ehcache.Ehcache;
import org.ehcache.EhcacheHackAccessor;
import org.ehcache.event.EventFiring;
import org.ehcache.event.EventOrdering;
import org.ehcache.function.BiFunction;
import org.ehcache.function.Function;
import org.ehcache.function.NullaryFunction;
import org.ehcache.jsr107.CacheResources.ListenerResources;
import org.ehcache.jsr107.EventListenerAdaptors.EventListenerAdaptor;
import org.ehcache.spi.loader.CacheLoader;
import org.ehcache.spi.writer.CacheWriter;

/**
 * @author teck
 */
class Eh107Cache implements Cache {

  private final org.ehcache.Cache ehCache;
  private final org.ehcache.Jsr107Cache jsr107Cache;
  private final Eh107CacheManager cacheManager;
  private final String name;
  private final AtomicBoolean closed = new AtomicBoolean();
  private final CacheResources cacheResources;
  private final Eh107CacheMXBean managementBean;
  private final Eh107CacheStatisticsMXBean statisticsBean;
  private final Eh107Configuration config;
  private final CacheLoader cacheLoader;
  private final CacheWriter cacheWriter;
  private final Eh107Expiry expiry;

  Eh107Cache(String name, Eh107Configuration config, CacheResources cacheResources,
      org.ehcache.Cache ehCache, Eh107CacheManager cacheManager, Eh107Expiry expiry) {
    this.expiry = expiry;
    this.cacheLoader = cacheResources.getCacheLoader();
    this.cacheWriter = cacheResources.getCacheWriter();
    this.config = config;
    this.ehCache = ehCache;
    this.cacheManager = cacheManager;
    this.name = name;
    this.cacheResources = cacheResources;
    this.managementBean = new Eh107CacheMXBean(name, cacheManager, config);
    this.statisticsBean = new Eh107CacheStatisticsMXBean(name, cacheManager, ehCache.getStatistics());

    for (Map.Entry, ListenerResources> entry : cacheResources
        .getListenerResources().entrySet()) {
      registerEhcacheListeners(entry.getKey(), entry.getValue());
    }

    this.jsr107Cache = EhcacheHackAccessor.getJsr107Cache((Ehcache) ehCache);
  }

  @Override
  public V get(K key) {
    checkClosed();
    try {
      return ehCache.get(key);
    } catch (org.ehcache.exceptions.CacheLoaderException e) {
      throw jsr107CacheLoaderException(e);
    }
  }

  @Override
  public Map getAll(Set keys) {
    checkClosed();
    try {
      return jsr107Cache.getAll(keys);
    } catch (org.ehcache.exceptions.CacheLoaderException e) {
      throw jsr107CacheLoaderException(e);
    }
  }

  @Override
  public boolean containsKey(K key) {
    checkClosed();
    return ehCache.containsKey(key);
  }

  @Override
  public void loadAll(Set keys, boolean replaceExistingValues, CompletionListener completionListener) {
    // TODO: this method is allowed to be async. Hand it off to some thread(s)?
    checkClosed();

    if (keys == null) {
      throw new NullPointerException();
    }

    for (K key : keys) {
      if (key == null) {
        throw new NullPointerException();
      }
    }

    completionListener = completionListener != null ? completionListener : NullCompletionListener.INSTANCE;

    if (cacheLoader == null) {
      completionListener.onCompletion();
      return;
    }

    try {
      jsr107Cache.loadAll(keys, replaceExistingValues, new Function, Map>() {
        @Override
        public Map apply(Iterable keys) {
          try {
            return cacheLoader.loadAll(keys);
          } catch (Exception e) {
            final CacheLoaderException cle;
            if (e instanceof CacheLoaderException) {
              cle = (CacheLoaderException) e;
            } else if (e.getCause() instanceof CacheLoaderException) {
              cle = (CacheLoaderException) e.getCause();
            } else {
              cle = new CacheLoaderException(e);
            }

            throw cle;
          }
        }
      });
    } catch (Exception e) {
      final CacheLoaderException cle;
      if (e instanceof CacheLoaderException) {
        cle = (CacheLoaderException) e;
      } else if (e.getCause() instanceof CacheLoaderException) {
        cle = (CacheLoaderException) e.getCause();
      } else {
        cle = new CacheLoaderException(e);
      }

      completionListener.onException(cle);
      return;
    }

    completionListener.onCompletion();
  }

  @Override
  public void put(K key, V value) {
    checkClosed();
    try {
      ehCache.put(key, value);
    } catch (org.ehcache.exceptions.CacheWriterException cwe) {
      throw jsr107CacheWriterException(cwe);
    }
  }

  @Override
  public V getAndPut(final K key, final V value) {
    checkClosed();

    if (key == null || value == null) {
      throw new NullPointerException();
    }

    try {
      return jsr107Cache.getAndPut(key, value);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public void putAll(Map map) {
    checkClosed();
    try {
      ehCache.putAll(map);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public boolean putIfAbsent(K key, V value) {
    checkClosed();
    try {
      expiry.enableShortCircuitAccessCalls();
      return ehCache.putIfAbsent(key, value) == null;
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    } finally {
      expiry.disableShortCircuitAccessCalls();
    }
  }

  @Override
  public boolean remove(final K key) {
    checkClosed();

    if (key == null) {
      throw new NullPointerException();
    }

    try {
      return jsr107Cache.remove(key);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public boolean remove(K key, V oldValue) {
    checkClosed();
    try {
      return ehCache.remove(key, oldValue);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public V getAndRemove(K key) {
    checkClosed();

    if (key == null) {
      throw new NullPointerException();
    }

    try {
      return jsr107Cache.getAndRemove(key);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public boolean replace(K key, V oldValue, V newValue) {
    checkClosed();
    try {
      return ehCache.replace(key, oldValue, newValue);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public boolean replace(K key, V value) {
    checkClosed();
    try {
      return ehCache.replace(key, value) != null;
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public V getAndReplace(K key, V value) {
    try {
      checkClosed();
      return ehCache.replace(key, value);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public void removeAll(Set keys) {
    checkClosed();
    try {
      ehCache.removeAll(keys);
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public void removeAll() {
    checkClosed();
    try {
      jsr107Cache.removeAll();
    } catch (org.ehcache.exceptions.CacheWriterException e) {
      throw jsr107CacheWriterException(e);
    }
  }

  @Override
  public void clear() {
    clear(true);
  }

  private void clear(boolean checkClosed) {
    if (checkClosed) {
      checkClosed();
    }

    ehCache.clear();
  }

  @Override
  public > C getConfiguration(Class clazz) {
    checkClosed();
    return Unwrap.unwrap(clazz, config);
  }

  @Override
  public  T invoke(final K key, final EntryProcessor entryProcessor, final Object... arguments)
      throws EntryProcessorException {
    checkClosed();

    if (key == null || entryProcessor == null) {
      throw new NullPointerException();
    }

    final AtomicReference mutableEntryRef = new AtomicReference();
    final AtomicReference invokeResult = new AtomicReference();

    jsr107Cache.compute(key, new BiFunction() {
      @Override
      public V apply(K mappedKey, V mappedValue) {
        MutableEntry mutableEntry = new MutableEntry(mappedKey, mappedValue);
        mutableEntryRef.set(mutableEntry);

        T processResult;
        try {
          processResult = entryProcessor.process(mutableEntry, arguments);
        } catch (Exception e) {
          if (e instanceof EntryProcessorException) {
            throw (EntryProcessorException) e;
          }
          throw new EntryProcessorException(e);
        }

        invokeResult.set(processResult);

        return mutableEntry.apply(config.isWriteThrough(), cacheWriter);
      }
    }, new NullaryFunction() {
      @Override
      public Boolean apply() {
        MutableEntry mutableEntry = mutableEntryRef.get();
        return mutableEntry.shouldReplace();
      }
    }, new NullaryFunction() {
      @Override
      public Boolean apply() {
        MutableEntry mutableEntry = mutableEntryRef.get();
        return mutableEntry.shouldInvokeWriter();
      }
    }, new NullaryFunction() {
      @Override
      public Boolean apply() {
        MutableEntry mutableEntry = mutableEntryRef.get();
        return mutableEntry.shouldGenerateEvent();
      }
    });

    return invokeResult.get();
  }

  @Override
  public  Map> invokeAll(Set keys, EntryProcessor entryProcessor,
      Object... arguments) {
    checkClosed();

    if (keys == null || entryProcessor == null) {
      throw new NullPointerException();
    }

    for (K key : keys) {
      if (key == null) {
        throw new NullPointerException();
      }
    }

    // TODO: maybe hand off to threads for parallel execution?
    Map> results = new HashMap>(keys.size());
    for (K key : keys) {
      EntryProcessorResult result = null;
      try {
        T invokeResult = invoke(key, entryProcessor, arguments);
        if (invokeResult != null) {
          result = newEntryProcessorResult(invokeResult);
        }
      } catch (final Exception e) {
        result = newErrorThrowingEntryProcessorResult(e);
      }

      if (result != null) {
        results.put(key, result);
      }
    }

    return results;
  }

  @Override
  public String getName() {
    return name;
  }

  @Override
  public CacheManager getCacheManager() {
    return cacheManager;
  }

  @Override
  public void close() {
    MultiCacheException closeException = new MultiCacheException();
    cacheManager.close(this, closeException);
    closeException.throwIfNotEmpty();
  }

  @Override
  public boolean isClosed() {
    return closed.get();
  }

  void closeInternal(MultiCacheException closeException) {
    closeInternal(false, closeException);
  }

  private void closeInternal(boolean destroy, MultiCacheException closeException) {
    if (closed.compareAndSet(false, true)) {
      if (destroy) {
        try {
          clear(false);
        } catch (Throwable t) {
          closeException.addThrowable(t);
        }
      }

      cacheResources.closeResources(closeException);
    }
  }

  void destroy(MultiCacheException destroyException) {
    closeInternal(true, destroyException);
  }

  @Override
  public  T unwrap(Class clazz) {
    return Unwrap.unwrap(clazz, ehCache);
  }

  @Override
  public void registerCacheEntryListener(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
    checkClosed();

    if (cacheEntryListenerConfiguration == null) {
      throw new NullPointerException();
    }

    ListenerResources resources = cacheResources.registerCacheEntryListener(cacheEntryListenerConfiguration);
    config.addCacheEntryListenerConfiguration(cacheEntryListenerConfiguration);

    registerEhcacheListeners(cacheEntryListenerConfiguration, resources);
  }

  private void registerEhcacheListeners(CacheEntryListenerConfiguration config, ListenerResources resources) {
    final boolean synchronous = config.isSynchronous();
    final EventOrdering ordering = synchronous ? EventOrdering.ORDERED : EventOrdering.UNORDERED;
    final EventFiring firing = synchronous ? EventFiring.SYNCHRONOUS : EventFiring.ASYNCHRONOUS;

    final boolean requestsOld = config.isOldValueRequired();

    for (EventListenerAdaptor ehcacheListener : resources.getEhcacheListeners(this, requestsOld)) {
      ehCache.getRuntimeConfiguration().registerCacheEventListener(ehcacheListener, ordering, firing,
          EnumSet.of(ehcacheListener.getEhcacheEventType()));
    }
  }

  @Override
  public void deregisterCacheEntryListener(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) {
    checkClosed();

    if (cacheEntryListenerConfiguration == null) {
      throw new NullPointerException();
    }

    ListenerResources resources = cacheResources.deregisterCacheEntryListener(cacheEntryListenerConfiguration);

    if (resources != null) {
      config.removeCacheEntryListenerConfiguration(cacheEntryListenerConfiguration);

      for (EventListenerAdaptor ehListener : resources.getEhcacheListeners(this,
          cacheEntryListenerConfiguration.isOldValueRequired())) {
        ehCache.getRuntimeConfiguration().deregisterCacheEventListener(ehListener);
      }
    }
  }

  @Override
  public java.util.Iterator> iterator() {
    return new Iterator(ehCache);
  }

  private void checkClosed() {
    if (closed.get()) {
      throw new IllegalStateException("Cache[" + name + "] is closed");
    }
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "[" + name + "]";
  }

  Eh107MXBean getManagementMBean() {
    return managementBean;
  }

  Eh107MXBean getStatisticsMBean() {
    return statisticsBean;
  }

  void setStatisticsEnaled(boolean enabled) {
    config.setStatisticsEnabled(enabled);
  }

  void setManagementEnabled(boolean enabled) {
    config.setManagementEnabled(enabled);
  }

  private static CacheLoaderException jsr107CacheLoaderException(org.ehcache.exceptions.CacheLoaderException e) {
    if (e.getCause() instanceof CacheLoaderException) {
      return (CacheLoaderException) e.getCause();
    }
    return new CacheLoaderException(e);
  }

  private static CacheWriterException jsr107CacheWriterException(org.ehcache.exceptions.CacheWriterException e) {
    if (e.getCause() instanceof CacheWriterException) {
      return (CacheWriterException) e.getCause();
    }
    throw new CacheWriterException(e);
  }

  private static  EntryProcessorResult newEntryProcessorResult(final T result) {
    if (result == null) {
      throw new NullPointerException();
    }

    return new EntryProcessorResult() {
      @Override
      public T get() throws EntryProcessorException {
        return result;
      }
    };
  }

  private static  EntryProcessorResult newErrorThrowingEntryProcessorResult(final Exception e) {
    return new EntryProcessorResult() {
      @Override
      public T get() throws EntryProcessorException {
        if (e instanceof EntryProcessorException) {
          throw (EntryProcessorException) e;
        }
        throw new EntryProcessorException(e);
      }
    };
  }

  private static class Iterator implements java.util.Iterator> {

    private final java.util.Iterator> ehIterator;
    private final org.ehcache.Cache ehCache;
    private org.ehcache.Cache.Entry current = null;

    Iterator(org.ehcache.Cache ehCache) {
      this.ehCache = ehCache;
      this.ehIterator = ehCache.iterator();
    }

    @Override
    public boolean hasNext() {
      return ehIterator.hasNext();
    }

    @Override
    public javax.cache.Cache.Entry next() {
      current = ehIterator.next();
      return new WrappedEhcacheEntry(current);
    }

    @Override
    public void remove() {
      if (current == null) {
        throw new IllegalStateException();
      }

      // XXX: Not using iter.remove() on the ehcache iterator since it
      // does 2-arg remove and will add cache hits that 107 txk doesn't expect
      ehCache.remove(current.getKey());

      current = null;
    }
  }

  private static class WrappedEhcacheEntry implements javax.cache.Cache.Entry {

    private final org.ehcache.Cache.Entry ehEntry;

    WrappedEhcacheEntry(org.ehcache.Cache.Entry ehEntry) {
      this.ehEntry = ehEntry;
    }

    @Override
    public K getKey() {
      return ehEntry.getKey();
    }

    @Override
    public V getValue() {
      return ehEntry.getValue();
    }

    @Override
    public  T unwrap(Class clazz) {
      return Unwrap.unwrap(clazz, ehEntry);
    }
  }

  private static enum MutableEntryOperation {
    NONE, ACCESS, CREATE, LOAD, REMOVE, UPDATE;
  }

  private static final Object UNDEFINED = new Object();

  private class MutableEntry implements javax.cache.processor.MutableEntry {

    private final K key;
    private final V initialValue;
    private volatile V finalValue = undefined();
    private volatile MutableEntryOperation operation = MutableEntryOperation.NONE;

    MutableEntry(K key, V initialValue) {
      this.key = key;
      this.initialValue = initialValue;
    }

    @Override
    public K getKey() {
      return key;
    }

    @Override
    public V getValue() {
      if (finalValue == UNDEFINED) {
        if (initialValue == null && config.isReadThrough() && cacheLoader != null) {
          finalValue = tryLoad();
          if (finalValue != null) {
            operation = MutableEntryOperation.LOAD;
          }
        } else {
          finalValue = initialValue;
          operation = MutableEntryOperation.ACCESS;
        }
      }

      return finalValue;
    }

    private V tryLoad() {
      try {
        return cacheLoader.load(key);
      } catch (Exception e) {
        if (e instanceof CacheLoaderException) {
          throw (CacheLoaderException) e;
        }
        throw new CacheLoaderException(e);
      }
    }

    @Override
    public boolean exists() {
      // None of getValue(), remove() or setValue() have been called
      if (finalValue == UNDEFINED) {
        return initialValue != null;
      }

      return finalValue != null;
    }

    @Override
    public void remove() {
      if (operation == MutableEntryOperation.CREATE) {
        operation = MutableEntryOperation.NONE;
      } else {
        operation = MutableEntryOperation.REMOVE;
      }
      finalValue = null;
    }

    @Override
    public void setValue(V value) {
      if (value == null) {
        throw new NullPointerException();
      }

      operation = initialValue == null ? MutableEntryOperation.CREATE : MutableEntryOperation.UPDATE;
      finalValue = value;
    }

    V apply(boolean isWriteThrough, CacheWriter cacheWriter) {
      switch (operation) {
      case NONE:
      case ACCESS:
        return initialValue;
      case LOAD:
      case CREATE:
      case UPDATE:
        return finalValue;
      case REMOVE:
        return null;
      }

      throw new AssertionError("unhandled case: " + operation);
    }

    boolean shouldReplace() {
      switch (operation) {
      case NONE:
      case ACCESS:
        return false;
      case CREATE:
      case LOAD:
      case UPDATE:
      case REMOVE:
        return true;
      }

      throw new AssertionError("unhandled case: " + operation);
    }

    boolean shouldGenerateEvent() {
      switch (operation) {
      case NONE:
      case ACCESS:
      case LOAD:
        return false;
      case CREATE:
      case UPDATE:
      case REMOVE:
        return true;
      }

      throw new AssertionError("unhandled case: " + operation);
    }

    boolean shouldInvokeWriter() {
      switch (operation) {
      case NONE:
      case ACCESS:
      case LOAD:
        return false;
      case CREATE:
      case UPDATE:
      case REMOVE:
        return true;
      }

      throw new AssertionError("unhandled case: " + operation);
    }

    @SuppressWarnings("unchecked")
    private V undefined() {
      return (V) Eh107Cache.UNDEFINED;
    }

    @Override
    public  T unwrap(Class clazz) {
      throw new IllegalArgumentException();
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy