io.ebeaninternal.server.deploy.parse.AnnotationBase Maven / Gradle / Ivy
Show all versions of ebean Show documentation
package io.ebeaninternal.server.deploy.parse;
import io.ebean.Platform;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Where;
import io.ebean.config.NamingConvention;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Provides some base methods for processing deployment annotations.
*/
public abstract class AnnotationBase {
protected final DatabasePlatform databasePlatform;
protected final Platform platform;
protected final NamingConvention namingConvention;
protected final DeployUtil util;
protected AnnotationBase(DeployUtil util) {
this.util = util;
this.databasePlatform = util.getDbPlatform();
this.platform = databasePlatform.getPlatform();
this.namingConvention = util.getNamingConvention();
}
/**
* read the deployment annotations.
*/
public abstract void parse();
/**
* Checks string is null or empty .
*/
protected boolean isEmpty(String s) {
return s == null || s.trim().isEmpty();
}
/**
* Return the annotation for the property.
*
* Looks first at the field and then at the getter method.
*
*
* If a repeatable
annotation class is specified and the annotation is platform
* specific(see {@link #getPlatformMatchingAnnotation(Set, Platform)}), then the platform specific
* annotation is returned. Otherwise the first annotation is returned. Note that you must no longer
* handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself.
*
*
*/
protected T get(DeployBeanProperty prop, Class annClass) {
T a = null;
Field field = prop.getField();
if (field != null) {
a = findAnnotation(field, annClass);
}
if (a == null) {
Method method = prop.getReadMethod();
if (method != null) {
a = findAnnotation(method, annClass);
}
}
return a;
}
/**
* Return all annotations for this property. Annotations are not filtered by platfrom and you'll get
* really all annotations that are directly, indirectly or meta-present.
*/
protected Set getAll(DeployBeanProperty prop, Class annClass) {
Set ret = null;
Field field = prop.getField();
if (field != null) {
ret = findAnnotations(field, annClass);
}
Method method = prop.getReadMethod();
if (method != null) {
if (ret != null) {
ret.addAll(findAnnotations(method, annClass));
} else {
ret = findAnnotations(method, annClass);
}
}
return ret;
}
/**
* Return the annotation for the property.
*
* Looks first at the field and then at the getter method. then at class level.
*
*/
protected T find(DeployBeanProperty prop, Class annClass) {
T a = get(prop, annClass);
if (a == null) {
a = findAnnotation(prop.getOwningType(), annClass, platform);
}
return a;
}
// this code is taken from the spring framework to find annotations recursively
/**
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
*/
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
return annotation.annotationType().getName().startsWith("java.lang.annotation");
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}.
*
* Meta-annotations will be searched if the annotation is not directly present on the supplied element.
*
* Warning: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
* It also does not filter out platform dependent annotations!
*/
public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start recursive traversal
A ann = annotatedElement.getAnnotation(annotationType);
if (ann != null) {
return ann;
} else {
return findAnnotation(annotatedElement, annotationType, new HashSet<>());
}
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied class.
* Meta-annotations will be searched if the annotation is not directly present on
* the supplied element.
*
Note: this method searches for annotations at class & superClass(es)!
*/
public static A findAnnotation(Class> clazz, Class annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start recursive traversal
A ann = clazz.getAnnotation(annotationType);
if (ann != null) {
return ann;
} else {
while (clazz != null && clazz != Object.class) {
ann = findAnnotation(clazz, annotationType, new HashSet<>());
if (ann != null) {
return ann;
}
// not present at this class - traverse to superclass
clazz = clazz.getSuperclass();
}
return null;
}
}
/**
* Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first
* found annotation is returned)
*/
public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Platform platform) {
if (annotationType == null) {
return null;
}
Set anns = findAnnotations(annotatedElement, annotationType);
return getPlatformMatchingAnnotation(anns, platform);
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) {
Annotation[] anns = annotatedElement.getAnnotations(); // directly annotatated or inherited
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
return null;
}
/**
* Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}.
*
* Meta-annotations will be searched if the annotation is not directly present on the supplied element.
*
* Warning: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
*/
public static Set findAnnotations(AnnotatedElement annotatedElement, Class annotationType) {
if (annotationType == null) {
return null;
}
Set ret = new LinkedHashSet();
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet());
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static void findMetaAnnotations(AnnotatedElement annotatedElement, Class annotationType, Set ret, Set visited) {
Annotation[] anns = annotatedElement.getAnnotations();
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
if (ann.annotationType() == annotationType) {
ret.add((A) ann);
} else {
Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType);
if (repeatableValueMethod != null) {
try {
A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann);
for (Annotation repeatedAnn : repeatedAnns) {
ret.add((A) repeatedAnn);
findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited);
}
} catch (Exception e) { // catch all exceptions (thrown by invoke)
throw new RuntimeException(e);
}
} else {
findMetaAnnotations(ann.annotationType(), annotationType, ret, visited);
}
}
}
}
}
// caches for getRepeatableValueMethod
private static Method getNullMethod() {
try {
return AnnotationBase.class.getDeclaredMethod("getNullMethod");
} catch (NoSuchMethodException e) {
return null;
}
}
private static final ConcurrentMap valueMethods = new ConcurrentHashMap();
private static final Method nullMethod = getNullMethod();
/**
* Returns the value()
method for a possible containerAnnotation.
* Method is retuned only, if its signature is array of containingType
.
*/
private static Method getRepeatableValueMethod(
Annotation containerAnnotation, Class containingType) {
Method method = valueMethods.get(containerAnnotation);
if (method == null) {
try {
method = containerAnnotation.annotationType().getMethod("value");
} catch (NoSuchMethodException e) {
method = nullMethod;
} catch (Exception e) {
throw new RuntimeException(e);
}
Method prev = valueMethods.putIfAbsent(containerAnnotation, method);
method = prev == null ? method : prev;
}
if (method != nullMethod) {
Class> retType = method.getReturnType();
if (retType.isArray() && retType.getComponentType() == containingType) {
return method;
}
}
return null;
}
/**
* Finds a suitable annotation from Set anns
for this platform.
* To distinguish between platforms, annotation type T
must define
* a method withthis signature:
*
* Class extends DatabasePlatform>[] platforms() default {};
*
* The finding rules are:
*
* - Check if T has method "platforms" if not, return
ann[0]
*
databasePlatform