blob: 57b344bf07ff8bb0b2c6fef95e71700d6975ceb7 [file] [log] [blame]
/* 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.pluto.container.bean.processor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class recognizes "interesting" classes and methods by their annotations and
* verifies the method signatures.
*
* @author Scott Nicklous
*
*/
public abstract class AnnotationRecognizer {
private static final Logger LOG = LoggerFactory.getLogger(AnnotationRecognizer.class);
private static final boolean isDebug = LOG.isDebugEnabled();
private static final boolean isTrace = LOG.isTraceEnabled();
protected final Set<Class<? extends Annotation>> classAnnotations;
protected final Map<Class<? extends Annotation>, List<MethodDescription>> descriptions;
// Holds deployment error descriptions and other information for display.
protected final ConfigSummary summary;
protected AnnotationRecognizer(Set<Class<? extends Annotation>> cla,
Map<Class<? extends Annotation>, List<MethodDescription>> descriptions,
ConfigSummary summary) {
this.classAnnotations = cla;
this.descriptions = descriptions;
this.summary = summary;
}
/**
* Returns the annotation found if the specified annotation is in the specified Set
* and <code>null</code> if not.
*
* @param clsSet Set containing candidate annotations
* @param anno Annotation to be checked
* @return the annotation, if annotation is contained in Set, otherwise null
*/
private Class<? extends Annotation> setContains(Set<Class<? extends Annotation>> clsSet, Annotation anno) {
for (Class<? extends Annotation> cls : clsSet) {
if (anno.annotationType().isAssignableFrom(cls)) {
return cls;
}
}
return null;
}
/**
* Checks annotations on an input AnnotatedType. Any portlet-related annotation
* data found is stored as configuration data.
*
* The methods on the annotated type are scanned for relevant annotations as well. If a
* portlet-related method annotation is found, the method and bean instance is stored.
*
* A class can have multiple portlet annotations, but they all must be for the same portlet.
*
* @param aType The type to check
* @throws InvalidAnnotationException If multiple portlet annotations don't have
* the same portlet names.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void checkAnnotatedType(ProcessAnnotatedType pat) throws InvalidAnnotationException {
AnnotatedType<?> aType = pat.getAnnotatedType();
try {
// Process the class annotations
Set<Annotation> annos = aType.getAnnotations();
for (Annotation anno : annos) {
if (setContains(classAnnotations, anno) != null) {
if (isDebug) {
StringBuilder txt = new StringBuilder(128);
txt.append("Found Annotation: ").append(anno.toString());
txt.append(", portlet name: ").append(getDisplayNames(anno));
LOG.debug(txt.toString());
}
AnnotatedType<?> ret = handleClassAnnotation(anno, aType);
if (ret != null) {
pat.setAnnotatedType(ret);
}
}
}
checkForMethodAnnotations(aType.getJavaClass());
} catch (Exception e) {
// The rest of the possible exceptions are unexpected, so someone will
// have to analyze a stack trace.
StringBuilder txt = new StringBuilder("Error processing annotations for ");
txt.append(aType.toString());
throw new InvalidAnnotationException(txt.toString(), e);
}
}
/**
* recognizes and registers all portlet method annotations for the given class.
*
* @param aClass the annotated class
*/
protected void checkForMethodAnnotations(Class<?> aClass) {
String typeName = aClass.getCanonicalName();
// Get the annotated methods and handle them
for (Method meth : aClass.getMethods()) {
for (Annotation anno : meth.getAnnotations()) {
Class<? extends Annotation> keyAnno =
setContains(descriptions.keySet(), anno);
if (keyAnno != null) {
// Get the valid method descriptions for this annotation
List<MethodDescription> descs = descriptions.get(keyAnno);
assert descs != null;
assert descs.size() > 0;
// try to find the matching method description
MethodDescription matchingDesc = null;
StringBuilder errtxt = new StringBuilder(128);
String sep = " ";
for (MethodDescription desc : descs) {
if (desc.isMethodMatched(meth)) {
matchingDesc = desc;
break;
} else {
errtxt.append(sep).append(desc.getExpectedSignature(true));
sep = "\n ";
}
}
if (isTrace) {
StringBuilder txt = new StringBuilder(128);
txt.append("For method annotation: ").append(anno.annotationType().getSimpleName());
txt.append(" on class: ").append(typeName);
txt.append(", method: ").append(meth.getName());
if (matchingDesc != null) {
txt.append(", recognized type: ").append(matchingDesc.getType());
txt.append(", signature variant: ").append(matchingDesc.getVariant());
} else {
txt.append(", No match found. Error string:\n");
txt.append(errtxt);
}
LOG.trace(txt.toString());
}
if (matchingDesc != null) {
// Found a matching method, so handle.
handleMethod(anno, aClass, meth, matchingDesc);
} else {
// Method doesn't match any of the descriptions.
// this might occur when someone makes a mistake with configuration,
// so handle gracefully.
StringBuilder txt = new StringBuilder(128);
txt.append("Unrecognized method annotation: ")
.append(anno.annotationType().getSimpleName());
txt.append(", Class: ").append(typeName);
txt.append(", Method: ").append(meth.getName());
txt.append("\n").append(errtxt);
LOG.debug(txt.toString());
// Store the error for each portlet name in array
for (String n : getDisplayNames(anno)) {
summary.addErrorString(n, txt.toString());
}
}
}
}
}
}
/**
* extracts array of portlet names from the annotation
*
* @param anno the annotation
* @return array of names
*/
protected String[] getDisplayNames(Annotation anno) {
String[] portletNames = {"unknown"};
try {
Method getPN = anno.getClass().getMethod("portletName");
String[] annoNames = new String[] {(String)getPN.invoke(anno)};
if (annoNames[0] != null && annoNames[0].length() > 0) {
portletNames = annoNames;
}
} catch(Exception e) {
try {
Method getPN = anno.getClass().getMethod("portletNames");
String[] names = (String[])getPN.invoke(anno);
if (names != null && names.length > 0) {
portletNames = names;
}
} catch(Exception e2) {
try {
Method getPN = anno.getClass().getMethod("value");
String[] annoNames = new String[] {(String)getPN.invoke(anno)};
if (annoNames[0] != null && annoNames[0].length() > 0) {
portletNames = annoNames;
}
} catch (Exception e3) {}
}
}
return portletNames;
}
/**
* Extracts data from the class annotation and stores it for later use.
*
* @param anno
* @throws InvalidAnnotationException
*/
protected abstract AnnotatedType<?> handleClassAnnotation(Annotation anno, AnnotatedType<?> aType)
throws InvalidAnnotationException;
/**
* Extracts and stores relevant information from the specified annotation, bean
* class, and Method.
*
* @param anno
* @param beanClass
* @param meth
* @throws InvalidAnnotationException If the specified information is inconsistent
* or if a duplicate method has already been stored.
*/
protected abstract void handleMethod(Annotation anno, Class<?> beanClass,
Method meth, MethodDescription desc);
/**
* To be called by the CDI extension afterDeploymentValidation method to
* activate the custom scoped beans by providing a bean manager.
*
* @param bm BeanManager needed to activate the beans.
*/
protected abstract void activateCustomScopes(BeanManager bm);
/**
* To be after the beans have been recognized and CDI is initialized to
* verify that the stored methods are consistent.
*/
protected abstract void activateAnnotatedMethods(BeanManager bm);
}