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

io.smallrye.faulttolerance.internal.SecurityActions Maven / Gradle / Ivy

There is a newer version: 6.4.0
Show newest version
/*
 * Copyright 2017 Red Hat, Inc, and individual contributors.
 *
 * Licensed 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 io.smallrye.faulttolerance.internal;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

final class SecurityActions {
    private SecurityActions() {
    }

    static void setAccessible(final AccessibleObject accessibleObject) {
        if (System.getSecurityManager() == null) {
            accessibleObject.setAccessible(true);
        }
        AccessController.doPrivileged((PrivilegedAction) () -> {
            accessibleObject.setAccessible(true);
            return accessibleObject;
        });
    }

    /**
     * Finds a fallback method for given guarded method. If the guarded method is present on given {@code beanClass}
     * and is actually declared by given {@code declaringClass} and has given {@code parameterTypes} and {@code returnType},
     * then a fallback method of given {@code name}, with parameter types and return type matching the parameter types
     * and return type of the guarded method, is searched for on the {@code beanClass} and its superclasses and
     * superinterfaces, according to the specification rules. Returns {@code null} if no matching fallback method exists.
     *
     * @param beanClass the class of the bean that has the guarded method
     * @param declaringClass the class that actually declares the guarded method (can be a supertype of bean class)
     * @param name name of the fallback method
     * @param parameterTypes parameter types of the guarded method
     * @param returnType return type of the guarded method
     * @return the fallback method or {@code null} if none exists
     */
    static Method findFallbackMethod(Class beanClass, Class declaringClass,
            String name, Type[] parameterTypes, Type returnType) throws PrivilegedActionException {

        Set result;
        if (System.getSecurityManager() == null) {
            result = doFindFallbackMethod(beanClass, declaringClass, name, parameterTypes, returnType, false);
        } else {
            result = AccessController.doPrivileged((PrivilegedExceptionAction>) () -> {
                return doFindFallbackMethod(beanClass, declaringClass, name, parameterTypes, returnType, false);
            });
        }

        return result.isEmpty() ? null : result.iterator().next();
    }

    /**
     * Finds a set of fallback methods with exception parameter for given guarded method. If the guarded method
     * is present on given {@code beanClass} and is actually declared by given {@code declaringClass} and has given
     * {@code parameterTypes} and {@code returnType}, then fallback methods of given {@code name}, with parameter types
     * and return type matching the parameter types and return type of the guarded method, and with one additional
     * parameter assignable to {@code Throwable} at the end of parameter list, is searched for on the {@code beanClass}
     * and its superclasses and superinterfaces, according to the specification rules. Returns {@code null} if no
     * matching fallback method exists.
     *
     * @param beanClass the class of the bean that has the guarded method
     * @param declaringClass the class that actually declares the guarded method (can be a supertype of bean class)
     * @param name name of the fallback method
     * @param parameterTypes parameter types of the guarded method
     * @param returnType return type of the guarded method
     * @return the fallback method or {@code null} if none exists
     */
    static Set findFallbackMethodsWithExceptionParammeter(Class beanClass, Class declaringClass,
            String name, Type[] parameterTypes, Type returnType) throws PrivilegedActionException {
        if (System.getSecurityManager() == null) {
            return doFindFallbackMethod(beanClass, declaringClass, name, parameterTypes, returnType, true);
        }
        return AccessController.doPrivileged((PrivilegedExceptionAction>) () -> {
            return doFindFallbackMethod(beanClass, declaringClass, name, parameterTypes, returnType, true);
        });
    }

    private static Set doFindFallbackMethod(Class beanClass, Class declaringClass, String name,
            Type[] expectedParameterTypes, Type expectedReturnType, boolean expectedExceptionParameter) {

        Set result = new HashSet<>();

        TypeMapping expectedMapping = TypeMapping.createFor(beanClass, declaringClass);
        TypeMapping actualMapping = new TypeMapping();

        // if we find a matching method on the bean class or one of its superclasses or superinterfaces,
        // then we have to check that the method is either identical to or an override of a method that:
        // - is declared on a class which is a supertype of the declaring class, or
        // - is declared on an interface which implemented by the declaring class
        //
        // this is to satisfy the specification, which says: fallback method must be on the same class, a superclass
        // or an implemented interface of the class which declares the annotated method
        //
        // we fake this by checking that the matching method has the same name as one of the method declared on
        // the declaring class or any of its superclasses or any of its implemented interfaces (this is actually
        // quite precise, the only false positive would occur in presence of overloads)
        Set possibleFallbackMethodNames = findPossibleFallbackMethodNames(declaringClass);

        Class clazz = beanClass;
        while (true) {
            Set methods = getMethodsFromClass(clazz, name, expectedParameterTypes, expectedReturnType,
                    expectedExceptionParameter, declaringClass, actualMapping, expectedMapping);
            for (Method method : methods) {
                if (possibleFallbackMethodNames.contains(method.getName())) {
                    result.add(method);
                    if (!expectedExceptionParameter) {
                        return result;
                    }
                }
            }

            if (clazz.getSuperclass() == null) {
                break;
            }

            actualMapping = actualMapping.getSuperclassMapping(clazz);
            clazz = clazz.getSuperclass();
        }

        for (Class iface : beanClass.getInterfaces()) {
            Set methods = getMethodsFromClass(iface, name, expectedParameterTypes, expectedReturnType,
                    expectedExceptionParameter, declaringClass, actualMapping, expectedMapping);
            for (Method method : methods) {
                if (possibleFallbackMethodNames.contains(method.getName())) {
                    result.add(method);
                    if (!expectedExceptionParameter) {
                        return result;
                    }
                }
            }
        }

        return result;
    }

    private static Set findPossibleFallbackMethodNames(Class declaringClass) {
        Set result = new HashSet<>();

        Class clazz = declaringClass;
        while (clazz != null) {
            for (Method m : clazz.getDeclaredMethods()) {
                result.add(m.getName());
            }
            clazz = clazz.getSuperclass();
        }

        for (Class iface : declaringClass.getInterfaces()) {
            for (Method m : iface.getMethods()) {
                result.add(m.getName());
            }
        }

        return result;
    }

    /**
     * Returns all methods that:
     * 
    *
  • are declared directly on given {@code classToSearch},
  • *
  • have given {@code name},
  • *
  • have matching {@code parameterTypes},
  • *
  • have matching {@code returnType},
  • *
  • have an additional {@code exceptionParameter} if required,
  • *
  • are accessible from given {@code guardedMethodDeclaringClass}.
  • *
*/ private static Set getMethodsFromClass(Class classToSearch, String name, Type[] parameterTypes, Type returnType, boolean exceptionParameter, Class guardedMethodDeclaringClass, TypeMapping actualMapping, TypeMapping expectedMapping) { Set set = new HashSet<>(); for (Method method : classToSearch.getDeclaredMethods()) { if (method.getName().equals(name) && isAccessibleFrom(method, guardedMethodDeclaringClass) && signaturesMatch(method, parameterTypes, returnType, exceptionParameter, actualMapping, expectedMapping)) { set.add(method); } } return set; } private static boolean isAccessibleFrom(Method method, Class guardedMethodDeclaringClass) { if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) { return true; } if (Modifier.isPrivate(method.getModifiers())) { return method.getDeclaringClass() == guardedMethodDeclaringClass; } // not public, not protected and not private => default // accessible only if in the same package return method.getDeclaringClass().getPackage() == guardedMethodDeclaringClass.getPackage(); } private static boolean signaturesMatch(Method method, Type[] expectedParameterTypes, Type expectedReturnType, boolean expectedExceptionParameter, TypeMapping actualMapping, TypeMapping expectedMapping) { int expectedParameters = expectedParameterTypes.length; if (expectedExceptionParameter) { // need to figure this out _before_ expanding the `expectedParameterTypes` array boolean kotlinSuspendingFunction = KotlinSupport.isSuspendingFunction(expectedParameterTypes); // adjust `expectedParameterTypes` so that there's one more element on the position // where the exception parameter should be, and the value on that position is `null` expectedParameterTypes = Arrays.copyOfRange(expectedParameterTypes, 0, expectedParameters + 1); if (kotlinSuspendingFunction) { expectedParameterTypes[expectedParameters] = expectedParameterTypes[expectedParameters - 1]; expectedParameterTypes[expectedParameters - 1] = null; } expectedParameters++; } Type[] methodParams = method.getGenericParameterTypes(); if (expectedParameters != methodParams.length) { return false; } for (int i = 0; i < expectedParameters; i++) { Type methodParam = methodParams[i]; Type expectedParamType = expectedParameterTypes[i]; if (expectedParamType != null) { if (!typeMatches(methodParam, expectedParamType, actualMapping, expectedMapping)) { return false; } } else { // exception parameter boolean isThrowable = methodParam instanceof Class && Throwable.class.isAssignableFrom((Class) methodParam); if (!isThrowable) { return false; } } } if (!typeMatches(method.getGenericReturnType(), expectedReturnType, actualMapping, expectedMapping)) { return false; } return true; } private static boolean typeMatches(Type actualType, Type expectedType, TypeMapping actualMapping, TypeMapping expectedMapping) { actualType = actualMapping.map(actualType); expectedType = expectedMapping.map(expectedType); if (actualType instanceof Class) { return expectedType == actualType; } else if (isArray(actualType) && isArray(expectedType)) { return typeMatches(getArrayComponentType(actualType), getArrayComponentType(expectedType), actualMapping, expectedMapping); } else if (actualType instanceof ParameterizedType && expectedType instanceof ParameterizedType) { return parameterizedTypeMatches((ParameterizedType) actualType, (ParameterizedType) expectedType, actualMapping, expectedMapping); } else if (actualType instanceof WildcardType && expectedType instanceof WildcardType) { return wildcardTypeMatches((WildcardType) actualType, (WildcardType) expectedType, actualMapping, expectedMapping); } else { return false; } } private static boolean wildcardTypeMatches(WildcardType actualType, WildcardType expectedType, TypeMapping actualMapping, TypeMapping expectedMapping) { boolean lowerBoundsMatch = typeArrayMatches(actualType.getLowerBounds(), expectedType.getLowerBounds(), actualMapping, expectedMapping); boolean upperBoundsMatch = typeArrayMatches(actualType.getUpperBounds(), expectedType.getUpperBounds(), actualMapping, expectedMapping); return lowerBoundsMatch && upperBoundsMatch; } private static boolean parameterizedTypeMatches(ParameterizedType actualType, ParameterizedType expectedType, TypeMapping actualMapping, TypeMapping expectedMapping) { boolean genericClassMatch = typeMatches(actualType.getRawType(), expectedType.getRawType(), actualMapping, expectedMapping); boolean typeArgumentsMatch = typeArrayMatches(actualType.getActualTypeArguments(), expectedType.getActualTypeArguments(), actualMapping, expectedMapping); return genericClassMatch && typeArgumentsMatch; } private static boolean typeArrayMatches(Type[] actualTypes, Type[] expectedTypes, TypeMapping actualMapping, TypeMapping expectedMapping) { if (actualTypes.length != expectedTypes.length) { return false; } for (int i = 0; i < actualTypes.length; i++) { if (!typeMatches(actualTypes[i], expectedTypes[i], actualMapping, expectedMapping)) { return false; } } return true; } /** * Accepts only array {@code Class}es and {@code GenericArrayType}s. * In other words, {@code isArray(type)} must be {@code true}. */ private static Type getArrayComponentType(Type type) { if (type instanceof Class) { return ((Class) type).getComponentType(); } else if (type instanceof GenericArrayType) { return ((GenericArrayType) type).getGenericComponentType(); } else { throw new IllegalArgumentException("Not an array: " + type); } } private static boolean isArray(Type parameterType) { if (parameterType instanceof Class) { return ((Class) parameterType).isArray(); } else { return parameterType instanceof GenericArrayType; } } private static class TypeMapping { private final Map map; private TypeMapping() { this.map = Collections.emptyMap(); } private TypeMapping(Map map) { this.map = map; } /** * Bean class can be a subclass of the class that declares the guarded method. * This method returns a mapping of the type parameters of the method's declaring class * to the type arguments provided on the bean class or any class between it and the declaring class. * * @param beanClass class of the bean which has the guarded method * @param declaringClass class that actually declares the guarded method * @return type mapping */ private static TypeMapping createFor(Class beanClass, Class declaringClass) { TypeMapping result = new TypeMapping(); if (beanClass == declaringClass) { return result; } Class current = beanClass; while (current != declaringClass && current != null) { if (current.getSuperclass() == null) { break; } result = result.getSuperclassMapping(current); current = current.getSuperclass(); } return result; } private Type map(Type type) { Type result = map.get(type); return result != null ? result : type; } private TypeMapping getSuperclassMapping(Class current) { return new TypeMapping(mappingForSuperclass(current, this.map)); } private static Map mappingForSuperclass(Class clazz, Map previousMapping) { Class superclass = clazz.getSuperclass(); TypeVariable[] typeParameters = superclass.getTypeParameters(); Type genericSuperclass = clazz.getGenericSuperclass(); Type[] typeArguments = (genericSuperclass instanceof ParameterizedType) ? ((ParameterizedType) genericSuperclass).getActualTypeArguments() : new Type[0]; Map result = new HashMap<>(); for (int i = 0; i < typeArguments.length; i++) { Type typeArgument = typeArguments[i]; if (typeArgument instanceof Class) { result.put(typeParameters[i], typeArgument); } else { Type type = previousMapping.get(typeArgument); result.put(typeParameters[i], type != null ? type : typeArgument); } } return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy