/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * 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 com.google.common.base.Preconditions; import com.qcadoo.view.internal.xml.ViewDefinitionParser; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.w3c.dom.Node; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; 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; } } }