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

com.tangosol.coherence.jcache.CoherenceBasedCacheManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.tangosol.coherence.jcache;

import com.oracle.coherence.common.base.Disposable;
import com.oracle.coherence.common.base.Logger;

import com.tangosol.coherence.jcache.common.JCacheIdentifier;
import com.tangosol.coherence.jcache.localcache.LocalCache;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.ConfigurableCacheFactory;
import com.tangosol.net.ExtensibleConfigurableCacheFactory;
import com.tangosol.net.NamedCache;

import com.tangosol.util.Base;
import com.tangosol.util.Builder;
import com.tangosol.util.RegistrationBehavior;

import java.lang.Override;

import java.lang.ref.WeakReference;

import java.net.URI;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.TreeSet;

import java.util.concurrent.ConcurrentHashMap;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;

import javax.cache.configuration.CompleteConfiguration;
import javax.cache.configuration.Configuration;

import javax.cache.spi.CachingProvider;

/**
 * The Coherence-based implementation of a {@link CacheManager}.
 *
 * @author jf  2013.06.18
 * @author bo  2013.12.17
 */
public class CoherenceBasedCacheManager
        implements CacheManager, Disposable
    {
    // ----- constructors ---------------------------------------------------

    /**
     * Constructs a new {@link CoherenceBasedCacheManager}.
     *
     * @param provider     the {@link CachingProvider} that owns the {@link CacheManager}
     * @param ccf          the {@link ConfigurableCacheFactory} to use for {@link CoherenceBasedCache}s
     * @param uri          the {@link URI} of the {@link CacheManager}
     *                     (this must point to a valid Coherence Cache configuration file)
     * @param classLoader  the {@link ClassLoader} to use for loading {@link Cache}
     *                     resources, including keys, values, loaders, writers etc
     * @param properties   the custom configuration {@link Properties}
     *
     */
    public CoherenceBasedCacheManager(CoherenceBasedCachingProvider provider, ConfigurableCacheFactory ccf, URI uri,
                                      ClassLoader classLoader, Properties properties)
        {
        if (provider == null)
            {
            throw new NullPointerException("No CachingProvider specified");
            }

        if (ccf == null)
            {
            throw new NullPointerException("No Configurable Cache Factory specified");
            }

        if (classLoader == null)
            {
            throw new NullPointerException("No ClassLoader specified");
            }

        m_provider          = provider;
        m_ccf               = ccf;
        m_uri               = uri;
        m_refClassLoader    = new WeakReference(classLoader);
        m_properties        = properties == null ? new Properties() : (Properties) properties.clone();
        m_fClosed           = false;

        // register disposable CacheManager with CCF resource registry.  If CCF is disposed, CacheManager will be disposed.

        if (ccf instanceof ExtensibleConfigurableCacheFactory)
            {
            final CacheManager    thisOne = this;
            Builder bldr    = new Builder()
                {
                @Override
                public CacheManager realize()
                    {
                    return thisOne;
                    }
                };

            // while desirable to have a one to one relationship between CacheManager and CCF,
            // the ScopedCachedFactoryBuilder.getConfigurableCacheFactory(ClassLoader) searches up ClassLoader parent hierarchy
            // searching for a CCF registered with specified ClassLoader or one of its parents.
            // CacheManager is defined to be mapped one to one with ClassLoader, independent of parent ClassLoader's.

            // 7 JSR 107 TCK test use ClassLoaders that are in same ClassLoader hierarchy, so the following
            // code allows for Coherence implementation of ScopedCacheFactoryBuilder to associate one CCF with
            // a classloader or its parent classloaders.  This results in a one CCF to multiple CacheManager instances.

            // WORKAROUND:
            // register additional CacheManagers with CCF using a unique identifier generated by ALWAYS registration.
            // goal is to have CacheManager associated with CCF so when CCF is destroyed, the cachemanager will be closed at same time.

            m_sResourceName = ccf.getResourceRegistry().registerResource(
                    CacheManager.class, bldr, RegistrationBehavior.ALWAYS, null);
            }
        }

    // ----- CoherenceBasedCacheManager methods -----------------------------

    /**
     * Provide access to meta cache that maps JCache cache names to JCache configuration.
     * The mapping is a string to a JCache Configuration object.
     *
     * @param cacheId JCache unique identifier
     * @return a named cache that maps JCache cache names to JCache configurations.
     */
    public CoherenceBasedCompleteConfiguration getCacheToConfigurationMapping(JCacheIdentifier cacheId)
        {
        return (CoherenceBasedCompleteConfiguration) getConfigurationCache().get(cacheId.getCanonicalCacheName());
        }

    /**
     * put entry with key of sName
     *
     * @param cacheId key of entry to put into _metaJCacheNameToConfig
     */
    public void putCacheToConfigurationMapping(JCacheIdentifier cacheId, CoherenceBasedCompleteConfiguration config)
        {
        CoherenceBasedCompleteConfiguration existingConfig = getCacheToConfigurationMapping(cacheId);

        // NOTE: if there already exist a configuration for cacheIdentifier in the metaconfigurationcache, it
        // indicates either another member already has created the cache and is using it OR another member
        // has created the JCache and exited without cleaning up afterwards.
        // For time being, allowing createCache to succeed iff the configuration for the cache being created exactly
        // matches the configuration for the cacheIdentier in the jcache-configurations cache table (meta cache
        // mapping jcacheIdentifiers to JCache configuration.

        if (existingConfig != null && !existingConfig.equals(config))
            {
            throw new IllegalStateException("CacheCreationFailure: Failed to create cache named "
                + cacheId.getCanonicalCacheName() + " with configuration: " + config + "\n"
                + "A cache with that name already exists with the different configuration: " + existingConfig);
            }

        getConfigurationCache().put(cacheId.getCanonicalCacheName(), config);
        }

    /**
     * remove entry with key of sName
     *
     * @param cacheId key of entry to remove from _metaJCacheNameToConfig
     */
    public void removeCacheToConfigurationMapping(JCacheIdentifier cacheId)
        {
        getConfigurationCache().remove(cacheId.getCanonicalCacheName());
        }

    /**
     * Obtains the {@link NamedCache} to be used for storing
     * {@link CoherenceBasedConfiguration}s when they need to be shared across a cluster
     * or made available to clients.
     *
     * @return  the {@link NamedCache} for {@link CoherenceBasedConfiguration}s
     */
    private NamedCache getConfigurationCache()
        {
        // we don't specify a serializer to allow Coherence to use the service-level classloader
        ClassLoader loader = null;

        // acquire the NamedCache that holds the JCache Configurations
        return m_ccf.ensureCache(CoherenceBasedCache.JCACHE_CONFIG_CACHE_NAME, loader);
        }

    // ----- CacheManager interface -----------------------------------------

    @Override
    public CachingProvider getCachingProvider()
        {
        return m_provider;
        }

    @Override
    public URI getURI()
        {
        return m_uri;
        }

    @Override
    public Properties getProperties()
        {
        return m_properties;
        }

    @Override
    public ClassLoader getClassLoader()
        {
        return m_refClassLoader.get();
        }

    @Override
    public > Cache createCache(String sJCacheName, C cfgJCache)
            throws IllegalArgumentException
        {
        ensureOpen();

        if (sJCacheName == null)
            {
            throw new NullPointerException("cacheName must not be null");
            }

        if (cfgJCache == null)
            {
            throw new NullPointerException("config must not be null");
            }

        if (cfgJCache instanceof CoherenceBasedConfiguration)
            {
            // make this instance thread safe, only allow one create cache at any point in time.
            synchronized (this)
                {
                CoherenceBasedConfiguration cfgCoherence = (CoherenceBasedConfiguration) cfgJCache;
                CoherenceBasedCache         cache        = m_mapNameToJCache.get(sJCacheName);

                if (cache == null)
                    {
                    CoherenceBasedCache cacheCreated = cfgCoherence.createCache(this, sJCacheName);

                    cache = m_mapNameToJCache.putIfAbsent(sJCacheName, cacheCreated);

                    if (cache == null)
                        {
                        // put succeeded, return the newCache which we would get with a m_mapNameToJCache.get(cacheName)
                        return cacheCreated;
                        }
                    }
                }
            }
        else if (cfgJCache instanceof Configuration)
            {
            CoherenceBasedConfiguration cfgCoherence = m_provider.convertConfiguration(cfgJCache,
                                                                 getClassLoader());

            return createCache(sJCacheName, cfgCoherence);
            }
        else
            {
            throw new CacheException("Cache creation failed due to unknown Configuration type: "
                                     + cfgJCache.getClass().getName());
            }

        // unable to create a cache since one already existed.
        throw new CacheException("A cache named " + sJCacheName + " already exists.");
        }

    @Override
    public  Cache getCache(String sJCacheName, Class clzKey, Class clzType)
        {
        if (isClosed())
            {
            throw new IllegalStateException();
            }

        if (clzKey == null)
            {
            throw new NullPointerException("clzKey can not be null");
            }

        if (clzType == null)
            {
            throw new NullPointerException("clzType can not be null");
            }

        Cache         cache         = m_mapNameToJCache.get(sJCacheName);
        Configuration configuration = cache == null ?
                getCacheToConfigurationMapping(new JCacheIdentifier(getURI().toString(), sJCacheName)) :
                cache.getConfiguration(Configuration.class);

        if (cache == null)
            {
            if (configuration == null)
                {
                return null;
                }
            else
                {
                // cache was previously closed, create a new Coherence JCache wrapper
                cache = createCache(sJCacheName, configuration);
                }
            }


        if (configuration.getKeyType() != null && configuration.getKeyType().equals(clzKey))
            {
            if (configuration.getValueType() != null && configuration.getValueType().equals(clzType))
                {
                return (Cache) cache;
                }
            else
                {
                throw new ClassCastException("Incompatible cache value types specified, expected "
                        + configuration.getValueType() + " but " + clzType + " was specified");
                }
            }
        else
            {
            throw new ClassCastException("Incompatible cache key types specified, expected "
                    + configuration.getKeyType() + " but " + clzKey + " was specified");
            }
        }

    @Override
    public Cache getCache(String sJCacheName)
        {
        /**
         * https://github.com/jsr107/jsr107spec/issues/340
         * in 1.1 relaxed {@link CacheManager#getCache(String)} to not enforce a check.
         */
        return getCacheInternal(sJCacheName, false);
        }

    @Override
    public Iterable getCacheNames()
        {
        // Added check to require this to not be closed for JSR 107 1.1.0
        ensureOpen();

        return Collections.unmodifiableSet(new TreeSet(m_mapNameToJCache.keySet()));
        }

    @Override
    public void destroyCache(String sName)
        {
        ensureOpen();

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

        CoherenceBasedCache cache = m_mapNameToJCache.remove(sName);

        // allow for destroy after cache has been closed in same process.
        // This works around TCK case usage of closing cache before destroying cache.
        if (cache == null)
            {
            // cache.close() allows for multiple invocations.
            // closing a closed cache is no-op.
            WeakReference> refCache = m_closedMapNameToJCache.remove(sName);

            cache = refCache == null ? null : refCache.get();
            }

        if (cache == null)
            {
            // cover case that cache was created in another jvm and there is no local reference to it.
            JCacheIdentifier                    id     = new JCacheIdentifier(getURI().toString(), sName);
            CoherenceBasedCompleteConfiguration config = getCacheToConfigurationMapping(id);

            if (config != null)
                {
                config.destroyCache(this, sName);
                }
            }
        else
            {
            cache.close();
            cache.destroy();
            }
        }

    @Override
    public void enableStatistics(String sName, boolean fEnabled)
        {
        ensureOpen();

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

        @SuppressWarnings("unchecked") final AbstractCoherenceBasedCache cache =
            (AbstractCoherenceBasedCache) getCacheInternal(sName, false);

        if (cache != null)
            {
            cache.setStatisticsEnabled(fEnabled);
            }
        else
            {
            throw new CacheException("no cache named " + sName);
            }
        }

    @Override
    public void enableManagement(String sName, boolean fEnabled)
        {
        ensureOpen();

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

        @SuppressWarnings("unchecked") final AbstractCoherenceBasedCache cache =
            (AbstractCoherenceBasedCache) getCacheInternal(sName, false);

        if (cache != null)
            {
            cache.setManagementEnabled(fEnabled);
            }
        else
            {
            throw new CacheException("no cache named " + sName);
            }
        }

    @Override
    public synchronized void close()
        {
        if (isClosed())
            {
            return;
            }

        m_fClosed = true;

        // first release the CacheManager from the CacheProvider so that
        // future requests for this CacheManager won't return this one
        m_provider.release(getClassLoader(), getURI());

        ArrayList> cacheList;

        cacheList = new ArrayList>(m_mapNameToJCache.values());
        m_mapNameToJCache.clear();

        for (Cache cache : cacheList)
            {
            try
                {
                cache.close();
                }
            catch (Exception e)
                {
                Logger.warn("Error stopping cache " + cache + ": " + Base.printStackTrace(e));
                }
            }

        // unregister this CacheManager from eccf.
        m_ccf.getResourceRegistry().unregisterResource(CacheManager.class, m_sResourceName);

        // only dispose eccf if no more CacheManagers referencing the eccf.
        if (m_ccf.getResourceRegistry().getResource(CacheManager.class) == null)
            {
            CacheFactory.getCacheFactoryBuilder().release(m_ccf);
            m_ccf.dispose();
            }

        m_ccf = null;
        }

    @Override
    public void dispose()
        {
        if (!isClosed())
            {
            close();
            }
        }

    @Override
    public boolean isClosed()
        {
        return m_fClosed;
        }

    @Override
    public  T unwrap(Class clz)
        {
        if (clz != null && clz.isInstance(m_ccf))
            {
            return (T) m_ccf;
            }
        else if (clz != null && clz.isInstance(this))
            {
            return (T) this;
            }
        else
            {
            throw new IllegalArgumentException("Unsupported unwrap(" + clz + ")");
            }
        }

    // ----- helpers --------------------------------------------------------

    /**
     * Lookup JCache Adapter sNamein current {@link CacheManager} context.
     *
     * @param sName JCache Adapter map name
     * @param fTypeCheck true iff the map type should be checked
     * @param  key type
     * @param  value type
     *
     * @return JCache {@link Cache} if found, null otherwise.
     */
    private  Cache getCacheInternal(String sName, boolean fTypeCheck)
        {
        ensureOpen();

        /*
         * Can't really verify that the K/V cast is safe but it is required by the API, using a
         * local variable for the cast to allow for a minimal scoping of @SuppressWarnings
         */
        @SuppressWarnings("unchecked") Cache cache = (Cache) m_mapNameToJCache.get(sName);

        // when cache is null, still a chance that the content still exists if JCache cache was closed but not destroyed.
        Configuration     configuration = cache == null ?
                getCacheToConfigurationMapping(new JCacheIdentifier(getURI().toString(), sName)) : cache.getConfiguration(Configuration.class);

        if (cache == null)
            {
            if (configuration == null)
                {
                return null;
                }
            else
                {
                // cache was previously closed, create a new Coherence JCache wrapper for existing cache content
                cache = (Cache) createCache(sName, configuration);
                }
            }

        if (!fTypeCheck || (configuration.getKeyType().equals(Object.class)
                    && configuration.getValueType().equals(Object.class)))
            {
            return cache;
            }
        else
            {
            throw new IllegalArgumentException("Cache " + sName + " was " + "defined with specific types Cache<" + configuration.getKeyType() + ", " + configuration.getValueType() + "> "
                    + "in which case CacheManager.getCache(String, Class, Class) must be used");
            }
        }

    /**
     * Ensure open context.
     */
    private void ensureOpen()
        {
        if (isClosed())
            {
            throw new IllegalStateException();
            }
        }

    /**
     * Get ConfigurableCacheFactory context.
     *
     * @return {@link ConfigurableCacheFactory}
     */
    public ConfigurableCacheFactory getConfigurableCacheFactory()
        {
        ensureOpen();

        return m_ccf;
        }

    /**
     * release cache named sName
     *
     * @param sName JCache Adapter map to release
     */
    public void releaseCache(String sName)
        {
        CoherenceBasedCache closedCache = m_mapNameToJCache.remove(sName);

        if (closedCache != null)
            {
            m_closedMapNameToJCache.put(sName, new WeakReference>(closedCache));
            }
        }

    /**
     * Validate if configurations map is valid with this instance.
     * @return true iff it is valid
     */
    public boolean validate()
        {
        boolean result = true;

        if (m_mapNameToJCache.size() > 0)
            {
            for (Map.Entry> entry : m_mapNameToJCache.entrySet())
                {
                CoherenceBasedCache cache = entry.getValue();

                if (cache instanceof LocalCache)
                    {
                    // nothing to check.  not stored in cache to configuration mapping.
                    continue;
                    }

                // validate partitioned cache
                JCacheIdentifier      cacheId = cache.getIdentifier();
                CompleteConfiguration config  = getCacheToConfigurationMapping(cacheId);

                if (config == null)
                    {
                    result = false;
                    Logger.warn("CoherenceBasedCacheManager.validate failed.  No mapping for JCache " + cacheId
                                     + " in meta mapping of jcacheId to configuration.");
                    break;
                    }
                else if (!config.equals(cache.getConfiguration(CompleteConfiguration.class)))
                    {
                    result = false;

                    CoherenceBasedCompleteConfiguration inMemoryConfiguration =
                        cache.getConfiguration(CoherenceBasedCompleteConfiguration.class);

                    Logger.warn("CoherenceBasedCacheManager.validate failed due to differing Configurations. JCache "
                             + cacheId + "\nInMemory Configuration is:\n" + inMemoryConfiguration
                             + "\n replicated meta cache configuration is:\n" + config);
                    break;
                    }
                }
            }

        return result;
        }

    // ----- Object methods -------------------------------------------------

    @Override
    public String toString()
        {
        StringBuilder sb = new StringBuilder();

        sb.append("CoherenceBasedCacheManager uri=").append(m_uri).append(" clzLdr=").append(getClassLoader())
            .append(" eccf=")
            .append(getConfigurableCacheFactory()).append(" isclosed=").append(isClosed());

        return sb.toString();
        }

    // ----- data members ---------------------------------------------------

    private final ConcurrentHashMap> m_mapNameToJCache =
        new ConcurrentHashMap>();

    /**
     * Save closed JCaches so can still implement destroy on them.
     */
    private final ConcurrentHashMap>> m_closedMapNameToJCache =
        new ConcurrentHashMap>>();
    private final CoherenceBasedCachingProvider m_provider;
    private final URI                           m_uri;
    private final WeakReference    m_refClassLoader;
    private final Properties                    m_properties;
    private ConfigurableCacheFactory            m_ccf;
    private volatile boolean                    m_fClosed = false;

    /**
     * resource name for this instance's registration with m_ccf.
     */
    private String m_sResourceName;
    }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy