All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.coherentlogic.coherent.data.adapter.aop.aspects.AbstractPropertyChangeGenerator Maven / Gradle / Ivy

package com.coherentlogic.coherent.data.adapter.aop.aspects;

import java.beans.PropertyChangeEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.coherentlogic.coherent.data.adapter.aop.exceptions.PropertyChangeGeneratorException;
import com.coherentlogic.coherent.data.model.core.annotations.Changeable;
import com.coherentlogic.coherent.data.model.core.domain.SerializableBean;
import com.coherentlogic.coherent.data.model.core.exceptions.MisconfiguredException;

/**
 * Base class that is used for intercepting setter method calls from either AspectJ (where code is augmented
 * outside of the Spring container), or for use within the Spring container, using Spring AOP.
 *
 * PropertyChangeGenerator implementation that uses the AspectJ ProceedingJoinPoint in order to execute the
 * property change event notification in the {@link AbstractPropertyChangeGenerator}. Classes that extend
 * this class must be annotated as an Aspect and also include the specific advice on the
 * {@link #onSetterMethodCalled(ProceedingJoinPoint)}.
 *
 * Note that the org.codehaus.mojo:aspectj-maven-plugin plugin must be added to the Maven pom, similar to
 * what's included below.
 *
 *  
 *
 * 
 *     
 *         
 *             org.codehaus.mojo
 *             aspectj-maven-plugin
 *             1.10
 *             
 *                 1.8
 *                 1.8
 *                 1.8
 *                 
 *                     ...
 *                 
 *                 
 *                 true
 *             
 *             
 *                 
 *                     
 *                         compile
 *                         test-compile
 *                     
 *                 
 *             
 *         
 *     
 * 
 *
 * @TODO Move this class into src and force developers to extend from this.
 *
 * @see {@link DefaultPropertyChangeGeneratorAspect}
 * @see /src/test/java/com.coherentlogic.coherent.data.adapter.aop.aspects.PropertyChangeGeneratorAspect
 *
 * @author Thomas P. Fuller
 * @author Support
 */
public abstract class AbstractPropertyChangeGenerator {

    private static final Logger log = LoggerFactory.getLogger(AbstractPropertyChangeGenerator.class);

    static final String FIRE_PROPERTY_CHANGE = "firePropertyChange";

    /**
     * @see {@link SerializableBean#firePropertyChange}
     */
    private final Method firePropertyChangeMethod;

    public AbstractPropertyChangeGenerator() {

        try {
            firePropertyChangeMethod = SerializableBean.class.getDeclaredMethod(
                FIRE_PROPERTY_CHANGE, PropertyChangeEvent.class);
        } catch (Exception e) {
            throw new RuntimeException ("An exception was thrown when attempting to get the method named " +
                FIRE_PROPERTY_CHANGE + ".", e);
        }
    }

    /*
     * See if you can use method overloading instead of a closure.
     *
     * Object result = joinPoint.proceed ()
     *
     * or
     *
     * Object result = invocation.proceed();
     */
    public Object invoke(
        Object targetObject,
        Method method,
        ProceedClosure invocationOrJoinPointWrapper
    ) throws Throwable {

        SerializableBean target = asSerializableBean (targetObject);

        int parameterCount = method.getParameterCount();

        Object result = null;

        if (parameterCount == 1) { // Single parameter setter methods only.

            Parameter[] parameters = method.getParameters();

            Parameter parameter = parameters[0];

            if (parameter.isAnnotationPresent(Changeable.class)) {

                Changeable changeable = parameter.getAnnotation(Changeable.class);

                String parameterName = changeable.value();

                if (Changeable.BLANK.equals(parameterName))
                    throw new MisconfiguredException (
                        "The changeable.value (parameterName) cannot be blank.");

                Class targetClass = target.getClass();

                Field field = targetClass.getDeclaredField(parameterName);

                log.debug("parameterName: " + parameterName + ", field: " + field.getName());

                doSetAndFireUpdate (
                    target,
                    field,
                    targetObject,
                    parameterName,
                    invocationOrJoinPointWrapper
                 );

            } else {
                log.debug("The method " + method + " exists and has one parameter however the parameter "
                    + "has not been annotated with the " + Changeable.class + " annotation, hence no"
                    + "PropertyChangeEvents will be fired unless the code has been manually added to "
                    + "the method.");

                result = invocationOrJoinPointWrapper.proceed();
            }
        } else {
            log.debug("The method " + method + " exists and has either zero or more than one parameter "
                + "so no PropertyChangeEvents will be fired unless the code has been manually added to "
                + "the method.");

            result = invocationOrJoinPointWrapper.proceed();
        }
        return result;
    }

    SerializableBean asSerializableBean (Object targetObject) {

        SerializableBean result = null;

        if (targetObject != null && targetObject instanceof SerializableBean)
            result = (SerializableBean) targetObject;
        else if (targetObject == null)
            throw new NullPointerException("The targetObject is null.");
        else
            throw new MisconfiguredException("The bean must extend SerializableBean; targetObject class: " +
                targetObject.getClass());

        return result;
    }

    Object doSetAndFireUpdate (
        SerializableBean target,
        Field field,
        Object targetObject,
        String parameterName,
        ProceedClosure invocationOrJoinPointWrapper
    ) throws Throwable {

        field.setAccessible(true);

        Object oldValue = field.get(targetObject);

        Object result = invocationOrJoinPointWrapper.proceed();

        Object newValue = field.get(targetObject);

        log.debug("oldValue: " + oldValue + ", newValue: " + newValue);

        field.setAccessible(false);

        PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
            target,
            parameterName,
            oldValue,
            newValue
        );

        firePropertyChangeMethod.setAccessible(true);

        firePropertyChangeMethod.invoke(target, propertyChangeEvent);

        firePropertyChangeMethod.setAccessible(false);

        return result;
    }

    public void onSetterMethodCalled (ProceedingJoinPoint joinPoint) {

        log.debug("onSetterMethodCalled: method begins; joinPoint: " + joinPoint);

        Object targetObject = joinPoint.getTarget();

        Class clazz = targetObject.getClass();

        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

        Method method = null;

        if (codeSignature instanceof MethodSignature) {

            String name = codeSignature.getName();

            Class[] parameterTypes = codeSignature.getParameterTypes();

            try {
                method = clazz.getMethod(name, parameterTypes);
            } catch (NoSuchMethodException | SecurityException e) {
                throw new PropertyChangeGeneratorException("Unable to get the method using the name: " + name
                    + " and parameterTypes: " + parameterTypes + " on the clazz: " + clazz);
            }

        } else
            throw new PropertyChangeGeneratorException (
                "codeSignature is not an instanceof MethodSignature; codeSignature: " + codeSignature);

        try {
            invoke(targetObject, method, () -> { return joinPoint.proceed(); });
        } catch (Throwable thrown) {
            throw new PropertyChangeGeneratorException ("The call to super.invoke resulted in an exception "
                + "being thrown; targetObject: " + targetObject + ", method: " + method, thrown);
        }

        log.debug("onSetterMethodCalled: method ends.");
    }
}