diff --git a/src/main/java/io/ebean/config/AbstractNamingConvention.java b/src/main/java/io/ebean/config/AbstractNamingConvention.java
index b435d2e46..e123bec55 100644
--- a/src/main/java/io/ebean/config/AbstractNamingConvention.java
+++ b/src/main/java/io/ebean/config/AbstractNamingConvention.java
@@ -1,13 +1,13 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DatabasePlatform;
+import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
-import java.lang.annotation.Annotation;
/**
* Provides some base implementation for NamingConventions.
@@ -219,15 +219,10 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* Return true if this class is part of entity inheritance.
*/
protected boolean hasInheritance(Class> supCls) {
- return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
+ return AnnotationBase.findAnnotationRecursive(supCls, Inheritance.class) != null
+ || AnnotationBase.findAnnotation(supCls, DiscriminatorValue.class) != null;
}
- /**
- * Return true if the class has the given annotation.
- */
- protected boolean hasAnnotation(Class> supCls, Class extends Annotation> annotation) {
- return supCls.getAnnotation(annotation) != null;
- }
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
@@ -258,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
*/
protected TableName getTableNameFromAnnotation(Class> beanClass) {
- final Table t = findTableAnnotation(beanClass);
+ final Table t = AnnotationBase.findAnnotationRecursive(beanClass, Table.class);
// Take the annotation if defined
if (t != null && !isEmpty(t.name())) {
@@ -271,22 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
- /**
- * Search recursively for an @Table in the class hierarchy.
- */
- protected Table findTableAnnotation(Class> cls) {
- while (true) {
- if (cls.equals(Object.class)) {
- return null;
- }
- Table table = cls.getAnnotation(Table.class);
- if (table != null) {
- return table;
- }
- cls = cls.getSuperclass();
- }
- }
-
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
diff --git a/src/main/java/io/ebeaninternal/server/cache/DefaultCacheHolder.java b/src/main/java/io/ebeaninternal/server/cache/DefaultCacheHolder.java
index 996f82684..eb87d393b 100644
--- a/src/main/java/io/ebeaninternal/server/cache/DefaultCacheHolder.java
+++ b/src/main/java/io/ebeaninternal/server/cache/DefaultCacheHolder.java
@@ -7,6 +7,7 @@ import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
+import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import java.util.concurrent.ConcurrentHashMap;
@@ -82,7 +83,7 @@ class DefaultCacheHolder {
}
private ServerCacheOptions getQueryOptions(Class> cls) {
- CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
+ CacheQueryTuning tuning = AnnotationBase.findAnnotation(cls, CacheQueryTuning.class);
if (tuning != null) {
return new ServerCacheOptions(tuning).applyDefaults(queryDefault);
}
diff --git a/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogRegister.java b/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogRegister.java
index 80e6620f8..bd9b4210c 100644
--- a/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogRegister.java
+++ b/src/main/java/io/ebeaninternal/server/changelog/DefaultChangeLogRegister.java
@@ -52,7 +52,7 @@ public class DefaultChangeLogRegister implements ChangeLogRegister {
* Find and return the ChangeLog annotation in the inheritance hierarchy.
*/
private ChangeLog getChangeLog(Class> beanType) {
- return AnnotationBase.findAnnotation(beanType, ChangeLog.class);
+ return AnnotationBase.findAnnotationRecursive(beanType, ChangeLog.class);
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java b/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
index ad82e833b..5e57ed8e7 100644
--- a/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
+++ b/src/main/java/io/ebeaninternal/server/core/bootup/BootupClasses.java
@@ -16,6 +16,7 @@ import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
+import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeaninternal.server.type.ScalarType;
import org.avaje.classpath.scanner.ClassFilter;
import org.slf4j.Logger;
@@ -470,7 +471,10 @@ public class BootupClasses implements ClassFilter {
return has(cls, Embeddable.class);
}
+ /**
+ * Returns true if this class has the annotation (or meta annotation). Does not search recursively.
+ */
private boolean has(Class> cls, Class extends Annotation> ann) {
- return cls.getAnnotation(ann) != null;
+ return AnnotationBase.findAnnotation(cls, ann) != null;
}
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
index 9554b8470..32faf53fa 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -38,6 +38,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
+import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
@@ -1451,8 +1452,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* If so it is ok for it not to be enhanced.
*/
private boolean isMappedSuperWithNoProperties(Class> beanClass) {
- // Attention: do not use AnnotationBase.findAnnotation(cls,...) here.
- MappedSuperclass annotation = beanClass.getAnnotation(MappedSuperclass.class);
+ // do not search recursive here
+ MappedSuperclass annotation = AnnotationBase.findAnnotation(beanClass, MappedSuperclass.class);
if (annotation == null) {
return false;
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java
index 9dcbd74c1..0aeb20e69 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationBase.java
@@ -20,7 +20,29 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
- * Provides some base methods for processing deployment annotations.
+ * Provides some base methods for processing deployment annotations. All findAnnotation* methods
+ * are capable to search for meta-annotations (annotation that has an other annotation)
+ *
+ *
search algorithm for ONE annotation:
+ *
+ * - Check if annotation is direct on the property
+ * - if not found: Check all annotations at the annotateElement
+ * if they have the needed annotation as meta annotation
+ * - if not found: go up to super class and try again
+ * (only findAnnotationRecursive)
+ *
+ * DFS (Depth-First-Search) is used. The algorithm is the same as it is used in Spring-Framework,
+ * as the code is taken from there.
+ *
+ * search algoritm for a Set<Annotation> works a litte bit different, as it does not stop
+ * on the first match, but continues searching down to the last corner to find all annotations.
+ *
+ * To prevent endless recursion, the search algoritm tracks all visited annotations
+ *
+ * Supports also "java 1.6 repeatable containers" like{@link JoinColumn} / {@link JoinColumns}.
+ *
+ * This means, searching for JoinColumn will find them also if they are inside a
+ * JoinColumns annotation
*/
public abstract class AnnotationBase {
@@ -51,12 +73,13 @@ public abstract class AnnotationBase {
/**
* Return the annotation for the property.
*
- * Looks first at the field and then at the getter method.
+ * Looks first at the field and then at the getter method. It searches for meta-annotations, but not
+ * recursively in the class hierarchy.
*
*
* 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
+ * annotation is returned. Otherwise the first annotation is returned. Note that you need no longer
* handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself.
*
*
@@ -101,6 +124,7 @@ public abstract class AnnotationBase {
* Return the annotation for the property.
*
* Looks first at the field and then at the getter method. then at class level.
+ * (This is used for SequenceGenerator e.g.)
*
*/
protected T find(DeployBeanProperty prop, Class annClass) {
@@ -134,7 +158,7 @@ public abstract class AnnotationBase {
if (annotationType == null) {
return null;
}
- // check if directly present, if not, start recursive traversal
+ // check if directly present, if not, start search for meta-annotations.
A ann = annotatedElement.getAnnotation(annotationType);
if (ann != null) {
return ann;
@@ -149,11 +173,11 @@ public abstract class AnnotationBase {
* the supplied element.
* Note: this method searches for annotations at class & superClass(es)!
*/
- public static A findAnnotation(Class> clazz, Class annotationType) {
+ public static A findAnnotationRecursive(Class> clazz, Class annotationType) {
if (annotationType == null) {
return null;
}
- // check if directly present, if not, start recursive traversal
+ // check if directly present, if not, start search for meta-annotations.
A ann = clazz.getAnnotation(annotationType);
if (ann != null) {
return ann;
@@ -163,7 +187,7 @@ public abstract class AnnotationBase {
if (ann != null) {
return ann;
}
- // not present at this class - traverse to superclass
+ // no meta-annotation present at this class - traverse to superclass
clazz = clazz.getSuperclass();
}
return null;
@@ -182,6 +206,22 @@ public abstract class AnnotationBase {
return getPlatformMatchingAnnotation(anns, platform);
}
+ /**
+ * Finds all annotations recusively for a class and its superclasses.
+ */
+ public static Set findAnnotationsRecursive(Class> clazz, Class annotationType) {
+ if (annotationType == null) {
+ return null;
+ }
+ Set ret = new LinkedHashSet<>();
+ Set visited = new HashSet<>();
+ while (clazz != null && clazz != Object.class) {
+ findMetaAnnotations(clazz, annotationType, ret, visited);
+ clazz = clazz.getSuperclass();
+ }
+ return ret;
+ }
+
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
@@ -265,6 +305,7 @@ public abstract class AnnotationBase {
}
private static final ConcurrentMap valueMethods = new ConcurrentHashMap<>();
+ // only a non-null-marker the valueMethods - Cache
private static final Method nullMethod = getNullMethod();
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
index f63fd81a4..fbab35764 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
@@ -67,7 +67,7 @@ public class AnnotationClass extends AnnotationParser {
public void parseAttributeOverride() {
Class> cls = descriptor.getBeanType();
- AttributeOverride override = AnnotationBase.findAnnotation(cls, AttributeOverride.class);
+ AttributeOverride override = AnnotationBase.findAnnotationRecursive(cls, AttributeOverride.class);
if (override != null) {
String propertyName = override.name();
Column column = override.column();
@@ -109,14 +109,14 @@ public class AnnotationClass extends AnnotationParser {
private void read(Class> cls) {
// maybe doc store only so check for this before @Entity
- DocStore docStore = AnnotationBase.findAnnotation(cls, DocStore.class);
+ DocStore docStore = AnnotationBase.findAnnotationRecursive(cls, DocStore.class);
if (docStore != null) {
descriptor.readDocStore(docStore);
descriptor.setEntityType(EntityType.DOC);
descriptor.setName(cls.getSimpleName());
}
- Entity entity = AnnotationBase.findAnnotation(cls, Entity.class);
+ Entity entity = AnnotationBase.findAnnotationRecursive(cls, Entity.class);
if (entity != null) {
descriptor.setEntityType(EntityType.ORM);
if (entity.name().isEmpty()) {
@@ -126,27 +126,27 @@ public class AnnotationClass extends AnnotationParser {
}
}
- Embeddable embeddable = AnnotationBase.findAnnotation(cls, Embeddable.class);
+ Embeddable embeddable = AnnotationBase.findAnnotationRecursive(cls, Embeddable.class);
if (embeddable != null) {
descriptor.setEntityType(EntityType.EMBEDDED);
descriptor.setName("Embeddable:" + cls.getSimpleName());
}
- Set indices = AnnotationBase.findAnnotations(cls, Index.class);
+ Set indices = AnnotationBase.findAnnotationsRecursive(cls, Index.class);
for (Index index : indices) {
descriptor.addIndex(new IndexDefinition(index.columnNames(), index.name(), index.unique()));
}
- UniqueConstraint uc = AnnotationBase.findAnnotation(cls, UniqueConstraint.class);
+ UniqueConstraint uc = AnnotationBase.findAnnotationRecursive(cls, UniqueConstraint.class);
if (uc != null) {
descriptor.addIndex(new IndexDefinition(uc.columnNames()));
}
- View view = AnnotationBase.findAnnotation(cls, View.class);
+ View view = AnnotationBase.findAnnotationRecursive(cls, View.class);
if (view != null) {
descriptor.setView(view.name(), view.dependentTables());
}
- Table table = AnnotationBase.findAnnotation(cls, Table.class);
+ Table table = AnnotationBase.findAnnotationRecursive(cls, Table.class);
if (table != null) {
UniqueConstraint[] uniqueConstraints = table.uniqueConstraints();
for (UniqueConstraint c : uniqueConstraints) {
@@ -154,42 +154,42 @@ public class AnnotationClass extends AnnotationParser {
}
}
- Draftable draftable = AnnotationBase.findAnnotation(cls, Draftable.class);
+ Draftable draftable = AnnotationBase.findAnnotationRecursive(cls, Draftable.class);
if (draftable != null) {
descriptor.setDraftable();
}
- DraftableElement draftableElement = AnnotationBase.findAnnotation(cls, DraftableElement.class);
+ DraftableElement draftableElement = AnnotationBase.findAnnotationRecursive(cls, DraftableElement.class);
if (draftableElement != null) {
descriptor.setDraftableElement();
}
- ReadAudit readAudit = AnnotationBase.findAnnotation(cls, ReadAudit.class);
+ ReadAudit readAudit = AnnotationBase.findAnnotationRecursive(cls, ReadAudit.class);
if (readAudit != null) {
descriptor.setReadAuditing();
}
- History history = AnnotationBase.findAnnotation(cls, History.class);
+ History history = AnnotationBase.findAnnotationRecursive(cls, History.class);
if (history != null) {
descriptor.setHistorySupport();
}
- DbComment comment = AnnotationBase.findAnnotation(cls, DbComment.class);
+ DbComment comment = AnnotationBase.findAnnotationRecursive(cls, DbComment.class);
if (comment != null) {
descriptor.setDbComment(comment.value());
}
- UpdateMode updateMode = AnnotationBase.findAnnotation(cls, UpdateMode.class);
+ UpdateMode updateMode = AnnotationBase.findAnnotationRecursive(cls, UpdateMode.class);
if (updateMode != null) {
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
}
- Cache cache = AnnotationBase.findAnnotation(cls, Cache.class);
+ Cache cache = AnnotationBase.findAnnotationRecursive(cls, Cache.class);
if (cache != null && !disableL2Cache) {
descriptor.setCache(cache);
}
- Set namedQueries = AnnotationBase.findAnnotations(cls, NamedQuery.class);
+ Set namedQueries = AnnotationBase.findAnnotationsRecursive(cls, NamedQuery.class);
for (NamedQuery namedQuery : namedQueries) {
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java
index ab3dbb758..aa0205632 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationSql.java
@@ -15,7 +15,7 @@ public class AnnotationSql extends AnnotationParser {
@Override
public void parse() {
Class> cls = descriptor.getBeanType();
- Sql sql = AnnotationBase.findAnnotation(cls, Sql.class);
+ Sql sql = AnnotationBase.findAnnotationRecursive(cls, Sql.class);
if (sql != null) {
descriptor.setEntityType(BeanDescriptor.EntityType.SQL);
}
diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java
index 21b4bc82e..30fab9aca 100644
--- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java
+++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java
@@ -109,11 +109,11 @@ public class DeployInherit {
info.setParent(parent);
}
- Inheritance ia = AnnotationBase.findAnnotation(cls, Inheritance.class);
+ Inheritance ia = AnnotationBase.findAnnotationRecursive(cls, Inheritance.class);
if (ia != null) {
ia.strategy();
}
- DiscriminatorColumn da = AnnotationBase.findAnnotation(cls, DiscriminatorColumn.class);
+ DiscriminatorColumn da = AnnotationBase.findAnnotationRecursive(cls, DiscriminatorColumn.class);
if (da != null) {
// lowercase the discriminator column for RawSql and JSON
info.setColumnName(da.name().toLowerCase());
@@ -122,7 +122,7 @@ public class DeployInherit {
info.setColumnDefn(da.columnDefinition());
}
- DiscriminatorValue dv = AnnotationBase.findAnnotation(cls, DiscriminatorValue.class);
+ DiscriminatorValue dv = AnnotationBase.findAnnotationRecursive(cls, DiscriminatorValue.class);
if (dv != null) {
info.setDiscriminatorValue(dv.value());
}
@@ -144,7 +144,7 @@ public class DeployInherit {
if (cls.equals(Object.class)) {
return false;
}
- Annotation a = AnnotationBase.findAnnotation(cls, Inheritance.class);
+ Annotation a = AnnotationBase.findAnnotationRecursive(cls, Inheritance.class);
if (a != null) {
return true;
}
diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
index 68c77e9af..121e0c771 100644
--- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
@@ -13,6 +13,7 @@ import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.dbmigration.DbOffline;
import io.ebean.plugin.ExtraTypeFactory;
import io.ebeaninternal.server.core.bootup.BootupClasses;
+import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -543,7 +544,7 @@ public final class DefaultTypeManager implements TypeManager {
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
- EnumValue enumValue = field.getAnnotation(EnumValue.class);
+ EnumValue enumValue = AnnotationBase.findAnnotation(field, EnumValue.class);
if (enumValue != null) {
nameValueMap.put(field.getName(), enumValue.value());
if (integerType && !isIntegerType(enumValue.value())) {
@@ -574,7 +575,7 @@ public final class DefaultTypeManager implements TypeManager {
Method[] methods = enumType.getMethods();
for (Method method : methods) {
- DbEnumValue dbValue = method.getAnnotation(DbEnumValue.class);
+ DbEnumValue dbValue = AnnotationBase.findAnnotation(method, DbEnumValue.class);
if (dbValue != null) {
boolean integerValues = DbEnumType.INTEGER == dbValue.storage();
return createEnumScalarTypeDbValue(enumType, method, integerValues);