/* * JBoss, Home of Professional Open Source * Copyright 2012, Red Hat, Inc., and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of 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 org.jboss.weld.util; import java.lang.reflect.Method; import java.security.AccessController; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.event.Observes; import javax.enterprise.event.ObservesAsync; import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.AnnotatedMethod; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.inject.Inject; import org.jboss.weld.annotated.enhanced.EnhancedAnnotatedMethod; import org.jboss.weld.annotated.enhanced.EnhancedAnnotatedType; import org.jboss.weld.injection.InjectionPointFactory; import org.jboss.weld.injection.MethodInjectionPoint; import org.jboss.weld.injection.MethodInjectionPoint.MethodInjectionPointType; import org.jboss.weld.interceptor.reader.InterceptorMetadataUtils; import org.jboss.weld.interceptor.spi.model.InterceptionType; import org.jboss.weld.interceptor.util.InterceptionTypeRegistry; import org.jboss.weld.logging.BeanLogger; import org.jboss.weld.logging.EventLogger; import org.jboss.weld.logging.UtilLogger; import org.jboss.weld.manager.BeanManagerImpl; import org.jboss.weld.security.SetAccessibleAction; import org.jboss.weld.util.collections.ImmutableList; import org.jboss.weld.util.collections.ImmutableSet; import org.jboss.weld.util.reflection.Formats; public class BeanMethods { private BeanMethods() { } /** * We need to employ different strategies when discovering a list of specific methods of a {@link Bean} (e.g. initializer methods, producer methods, * lifecycle event callback listeners, etc.) An implementation of this interface knows how to establish a list of certain methods. * * The user of this implementation starts interaction by calling {@link #getAllMethods(EnhancedAnnotatedType)} which obtains a collection of all methods of * a given kind. Afterwards, iterates over the class hierarchy of {@link AnnotatedType#getJavaClass()} from the most specific class to {@link Object}. For * each class in the hierarchy it calls {@link #levelStart(Class)}. Then it calls {@link #processMethod(EnhancedAnnotatedMethod)} for each method out of the * collection of all methods of the given kind (see above) which is declared by the current class (current level). Once all the methods declared by the * current class are processed, {@link #levelFinish()} is called and the iteration may continue with a superclass of the current class (provided there is * any). * * Finally, {@link #create()} is called to obtain the result. * * @author Jozef Hartinger * * @param <T> the class declaring the annotated type * @param <R> type of result (e.g. a list of AnnotatedMethods, a list of sets of AnnotatedMethods, etc.) */ private interface MethodListBuilder<T, R> { /** * Returns all methods of a given kind (e.g. all observer methods) of a given {@link EnhancedAnnotatedType}. This includes methods defined on classes * upper in the class hierarchy. Overridden methods are not returned. */ Collection<EnhancedAnnotatedMethod<?, ? super T>> getAllMethods(EnhancedAnnotatedType<T> type); /** * This method is called before methods declared by a specific class in the class hierarchy are processed. Only classes declared by this specific class * are processed until {@link #levelFinish()} is called. */ void levelStart(Class<? super T> clazz); /** * Allows an implementation to process a method. By default the method would be added to the list of methods being built. */ void processMethod(EnhancedAnnotatedMethod<?, ? super T> method); /** * Indicates that processing of methods declared by a given class has ended. There are no more methods declared by the given class to be processed. */ void levelFinish(); /** * Obtains the result. This method may not be idempotent and it is therefore not safe to call it multiple times. * * @return the list of methods of a given kind */ R create(); } /** * Get all methods of a given kind using a given {@link MethodListBuilder}. */ private static <T, R> R getMethods(EnhancedAnnotatedType<T> type, MethodListBuilder<T, R> builder) { Collection<EnhancedAnnotatedMethod<?, ? super T>> methods = filterMethods(builder.getAllMethods(type)); for (Class<? super T> clazz = type.getJavaClass(); clazz != null && clazz != Object.class; clazz = clazz.getSuperclass()) { builder.levelStart(clazz); for (EnhancedAnnotatedMethod<?, ? super T> method : methods) { if (method.getJavaMember().getDeclaringClass().equals(clazz)) { builder.processMethod(method); } } builder.levelFinish(); } return builder.create(); } /** * For lifecycle event callback we need an ordered list. Lifecycle callback methods defined on the most specific class go first in the list. A given class * in the class hierarchy may define at most one method. * * @author Jozef Hartinger */ private abstract static class AbstractLifecycleEventCallbackMethodListBuilder<T> implements MethodListBuilder<T, List<AnnotatedMethod<? super T>>> { protected List<AnnotatedMethod<? super T>> result = new ArrayList<AnnotatedMethod<? super T>>(); protected EnhancedAnnotatedMethod<?, ? super T> foundMethod = null; @Override public void levelStart(Class<? super T> clazz) { foundMethod = null; } @Override public void processMethod(EnhancedAnnotatedMethod<?, ? super T> method) { if (methodHasNoParameters(method)) { if (foundMethod != null) { duplicateMethod(method); } foundMethod = method; } } private boolean methodHasNoParameters(EnhancedAnnotatedMethod<?, ? super T> method) { return method.getParameterTypesAsArray().length == 0; } @Override public void levelFinish() { if (foundMethod != null) { result.add(processLevelResult(foundMethod).slim()); } } @Override public List<AnnotatedMethod<? super T>> create() { Collections.reverse(result); return ImmutableList.copyOf(result); } /** * Called when a given hierarchy level defines multiple lifecycle event callback of a given type. */ protected abstract void duplicateMethod(EnhancedAnnotatedMethod<?, ? super T> method); /** * Called when a unique method is found for a given hierarchy level. */ protected abstract EnhancedAnnotatedMethod<?, ? super T> processLevelResult(EnhancedAnnotatedMethod<?, ? super T> method); } /** * For initializers we need to return a {@link List} of {@link Set}s of initializer methods (because a class may declare multiple initializers). The list is * ordered the same way as for lifecycle event callbacks. * * @author Jozef Hartinger */ private static class InitializerMethodListBuilder<T> implements MethodListBuilder<T, List<Set<MethodInjectionPoint<?, ?>>>> { private final List<Set<MethodInjectionPoint<?, ?>>> result = new ArrayList<Set<MethodInjectionPoint<?, ?>>>(); private ImmutableSet.Builder<MethodInjectionPoint<?, ?>> currentLevel = null; private final EnhancedAnnotatedType<T> type; private final BeanManagerImpl manager; private final Bean<?> declaringBean; public InitializerMethodListBuilder(EnhancedAnnotatedType<T> type, Bean<?> declaringBean, BeanManagerImpl manager) { this.type = type; this.manager = manager; this.declaringBean = declaringBean; } @Override public Collection<EnhancedAnnotatedMethod<?, ? super T>> getAllMethods(EnhancedAnnotatedType<T> type) { return type.getEnhancedMethods(Inject.class); } @Override public void levelStart(Class<? super T> clazz) { currentLevel = ImmutableSet.builder(); } @Override public void processMethod(EnhancedAnnotatedMethod<?, ? super T> method) { if (method.isAnnotationPresent(Inject.class)) { if (method.getAnnotation(Produces.class) != null) { throw UtilLogger.LOG.initializerCannotBeProducer(method, Formats.formatAsStackTraceElement(method.getJavaMember())); } else if (method.getEnhancedParameters(Disposes.class).size() > 0) { throw UtilLogger.LOG.initializerCannotBeDisposalMethod(method, Formats.formatAsStackTraceElement(method.getJavaMember())); } else if (method.getEnhancedParameters(Observes.class).size() > 0) { throw EventLogger.LOG.invalidInitializer(method, Formats.formatAsStackTraceElement(method.getJavaMember())); } else if (method.isGeneric()) { throw UtilLogger.LOG.initializerMethodIsGeneric(method, Formats.formatAsStackTraceElement(method.getJavaMember())); } if (!method.isStatic()) { currentLevel.add(InjectionPointFactory.instance().createMethodInjectionPoint(MethodInjectionPointType.INITIALIZER, method, declaringBean, type.getJavaClass(), null, manager)); } } } @Override public void levelFinish() { result.add(currentLevel.build()); } @Override public List<Set<MethodInjectionPoint<?, ?>>> create() { Collections.reverse(result); // because we want methods that are lower in the hierarchy to be called first return ImmutableList.copyOf(result); } } public static <T> List<AnnotatedMethod<? super T>> getPostConstructMethods(final EnhancedAnnotatedType<T> type) { return getMethods(type, new AbstractLifecycleEventCallbackMethodListBuilder<T>() { @Override public Collection<EnhancedAnnotatedMethod<?, ? super T>> getAllMethods(EnhancedAnnotatedType<T> type) { return type.getEnhancedMethods(PostConstruct.class); } @Override protected void duplicateMethod(EnhancedAnnotatedMethod<?, ? super T> method) { throw UtilLogger.LOG.tooManyPostConstructMethods(type); } @Override protected EnhancedAnnotatedMethod<?, ? super T> processLevelResult(EnhancedAnnotatedMethod<?, ? super T> method) { BeanLogger.LOG.foundOnePostConstructMethod(method, type); return method; } }); } public static <T> List<AnnotatedMethod<? super T>> getPreDestroyMethods(final EnhancedAnnotatedType<T> type) { return getMethods(type, new AbstractLifecycleEventCallbackMethodListBuilder<T>() { @Override public Collection<EnhancedAnnotatedMethod<?, ? super T>> getAllMethods(EnhancedAnnotatedType<T> type) { return type.getEnhancedMethods(PreDestroy.class); } @Override protected void duplicateMethod(EnhancedAnnotatedMethod<?, ? super T> method) { throw UtilLogger.LOG.tooManyPreDestroyMethods(type); } @Override protected EnhancedAnnotatedMethod<?, ? super T> processLevelResult(EnhancedAnnotatedMethod<?, ? super T> method) { BeanLogger.LOG.foundOnePreDestroyMethod(method, type); return method; } }); } public static <T> List<Set<MethodInjectionPoint<?, ?>>> getInitializerMethods(Bean<?> declaringBean, EnhancedAnnotatedType<T> type, BeanManagerImpl manager) { return getMethods(type, new InitializerMethodListBuilder<T>(type, declaringBean, manager)); } public static <T> Collection<EnhancedAnnotatedMethod<?, ? super T>> getObserverMethods(final EnhancedAnnotatedType<T> type) { return filterMethods(type.getEnhancedMethodsWithAnnotatedParameters(Observes.class)); } public static <T> Collection<EnhancedAnnotatedMethod<?, ? super T>> getAsyncObserverMethods(final EnhancedAnnotatedType<T> type) { return filterMethods(type.getEnhancedMethodsWithAnnotatedParameters(ObservesAsync.class)); } /** * Oracle JDK 8 compiler (unlike prev versions) generates bridge methods which have method and parameter annotations copied from the original method. * However such methods should not become observers, producers, disposers, initializers and lifecycle callbacks. * * Moreover, JDK8u60 propagates parameter annotations to the synthetic method generated for a lambda. Therefore, we should also ignore synthetic methods. * * @param methods * @return a collection with bridge and synthetic methods filtered out * @see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6695379 * @see https://issues.jboss.org/browse/WELD-2019 */ public static <T> Collection<EnhancedAnnotatedMethod<?, ? super T>> filterMethods(final Collection<EnhancedAnnotatedMethod<?, ? super T>> methods) { return methods.stream().filter(m -> !m.getJavaMember().isBridge() && !m.getJavaMember().isSynthetic()).collect(Collectors.toList()); } public static <T> List<Method> getInterceptorMethods(EnhancedAnnotatedType<T> type, final InterceptionType interceptionType, final boolean targetClass) { return getMethods(type, new MethodListBuilder<T, List<Method>>() { List<Method> methodMetadata = null; @Override public Collection<EnhancedAnnotatedMethod<?, ? super T>> getAllMethods(EnhancedAnnotatedType<T> type) { return type.getEnhancedMethods(InterceptionTypeRegistry.getAnnotationClass(interceptionType)); } @Override public void levelStart(Class<? super T> clazz) { } @Override public void processMethod(EnhancedAnnotatedMethod<?, ? super T> method) { final Method javaMethod = method.getJavaMember(); if (InterceptorMetadataUtils.isInterceptorMethod(interceptionType, javaMethod, targetClass)) { if (methodMetadata == null) { methodMetadata = new LinkedList<Method>(); } if (System.getSecurityManager() == null) { javaMethod.setAccessible(true); } else { AccessController.doPrivileged(SetAccessibleAction.of(javaMethod)); } methodMetadata.add(method.getJavaMember()); } } @Override public void levelFinish() { } @Override public List<Method> create() { if (methodMetadata == null) { return Collections.emptyList(); } Collections.reverse(methodMetadata); return ImmutableList.copyOf(methodMetadata); } }); } }