org.orderofthebee.addons.support.tools.repo.caches.CacheLookupUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.orderofthebee.addons.support.tools.repo.caches.CacheLookupUtils.java

Source

/**
 * Copyright (C) 2016, 2017 Axel Faust
 * Copyright (C) 2016, 2017 Order of the Bee
 *
 * This file is part of Community Support Tools
 *
 * Community Support Tools is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * Community Support Tools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Community Support Tools. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package org.orderofthebee.addons.support.tools.repo.caches;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;

import org.alfresco.repo.cache.CacheStatistics;
import org.alfresco.repo.cache.DefaultSimpleCache;
import org.alfresco.repo.cache.NoStatsForCache;
import org.alfresco.repo.cache.OperationStats;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.cache.TransactionStats.OpType;
import org.alfresco.repo.cache.TransactionalCache;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.ParameterCheck;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;

/**
 * This utility class abstracts common reverse or reflection-based lookup logic to access low-level cache implementations or statistics
 * objects.
 *
 * @author Axel Faust, <a href="http://acosix.de">Acosix GmbH</a>
 */
public class CacheLookupUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(CacheLookupUtils.class);

    /**
     * Retrieves the statistics data from an Alfresco Enterprise Edition {@code InvalidatingCache}
     *
     * @param invalidatingCache
     *            the invalidating cache
     * @return the statistics object
     * @throws Exception
     *             if the reflective access fails for any reason
     */
    public static Object getHzInvalidatingCacheStats(final SimpleCache<?, ?> invalidatingCache) throws Exception {
        ParameterCheck.mandatory("invalidatingCache", invalidatingCache);

        final Field cacheField = invalidatingCache.getClass().getDeclaredField("cache");
        // will fail in Java 9 but no other way due to private visibility, Enterprise-only class and lack of accessor
        cacheField.setAccessible(true);
        final Object internalCache = cacheField.get(invalidatingCache);

        if (!(internalCache instanceof DefaultSimpleCache<?, ?>)) {
            throw new IllegalArgumentException("internalCache should be an instance of DefaultSimpleCache");
        }

        return getDefaultSimpleCacheStats((DefaultSimpleCache<?, ?>) internalCache);
    }

    /**
     * Retrieves the statistics data from an Alfresco Enterprise Edition {@code HazelcastSimpleCache}
     *
     * @param simpleCache
     *            the simple cache
     * @return the statistics object
     * @throws Exception
     *             if the reflective access fails for any reason
     */
    public static Object getHzSimpleCacheStats(final SimpleCache<?, ?> simpleCache) throws Exception {
        ParameterCheck.mandatory("simpleCache", simpleCache);

        final Field mapField = simpleCache.getClass().getDeclaredField("map");
        // will fail in Java 9 but no other way due to private visibility, Enterprise-only class and lack of accessor
        mapField.setAccessible(true);
        final Object internalMap = mapField.get(simpleCache);
        final Method mapStatsGetter = internalMap.getClass().getMethod("getLocalMapStats");
        final Object stats = mapStatsGetter.invoke(internalMap);

        return stats;
    }

    /**
     * Retrieves the statistics data from an Alfresco default {@code DefaultSimpleCache}
     *
     * @param defaultSimpleCache
     *            the simple cache instance
     * @return the statistics object
     * @throws Exception
     *             if the reflective access fails for any reason
     */
    public static Object getDefaultSimpleCacheStats(final DefaultSimpleCache<?, ?> defaultSimpleCache)
            throws Exception {
        ParameterCheck.mandatory("defaultSimpleCache", defaultSimpleCache);

        final Field googleCacheField = DefaultSimpleCache.class.getDeclaredField("cache");
        // will fail in Java 9 but no other way due to private visibility and lack of accessor
        googleCacheField.setAccessible(true);
        final Object googleCache = googleCacheField.get(defaultSimpleCache);

        final CacheStats stats = ((Cache<?, ?>) googleCache).stats();
        return stats;
    }

    /**
     * Resolves the {@link CacheStatistics cache statistics} of the {@link TransactionalCache transactional cache} instance that facades a
     * specific {@link SimpleCache shared
     * cache} and provides it in a more script-friendly representation.
     *
     * @param sharedCacheName
     *            the name of the shared cache
     * @return a facade to a snapshot of the cache statistics
     */
    public static AlfrescoCacheStatsFacade resolveStatisticsViaTransactional(final String sharedCacheName) {
        ParameterCheck.mandatoryString("sharedCacheName", sharedCacheName);

        LOGGER.debug("Trying to resolve transactional cache for shared cache {}", sharedCacheName);

        String txnCacheName = null;

        final WebApplicationContext applicationContext = ContextLoader.getCurrentWebApplicationContext();

        if (applicationContext instanceof ConfigurableApplicationContext) {
            final ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext) applicationContext)
                    .getBeanFactory();
            final String[] txnCacheBeanNames = applicationContext.getBeanNamesForType(TransactionalCache.class,
                    false, false);

            // this is a rather ugly reference lookup, but so far I see no other way
            for (final String txnCacheBeanName : txnCacheBeanNames) {
                final BeanDefinition txnCacheDefinition = beanFactory.getBeanDefinition(txnCacheBeanName);

                final PropertyValue sharedCacheValue = txnCacheDefinition.getPropertyValues()
                        .getPropertyValue("sharedCache");
                final PropertyValue nameValue = txnCacheDefinition.getPropertyValues().getPropertyValue("name");
                if (nameValue != null && sharedCacheValue != null) {
                    final Object sharedCacheRef = sharedCacheValue.getValue();
                    if (sharedCacheRef instanceof RuntimeBeanReference) {
                        final String sharedCacheBeanName = ((RuntimeBeanReference) sharedCacheRef).getBeanName();

                        Object nameValueObj = nameValue.getValue();
                        if (nameValueObj instanceof TypedStringValue) {
                            nameValueObj = ((TypedStringValue) nameValueObj).getValue();
                        }

                        if (EqualsHelper.nullSafeEquals(sharedCacheBeanName, sharedCacheName)) {
                            if (txnCacheName != null) {
                                LOGGER.info("Shared cache {} is referenced by multiple transactional caches",
                                        sharedCacheName);
                                txnCacheName = null;
                                break;
                            }
                            txnCacheName = String.valueOf(nameValueObj);
                            LOGGER.debug("Resolved transactional cache {} for shared cache {}", txnCacheName,
                                    sharedCacheName);
                        } else {
                            try {
                                final DefaultSimpleCache<?, ?> defaultSimpleCache = applicationContext
                                        .getBean(sharedCacheBeanName, DefaultSimpleCache.class);
                                if (EqualsHelper.nullSafeEquals(defaultSimpleCache.getCacheName(),
                                        sharedCacheName)) {
                                    if (txnCacheName != null) {
                                        LOGGER.info(
                                                "Shared cache {} is referenced by multiple transactional caches",
                                                sharedCacheName);
                                        txnCacheName = null;
                                        break;
                                    }
                                    txnCacheName = String.valueOf(nameValueObj);
                                    LOGGER.debug("Resolved transactional cache {} for shared cache {}",
                                            txnCacheName, sharedCacheName);
                                }
                                continue;
                            } catch (final BeansException be) {
                                // ignore - can be expected e.g. in EE or with alternative cache implementations
                            }
                        }
                    }
                }
            }

            if (txnCacheName == null) {
                LOGGER.debug("Unable to resolve unique transactional cache for shared cache {}", sharedCacheName);
            }
        } else {
            LOGGER.debug(
                    "Application context is not a configurable application context - unable to resolve transactional cache");
        }

        AlfrescoCacheStatsFacade facade = null;
        if (txnCacheName != null) {
            final CacheStatistics cacheStatistics = applicationContext.getBean("cacheStatistics",
                    CacheStatistics.class);
            try {
                final Map<OpType, OperationStats> allStats = cacheStatistics.allStats(txnCacheName);
                facade = new AlfrescoCacheStatsFacade(allStats);
            } catch (final NoStatsForCache e) {
                facade = new AlfrescoCacheStatsFacade(Collections.emptyMap());
            }
        }

        return facade;
    }

    public static class AlfrescoCacheStatsFacade implements CacheMetrics {

        private final Map<OpType, OperationStats> stats;

        public AlfrescoCacheStatsFacade(final Map<OpType, OperationStats> stats) {
            this.stats = stats;
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public long getCacheGets() {
            return this.getCacheHits() + this.getCacheMisses();
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public long getCacheHits() {
            final long cacheHits;

            final OperationStats hitStats = this.stats.get(OpType.GET_HIT);
            cacheHits = hitStats != null ? hitStats.getCount() : 0;

            return cacheHits;
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public long getCacheMisses() {
            final long cacheMisses;

            final OperationStats missStats = this.stats.get(OpType.GET_MISS);
            cacheMisses = missStats != null ? missStats.getCount() : 0;

            return cacheMisses;
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public double getCacheHitPercentage() {
            double cacheHitPercentage;

            final long cacheGets = this.getCacheGets();
            if (cacheGets == 0) {
                cacheHitPercentage = 100;
            } else {
                cacheHitPercentage = this.getCacheHits() / cacheGets * 100;
            }
            return cacheHitPercentage;
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public double getCacheMissPercentage() {
            double cacheMissPercentage;

            final long cacheGets = this.getCacheGets();
            if (cacheGets == 0) {
                cacheMissPercentage = 0;
            } else {
                cacheMissPercentage = this.getCacheMisses() / cacheGets * 100;
            }
            return cacheMissPercentage;
        }

        /**
         *
         * {@inheritDoc}
         */
        @Override
        public long getCacheEvictions() {
            // no way to know due to lack of details
            return -1;
        }
    }
}