/* * JBoss, Home of Professional Open Source * Copyright 2013, 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.bootstrap; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.function.Predicate; import javax.enterprise.inject.Any; import javax.enterprise.inject.spi.ObserverMethod; import javax.enterprise.inject.spi.ProcessAnnotatedType; import org.jboss.weld.bootstrap.api.helpers.AbstractBootstrapService; import org.jboss.weld.event.ContainerLifecycleEventObserverMethod; import org.jboss.weld.resolution.TypeSafeObserverResolver; import org.jboss.weld.resources.spi.ClassFileInfo; import org.jboss.weld.resources.spi.ClassFileServices; import org.jboss.weld.util.Types; import org.jboss.weld.util.reflection.Reflections; /** * ProcessAnnotatedType observer method resolver. It uses {@link ClassFileServices} for resolution and thus entirely avoids loading the classes which speeds up * especially large deployments. * * Although this resolver covers most of the possible PAT observer method types, there are several cases when {@link ClassFileInfo} used by this resolver is not * sufficient to perform observer method resolution correctly. If such observer method is present in the deployment, the constructor of this class throws * {@link UnsupportedObserverMethodException}. This exception is expected to be caught by the deployer and observer method resolution using the default * {@link TypeSafeObserverResolver} is performed instead. * * @author Jozef Hartinger * */ public class FastProcessAnnotatedTypeResolver extends AbstractBootstrapService { private static class ExactTypePredicate implements Predicate<ClassFileInfo> { private final Class<?> type; public ExactTypePredicate(Class<?> type) { this.type = type; } @Override public boolean test(ClassFileInfo input) { return type.getName().equals(input.getClassName()); } } private static class AssignableToPredicate implements Predicate<ClassFileInfo> { private final Class<?> type; public AssignableToPredicate(Class<?> type) { this.type = type; } @Override public boolean test(ClassFileInfo input) { return input.isAssignableTo(type); } } @SuppressWarnings("unchecked") private static class CompositePredicate implements Predicate<ClassFileInfo> { private static CompositePredicate assignable(Class<?>[] classes) { Predicate<ClassFileInfo>[] predicates = new Predicate[classes.length]; for (int i = 0; i < classes.length; i++) { predicates[i] = new AssignableToPredicate(classes[i]); } return new CompositePredicate(predicates); } private final Predicate<ClassFileInfo>[] predicates; public CompositePredicate(Predicate<ClassFileInfo>[] predicates) { this.predicates = predicates; } @Override public boolean test(ClassFileInfo input) { for (Predicate<ClassFileInfo> predicate : predicates) { if (!predicate.test(input)) { return false; } } return true; } } private final Set<ContainerLifecycleEventObserverMethod<?>> catchAllObservers; private final Map<ContainerLifecycleEventObserverMethod<?>, Predicate<ClassFileInfo>> observers; public FastProcessAnnotatedTypeResolver(Iterable<ObserverMethod<?>> observers) throws UnsupportedObserverMethodException { this.catchAllObservers = new HashSet<>(); this.observers = new LinkedHashMap<ContainerLifecycleEventObserverMethod<?>, Predicate<ClassFileInfo>>(); for (ObserverMethod<?> o : observers) { if (o instanceof ContainerLifecycleEventObserverMethod<?>) { final Set<Annotation> qualifiers = o.getObservedQualifiers(); // only process observer methods with no qualifiers or with @Any if (qualifiers.isEmpty() || (qualifiers.size() == 1 && Any.class.equals(qualifiers.iterator().next().annotationType()))) { process((ContainerLifecycleEventObserverMethod<?>) o, o.getObservedType()); } } } } private void process(ContainerLifecycleEventObserverMethod<?> observer, Type observedType) throws UnsupportedObserverMethodException { if (Object.class.equals(observedType)) { // void observe(Object event) catchAllObservers.add(observer); } else if (ProcessAnnotatedType.class.equals(observedType)) { // void observe(ProcessAnnotatedType event) catchAllObservers.add(observer); } else if (observedType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) observedType; if (ProcessAnnotatedType.class.equals(type.getRawType())) { Type typeParameter = type.getActualTypeArguments()[0]; if (typeParameter instanceof Class<?>) { this.observers.put(observer, new ExactTypePredicate(Reflections.getRawType(typeParameter))); } else if (typeParameter instanceof ParameterizedType) { /* * The event type always has the form of ProcessAnnotatedType<X> where X is a raw type. * Therefore, no event will ever match an observer with type ProcessAnnotatedType<Foo<Y>> no matter * what Y is. This would be because primarily because parameterized are invariant. Event for an exact match * of the raw type, Foo raw event type is not assignable to Foo<?> parameterized type according to CDI assignability rules. */ return; } else if (typeParameter instanceof WildcardType) { // void observe(ProcessAnnotatedType<?> event) WildcardType wildCard = (WildcardType) typeParameter; checkBounds(observer, wildCard.getUpperBounds()); this.observers.put(observer, CompositePredicate.assignable(Types.getRawTypes(wildCard.getUpperBounds()))); } else if (typeParameter instanceof TypeVariable<?>) { // <T> void observe(ProcessAnnotatedType<T> event) TypeVariable<?> variable = (TypeVariable<?>) typeParameter; checkBounds(observer, variable.getBounds()); this.observers.put(observer, CompositePredicate.assignable(Types.getRawTypes(variable.getBounds()))); } } } else if (observedType instanceof TypeVariable<?>) { defaultRules(observer, observedType); } } private void checkBounds(ContainerLifecycleEventObserverMethod<?> observer, Type[] bounds) throws UnsupportedObserverMethodException { for (Type type : bounds) { if (!(type instanceof Class<?>)) { throw new UnsupportedObserverMethodException(observer); } } } private void defaultRules(ContainerLifecycleEventObserverMethod<?> observer, Type observedType) throws UnsupportedObserverMethodException { if (ProcessAnnotatedType.class.equals(observedType)) { catchAllObservers.add(observer); } else if (observedType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) observedType; if (ProcessAnnotatedType.class.equals(parameterizedType.getRawType())) { Type argument = parameterizedType.getActualTypeArguments()[0]; if (argument instanceof Class<?>) { this.observers.put(observer, new ExactTypePredicate(Reflections.getRawType(argument))); } else { throw new UnsupportedObserverMethodException(observer); } } } else if (observedType instanceof TypeVariable) { final TypeVariable<?> typeVariable = (TypeVariable<?>) observedType; if (Reflections.isUnboundedTypeVariable(observedType)) { // <T> void observe(T event) catchAllObservers.add(observer); } else { if (typeVariable.getBounds().length == 1) { // here we expect that a PAT impl only implements the PAT interface defaultRules(observer, typeVariable.getBounds()[0]); } } } } /** * Resolves a set of {@code ProcessAnnotatedType} observer methods for the specified class. If no observer methods are resolved, an * empty set is returned. * * @param className the specified class name * @return the set of resolved ProcessAnnotatedType observer methods */ public Set<ContainerLifecycleEventObserverMethod<?>> resolveProcessAnnotatedTypeObservers(ClassFileServices classFileServices, String className) { Set<ContainerLifecycleEventObserverMethod<?>> result = new HashSet<ContainerLifecycleEventObserverMethod<?>>(); result.addAll(catchAllObservers); ClassFileInfo classInfo = classFileServices.getClassFileInfo(className); for (Map.Entry<ContainerLifecycleEventObserverMethod<?>, Predicate<ClassFileInfo>> entry : observers.entrySet()) { ContainerLifecycleEventObserverMethod<?> observer = entry.getKey(); if (containsRequiredAnnotation(classInfo, observer) && entry.getValue().test(classInfo)) { result.add(observer); } } return result; } private boolean containsRequiredAnnotation(ClassFileInfo classInfo, ContainerLifecycleEventObserverMethod<?> observer) { if (observer.getRequiredAnnotations().isEmpty()) { return true; } for (Class<? extends Annotation> annotation : observer.getRequiredAnnotations()) { if (classInfo.containsAnnotation(annotation)) { return true; } } return false; } @Override public void cleanupAfterBoot() { catchAllObservers.clear(); observers.clear(); } }