From 7b199738361b620bdd642a2ceeee440b31044789 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 31 Oct 2016 23:36:43 +0100 Subject: [PATCH 1/4] Better support for repeatable annotations (needs Java 8) --- pom.xml | 4 +- .../com/avaje/ebean/annotation/EbeanDDL.java | 11 + .../com/avaje/ebean/annotation/Formula.java | 15 ++ .../com/avaje/ebean/annotation/Where.java | 18 ++ .../server/deploy/meta/DeployTableJoin.java | 10 + .../deploy/parse/AnnotationAssocManys.java | 11 +- .../deploy/parse/AnnotationAssocOnes.java | 21 +- .../server/deploy/parse/AnnotationBase.java | 209 +++++++++++++++++- .../server/deploy/parse/AnnotationClass.java | 24 +- .../server/deploy/parse/AnnotationFields.java | 35 +-- .../server/deploy/parse/AnnotationParser.java | 37 +++- 11 files changed, 316 insertions(+), 79 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/annotation/EbeanDDL.java diff --git a/pom.xml b/pom.xml index 88c36e185..b49ee2142 100644 --- a/pom.xml +++ b/pom.xml @@ -241,8 +241,8 @@ maven-compiler-plugin 3.2 - 1.6 - 1.6 + 1.8 + 1.8 diff --git a/src/main/java/com/avaje/ebean/annotation/EbeanDDL.java b/src/main/java/com/avaje/ebean/annotation/EbeanDDL.java new file mode 100644 index 000000000..a22f22e15 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/EbeanDDL.java @@ -0,0 +1,11 @@ +package com.avaje.ebean.annotation; + +import javax.validation.constraints.NotNull; +/** + * special validation group for @NotNull annotation to enforce NOT NULL generation on DDL. + * Normally if you put the {@link NotNull} annotation on a property, Ebean will only generate a + * NOT NULL in DDL if you do not change the validation-groups! + */ +public interface EbeanDDL { + +} diff --git a/src/main/java/com/avaje/ebean/annotation/Formula.java b/src/main/java/com/avaje/ebean/annotation/Formula.java index 647edcfd1..e0b9cb682 100644 --- a/src/main/java/com/avaje/ebean/annotation/Formula.java +++ b/src/main/java/com/avaje/ebean/annotation/Formula.java @@ -1,11 +1,13 @@ package com.avaje.ebean.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.avaje.ebean.Query; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; /** * Assign to a property to be based on a SQL formula. @@ -67,6 +69,7 @@ import com.avaje.ebean.Query; */ @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(Formula.List.class) public @interface Formula { /** @@ -102,5 +105,17 @@ public @interface Formula { * } */ String join() default ""; + + Class[] platforms() default {}; + + /** + * Repeatable support for {@link Formula}. + */ + @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface List { + + Formula[] value() default {}; + } } diff --git a/src/main/java/com/avaje/ebean/annotation/Where.java b/src/main/java/com/avaje/ebean/annotation/Where.java index 5fc586593..2c9ccf03a 100644 --- a/src/main/java/com/avaje/ebean/annotation/Where.java +++ b/src/main/java/com/avaje/ebean/annotation/Where.java @@ -1,10 +1,13 @@ package com.avaje.ebean.annotation; import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; + /** * Add an Literal to add to the where clause when a many property (List, Set or * Map) is loaded or refreshed. @@ -40,6 +43,7 @@ import java.lang.annotation.Target; */ @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(Where.List.class) public @interface Where { /** @@ -50,5 +54,19 @@ public @interface Where { *

*/ String clause(); + + /** + * The platform where this annotation is active. Default: any platform + */ + Class[] platforms() default {}; + /** + * Repeatable support for {@link Where}. + */ + @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface List { + + Where[] value() default {}; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java index fad7550a0..9ef4fb95a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployTableJoin.java @@ -6,6 +6,7 @@ import com.avaje.ebeaninternal.server.query.SqlJoinType; import javax.persistence.JoinColumn; import java.util.ArrayList; +import java.util.Set; /** * Represents a join to another table during deployment phase. @@ -91,6 +92,15 @@ public class DeployTableJoin { } } + /** + * Add a JoinColumn set. + */ + public void addJoinColumn(boolean order, Set jcSet, BeanTable beanTable) { + for (JoinColumn jc: jcSet) { + addJoinColumn(order, jc, beanTable); + } + } + /** * Return the join columns. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java index 2bda44973..a67bde018 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java @@ -1,7 +1,8 @@ package com.avaje.ebeaninternal.server.deploy.parse; +import java.util.Set; + import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.MapKey; @@ -87,14 +88,10 @@ public class AnnotationAssocManys extends AnnotationParser { // check for manually defined joins BeanTable beanTable = prop.getBeanTable(); - JoinColumn joinColumn = get(prop, JoinColumn.class); - if (joinColumn != null) { - prop.getTableJoin().addJoinColumn(true, joinColumn, beanTable); - } - JoinColumns joinColumns = get(prop, JoinColumns.class); + Set joinColumns = getAll(prop, JoinColumn.class); if (joinColumns != null) { - prop.getTableJoin().addJoinColumn(true, joinColumns.value(), beanTable); + prop.getTableJoin().addJoinColumn(true, joinColumns, beanTable); } JoinTable joinTable = get(prop, JoinTable.class); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index 6ade29cba..73aeba43e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -1,13 +1,13 @@ package com.avaje.ebeaninternal.server.deploy.parse; import java.util.Map; +import java.util.Set; import javax.persistence.Column; import javax.persistence.Embedded; import javax.persistence.EmbeddedId; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; import javax.persistence.JoinTable; import javax.persistence.ManyToOne; import javax.persistence.OneToOne; @@ -93,7 +93,7 @@ public class AnnotationAssocOnes extends AnnotationParser { if (validationAnnotations) { NotNull notNull = get(prop, NotNull.class); - if (notNull != null) { + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { prop.setNullable(false); // overrides optional attribute of ManyToOne etc prop.getTableJoin().setType(SqlJoinType.INNER); @@ -102,8 +102,7 @@ public class AnnotationAssocOnes extends AnnotationParser { // check for manually defined joins BeanTable beanTable = prop.getBeanTable(); - JoinColumn joinColumn = get(prop, JoinColumn.class); - if (joinColumn != null) { + for (JoinColumn joinColumn : getAll(prop, JoinColumn.class)) { prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable); if (!joinColumn.updatable()) { prop.setDbUpdateable(false); @@ -113,14 +112,18 @@ public class AnnotationAssocOnes extends AnnotationParser { } } - JoinColumns joinColumns = get(prop, JoinColumns.class); - if (joinColumns != null) { - prop.getTableJoin().addJoinColumn(false, joinColumns.value(), beanTable); - } JoinTable joinTable = get(prop, JoinTable.class); if (joinTable != null) { - prop.getTableJoin().addJoinColumn(false, joinTable.joinColumns(), beanTable); + for (JoinColumn joinColumn : joinTable.joinColumns()) { + prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable); + if (!joinColumn.updatable()) { + prop.setDbUpdateable(false); + } + if (!joinColumn.nullable()) { + prop.setNullable(false); + } + } } info.setBeanJoinType(prop, prop.isNullable()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java index d6fc3b540..9048a3d69 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -1,5 +1,7 @@ package com.avaje.ebeaninternal.server.deploy.parse; +import com.avaje.ebean.annotation.Formula; +import com.avaje.ebean.annotation.Where; import com.avaje.ebean.config.NamingConvention; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; @@ -9,7 +11,14 @@ 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; + +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.validation.constraints.Size; /** * Provides some base methods for processing deployment annotations. @@ -38,28 +47,58 @@ public abstract class AnnotationBase { return s == null || s.trim().isEmpty(); } - /** - * Return the annotation for the property. + * 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, Class)}), then the platform specific + * annotation is returned. Otherwise the first annotation is retured. 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 != findAnnotation(field, annClass, databasePlatform.getClass())) { + System.out.println("Difference (field) " + field + ", " + annClass); + } } if (a == null) { - Method m = prop.getReadMethod(); - if (m != null) { - a = findAnnotation(m, annClass); + Method method = prop.getReadMethod(); + if (method != null) { + a = findAnnotation(method, annClass); + if (a != findAnnotation(method, annClass, databasePlatform.getClass())) { + System.out.println("Difference (method) " + method + ", " + annClass); + } } } return a; } + 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. *

@@ -69,7 +108,7 @@ public abstract class AnnotationBase { protected T find(DeployBeanProperty prop, Class annClass) { T a = get(prop, annClass); if (a == null) { - a = findAnnotation(prop.getOwningType(), annClass); + a = findAnnotation(prop.getOwningType(), annClass, databasePlatform.getClass()); } return a; } @@ -91,6 +130,7 @@ public abstract class AnnotationBase { *

* 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) { @@ -132,6 +172,19 @@ public abstract class AnnotationBase { } } + /** + * 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, + Class databasePlatform) { + if (annotationType == null) { + return null; + } + Set anns = findAnnotations(annotatedElement, annotationType); + return getPlatformMatchingAnnotation(anns, databasePlatform); + } + /** * Perform the search algorithm avoiding endless recursion by tracking which * annotations have already been visited. @@ -139,7 +192,7 @@ public abstract class AnnotationBase { @SuppressWarnings("unchecked") private static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType, Set visited) { - Annotation[] anns = annotatedElement.getDeclaredAnnotations(); + Annotation[] anns = annotatedElement.getAnnotations(); // directly annotatated or inherited for (Annotation ann : anns) { if (ann.annotationType() == annotationType) { return (A) ann; @@ -155,4 +208,144 @@ public abstract class AnnotationBase { } 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 final 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[] platforms() default {}; + *

+ * The finding rules are: + *
    + *
  1. Check if T has method "platforms" if not, return ann[0] + *
  2. find the annotation that is defined for databasePlatform
  3. + *
  4. otherwise return the annotation for default platform (platforms = {})
  5. + *
  6. return null + *
+ * (This mechanism is currently used by {@link Where} and {@link Formula}) + */ + public static T getPlatformMatchingAnnotation(Set anns, Class databasePlatform) { + if (anns.isEmpty()) { + return null; + } + Method getPlatformsMethod = null; + T fallback = null; + for (T ann : anns) { + try { + if (getPlatformsMethod == null) { + getPlatformsMethod = ann.getClass().getMethod("platforms"); + } + if (!Class[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) { + return ann; + } + Class[] platforms = (Class[]) getPlatformsMethod.invoke(ann); + if (platforms.length == 0) { + fallback = ann; + } else { + for (Class platform : platforms) { + if (databasePlatform.isAssignableFrom(platform)) { + return ann; + } + } + } + } catch (NoSuchMethodException e) { + return ann; // not platform specific - return first one + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return fallback; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java index 3fe8be9a7..32de78996 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -18,6 +18,8 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Set; + import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embeddable; @@ -119,15 +121,8 @@ public class AnnotationClass extends AnnotationParser { descriptor.setName("Embeddable:" + cls.getSimpleName()); } - Indices indices = AnnotationBase.findAnnotation(cls, Indices.class); - if (indices != null) { - for (Index index: indices.value()) { - descriptor.addIndex(new IndexDefinition(index.columnNames(), index.name(), index.unique())); - } - } - - Index index = AnnotationBase.findAnnotation(cls,Index.class); - if (index != null) { + Set indices = AnnotationBase.findAnnotations(cls, Index.class); + for (Index index: indices) { descriptor.addIndex(new IndexDefinition(index.columnNames(), index.name(), index.unique())); } @@ -188,15 +183,8 @@ public class AnnotationClass extends AnnotationParser { descriptor.setCache(cache); } - NamedQueries namedQueries = AnnotationBase.findAnnotation(cls,NamedQueries.class); - if (namedQueries != null) { - for (NamedQuery namedQuery : namedQueries.value()) { - descriptor.addNamedQuery(namedQuery.name(), namedQuery.query()); - } - } - - NamedQuery namedQuery = AnnotationBase.findAnnotation(cls,NamedQuery.class); - if (namedQuery != null) { + Set namedQueries = AnnotationBase.findAnnotations(cls,NamedQuery.class); + for (NamedQuery namedQuery : namedQueries) { descriptor.addNamedQuery(namedQuery.name(), namedQuery.query()); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 022044ec8..7d6b988c5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -26,6 +26,7 @@ import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.sql.Types; import java.util.Map; +import java.util.Set; import java.util.UUID; /** @@ -251,18 +252,19 @@ public class AnnotationFields extends AnnotationParser { if (validationAnnotations) { NotNull notNull = get(prop, NotNull.class); - if (notNull != null && isNotNullOnAllValidationGroups(notNull.groups())) { + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { // Not null on all validation groups so enable // DDL generation of Not Null Constraint prop.setNullable(false); } - Size size = get(prop, Size.class); - if (size != null) { - if (size.max() < Integer.MAX_VALUE) { - // explicitly specify a version column - prop.setDbLength(size.max()); - } + // take the max size of all @Size annotations + int maxSize = -1; + for (Size size : getAll(prop, Size.class)) { + maxSize = Math.max(maxSize, size.max()); + } + if (maxSize !=1 && maxSize < Integer.MAX_VALUE) { + prop.setDbLength(maxSize); } } @@ -310,15 +312,8 @@ public class AnnotationFields extends AnnotationParser { } } - Indices indices = get(prop, Indices.class); - if (indices != null) { - for (Index index: indices.value()) { - addIndex(prop, index); - } - } - - Index index = get(prop, Index.class); - if (index != null) { + Set indices = getAll(prop, Index.class); + for (Index index: indices) { addIndex(prop, index); } } @@ -378,14 +373,6 @@ public class AnnotationFields extends AnnotationParser { get(prop, OneToOne.class) != null; } - /** - * Return true if the validation is on all validation groups and hence - * can be applied to DDL generation. - */ - private boolean isNotNullOnAllValidationGroups(Class[] groups) { - return groups.length == 0 || groups.length == 1 && javax.validation.groups.Default.class.isAssignableFrom(groups[0]); - } - private void setEncryption(DeployBeanProperty prop, boolean dbEncString, int dbLen) { util.checkEncryptKeyManagerDefined(prop.getFullBeanName()); diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java index 031681a5b..b56f1ee3c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -1,12 +1,14 @@ package com.avaje.ebeaninternal.server.deploy.parse; import java.util.HashMap; +import java.util.Set; import javax.persistence.AttributeOverride; -import javax.persistence.AttributeOverrides; import javax.persistence.CascadeType; import javax.persistence.Column; +import javax.validation.groups.Default; +import com.avaje.ebean.annotation.EbeanDDL; import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; @@ -52,17 +54,12 @@ public abstract class AnnotationParser extends AnnotationBase { */ protected void readEmbeddedAttributeOverrides(DeployBeanPropertyAssocOne prop) { - AttributeOverrides attrOverrides = get(prop, AttributeOverrides.class); - if (attrOverrides != null) { - HashMap propMap = new HashMap(); - AttributeOverride[] aoArray = attrOverrides.value(); - for (int i = 0; i < aoArray.length; i++) { - String propName = aoArray[i].name(); - String columnName = aoArray[i].column().name(); - - propMap.put(propName, columnName); + Set attrOverrides = getAll(prop, AttributeOverride.class); + if (!attrOverrides.isEmpty()) { + HashMap propMap = new HashMap<>(); + for (AttributeOverride attrOverride : attrOverrides) { + propMap.put(attrOverride.name(), attrOverride.column().name()); } - prop.getDeployEmbedded().putAll(propMap); } @@ -95,4 +92,22 @@ public abstract class AnnotationParser extends AnnotationBase { prop.setSecondaryTable(tableName); } } + + /** + * Return true if the validation groups are {@link Default} (respectively empty) or contains {@link EbeanDDL} + * can be applied to DDL generation. + */ + protected boolean isEbeanValidationGroups(Class[] groups) { + if (groups.length == 0 + || groups.length == 1 && javax.validation.groups.Default.class.isAssignableFrom(groups[0])) { + return true; + } else { + for (Class group : groups) { + if (EbeanDDL.class.isAssignableFrom(group)) { + return true; + } + } + } + return false; + } } From 06e265ec476263b89d2a9114ad6aea780977df8a Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 1 Nov 2016 00:02:44 +0100 Subject: [PATCH 2/4] Provide test case --- .../server/deploy/parse/AnnotationFields.java | 6 +- .../avaje/tests/basic/TestAnnotationBase.java | 197 ++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/avaje/tests/basic/TestAnnotationBase.java diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 7d6b988c5..1ebefdec8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -261,9 +261,11 @@ public class AnnotationFields extends AnnotationParser { // take the max size of all @Size annotations int maxSize = -1; for (Size size : getAll(prop, Size.class)) { - maxSize = Math.max(maxSize, size.max()); + if (size.max() < Integer.MAX_VALUE) { + maxSize = Math.max(maxSize, size.max()); + } } - if (maxSize !=1 && maxSize < Integer.MAX_VALUE) { + if (maxSize != -1) { prop.setDbLength(maxSize); } } diff --git a/src/test/java/com/avaje/tests/basic/TestAnnotationBase.java b/src/test/java/com/avaje/tests/basic/TestAnnotationBase.java new file mode 100644 index 000000000..64565a9cc --- /dev/null +++ b/src/test/java/com/avaje/tests/basic/TestAnnotationBase.java @@ -0,0 +1,197 @@ +package com.avaje.tests.basic; + +import static org.junit.Assert.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; + +import javax.persistence.Entity; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.annotation.EbeanDDL; +import com.avaje.ebean.annotation.Where; +import com.avaje.ebean.config.dbplatform.H2Platform; +import com.avaje.ebean.config.dbplatform.MySqlPlatform; +import com.avaje.ebean.config.dbplatform.OraclePlatform; +import com.avaje.ebean.config.dbplatform.PostgresPlatform; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.parse.AnnotationBase; +import com.avaje.tests.model.basic.ValidationGroupSomething; + + +public class TestAnnotationBase extends BaseTestCase { + + @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Where(clause = "SELECT 'mysql' from 1", platforms = MySqlPlatform.class) + @Where(clause = "SELECT 'h2' from 1", platforms = H2Platform.class) + @Where(clause = "SELECT 'other' from 1") + public @interface MetaTest { + + } + + @Entity + public static class TestAnnotationBaseEntity { + + @Where(clause = "SELECT 'mysql' from 1", platforms = MySqlPlatform.class) + @Where(clause = "SELECT 'h2' from 1", platforms = H2Platform.class) + @Where(clause = "SELECT 'other' from 1") + private String direct; + + @MetaTest + private String meta; + + + @MetaTest + @Where(clause = "SELECT 'oracle' from 1", platforms = OraclePlatform.class) + private String mixed; + + @Size.List({ + @Size(max=10, message="max length for you is 10"), + @Size(min=1), + @Size(max=40, message="max value for you is 40", groups = ValidationGroupSomething.class) + }) + private String constraintAnnotation; + + @NotNull + private String null1; + + + @NotNull(groups = ValidationGroupSomething.class) + private String null2; + + @NotNull(groups = {ValidationGroupSomething.class, EbeanDDL.class}) + private String null3; + + + public String getDirect() { + return direct; + } + + public void setDirect(String direct) { + this.direct = direct; + } + + public String getMeta() { + return meta; + } + + public void setMeta(String meta) { + this.meta = meta; + } + + public String getMixed() { + return mixed; + } + + public void setMixed(String mixed) { + this.mixed = mixed; + } + + public String getConstraintAnnotation() { + return constraintAnnotation; + } + + public void setConstraintAnnotation(String constraintAnnotation) { + this.constraintAnnotation = constraintAnnotation; + } + + public String getNull1() { + return null1; + } + + public void setNull1(String null1) { + this.null1 = null1; + } + + public String getNull2() { + return null2; + } + + public void setNull2(String null2) { + this.null2 = null2; + } + + public String getNull3() { + return null3; + } + + public void setNull3(String null3) { + this.null3 = null3; + } + } + + + @Test + public void testFindMaxSize() throws NoSuchFieldException, SecurityException { + BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class); + BeanProperty bp = descriptor.findBeanProperty("constraintAnnotation"); + assertEquals(40, bp.getDbLength()); + } + + @Test + public void testNotNullWithGroup() throws NoSuchFieldException, SecurityException { + BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class); + + BeanProperty bp = descriptor.findBeanProperty("null1"); + assertFalse(bp.isNullable()); + + bp = descriptor.findBeanProperty("null2"); + assertTrue(bp.isNullable()); + + bp = descriptor.findBeanProperty("null3"); + assertFalse(bp.isNullable()); + } + + @Test + public void testFindAnnotation() throws NoSuchFieldException, SecurityException { + Field fld = TestAnnotationBaseEntity.class.getDeclaredField("direct"); + String s; + + s= AnnotationBase.findAnnotation(fld, Where.class, MySqlPlatform.class).clause(); + assertEquals("SELECT 'mysql' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, H2Platform.class).clause(); + assertEquals("SELECT 'h2' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, PostgresPlatform.class).clause(); + assertEquals("SELECT 'other' from 1",s); + + // meta + fld = TestAnnotationBaseEntity.class.getDeclaredField("meta"); + + s= AnnotationBase.findAnnotation(fld, Where.class, MySqlPlatform.class).clause(); + assertEquals("SELECT 'mysql' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, H2Platform.class).clause(); + assertEquals("SELECT 'h2' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, PostgresPlatform.class).clause(); + assertEquals("SELECT 'other' from 1",s); + + + // mixed + fld = TestAnnotationBaseEntity.class.getDeclaredField("mixed"); + + s= AnnotationBase.findAnnotation(fld, Where.class, MySqlPlatform.class).clause(); + assertEquals("SELECT 'mysql' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, H2Platform.class).clause(); + assertEquals("SELECT 'h2' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, PostgresPlatform.class).clause(); + assertEquals("SELECT 'other' from 1",s); + + s= AnnotationBase.findAnnotation(fld, Where.class, OraclePlatform.class).clause(); + assertEquals("SELECT 'oracle' from 1",s); + } + +} From 6142dd34f43a765c5f62381b26ec1c9085040e67 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 1 Nov 2016 00:27:12 +0100 Subject: [PATCH 3/4] No effective code change - removed unused imports --- .../avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java | 1 - .../ebeaninternal/server/deploy/parse/AnnotationClass.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java index 9048a3d69..f0d8d9b45 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -18,7 +18,6 @@ import java.util.concurrent.ConcurrentMap; import javax.persistence.JoinColumn; import javax.persistence.JoinColumns; -import javax.validation.constraints.Size; /** * Provides some base methods for processing deployment annotations. diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java index 32de78996..22a9c11ed 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -7,7 +7,6 @@ import com.avaje.ebean.annotation.Draftable; import com.avaje.ebean.annotation.DraftableElement; import com.avaje.ebean.annotation.History; import com.avaje.ebean.annotation.Index; -import com.avaje.ebean.annotation.Indices; import com.avaje.ebean.annotation.ReadAudit; import com.avaje.ebean.annotation.UpdateMode; import com.avaje.ebean.annotation.View; @@ -24,7 +23,6 @@ import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.Entity; -import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; From 64c67e37556040fec3e0068e4514b9fee83cc43a Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Tue, 1 Nov 2016 00:43:17 +0100 Subject: [PATCH 4/4] removed debug code and completed documentation --- .../server/deploy/parse/AnnotationBase.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java index f0d8d9b45..21df2ac49 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBase.java @@ -64,22 +64,20 @@ public abstract class AnnotationBase { Field field = prop.getField(); if (field != null) { a = findAnnotation(field, annClass); - if (a != findAnnotation(field, annClass, databasePlatform.getClass())) { - System.out.println("Difference (field) " + field + ", " + annClass); - } } if (a == null) { Method method = prop.getReadMethod(); if (method != null) { a = findAnnotation(method, annClass); - if (a != findAnnotation(method, annClass, databasePlatform.getClass())) { - System.out.println("Difference (method) " + 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();