/*
* JBoss, Home of Professional Open Source
* Copyright 2011, 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.solder.exception.control.extension;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.InjectionException;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Decorator;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.Interceptor;
import javax.enterprise.inject.spi.ProcessBean;
import org.jboss.solder.exception.control.log.CatchExtensionLog;
import org.jboss.solder.literal.AnyLiteral;
import org.jboss.solder.logging.Logger;
import org.jboss.solder.reflection.AnnotationInspector;
import org.jboss.solder.reflection.HierarchyDiscovery;
import org.jboss.solder.exception.control.ExceptionHandlerComparator;
import org.jboss.solder.exception.control.HandlerMethod;
import org.jboss.solder.exception.control.HandlerMethodContainer;
import org.jboss.solder.exception.control.HandlerMethodImpl;
import org.jboss.solder.exception.control.HandlesExceptions;
import org.jboss.solder.exception.control.TraversalMode;
/**
* CDI extension to find handlers at startup.
*/
@SuppressWarnings("unchecked")
public class CatchExtension implements Extension, HandlerMethodContainer {
private final Map<? super Type, Collection<HandlerMethod<? extends Throwable>>> allHandlers;
private final CatchExtensionLog log = Logger.getMessageLogger(CatchExtensionLog.class, CatchExtension.class.getPackage().getName());
public CatchExtension() {
this.allHandlers = new HashMap<Type, Collection<HandlerMethod<? extends Throwable>>>();
}
/**
* Listener to ProcessBean event to locate handlers.
*
* @param pmb Event from CDI SPI
* @param bm Activated Bean Manager
* @throws TypeNotPresentException if any of the actual type arguments refers to a non-existent type declaration when
* trying to obtain the actual type arguments from a {@link ParameterizedType}
* @throws java.lang.reflect.MalformedParameterizedTypeException
* if any of the actual type parameters refer to a parameterized type that cannot be
* instantiated for any reason when trying to obtain the actual type arguments from a
* {@link ParameterizedType}
*/
public <T> void findHandlers(@Observes final ProcessBean<?> pmb, final BeanManager bm) {
if (!(pmb.getAnnotated() instanceof AnnotatedType) || pmb.getBean() instanceof Interceptor ||
pmb.getBean() instanceof Decorator) {
return;
}
final AnnotatedType<T> type = (AnnotatedType<T>) pmb.getAnnotated();
if (AnnotationInspector.isAnnotationPresent(type, HandlesExceptions.class, bm)) {
final Set<AnnotatedMethod<? super T>> methods = type.getMethods();
for (AnnotatedMethod<? super T> method : methods) {
if (HandlerMethodImpl.isHandler(method)) {
final AnnotatedParameter<?> param = HandlerMethodImpl.findHandlerParameter(method);
if (method.getJavaMember().getExceptionTypes().length != 0) {
pmb.addDefinitionError(new IllegalArgumentException(
String.format("Handler method %s must not throw exceptions", method.getJavaMember())));
}
final Class<? extends Throwable> exceptionType = (Class<? extends Throwable>) ((ParameterizedType) param.getBaseType()).getActualTypeArguments()[0];
registerHandlerMethod(new HandlerMethodImpl(method, bm));
}
}
}
}
/**
* Verifies all injection points for every handler are valid.
*
* @param adv Lifecycle event
* @param bm BeanManager instance
*/
public void verifyInjectionPoints(@Observes final AfterDeploymentValidation adv, final BeanManager bm) {
for (Map.Entry<? super Type, Collection<HandlerMethod<? extends Throwable>>> entry : this.allHandlers.entrySet()) {
for (HandlerMethod<? extends Throwable> handler : entry.getValue()) {
for (InjectionPoint ip : ((HandlerMethodImpl<? extends Throwable>) handler).getInjectionPoints()) {
try {
bm.validate(ip);
} catch (InjectionException e) {
adv.addDeploymentProblem(e);
}
}
}
}
}
/**
* Obtains the applicable handlers for the given type or super type of the given type. Also makes use of {@link
* org.jboss.solder.exception.control.ExceptionHandlerComparator} to order the handlers.
*
* @param exceptionClass Type of exception to narrow handler list
* @param bm active BeanManager
* @param handlerQualifiers additional handlerQualifiers to limit handlers
* @param traversalMode traversal limiter
* @return An order collection of handlers for the given type.
*/
public Collection<HandlerMethod<? extends Throwable>> getHandlersForExceptionType(Type exceptionClass, BeanManager bm,
Set<Annotation> handlerQualifiers,
TraversalMode traversalMode) {
final Collection<HandlerMethod<? extends Throwable>> returningHandlers = new TreeSet<HandlerMethod<? extends Throwable>>(new ExceptionHandlerComparator());
final HierarchyDiscovery h = new HierarchyDiscovery(exceptionClass);
final Set<Type> closure = h.getTypeClosure();
for (Type hierarchyType : closure) {
if (this.allHandlers.get(hierarchyType) != null) {
for (HandlerMethod<?> handler : this.allHandlers.get(hierarchyType)) {
if (handler.getTraversalMode() == traversalMode) {
if (handler.getQualifiers().contains(AnyLiteral.INSTANCE)) {
returningHandlers.add(handler);
} else {
if (!handlerQualifiers.isEmpty() && this.containsAny(handler.getQualifiers(), handlerQualifiers)) {
returningHandlers.add(handler);
}
}
}
}
}
}
log.foundHandlers(returningHandlers, exceptionClass, handlerQualifiers, traversalMode);
return Collections.unmodifiableCollection(returningHandlers);
}
private boolean containsAny(final Collection<? extends Annotation> haystack,
final Collection<? extends Annotation> needles) {
for (Annotation a : needles) {
if (haystack.contains(a)) {
return true;
}
}
return false;
}
@Override
public <T extends Throwable> void registerHandlerMethod(HandlerMethod<T> handlerMethod) {
log.addingHandler(handlerMethod);
if (this.allHandlers.containsKey(handlerMethod.getExceptionType())) {
this.allHandlers.get(handlerMethod.getExceptionType()).add(handlerMethod);
} else {
this.allHandlers.put(handlerMethod.getExceptionType(), new HashSet<HandlerMethod<? extends Throwable>>(Arrays.asList(handlerMethod)));
}
}
}