com.qcadoo.view.internal.CustomMethodHolder.java Source code

Java tutorial

Introduction

Here is the source code for com.qcadoo.view.internal.CustomMethodHolder.java

Source

/**
 * ***************************************************************************
 * Copyright (c) 2010 Qcadoo Limited
 * Project: Qcadoo Framework
 * Version: 1.2.0
 *
 * This file is part of Qcadoo.
 *
 * Qcadoo is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ***************************************************************************
 */
package com.qcadoo.view.internal;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import org.apache.commons.lang.StringUtils;
import org.springframework.context.ApplicationContext;
import org.w3c.dom.Node;

import com.google.common.base.Preconditions;
import com.qcadoo.view.internal.xml.ViewDefinitionParser;

public final class CustomMethodHolder {

    private static final String CLASS_ATTRIBUTE = "class";

    private static final String METHOD_ATTRIBUTE = "method";

    private static final String INVOKE_FAIL_MSG = "Failed to invoke custom method";

    private static final String BEAN_NOT_FOUND_MSG = "Failed to find bean for class '%s', "
            + "in application context. Please make sure that there is no typo, class have @Service or @Component "
            + "annotation and its package is registered in the Spring component-scan feature "
            + "(check plugin's src/main/resources/root-context.xml)";

    private static final String CLASS_NOT_FOUND_MSG = "Failed to find class '%s', please make sure that there is no typo";

    private static final String WRONG_VISIBILITY_MSG = "Given method '%s.%s' has invalid visibility, must be public";

    private static final String WRONG_RETURN_TYPE_MSG = "Given method '%s.%s' has invalid return type, must be %s";

    private static final String NO_SUCH_METHOD_MSG = "Failed to find method '%s.%s', "
            + "please make sure that there is no typo, method returns %s and parameters' types are valid (%s)";

    private static final String SECURITY_EXCEPTION_MSG = "Failed to access method '%s.%s'";

    private final Object bean;

    private final Method method;

    private final Class<?> expectedReturnType;

    private final Class<?>[] expectedParameterTypes;

    /**
     * @param holderNode
     *            custom method holder node
     * @param parser
     *            view definition parser
     * @param applicationContext
     *            spring container's application context
     * @param expectedReturnType
     *            return type
     * @param expectedParameterTypes
     *            parameter types
     */
    public CustomMethodHolder(final Node holderNode, final ViewDefinitionParser parser,
            final ApplicationContext applicationContext, final Class<?> expectedReturnType,
            final Class<?>[] expectedParameterTypes) {
        this(parser.getStringAttribute(holderNode, CLASS_ATTRIBUTE),
                parser.getStringAttribute(holderNode, METHOD_ATTRIBUTE), applicationContext, expectedReturnType,
                expectedParameterTypes);
    }

    /**
     * @param className
     *            binary name (http://docs.oracle.com/javase/6/docs/api/java/lang/ClassLoader.html#name) of class containing
     *            resolver method
     * @param methodName
     *            method name
     * @param applicationContext
     * @param expectedReturnType
     *            return type
     * @param expectedParameterTypes
     *            parameter types
     */
    private CustomMethodHolder(final String className, final String methodName,
            final ApplicationContext applicationContext, final Class<?> expectedReturnType,
            final Class<?>[] expectedParameterTypes) {
        Preconditions.checkArgument(!StringUtils.isBlank(className), "class name attribute is not specified!");
        Preconditions.checkArgument(!StringUtils.isBlank(methodName), "method name attribute is not specified!");
        Preconditions.checkArgument(expectedReturnType != null, "expected return type is not specified!");
        Preconditions.checkArgument(expectedParameterTypes != null, "expected parameter types are not specified!");

        this.expectedReturnType = expectedReturnType;
        this.expectedParameterTypes = Arrays.copyOf(expectedParameterTypes, expectedParameterTypes.length);

        final Class<?> clazz = getCustomMethodClass(className);

        bean = getCustomMethodBean(clazz, applicationContext);
        method = getMethod(clazz, methodName);

        checkMethodSignature();
    }

    public Object invoke(final Object... args) {
        try {
            return method.invoke(bean, args);
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException(INVOKE_FAIL_MSG, e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(INVOKE_FAIL_MSG, e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException(INVOKE_FAIL_MSG, e);
        }
    }

    private Class<?> getCustomMethodClass(final String className) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            final String msg = String.format(CLASS_NOT_FOUND_MSG, className);
            throw new IllegalStateException(msg, e);
        }
    }

    private Object getCustomMethodBean(final Class<?> clazz, final ApplicationContext applicationContext) {
        Object hookBean = applicationContext.getBean(clazz);
        if (hookBean == null) {
            final String msg = String.format(BEAN_NOT_FOUND_MSG, clazz.getCanonicalName());
            throw new IllegalStateException(msg);
        }
        return hookBean;
    }

    private void checkMethodSignature() {
        if (!Modifier.isPublic(method.getModifiers())) {
            final String msg = String.format(WRONG_VISIBILITY_MSG, method.getDeclaringClass().getCanonicalName(),
                    method.getName());
            throw new IllegalStateException(msg);
        }
        if (!expectedReturnType.equals(method.getReturnType())) {
            final String msg = String.format(WRONG_RETURN_TYPE_MSG, method.getDeclaringClass().getCanonicalName(),
                    method.getName(), expectedReturnType);
            throw new IllegalStateException(msg);
        }
    }

    private Method getMethod(final Class<?> clazz, final String methodName) {
        try {
            return clazz.getMethod(methodName, expectedParameterTypes);
        } catch (SecurityException e) {
            final String msg = String.format(SECURITY_EXCEPTION_MSG, clazz.getCanonicalName(), methodName);
            throw new IllegalStateException(msg, e);
        } catch (NoSuchMethodException e) {
            final String msg = String.format(NO_SUCH_METHOD_MSG, clazz.getCanonicalName(), methodName,
                    expectedReturnType, Arrays.toString(expectedParameterTypes));
            throw new IllegalStateException(msg, e);
        }
    }

    public static boolean methodExists(final String className, final String methodName,
            final ApplicationContext applicationContext, final Class<?>[] expectedParameterTypes) {
        Preconditions.checkArgument(!StringUtils.isBlank(className), "class name attribute is not specified!");
        Preconditions.checkArgument(!StringUtils.isBlank(methodName), "method name attribute is not specified!");
        Preconditions.checkArgument(expectedParameterTypes != null, "expected parameter types are not specified!");

        try {
            final Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);

            for (Method method : clazz.getMethods()) {
                if (method.getName().equals(methodName)
                        && Arrays.deepEquals(method.getParameterTypes(), expectedParameterTypes)) {
                    return true;
                }
            }
            return false;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}