| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.openejb.assembler.classic; |
| |
| import org.apache.openejb.BeanContext; |
| import org.apache.openejb.util.Classes; |
| import org.apache.openejb.util.Join; |
| import org.apache.openejb.util.SetAccessible; |
| |
| import javax.ejb.EJBHome; |
| import javax.ejb.EJBLocalHome; |
| import javax.ejb.EJBLocalObject; |
| import javax.ejb.EJBObject; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import static java.util.Arrays.asList; |
| |
| /** |
| * @version $Rev$ $Date$ |
| */ |
| public class MethodInfoUtil { |
| |
| |
| /** |
| * Finds the nearest java.lang.reflect.Method with the given NamedMethodInfo |
| * Callbacks can be private so class.getMethod() cannot be used. Searching |
| * starts by looking in the specified class, if the method is not found searching continues with |
| * the immediate parent and continues recurssively until the method is found or java.lang.Object |
| * is reached. If the method is not found a IllegalStateException is thrown. |
| * |
| * @param clazz |
| * @param info |
| * @return Method |
| * @throws IllegalStateException if the method is not found in this class or any of its parent classes |
| */ |
| public static Method toMethod(Class clazz, final NamedMethodInfo info) { |
| final List<Class> parameterTypes = new ArrayList<>(); |
| |
| if (info.methodParams != null) { |
| for (final String paramType : info.methodParams) { |
| try { |
| parameterTypes.add(Classes.forName(paramType, clazz.getClassLoader())); |
| } catch (final ClassNotFoundException cnfe) { |
| throw new IllegalStateException("Parameter class could not be loaded for type " + paramType, cnfe); |
| } |
| } |
| } |
| |
| final Class[] parameters = parameterTypes.toArray(new Class[parameterTypes.size()]); |
| |
| IllegalStateException noSuchMethod = null; |
| while (clazz != null) { |
| try { |
| final Method method = clazz.getDeclaredMethod(info.methodName, parameters); |
| return SetAccessible.on(method); |
| } catch (final NoSuchMethodException e) { |
| if (noSuchMethod == null) { |
| noSuchMethod = new IllegalStateException("Callback method does not exist: " + clazz.getName() + "." + info.methodName, e); |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| } |
| |
| throw noSuchMethod; |
| } |
| |
| public static List<Method> matchingMethods(final Method signature, final Class clazz) { |
| final List<Method> list = new ArrayList<>(); |
| METHOD: |
| for (final Method method : clazz.getMethods()) { |
| if (!method.getName().equals(signature.getName())) { |
| continue; |
| } |
| |
| final Class<?>[] methodTypes = method.getParameterTypes(); |
| final Class<?>[] signatureTypes = signature.getParameterTypes(); |
| |
| if (methodTypes.length != signatureTypes.length) { |
| continue; |
| } |
| |
| for (int i = 0; i < methodTypes.length; i++) { |
| if (!methodTypes[i].equals(signatureTypes[i])) { |
| continue METHOD; |
| } |
| } |
| list.add(method); |
| } |
| return list; |
| } |
| |
| public static List<Method> matchingMethods(final MethodInfo mi, final Class clazz) { |
| final Method[] methods = clazz.getMethods(); |
| |
| return matchingMethods(mi, methods); |
| } |
| |
| public static List<Method> matchingMethods(final MethodInfo mi, final Method[] methods) { |
| |
| List<Method> filtered = filterByLevel(mi, methods); |
| |
| filtered = filterByView(mi, filtered); |
| |
| return filtered; |
| } |
| |
| private static List<Method> filterByView(final MethodInfo mi, final List<Method> filtered) { |
| final View view = view(mi); |
| switch (view) { |
| case CLASS: { |
| return filterByClass(mi, filtered); |
| } |
| } |
| |
| return filtered; |
| } |
| |
| private static List<Method> filterByClass(final MethodInfo mi, final List<Method> methods) { |
| final ArrayList<Method> list = new ArrayList<>(); |
| for (final Method method : methods) { |
| final String className = method.getDeclaringClass().getName(); |
| if (mi.className.equals(className)) { |
| list.add(method); |
| } |
| } |
| return list; |
| } |
| |
| private static List<Method> filterByLevel(final MethodInfo mi, final Method[] methods) { |
| final Level level = level(mi); |
| |
| switch (level) { |
| case BEAN: |
| case PACKAGE: { |
| return asList(methods); |
| } |
| case OVERLOADED_METHOD: { |
| return filterByName(methods, mi.methodName); |
| } |
| case EXACT_METHOD: { |
| return filterByNameAndParams(methods, mi); |
| } |
| } |
| |
| return Collections.EMPTY_LIST; |
| } |
| |
| public static Method getMethod(final Class clazz, final MethodInfo info) { |
| final ClassLoader cl = clazz.getClassLoader(); |
| |
| final List<Class> params = new ArrayList<>(); |
| for (final String methodParam : info.methodParams) { |
| try { |
| params.add(getClassForParam(methodParam, cl)); |
| } catch (final ClassNotFoundException cnfe) { |
| // no-op |
| } |
| } |
| Method method = null; |
| try { |
| method = clazz.getMethod(info.methodName, params.toArray(new Class[params.size()])); |
| } catch (final NoSuchMethodException e) { |
| return null; |
| } |
| |
| if (!info.className.equals("*") && !method.getDeclaringClass().getName().equals(info.className)) { |
| return null; |
| } |
| |
| return method; |
| } |
| |
| private static List<Method> filterByName(final Method[] methods, final String methodName) { |
| final List<Method> list = new ArrayList<>(); |
| for (final Method method : methods) { |
| if (method.getName().equals(methodName)) { |
| list.add(method); |
| } |
| } |
| return list; |
| } |
| |
| private static List<Method> filterByNameAndParams(final Method[] methods, final MethodInfo mi) { |
| final List<Method> list = new ArrayList<>(); |
| for (final Method method : methods) { |
| if (matches(method, mi)) { |
| list.add(method); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * This method splits the MethodPermissionInfo objects so that there is |
| * exactly one MethodInfo per MethodPermissionInfo. A single MethodPermissionInfo |
| * with three MethodInfos would be expanded into three MethodPermissionInfo with |
| * one MethodInfo each. |
| * |
| * The MethodPermissionInfo list is then sorted from least to most specific. |
| * |
| * @param infos |
| * @return a normalized list of new MethodPermissionInfo objects |
| */ |
| public static List<MethodPermissionInfo> normalizeMethodPermissionInfos(final List<MethodPermissionInfo> infos) { |
| final List<MethodPermissionInfo> normalized = new ArrayList<>(); |
| for (final MethodPermissionInfo oldInfo : infos) { |
| for (final MethodInfo methodInfo : oldInfo.methods) { |
| final MethodPermissionInfo newInfo = new MethodPermissionInfo(); |
| newInfo.description = oldInfo.description; |
| newInfo.methods.add(methodInfo); |
| newInfo.roleNames.addAll(oldInfo.roleNames); |
| newInfo.unchecked = oldInfo.unchecked; |
| newInfo.excluded = oldInfo.excluded; |
| |
| normalized.add(newInfo); |
| } |
| } |
| |
| normalized.sort(new MethodPermissionComparator()); |
| |
| return normalized; |
| } |
| |
| private static Class getClassForParam(final String className, final ClassLoader cl) throws ClassNotFoundException { |
| |
| switch (className) { |
| case "int": |
| return Integer.TYPE; |
| case "double": |
| return Double.TYPE; |
| case "long": |
| return Long.TYPE; |
| case "boolean": |
| return Boolean.TYPE; |
| case "float": |
| return Float.TYPE; |
| case "char": |
| return Character.TYPE; |
| case "short": |
| return Short.TYPE; |
| case "byte": |
| return Byte.TYPE; |
| default: |
| return Class.forName(className, false, cl); |
| } |
| |
| } |
| |
| public static Map<Method, MethodAttributeInfo> resolveAttributes(final List<? extends MethodAttributeInfo> infos, final BeanContext beanContext) { |
| final Map<Method, MethodAttributeInfo> attributes = new LinkedHashMap<>(); |
| |
| final Method[] wildCardView = getWildCardView(beanContext).toArray(new Method[]{}); |
| |
| for (final MethodAttributeInfo attributeInfo : infos) { |
| for (final MethodInfo methodInfo : attributeInfo.methods) { |
| |
| if (methodInfo.ejbName == null || methodInfo.ejbName.equals("*") || methodInfo.ejbName.equals(beanContext.getEjbName())) { |
| |
| final List<Method> methods = new ArrayList<>(); |
| |
| if (methodInfo.methodIntf == null) { |
| methods.addAll(matchingMethods(methodInfo, wildCardView)); |
| } else if (methodInfo.methodIntf.equals("Home")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getHomeInterface())); |
| } else if (methodInfo.methodIntf.equals("Remote")) { |
| if (beanContext.getRemoteInterface() != null) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getRemoteInterface())); |
| } |
| for (final Class intf : beanContext.getBusinessRemoteInterfaces()) { |
| methods.addAll(matchingMethods(methodInfo, intf)); |
| } |
| } else if (methodInfo.methodIntf.equals("LocalHome")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getLocalHomeInterface())); |
| } else if (methodInfo.methodIntf.equals("Local")) { |
| if (beanContext.getLocalInterface() != null) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getLocalInterface())); |
| } |
| for (final Class intf : beanContext.getBusinessRemoteInterfaces()) { |
| methods.addAll(matchingMethods(methodInfo, intf)); |
| } |
| } else if (methodInfo.methodIntf.equals("ServiceEndpoint")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getServiceEndpointInterface())); |
| } |
| |
| for (final Method method : methods) { |
| if (containerMethod(method)) { |
| continue; |
| } |
| |
| attributes.put(method, attributeInfo); |
| } |
| } |
| } |
| } |
| return attributes; |
| } |
| |
| public static Map<ViewMethod, MethodAttributeInfo> resolveViewAttributes(final List<? extends MethodAttributeInfo> infos, final BeanContext beanContext) { |
| final Map<ViewMethod, MethodAttributeInfo> attributes = new LinkedHashMap<>(); |
| |
| final Method[] wildCardView = getWildCardView(beanContext).toArray(new Method[]{}); |
| |
| for (final MethodAttributeInfo attributeInfo : infos) { |
| for (final MethodInfo methodInfo : attributeInfo.methods) { |
| |
| if (methodInfo.ejbName == null || methodInfo.ejbName.equals("*") || methodInfo.ejbName.equals(beanContext.getEjbName())) { |
| |
| final List<Method> methods = new ArrayList<>(); |
| |
| if (methodInfo.methodIntf == null) { |
| methods.addAll(matchingMethods(methodInfo, wildCardView)); |
| } else if (methodInfo.methodIntf.equals("Home")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getHomeInterface())); |
| } else if (methodInfo.methodIntf.equals("Remote")) { |
| if (beanContext.getRemoteInterface() != null) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getRemoteInterface())); |
| } |
| for (final Class intf : beanContext.getBusinessRemoteInterfaces()) { |
| methods.addAll(matchingMethods(methodInfo, intf)); |
| } |
| } else if (methodInfo.methodIntf.equals("LocalHome")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getLocalHomeInterface())); |
| } else if (methodInfo.methodIntf.equals("Local")) { |
| if (beanContext.getLocalInterface() != null) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getLocalInterface())); |
| } |
| for (final Class intf : beanContext.getBusinessRemoteInterfaces()) { |
| methods.addAll(matchingMethods(methodInfo, intf)); |
| } |
| } else if (methodInfo.methodIntf.equals("ServiceEndpoint")) { |
| methods.addAll(matchingMethods(methodInfo, beanContext.getServiceEndpointInterface())); |
| } |
| |
| for (final Method method : methods) { |
| if (containerMethod(method)) { |
| continue; |
| } |
| |
| final ViewMethod viewMethod = new ViewMethod(methodInfo.methodIntf, method); |
| attributes.put(viewMethod, attributeInfo); |
| // List<MethodAttributeInfo> methodAttributeInfos = attributes.get(method); |
| // if (methodAttributeInfos == null) { |
| // methodAttributeInfos = new ArrayList<MethodAttributeInfo>(); |
| // attributes.put(method, methodAttributeInfos); |
| // } |
| // methodAttributeInfos.add(attributeInfo); |
| } |
| } |
| } |
| } |
| return attributes; |
| } |
| |
| public static class ViewMethod { |
| private final String view; |
| private final Method method; |
| |
| public ViewMethod(final String view, final Method method) { |
| this.view = view; |
| this.method = method; |
| } |
| |
| public String getView() { |
| return view; |
| } |
| |
| public Method getMethod() { |
| return method; |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| final ViewMethod that = (ViewMethod) o; |
| |
| if (!method.equals(that.method)) { |
| return false; |
| } |
| if (!Objects.equals(view, that.view)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = view != null ? view.hashCode() : 0; |
| result = 31 * result + method.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s : %s(%s)", view, method.getName(), Join.join(", ", Classes.getSimpleNames(method.getParameterTypes()))); |
| } |
| } |
| |
| private static boolean containerMethod(final Method method) { |
| return (method.getDeclaringClass() == EJBObject.class || |
| method.getDeclaringClass() == EJBHome.class || |
| method.getDeclaringClass() == EJBLocalObject.class || |
| method.getDeclaringClass() == EJBLocalHome.class) && |
| !method.getName().equals("remove"); |
| } |
| |
| private static List<Method> getWildCardView(final BeanContext info) { |
| |
| final List<Method> beanMethods = asList(info.getBeanClass().getMethods()); |
| final List<Method> methods = new ArrayList<>(beanMethods); |
| |
| if (info.getRemoteInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getRemoteInterface().getMethods())); |
| } |
| |
| if (info.getHomeInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getHomeInterface().getMethods())); |
| } |
| |
| if (info.getLocalInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getLocalInterface().getMethods())); |
| } |
| |
| if (info.getLocalHomeInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getLocalHomeInterface().getMethods())); |
| } |
| if (info.getMdbInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getMdbInterface().getMethods())); |
| } |
| |
| if (info.getServiceEndpointInterface() != null) { |
| methods.addAll(exclude(beanMethods, info.getServiceEndpointInterface().getMethods())); |
| } |
| |
| for (final Class intf : info.getBusinessRemoteInterfaces()) { |
| methods.addAll(exclude(beanMethods, intf.getMethods())); |
| } |
| |
| for (final Class intf : info.getBusinessLocalInterfaces()) { |
| methods.addAll(exclude(beanMethods, intf.getMethods())); |
| } |
| |
| // Remove methods that cannot be controlled by the user |
| methods.removeIf(MethodInfoUtil::containerMethod); |
| |
| return methods; |
| } |
| |
| private static List<Method> exclude(final List<Method> excludes, final Method[] methods) { |
| final ArrayList<Method> list = new ArrayList<>(); |
| |
| for (final Method method : methods) { |
| if (!matches(excludes, method)) { |
| list.add(method); |
| } |
| } |
| return list; |
| } |
| |
| private static boolean matches(final List<Method> excludes, final Method method) { |
| for (final Method excluded : excludes) { |
| final boolean match = match(method, excluded); |
| if (match) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean match(final Method methodA, final Method methodB) { |
| if (!methodA.getName().equals(methodB.getName())) { |
| return false; |
| } |
| |
| if (methodA.getParameterTypes().length != methodB.getParameterTypes().length) { |
| return false; |
| } |
| |
| for (int i = 0; i < methodA.getParameterTypes().length; i++) { |
| final Class<?> a = methodA.getParameterTypes()[i]; |
| final Class<?> b = methodB.getParameterTypes()[i]; |
| if (!a.equals(b)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static boolean matches(final Method method, final MethodInfo methodInfo) { |
| return matches(method, methodInfo.methodName, methodInfo.methodParams); |
| } |
| |
| public static boolean matches(final Method method, final NamedMethodInfo methodInfo) { |
| return matches(method, methodInfo.methodName, methodInfo.methodParams); |
| } |
| |
| public static boolean matches(final Method method, final String methodName, final List<String> methodParams) { |
| |
| if (!methodName.equals(method.getName())) { |
| return false; |
| } |
| |
| // do we have parameters? |
| if (methodParams == null) { |
| return true; |
| } |
| |
| // do we have the same number of parameters? |
| if (methodParams.size() != method.getParameterTypes().length) { |
| return false; |
| } |
| |
| // match parameters names |
| final Class<?>[] parameterTypes = method.getParameterTypes(); |
| for (int i = 0; i < parameterTypes.length; i++) { |
| final Class<?> parameterType = parameterTypes[i]; |
| final String methodParam = methodParams.get(i); |
| if (!methodParam.equals(getName(parameterType).replace('$', '.')) && !methodParam.equals(parameterType.getName())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private static String getName(final Class<?> type) { |
| if (type.isArray()) { |
| return getName(type.getComponentType()) + "[]"; // depend on JVM? type.getName() seems to work on Oracle one |
| } else { |
| return type.getName(); |
| } |
| } |
| |
| |
| public static enum Level { |
| PACKAGE, BEAN, OVERLOADED_METHOD, EXACT_METHOD |
| } |
| |
| public static enum View { |
| CLASS, ANY, INTERFACE; |
| } |
| |
| public static View view(final MethodInfo methodInfo) { |
| if (methodInfo.className != null && !methodInfo.className.equals("*")) { |
| return View.CLASS; |
| } |
| if (methodInfo.methodIntf != null && !methodInfo.methodIntf.equals("*")) { |
| return View.INTERFACE; |
| } else { |
| return View.ANY; |
| } |
| } |
| |
| public static Level level(final MethodInfo methodInfo) { |
| if (methodInfo.ejbName != null && methodInfo.ejbName.equals("*")) { |
| return Level.PACKAGE; |
| } |
| if (methodInfo.methodName.equals("*")) { |
| return Level.BEAN; |
| } |
| if (methodInfo.methodParams == null) { |
| return Level.OVERLOADED_METHOD; |
| } |
| return Level.EXACT_METHOD; |
| } |
| |
| |
| public static class MethodPermissionComparator extends BaseComparator<MethodPermissionInfo> { |
| public int compare(final MethodPermissionInfo a, final MethodPermissionInfo b) { |
| return compare(a.methods.get(0), b.methods.get(0)); |
| } |
| } |
| |
| public abstract static class BaseComparator<T> implements Comparator<T> { |
| public int compare(final MethodInfo am, final MethodInfo bm) { |
| final Level levelA = level(am); |
| final Level levelB = level(bm); |
| |
| // Primary sort |
| if (levelA != levelB) { |
| return levelA.ordinal() - levelB.ordinal(); |
| } |
| |
| // Secondary sort |
| return view(am).ordinal() - view(bm).ordinal(); |
| } |
| } |
| |
| |
| public static String toString(final MethodInfo i) { |
| String s = i.ejbName; |
| s += " : "; |
| s += i.methodIntf == null ? "*" : i.methodIntf; |
| s += " : "; |
| s += i.className; |
| s += " : "; |
| s += i.methodName; |
| s += "("; |
| if (i.methodParams != null) { |
| s += Join.join(", ", i.methodParams); |
| } else { |
| s += "*"; |
| } |
| s += ")"; |
| return s; |
| } |
| |
| public static String toString(final MethodPermissionInfo i) { |
| String s = toString(i.methods.get(0)); |
| if (i.unchecked) { |
| s += " Unchecked"; |
| } else if (i.excluded) { |
| s += " Excluded"; |
| } else { |
| s += " " + Join.join(", ", i.roleNames); |
| } |
| return s; |
| } |
| |
| public static String toString(final MethodTransactionInfo i) { |
| String s = toString(i.methods.get(0)); |
| s += " " + i.transAttribute; |
| return s; |
| } |
| |
| public static String toString(final MethodConcurrencyInfo i) { |
| String s = toString(i.methods.get(0)); |
| s += " " + i.concurrencyAttribute; |
| return s; |
| } |
| |
| } |