Package com.tacitknowledge.flip.aspectj

Source Code of com.tacitknowledge.flip.aspectj.FlipAbstractAspect

/*
* Copyright 2012 Tacit Knowledge.
*
* 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 com.tacitknowledge.flip.aspectj;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.tacitknowledge.flip.FeatureService;
import com.tacitknowledge.flip.FlipContext;
import com.tacitknowledge.flip.aspectj.converters.Converter;
import com.tacitknowledge.flip.aspectj.converters.ConvertersHandler;
import com.tacitknowledge.flip.model.FeatureState;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
* The base abstract Aspect class which applies the interception of all
* methods marked with {@link Flippable} annotation. If the method is marked with
* this annotation will act in the feature toggling. If the feature declared in the
* annotation is disabled the {@link Flippable#disabledValue() } will be applied.
* If the class itself is marked with {@link Flippable} annotation, then all
* methods will act in the feature toggling. Also this aspect allows overriding
* the methods parameters which should be marked with {@link FlipParam} annotation.
*
* @author Serghei Soloviov <ssoloviov@tacitknowledge.com>
*/
@Aspect
public abstract class FlipAbstractAspect {

    /**
     * The prepared regular expression to extract value expression from a string.
     */
    private static final Pattern VALUE_EXPRESSION_REGEX = Pattern.compile("\\$\\{([^}]+)\\}");
   
    /**
     * The default value used if {@link Flippable} annotation has not declared the
     * {@link Flippable#disabledValue() }.
     */
    private String defaultValue;
   
    /**
     * Returns the {@link FeatureService} object used to calculate the features.
     * By default it returns the object set in {@link FlipContext}.
     *
     * @return {@link FeatureService} object.
     */
    public FeatureService getFeatureService() {
        return FlipContext.chooseFeatureService();
    }

    /**
     * Returns the {@link ConvertersHandler} instance used to manage converters.
     * If the value of {@link Flippable#disabledValue() } or {@link FlipParam#disabledValue() }
     * properties do not contains a value expression the converter is used to
     * obtain the required object.
     *
     * @return {@link ConvertersHandler} instance.
     */
    public ConvertersHandler getConvertersHandler() {
        return FlipAopContext.getConvertersHandler();
    }
   
    /**
     * Returns the value expressions evaluator. This evaluator is used to evaluate
     * expressions used in {@link Flippable#disabledValue() } or {@link FlipParam#disabledValue() }.
     * If these properties contains an expression like this <code>${var}</code>
     * this value expression evaluator will be used.
     *
     * @return {@link ValueExpressionEvaluator} instance.
     */
    public ValueExpressionEvaluator getValueExpressionEvaluator() {
        return FlipAopContext.getValueExpressionEvaluator();
    }

    /**
     * Returns the default value if there is no {@link Flippable#disabledValue() } specified.
     *
     * @return the default disabled value.
     */
    public String getDefaultValue() {
        return defaultValue;
    }

    /**
     * Sets the default disabled value. This value will be used if no {@link Flippable#disabledValue() } is
     * specified in the annotation of the method.
     */
    public void setDefaultValue(String defaultValue) {
        this.defaultValue = defaultValue;
    }
   
    /**
     * A pointcut declaration which marks all methods in the class.
     */
    @Pointcut("execution(* *(..))")
    public void anyMethod() {}
   
    /**
     * Intercept calls to the methods marked with {@link Flippable} annotation.
     * Firstly it processes the method parameters marked with {@link FlipParam}
     * annotation. Each parameter value is replaced with the value declared in
     * {@link FlipParam#disabledValue()} if the feature declared in {@link FlipParam#feature() }
     * is disabled.
     * After properties evaluation this method checks if the feature marked in
     * {@link Flippable#feature() } is disabled then the value from {@link Flippable#disabledValue() }
     * is returned.
     *
     * @param flip the {@link Flippable} annotation instance which marks the method to intercept.
     * @param pjp the object obtained from AspectJ method interceptor.
     * @return the processed value of the method depending from feature state.
     * @throws Throwable
     */
    @Around(value="anyMethod() && @annotation(flip)", argNames="flip")
    public Object aroundFlippableMethods(ProceedingJoinPoint pjp, Flippable flip) throws Throwable {
        MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
        Method method = methodSignature.getMethod();
       
        if (isFeatureEnabled(flip.feature())) {
            Annotation[][] paramAnnotations = method.getParameterAnnotations();
            Object[] params = pjp.getArgs();
            Class[] paramTypes = method.getParameterTypes();

            for(int i=0;i<paramAnnotations.length;i++) {
                FlipParam flipParam = findFlipParamAnnoattion(paramAnnotations[i]);
                if (!isFeatureEnabled(flipParam.feature())) {
                    params[i] = getProcessedDisabledValue(paramTypes[i], flipParam.disabledValue(), pjp.getThis());
                }
            }

            return pjp.proceed(params);
        } else {
            return getProcessedDisabledValue(method.getReturnType(), flip.disabledValue(), pjp.getThis());
        }
    }
   
