Package org.mule.munit.common.mp

Source Code of org.mule.munit.common.mp.MunitMessageProcessorInterceptorFactory

/*
* Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.munit.common.mp;

import org.mule.modules.interceptor.processors.MessageProcessorId;
import org.mule.modules.interceptor.spring.BeanFactoryMethodBuilder;
import org.mule.modules.interceptor.spring.MethodInterceptorFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.NoOp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;


/**
* <p>
* This is the Message processor interceptor factory.
* </p>
*
* @author Mulesoft Inc.
* @since 3.3.2
*/
public class MunitMessageProcessorInterceptorFactory extends MethodInterceptorFactory
{

    protected transient Log logger = LogFactory.getLog(getClass());

    /**
     * <p>
     * For operations that are not {@link org.mule.api.processor.MessageProcessor#process(org.mule.api.MuleEvent)} just do
     * nothing
     * </p>
     */
    private static Callback NULL_METHOD_INTERCEPTOR = new NoOp()
    {
    };

    private static CallbackFilter FACTORY_BEAN_FILTER = new CallbackFilter(){

        @Override
        public int accept(Method method)
        {
            if ("getObject".equals(method.getName()))
            {
                return 0;
            }
            return 1;
        }
    };

    private static CallbackFilter MESSAGE_PROCESSOR_FILTER = new CallbackFilter(){

        @Override
        public int accept(Method method)
        {
            if ("process".equals(method.getName()))
            {
                return 0;
            }
            return 1;
        }
    };

    /**
     * <p>
     * The Id in the spring registry of Mule
     * </p>
     */
    public static final String ID = "__messageProcessorEnhancerFactory";

    /**
     * <p>
     * Util method that creates a @see #BeanFactoryMethodBuilder based on an abstract bean definition
     * </p>
     * <p/>
     * <p>The usage:</p>
     * <p/>
     * <code>
     * addFactoryDefinitionTo(beanDefinition).withConstructorArguments(beanDefinition.getBeanClass());
     * </code>
     *
     * @param beanDefinition <p>
     *                       The bean definition that we want to modify
     *                       </p>
     * @return
     */
    public static BeanFactoryMethodBuilder addFactoryDefinitionTo(AbstractBeanDefinition beanDefinition)
    {
        return new BeanFactoryMethodBuilder(beanDefinition, "create", ID);
    }


    // TODO: Find a cleaner way to make spring find a constructor with dynamic size parameters. Now Munit allows mocking MP with 2 or less constructors

    /**
     * <p>
     * Factory Method to create Message processors with a constructor with one parameter ( {@param constructorArgument} )
     * </p>
     */
    public Object create(Class realMpClass, MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber,
                         Object constructorArgument)
    {
        return create(realMpClass, id, attributes, fileName, lineNumber, new Object[] {constructorArgument});
    }

    /**
     * <p>
     * Factory Method to create Message processors with a constructor with two parameters ( {@param constructorArgument} )
     * </p>
     */
    public Object create(Class realMpClass, MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber,
                         Object constructorArgument1, Object constructorArgument2)
    {
        return create(realMpClass, id, attributes, fileName, lineNumber, new Object[] {constructorArgument1, constructorArgument2});
    }

    /**
     * <p>
     * Factory method used to create Message Processors without constructor parameters.
     * </p>
     *
     * @param realMpClass The class that we want to mock
     * @param id          The {@link MessageProcessorId} that identifies the message processor
     * @param attributes  The Message Processor attributes used to identify the mock
     * @param fileName    The name of the file where the message processor is written down
     * @param lineNumber  The line number where the message processor is written down
     * @return The Mocked object, if it fails mocking then the real object.
     */
    public Object create(Class realMpClass, MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber)
    {
        try
        {
            Enhancer e = createEnhancer(realMpClass, id, attributes, fileName, lineNumber);
            return e.create();
        }
        catch (Throwable e)
        {
            logger.warn("The message processor " + id.getFullName() + " could not be mocked");
            try
            {
                return realMpClass.newInstance();
            }
            catch (Throwable e1)
            {
                throw new Error("The message processor " + id.getFullName() + " could not be created", e);
            }
        }
    }

