package org.ff4j.spring.autowire; /* * #%L * ff4j-aop * %% * Copyright (C) 2013 - 2015 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ff4j.FF4j; import org.ff4j.core.Feature; import org.ff4j.property.Property; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import java.lang.reflect.Field; /** * When Proxified, analyze bean to eventually invoke ANOTHER implementation (flip up). * * @author <a href="mailto:cedrick.lunven@gmail.com">Cedrick LUNVEN</a> */ @Component("ff4j.autowiringpostprocessor") public class AutowiredFF4JBeanPostProcessor implements BeanPostProcessor { /** * Logger for this class. */ protected final Log logger = LogFactory.getLog(getClass()); /** * Injection of current FF4J bean. */ @Autowired private FF4j ff4j; /** * {@inheritDoc} */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { // Nothing to do before initializations, will inject only on post treatment return bean; } /** * {@inheritDoc} */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean == null) return null; Class<?> beanClass = bean.getClass(); Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { // Expect to get annnotation Autowired if (field.isAnnotationPresent(FF4JProperty.class)) { autoWiredProperty(bean, field); } else if (field.isAnnotationPresent(FF4JFeature.class)) { autoWiredFeature(bean, field); } } return bean; } private void autoWiredFeature(Object bean, Field field) { // Find the required and name parameters FF4JFeature annFeature = field.getAnnotation(FF4JFeature.class); String annValue = annFeature.value(); String featureName = field.getName(); if (annValue != null && !"".equals(annValue)) { featureName = annValue; } Feature feature = readFeature(field, featureName, annFeature.required()); if (feature != null) { if (Feature.class.isAssignableFrom(field.getType())) { injectValue(field, bean, featureName, feature); } else if (Boolean.class.isAssignableFrom(field.getType())) { injectValue(field, bean, featureName, new Boolean(feature.isEnable())); } else if (boolean.class.isAssignableFrom(field.getType())) { injectValue(field, bean, featureName, feature.isEnable()); } else { throw new IllegalArgumentException("Field annotated with @FF4JFeature" + " must inherit from org.ff4j.Feature or be boolean " + field.getType() + " [class=" + bean.getClass().getName() + ", field=" + field.getName() + "]"); } } } private void autoWiredProperty(Object bean, Field field) { // Find the required and name parameters FF4JProperty annProperty = field.getAnnotation(FF4JProperty.class); String propertyName = StringUtils.hasLength(annProperty.value()) ? annProperty.value() : field.getName(); Property<?> property = readProperty(field, propertyName, annProperty.required()); // if not available in store if (property != null) { if (Property.class.isAssignableFrom(field.getType())) { injectValue(field, bean, propertyName, property); } else if (property.parameterizedType().isAssignableFrom(field.getType())) { injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Integer.class) && field.getType().equals(int.class) && (null != property.getValue())) { // Autoboxing Integer -> Int injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Long.class) && field.getType().equals(long.class) && (null != property.getValue())) { // Autoboxing Long -> long injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Double.class) && field.getType().equals(double.class) && (null != property.getValue())) { // Autoboxing Double -> double injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Byte.class) && field.getType().equals(byte.class) && (null != property.getValue())) { // Autoboxing Byte -> byte injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Boolean.class) && field.getType().equals(boolean.class) && (null != property.getValue())) { // Autoboxing Boolean -> boolean injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Short.class) && field.getType().equals(short.class) && (null != property.getValue())) { // Autoboxing Short -> short injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Character.class) && field.getType().equals(char.class) && (null != property.getValue())) { // Autoboxing Character -> char injectValue(field, bean, propertyName, property.getValue()); } else if (property.parameterizedType().equals(Float.class) && field.getType().equals(float.class) && (null != property.getValue())) { // Autoboxing Float -> float injectValue(field, bean, propertyName, property.getValue()); } else { throw new IllegalArgumentException("Field annotated with @FF4JProperty" + " must inherit from org.ff4j.property.AbstractProperty or be of type " + property.parameterizedType() + "but is " + field.getType() + " [class=" + bean.getClass().getName() + ", field=" + field.getName() + "]"); } } } private void injectValue(Field field, Object currentBean, String propName, Object propValue) { // Set as true for modifications ReflectionUtils.makeAccessible(field); // Update property ReflectionUtils.setField(field, currentBean, propValue); logger.debug("Injection of property '" + propName + "' on " + currentBean.getClass().getName() + "." + field.getName()); } private Feature readFeature(Field field, String featureName, boolean required) { if (!ff4j.getFeatureStore().exist(featureName)) { if (required) { throw new IllegalArgumentException("Cannot autowiring field '" + field.getName() + "' with FF4J property as" + " target feature has not been found"); } else { logger.warn("Feature '" + featureName + "' has not been found but not required"); return null; } } return ff4j.getFeatureStore().read(featureName); } private Property<?> readProperty(Field field, String propertyName, boolean required) { if (!ff4j.getPropertiesStore().existProperty(propertyName)) { if (required) { throw new IllegalArgumentException("Cannot autowiring field '" + field.getName() + "' with FF4J property as" + " target property has not been found"); } else { logger.warn("Property '" + propertyName + "' has not been found but not required"); return null; } } return ff4j.getPropertiesStore().readProperty(propertyName); } }