/* * Copyright 2016 the original author or authors. * * 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.springframework.data.gemfire.config.annotation; import static org.springframework.data.gemfire.config.annotation.EnableEviction.EvictionPolicy; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.geode.cache.EvictionAttributes; import org.apache.geode.cache.Region; import org.apache.geode.cache.util.ObjectSizer; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.gemfire.RegionFactoryBean; import org.springframework.data.gemfire.RegionLookupFactoryBean; import org.springframework.data.gemfire.client.ClientRegionFactoryBean; import org.springframework.data.gemfire.eviction.EvictionActionType; import org.springframework.data.gemfire.eviction.EvictionAttributesFactoryBean; import org.springframework.data.gemfire.eviction.EvictionPolicyType; import org.springframework.data.gemfire.util.ArrayUtils; import org.springframework.data.gemfire.util.CollectionUtils; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * The {@link EvictionConfiguration} class is a Spring {@link Configuration @Configuration} annotated class to enable * Eviction policy configuration on GemFire/Geode {@link Region Regions}. * * @author John Blum * @see org.springframework.beans.factory.config.BeanPostProcessor * @see org.springframework.context.ApplicationContext * @see org.springframework.context.ApplicationContextAware * @see org.springframework.context.annotation.Bean * @see org.springframework.context.annotation.Configuration * @see org.springframework.context.annotation.ImportAware * @see org.springframework.data.gemfire.eviction.EvictionActionType * @see org.springframework.data.gemfire.eviction.EvictionAttributesFactoryBean * @see org.springframework.data.gemfire.eviction.EvictionPolicyType * @see org.springframework.data.gemfire.RegionFactoryBean * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean * @see org.apache.geode.cache.EvictionAttributes * @see org.apache.geode.cache.Region * @since 1.9.0 */ @Configuration public class EvictionConfiguration implements ApplicationContextAware, ImportAware { private ApplicationContext applicationContext; private EvictionPolicyConfigurer evictionPolicyConfigurer; /** * Determines whether the Spring bean is an instance of {@link RegionFactoryBean} * or {@link ClientRegionFactoryBean}. * * @param bean Spring bean to evaluate. * @return a boolean value indicating whether the Spring bean is an instance of {@link RegionFactoryBean}. * @see org.springframework.data.gemfire.RegionFactoryBean * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean */ protected static boolean isRegionFactoryBean(Object bean) { return (bean instanceof RegionFactoryBean || bean instanceof ClientRegionFactoryBean); } /** * Returns the {@link Annotation} {@link Class type} that enables and configures Eviction. * * @return the {@link Annotation} {@link Class type} to enable and configure Eviction. * @see java.lang.annotation.Annotation * @see java.lang.Class */ protected Class<? extends Annotation> getAnnotationType() { return EnableEviction.class; } /** * Returns the name of the {@link Annotation} type that enables and configures Eviction. * * @return the name of the {@link Annotation} type that enables and configures Eviction. * @see java.lang.Class#getName() * @see #getAnnotationType() */ protected String getAnnotationTypeName() { return getAnnotationType().getName(); } /** * Returns the simple name of the {@link Annotation} type that enables and configures Eviction. * * @return the simple name of the {@link Annotation} type that enables and configures Eviction. * @see java.lang.Class#getSimpleName() * @see #getAnnotationType() */ @SuppressWarnings("unused") protected String getAnnotationTypeSimpleName() { return getAnnotationType().getSimpleName(); } /** * @inheritDoc */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * @inheritDoc */ @Override public void setImportMetadata(AnnotationMetadata importMetadata) { if (importMetadata.hasAnnotation(getAnnotationTypeName())) { Map<String, Object> enableEvictionAttributes = importMetadata.getAnnotationAttributes(getAnnotationTypeName()); AnnotationAttributes[] policies = (AnnotationAttributes[]) enableEvictionAttributes.get("policies"); for (AnnotationAttributes evictionPolicyAttributes : ArrayUtils.nullSafeArray(policies, AnnotationAttributes.class)) { this.evictionPolicyConfigurer = ComposableEvictionPolicyConfigurer.compose( this.evictionPolicyConfigurer, EvictionPolicyMetaData.from(evictionPolicyAttributes, this.applicationContext)); } this.evictionPolicyConfigurer = (this.evictionPolicyConfigurer != null ? this.evictionPolicyConfigurer : EvictionPolicyMetaData.fromDefaults()); } } /** * Returns a reference to the configured {@link EvictionPolicyConfigurer} used to configure the Eviction policy * of a {@link Region}. * * @return a reference to the configured {@link EvictionPolicyConfigurer}. * @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer */ protected EvictionPolicyConfigurer getEvictionPolicyConfigurer() { Assert.state(this.evictionPolicyConfigurer != null, "EvictionPolicyConfigurer was not properly configured and initialized"); return this.evictionPolicyConfigurer; } @Bean @SuppressWarnings("unused") public BeanPostProcessor evictionBeanPostProcessor() { return new BeanPostProcessor() { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return (isRegionFactoryBean(bean) ? getEvictionPolicyConfigurer().configure(bean) : bean); } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }; } /** * {@link EvictionPolicyConfigurer} configures the Eviction policy of a GemFire {@link Region}. */ protected interface EvictionPolicyConfigurer { /** * Configure the Eviction policy on the given SDG {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} * used to create a GemFire {@link Region}. * * @param regionFactoryBean {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} used to create * a GemFire {@link Region}. * @return the given {@code regionFactoryBean}. * @see org.springframework.data.gemfire.RegionFactoryBean * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean */ Object configure(Object regionFactoryBean); } /** * {@link ComposableEvictionPolicyConfigurer} is a {@link EvictionPolicyConfigurer} implementation that composes * multiple {@link EvictionPolicyConfigurer} objects into a composition using the Composite Software Design Pattern * making the composition appear as a single {@link EvictionPolicyConfigurer}. * * @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer */ protected static class ComposableEvictionPolicyConfigurer implements EvictionPolicyConfigurer { private final EvictionPolicyConfigurer one; private final EvictionPolicyConfigurer two; /** * Composes the array of {@link EvictionPolicyConfigurer} objects into a single * {@link EvictionPolicyConfigurer} implementation using the Composite Software Design Pattern. * * @param array array of {@link EvictionPolicyConfigurer} objects to compose. * @return an {@link EvictionPolicyConfigurer} implementation composed from the array * of {@link EvictionPolicyConfigurer} objects. * @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer * @see #compose(Iterable) */ @SuppressWarnings("unused") protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer[] array) { return compose(Arrays.asList(ArrayUtils.nullSafeArray(array, EvictionPolicyConfigurer.class))); } /** * Composes the {@link Iterable} of {@link EvictionPolicyConfigurer} objects into a single * {@link EvictionPolicyConfigurer} implementation using the Composite Software Design Pattern. * * @param iterable {@link Iterable} of {@link EvictionPolicyConfigurer} objects to compose. * @return an {@link EvictionPolicyConfigurer} implementation composed from the {@link Iterable} * of {@link EvictionPolicyConfigurer} objects. * @see org.springframework.data.gemfire.config.annotation.EvictionConfiguration.EvictionPolicyConfigurer * @see #compose(EvictionPolicyConfigurer, EvictionPolicyConfigurer) */ protected static EvictionPolicyConfigurer compose(Iterable<EvictionPolicyConfigurer> iterable) { EvictionPolicyConfigurer current = null; for (EvictionPolicyConfigurer evictionPolicyConfigurer : CollectionUtils.nullSafeIterable(iterable)) { current = compose(current, evictionPolicyConfigurer); } return current; } /** * Composes two {@link EvictionPolicyConfigurer} objects into a composition object * implementing the {@link EvictionPolicyConfigurer} interface. * * @param one first {@link EvictionPolicyConfigurer} object to compose. * @param two second {@link EvictionPolicyConfigurer} object to compose. * @return an {@link EvictionPolicyConfigurer} object implementation composed of * multiple {@link EvictionPolicyConfigurer} objects using the Composite Software Design Pattern. */ protected static EvictionPolicyConfigurer compose(EvictionPolicyConfigurer one, EvictionPolicyConfigurer two) { return (one == null ? two : (two == null ? one : new ComposableEvictionPolicyConfigurer(one, two))); } /** * Constructs a new instance of the {@link ComposableEvictionPolicyConfigurer} initialized with the two * {@link EvictionPolicyConfigurer} objects. * * @param one first {@link EvictionPolicyConfigurer} object to compose. * @param two second {@link EvictionPolicyConfigurer} object to compose. */ private ComposableEvictionPolicyConfigurer(EvictionPolicyConfigurer one, EvictionPolicyConfigurer two) { this.one = one; this.two = two; } /** * @inheritDoc */ @Override public Object configure(Object regionFactoryBean) { return two.configure(one.configure(regionFactoryBean)); } } protected static class EvictionPolicyMetaData implements EvictionPolicyConfigurer { protected static final String[] ALL_REGIONS = new String[0]; private final EvictionAttributes evictionAttributes; private final Set<String> regionNames = new HashSet<String>(); protected static EvictionPolicyMetaData from(AnnotationAttributes evictionPolicyAttributes, ApplicationContext applicationContext) { return from((Integer) evictionPolicyAttributes.get("maximum"), evictionPolicyAttributes.<EvictionPolicyType>getEnum("type"), evictionPolicyAttributes.<EvictionActionType>getEnum("action"), resolveObjectSizer(evictionPolicyAttributes.getString("objectSizerName"), applicationContext), evictionPolicyAttributes.getStringArray("regionNames")); } protected static EvictionPolicyMetaData from(EvictionPolicy evictionPolicy, ApplicationContext applicationContext) { return from(evictionPolicy.maximum(), evictionPolicy.type(), evictionPolicy.action(), resolveObjectSizer(evictionPolicy.objectSizerName(), applicationContext), evictionPolicy.regionNames()); } protected static EvictionPolicyMetaData from(int maximum, EvictionPolicyType type, EvictionActionType action, ObjectSizer objectSizer, String... regionNames) { EvictionAttributesFactoryBean factoryBean = new EvictionAttributesFactoryBean(); factoryBean.setAction(action.getEvictionAction()); factoryBean.setObjectSizer(objectSizer); factoryBean.setThreshold(resolveThreshold(maximum, type)); factoryBean.setType(type); factoryBean.afterPropertiesSet(); return new EvictionPolicyMetaData(factoryBean.getObject(), regionNames); } protected static EvictionPolicyMetaData fromDefaults() { return new EvictionPolicyMetaData(EvictionAttributes.createLRUEntryAttributes()); } protected static ObjectSizer resolveObjectSizer(String objectSizerName, ApplicationContext applicationContext) { boolean resolvable = StringUtils.hasText(objectSizerName) && applicationContext.containsBean(objectSizerName); return (resolvable ? applicationContext.getBean(objectSizerName, ObjectSizer.class) : null); } /** * Resolves the Eviction policy threshold (a.k.a. maximum) based on the {@link EvictionPolicyType}. * * For instance {@link EvictionPolicyType#HEAP_PERCENTAGE} does not support maximum/threshold since * the settings are determined by the GemFire/Geode cache critical heap percentage and eviction heap percentage * System property settings. * * @param maximum integer value specifying the configured Eviction threshold. * @param type {@link EvictionPolicyType} specifying the type of Eviction algorithm. * @return a resolved value for the Eviction maximum/threshold. * @see org.springframework.data.gemfire.eviction.EvictionPolicyType */ protected static Integer resolveThreshold(int maximum, EvictionPolicyType type) { return (EvictionPolicyType.HEAP_PERCENTAGE.equals(type) ? null : maximum); } /** * Constructs an instance of {@link EvictionPolicyMetaData} initialized with the given * {@link EvictionAttributes} applying to all {@link Region Regions}. * * @param evictionAttributes {@link EvictionAttributes} specifying the Eviction policy configuration * for a {@link Region}. * @see org.apache.geode.cache.EvictionAttributes * @see #EvictionPolicyMetaData(EvictionAttributes, String[]) */ protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes) { this(evictionAttributes, ALL_REGIONS); } /** * Constructs an instance of {@link EvictionPolicyMetaData} initialized with the given * {@link EvictionAttributes} to apply to the specific {@link Region Regions}. * * @param evictionAttributes {@link EvictionAttributes} specifying the Eviction policy configuration * for a {@link Region}. * @param regionNames names of {@link Region Regions} on which the Eviction policy is applied. * @see org.apache.geode.cache.EvictionAttributes */ protected EvictionPolicyMetaData(EvictionAttributes evictionAttributes, String[] regionNames) { Assert.notNull(evictionAttributes, "EvictionAttributes must not be null"); this.evictionAttributes = evictionAttributes; Collections.addAll(this.regionNames, ArrayUtils.nullSafeArray(regionNames, String.class)); } /** * Returns an instance of the {@link EvictionAttributes} specifying the Eviction policy configuration * captured in this Eviction policy meta-data. * * @return an instance of the {@link EvictionAttributes} specifying the {@link Region} * Eviction policy configuration. * @throws IllegalStateException if the {@link EvictionAttributes} were not properly initialized. * @see org.apache.geode.cache.EvictionAttributes */ protected EvictionAttributes getEvictionAttributes() { Assert.state(this.evictionAttributes != null, "EvictionAttributes was not properly configured and initialized"); return this.evictionAttributes; } /** * Determines whether the given {@link Object} (e.g. Spring bean) is accepted for Eviction policy configuration. * * @param regionFactoryBean {@link Object} being evaluated as an Eviction policy configuration candidate. * @return a boolean value indicating whether the {@link Object} is accepted for Eviction policy configuration. * @see #isRegionFactoryBean(Object) * @see #resolveRegionName(Object) * @see #accepts(String) */ protected boolean accepts(Object regionFactoryBean) { return (isRegionFactoryBean(regionFactoryBean) && accepts(resolveRegionName(regionFactoryBean))); } /** * Determine whether the {@link Region} identified by name is accepted for Eviction policy configuration. * * @param regionName name of the {@link Region} targeted for Eviction policy configuration. * @return a boolean value if the named {@link Region} is accepted for Eviction policy configuration. */ protected boolean accepts(String regionName) { return (this.regionNames.isEmpty() || this.regionNames.contains(regionName)); } /** * Resolves the name of a given {@link Region} from the corresponding {@link RegionLookupFactoryBean} object. * * @param regionFactoryBean {@link RegionLookupFactoryBean} from which to resolve the {@link Region} name. * @return the resolved name of the {@link Region} created from the given {@link RegionLookupFactoryBean}. * @see org.springframework.data.gemfire.RegionLookupFactoryBean#resolveRegionName() */ protected String resolveRegionName(Object regionFactoryBean) { return (regionFactoryBean instanceof RegionLookupFactoryBean ? ((RegionLookupFactoryBean) regionFactoryBean).resolveRegionName() : null); } /** * Sets the {@link EvictionAttributes} on the {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} * used to create the targeted {@link Region}. * * @param regionFactoryBean {@link RegionFactoryBean} or {@link ClientRegionFactoryBean} on which to * set the {@link EvictionAttributes} encapsulating the Eviction policy for the targeted {@link Region}. * @return the {@code regionFactoryBean}. * @see org.springframework.data.gemfire.RegionFactoryBean#setEvictionAttributes(EvictionAttributes) * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean#setEvictionAttributes(EvictionAttributes) * @see org.apache.geode.cache.EvictionAttributes * @see #getEvictionAttributes() */ protected Object setEvictionAttributes(Object regionFactoryBean) { if (regionFactoryBean instanceof RegionFactoryBean) { ((RegionFactoryBean) regionFactoryBean).setEvictionAttributes(getEvictionAttributes()); } else { ((ClientRegionFactoryBean) regionFactoryBean).setEvictionAttributes(getEvictionAttributes()); } return regionFactoryBean; } /** * @inheritDoc */ @Override public Object configure(Object regionFactoryBean) { return (accepts(regionFactoryBean) ? setEvictionAttributes(regionFactoryBean) : regionFactoryBean); } } }