    /**
     * <p>
     * Factory method used to create Message Processors with constructor parameters.
     * </p>
     *
     * @param realMpClass          The class that we want to mock
     * @param id                   The {@link MessageProcessorId} that identifies the message processor
     * @param attributes           The Message Processor attributes used to identify the mock
     * @param fileName             The name of the file where the message processor is written down
     * @param lineNumber           The line number where the message processor is written down
     * @param constructorArguments The Array of constructor arguments of the message processor
     * @return The Mocked object, if it fails mocking then the real object.
     */
    public Object create(Class  realMpClass, MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber,
                         Object[] constructorArguments)
    {
        try
        {
            Enhancer e = createEnhancer(realMpClass, id, attributes, fileName, lineNumber);
            if (constructorArguments != null && constructorArguments.length != 0)
            {
                Class[] classes = findConstructorArgumentTypes(realMpClass, constructorArguments);
                if (classes != null)
                {
                    return e.create(classes, constructorArguments);
                }
                else
                {
                    throw new Error("The message processor " + id.getFullName() + " could not be created, because " +
                                    "there is no matching constructor");
                }
            }
            else
            {
                return e.create();
            }
        }
        catch (Throwable e)
        {
            logger.warn("The message processor " + id.getFullName() + " could not be mocked");
            try
            {
                return realMpClass.newInstance();
            }
            catch (Throwable e1)
            {
                throw new Error("The message processor " + id.getFullName() + " could not be created", e1);
            }
        }
    }

    private Class[] findConstructorArgumentTypes(Class realMpClass, Object[] constructorArguments)
    {
        Constructor[] declaredConstructors = realMpClass.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors)
        {
            Class[] parameterTypes = constructor.getParameterTypes();
            if (parameterTypes.length == constructorArguments.length)
            {
                boolean mapsCorrectly = true;
                for (int i = 0; i < parameterTypes.length; i++)
                {
                    mapsCorrectly &= parameterTypes[i].isAssignableFrom(constructorArguments[i].getClass());
                }
                if (mapsCorrectly)
                {
                    return parameterTypes;
                }
            }
        }
        return null;
    }

    protected Enhancer createEnhancer(Class realMpClass, MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber)
    {

        Enhancer e = new Enhancer();
        e.setSuperclass(realMpClass);
        e.setUseCache(false);
        e.setAttemptLoad(true);
        e.setInterceptDuringConstruction(true);
        e.setNamingPolicy(new MunitNamingPolicy());

        if (FactoryBean.class.isAssignableFrom(realMpClass))
        {
            createFactoryBeanCallback(id, attributes, fileName, lineNumber, e);
        }
        else
        {
            createMessageProcessorCallback(id, attributes, fileName, lineNumber, e);
        }
        return e;
    }


    private void createMessageProcessorCallback(MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber, Enhancer e)
    {
        MunitMessageProcessorInterceptor callback = new MunitMessageProcessorInterceptor();
        callback.setId(id);
        callback.setAttributes(attributes);
        callback.setFileName(fileName);
        callback.setLineNumber(lineNumber);
        e.setCallbacks(new Callback[] {callback, NULL_METHOD_INTERCEPTOR});
        e.setCallbackFilter(MESSAGE_PROCESSOR_FILTER);
    }

    private void createFactoryBeanCallback(MessageProcessorId id, Map<String, String> attributes, String fileName, String lineNumber, Enhancer e)
    {
        MessageProcessorFactoryBeanInterceptor callback = new MessageProcessorFactoryBeanInterceptor();
        callback.setId(id);
        callback.setAttributes(attributes);
        callback.setFileName(fileName);
        callback.setLineNumber(lineNumber);
        e.setCallbacks(new Callback[] {callback, NULL_METHOD_INTERCEPTOR});
        e.setCallbackFilter(FACTORY_BEAN_FILTER);
    }

    /**
     * <p>
     * Actual implementation of the interceptor creation
     * </p>
     *
     * @return <p>
     *         A {@link MunitMessageProcessorInterceptor} object
     *         </p>
     */
    @Override
    protected MethodInterceptor createInterceptor()
    {
        return new MunitMessageProcessorInterceptor();
    }


    @Override
    public Object create(Class realMpClass, Object... objects) {
        return super.create(realMpClass, objects);
    }

}
TOP

Related Classes of org.mule.munit.common.mp.MunitMessageProcessorInterceptorFactory

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.