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 35b24c60e..f53de86ee 100644 --- a/src/main/java/com/avaje/ebean/annotation/Formula.java +++ b/src/main/java/com/avaje/ebean/annotation/Formula.java @@ -1,8 +1,10 @@ package com.avaje.ebean.annotation; import com.avaje.ebean.Query; +import com.avaje.ebean.config.dbplatform.DatabasePlatform; 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; @@ -66,6 +68,7 @@ import java.lang.annotation.Target; */ @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(Formula.List.class) public @interface Formula { /** @@ -100,5 +103,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 46ecc25ea..b73e81100 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. @@ -36,6 +39,7 @@ import java.lang.annotation.Target; */ @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(Where.List.class) public @interface Where { /** @@ -46,5 +50,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 d810f58ce..54211867a 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 e597d8a12..064de0d4d 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 64242e408..f95a19901 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 1b3d8acd5..c116b6ed9 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,13 @@ 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; /** * Provides some base methods for processing deployment annotations. @@ -38,12 +46,18 @@ 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; @@ -52,14 +66,36 @@ public abstract class AnnotationBase { a = findAnnotation(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); } } 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. *

@@ -69,7 +105,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 +127,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 +169,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 +189,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 +205,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 9aac99081..78974b3d2 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; @@ -19,11 +18,12 @@ 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; import javax.persistence.Entity; -import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.UniqueConstraint; @@ -123,15 +123,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())); } @@ -192,15 +185,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 8e04be070..b8055b1fa 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; /** @@ -261,19 +262,22 @@ 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) { + // take the max size of all @Size annotations + int maxSize = -1; + for (Size size : getAll(prop, Size.class)) { if (size.max() < Integer.MAX_VALUE) { - // explicitly specify a version column - prop.setDbLength(size.max()); + maxSize = Math.max(maxSize, size.max()); } } + if (maxSize != -1) { + prop.setDbLength(maxSize); + } } EmbeddedColumns columns = get(prop, EmbeddedColumns.class); @@ -320,15 +324,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); } } @@ -388,14 +385,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 da7ac51a0..31d269a3e 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,26 +54,15 @@ public abstract class AnnotationParser extends AnnotationBase { */ protected void readEmbeddedAttributeOverrides(DeployBeanPropertyAssocOne prop) { - AttributeOverrides attrOverrides = get(prop, AttributeOverrides.class); - if (attrOverrides != null) { + Set attrOverrides = getAll(prop, AttributeOverride.class); + if (!attrOverrides.isEmpty()) { HashMap propMap = new HashMap<>(); - AttributeOverride[] aoArray = attrOverrides.value(); - for (AttributeOverride anAoArray : aoArray) { - String propName = anAoArray.name(); - String columnName = anAoArray.column().name(); - - propMap.put(propName, columnName); + for (AttributeOverride attrOverride : attrOverrides) { + propMap.put(attrOverride.name(), attrOverride.column().name()); } prop.getDeployEmbedded().putAll(propMap); } - AttributeOverride attrOverride = get(prop, AttributeOverride.class); - if (attrOverride != null) { - HashMap propMap = new HashMap<>(); - propMap.put(attrOverride.name(), attrOverride.column().name()); - prop.getDeployEmbedded().putAll(propMap); - } - } protected void readColumn(Column columnAnn, DeployBeanProperty prop) { @@ -101,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; + } } 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); + } + +}