package org.ff4j.cache; import java.util.Collection; /* * #%L ff4j-core %% Copyright (C) 2013 Ff4J %% 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. #L% */ import java.util.Map; import java.util.Set; import org.ff4j.core.Feature; import org.ff4j.core.FeatureStore; import org.ff4j.property.Property; import org.ff4j.property.store.PropertyStore; /** * Access to {@link FeatureStore} could generate some overhead and decrease performances. This is the reason why cache is provided * though proxies. * * As applications are distributed, the cache itself could be distributed. The default implement is * {@link InMemoryFeatureStoreCacheProxy} but other are provided to use distributed cache system as redis or memcached. * * @author Cedrick Lunven (@clunven) */ public class FF4jCacheProxy implements FeatureStore, PropertyStore { /** Target feature store to be proxified to cache features. */ private FeatureStore targetFeatureStore; /** Target property store to be proxified to cache properties. */ private PropertyStore targetPropertyStore; /** cache manager. */ private FF4JCacheManager cacheManager; /** Daemon to fetch data from target store to cache on a fixed delay basis. */ private Store2CachePollingScheduler store2CachePoller = null; /** * Allow Ioc and defeine default constructor. */ public FF4jCacheProxy() {} /** * Initialization through constructor. * * @param store * target store to retrieve features * @param cache * cache manager to limit overhead of store */ public FF4jCacheProxy(FeatureStore fStore, PropertyStore pStore, FF4JCacheManager cache) { this.cacheManager = cache; this.targetFeatureStore = fStore; this.targetPropertyStore = pStore; this.store2CachePoller = new Store2CachePollingScheduler(fStore, pStore, cache); } /** * Start the polling of target store is required. */ public void startPolling(long delay) { if (store2CachePoller == null) { throw new IllegalStateException("The poller has not been initialize, please check"); } getStore2CachePoller().start(delay); } /** * Stop the polling of target store is required. */ public void stopPolling() { if (store2CachePoller == null) { throw new IllegalStateException("The poller has not been initialize, please check"); } getStore2CachePoller().stop(); } /** {@inheritDoc} */ @Override public void enable(String featureId) { // Reach target getTargetFeatureStore().enable(featureId); // Modification => flush cache getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public void disable(String featureId) { // Reach target getTargetFeatureStore().disable(featureId); // Cache Operations : As modification, flush cache for this getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public boolean exist(String featureId) { // not in cache but maybe created from last access if (getCacheManager().getFeature(featureId) == null) { return getTargetFeatureStore().exist(featureId); } return true; } /** {@inheritDoc} */ @Override public void createSchema() { // Create table for features but not only getTargetFeatureStore().createSchema(); // Also create tables for properties getTargetPropertyStore().createSchema(); } /** {@inheritDoc} */ @Override public void create(Feature fp) { getTargetFeatureStore().create(fp); getCacheManager().putFeature(fp); } /** {@inheritDoc} */ @Override public Feature read(String featureUid) { Feature fp = getCacheManager().getFeature(featureUid); // not in cache but may has been created from now if (null == fp) { fp = getTargetFeatureStore().read(featureUid); getCacheManager().putFeature(fp); } return fp; } /** {@inheritDoc} */ @Override public Map<String, Feature> readAll() { // Cannot be sure of whole cache - do not test any feature one-by-one : accessing FeatureStore return getTargetFeatureStore().readAll(); } /** {@inheritDoc} */ @Override public Set<String> readAllGroups() { // Cannot be sure of whole cache - do not test any feature one-by-one : accessing FeatureStore return getTargetFeatureStore().readAllGroups(); } /** {@inheritDoc} */ @Override public void delete(String featureId) { // Access target store getTargetFeatureStore().delete(featureId); // even is not present, evict won't failed getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public void update(Feature fp) { getTargetFeatureStore().update(fp); getCacheManager().evictFeature(fp.getUid()); } /** {@inheritDoc} */ @Override public void grantRoleOnFeature(String featureId, String roleName) { getTargetFeatureStore().grantRoleOnFeature(featureId, roleName); getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public void removeRoleFromFeature(String featureId, String roleName) { getTargetFeatureStore().removeRoleFromFeature(featureId, roleName); getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public void enableGroup(String groupName) { getTargetFeatureStore().enableGroup(groupName); // Cannot know wich feature to work with (exceptional event) : flush cache getCacheManager().clearFeatures(); } /** {@inheritDoc} */ @Override public void disableGroup(String groupName) { getTargetFeatureStore().disableGroup(groupName); // Cannot know wich feature to work with (exceptional event) : flush cache getCacheManager().clearFeatures(); } /** {@inheritDoc} */ @Override public boolean existGroup(String groupName) { // Cache cannot help you return getTargetFeatureStore().existGroup(groupName); } /** {@inheritDoc} */ @Override public Map<String, Feature> readGroup(String groupName) { // Cache cannot help you return getTargetFeatureStore().readGroup(groupName); } /** {@inheritDoc} */ @Override public void addToGroup(String featureId, String groupName) { getTargetFeatureStore().addToGroup(featureId, groupName); getCacheManager().evictFeature(featureId); } /** {@inheritDoc} */ @Override public void removeFromGroup(String featureId, String groupName) { getTargetFeatureStore().removeFromGroup(featureId, groupName); getCacheManager().evictFeature(featureId); } /** * Getter accessor for attribute 'target'. * * @return current value of 'target' */ public FeatureStore getTargetFeatureStore() { if (targetFeatureStore == null) { throw new IllegalArgumentException("ff4j-core: Target for cache proxy has not been provided"); } return targetFeatureStore; } /** * Getter accessor for attribute 'target'. * * @return current value of 'target' */ public PropertyStore getTargetPropertyStore() { if (targetPropertyStore == null) { throw new IllegalArgumentException("ff4j-core: Target for cache proxy has not been provided"); } return targetPropertyStore; } /** * Getter accessor for attribute 'cacheManager'. * * @return current value of 'cacheManager' */ public FF4JCacheManager getCacheManager() { if (cacheManager == null) { throw new IllegalArgumentException("ff4j-core: CacheManager for cache proxy has not been provided but it's required"); } return cacheManager; } /** * Setter accessor for attribute 'cacheManager'. * * @param cacheManager * new value for 'cacheManager ' */ public void setCacheManager(FF4JCacheManager cacheManager) { this.cacheManager = cacheManager; } // ------------ Cache related method -------------------- /** {@inheritDoc} */ public boolean isCached() { return true; } /** {@inheritDoc} */ public String getCacheProvider() { if (cacheManager != null) { return cacheManager.getCacheProviderName(); } else { return null; } } /** {@inheritDoc} */ public String getCachedTargetStore() { return getTargetFeatureStore().getClass().getCanonicalName(); } /** {@inheritDoc} */ @Override public Map<String, Property<?>> readAllProperties() { return getTargetPropertyStore().readAllProperties(); } /** {@inheritDoc} */ @Override public boolean existProperty(String propertyName) { // not in cache but maybe created from last access if (getCacheManager().getProperty(propertyName) == null) { return getTargetPropertyStore().existProperty(propertyName); } return true; } /** {@inheritDoc} */ @Override public <T> void createProperty(Property<T> property) { getTargetPropertyStore().createProperty(property); getCacheManager().putProperty(property); } /** {@inheritDoc} */ @Override public Property<?> readProperty(String name) { Property<?> fp = getCacheManager().getProperty(name); // not in cache but may has been created from now if (null == fp) { fp = getTargetPropertyStore().readProperty(name); getCacheManager().putProperty(fp); } return fp; } /** {@inheritDoc} */ @Override public Property<?> readProperty(String name, Property<?> defaultValue) { Property<?> fp = getCacheManager().getProperty(name); // Not in cache but may has been created from now // Or in cache but with different value that default if (null == fp) { fp = getTargetPropertyStore().readProperty(name, defaultValue); getCacheManager().putProperty(fp); } return fp; } /** {@inheritDoc} */ @Override public void updateProperty(String name, String newValue) { // Retrieve the full object from its name Property<?> fp = getTargetPropertyStore().readProperty(name); fp.setValueFromString(newValue); // Update value in target store getTargetPropertyStore().updateProperty(fp); // Remove from cache old value getCacheManager().evictProperty(fp.getName()); // Add new value in the cache getCacheManager().putProperty(fp); } /** {@inheritDoc} */ @Override public <T> void updateProperty(Property<T> propertyValue) { // Update the property getTargetPropertyStore().updateProperty(propertyValue); // Update the cache accordirly getCacheManager().evictProperty(propertyValue.getName()); // Update the property in cache getCacheManager().putProperty(propertyValue); } /** {@inheritDoc} */ @Override public void deleteProperty(String name) { // Access target store getTargetPropertyStore().deleteProperty(name); // even is not present, evict name failed getCacheManager().evictProperty(name); } /** {@inheritDoc} */ @Override public boolean isEmpty() { return listPropertyNames().isEmpty() && readAll().isEmpty(); } /** {@inheritDoc} */ @Override public Set<String> listPropertyNames() { return getTargetPropertyStore().listPropertyNames(); } /** {@inheritDoc} */ @Override public void clear() { // Cache Operations : As modification, flush cache for this getCacheManager().clearProperties(); getTargetPropertyStore().clear(); // Cache Operations : As modification, flush cache for this getCacheManager().clearFeatures(); getTargetFeatureStore().clear(); } /** {@inheritDoc} */ public void importProperties(Collection<Property<?>> properties) { getCacheManager().clearProperties(); getTargetPropertyStore().importProperties(properties); } /** {@inheritDoc} */ public void importFeatures(Collection<Feature> features) { getCacheManager().clearFeatures(); getTargetFeatureStore().importFeatures(features); } /** * Setter accessor for attribute 'targetFeatureStore'. * * @param targetFeatureStore * new value for 'targetFeatureStore ' */ public void setTargetFeatureStore(FeatureStore targetFeatureStore) { this.targetFeatureStore = targetFeatureStore; } /** * Setter accessor for attribute 'targetPropertyStore'. * * @param targetPropertyStore * new value for 'targetPropertyStore ' */ public void setTargetPropertyStore(PropertyStore targetPropertyStore) { this.targetPropertyStore = targetPropertyStore; } /** * Getter accessor for attribute 'store2CachePoller'. * * @return * current value of 'store2CachePoller' */ public Store2CachePollingScheduler getStore2CachePoller() { return store2CachePoller; } /** * Setter accessor for attribute 'store2CachePoller'. * @param store2CachePoller * new value for 'store2CachePoller ' */ public void setStore2CachePoller(Store2CachePollingScheduler store2CachePoller) { this.store2CachePoller = store2CachePoller; } }