    public Object aroundFlippableMethods(ProceedingJoinPoint pjp) throws Throwable {
        final Flippable flip = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(Flippable.class);
        return aroundFlippableMethods(pjp, flip);
    }
   
   
    /**
     * Returns if the feature is enabled. If the feature is empty then the feature
     * is considered enabled.
     *
     * @param feature the name of the feature to test.
     * @return true if the feature is enabled or the feature name is empty.
     */
    private boolean isFeatureEnabled(String feature) {
        return feature == null ||
                feature.isEmpty()
                || getFeatureService().getFeatureState(feature) == FeatureState.ENABLED;
    }
   
    /**
     * Finds the {@link FlipParam} annotation instance from a list of annotations.
     *
     * @param annotations the list of annotation which marks the parameter.
     * @return the {@link FlipParam} instance if found, otherwise null.
     */
    private FlipParam findFlipParamAnnoattion(Annotation[] annotations) {
        for(Annotation annotation : annotations) {
            if (FlipParam.class.isAssignableFrom(annotation.annotationType())) {
                return (FlipParam) annotation;
            }
        }
        return null;
    }
   
    /**
     * Processes the disabled value used. If the value contains the <code>${}</code>
     * statement then this statement is processed using {@link #getValueExpressionEvaluator() },
     * otherwise it is used converter found by {@link #getConvertersHandler() }.
     * The converter is found by ouputClass parameter. If no converter is found and
     * the outputClass is not a {@link CharSequence} then {@link IllegalArgumentException}
     * is thrown.
     *
     * @param outputClass the required object type
     * @param value the disabled value to evaluate.
     * @param context the object of the method intercepted. This object is used as context to
     *  evaluate value expression.
     *
     * @return the evaluated value
     * @throws IllegalArgumentException if cannot find the converter.
     */
    private Object getProcessedDisabledValue(Class outputClass, String value, Object context) {
        if (value == null || value.isEmpty()) {
            value = defaultValue;
        }
       
        Matcher m = VALUE_EXPRESSION_REGEX.matcher(value);
        if (m.find()) {
            ValueExpressionEvaluator evaluator = getValueExpressionEvaluator();
            if (evaluator == null) {
                return getUntouchedDisabledValue(outputClass, value);
            }
           
            return evaluator.evaluate(context, m.group(1));
        } else {
            Converter converter = getConvertersHandler().getConverter(outputClass);
            if (converter == null) {
                return getUntouchedDisabledValue(outputClass, value);
            }
           
            return converter.convert(value, outputClass);
        }
    }
   
    /**
     * Returns the value if it cannot be converted or evaluated as value expression.
     * If the outputClass parameter is no {@link CharSequence} then the
     * {@link IllegalArgumentException} is thrown, otherwise the value itself is
     * returned.
     *
     * @param outputClass the required output object type.
     * @param value the value.
     * @return the value string if is possible.
     * @throws IllegalArgumentException if cannot convert value parameter to outputClass.
     */
    private Object getUntouchedDisabledValue(Class outputClass, String value) {
        if (CharSequence.class.isAssignableFrom(outputClass)) {
            return value;
        } else {
            throw new IllegalArgumentException(String.format("Cannot find converter for class [%s].", outputClass.getName()));
        }
    }
   
}
TOP

Related Classes of com.tacitknowledge.flip.aspectj.FlipAbstractAspect

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.