/* 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); | |
} | |
} | |
} | |
} 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); | |
} |