From 3bb4126e2652425f89c6ffb5db0b0aace1ecd004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Camilo?= Date: Wed, 17 Feb 2021 05:04:43 +0000 Subject: [PATCH] #2169 Support for jakarta.validation.constraints.NotNull and (#2170) jakarta.validation.constraints.Size --- CONFIGURATION.md | 2 +- ebean-api/pom.xml | 6 ++ .../java/io/ebean/config/ClassLoadConfig.java | 7 ++ .../java/io/ebean/config/DatabaseConfig.java | 15 ++-- ebean-core/pom.xml | 6 ++ .../deploy/meta/DeployBeanProperty.java | 11 ++- .../deploy/parse/AnnotationAssocOnes.java | 12 ++- .../server/deploy/parse/AnnotationFields.java | 37 ++++++-- .../server/deploy/parse/AnnotationParser.java | 9 +- .../server/deploy/parse/DeployUtil.java | 8 +- .../InitMetaJakartaValidationAnnotation.java | 11 +++ ...=> InitMetaJavaxValidationAnnotation.java} | 2 +- .../deploy/parse/ReadAnnotationConfig.java | 6 ++ .../server/deploy/parse/ReadAnnotations.java | 5 +- .../org/tests/basic/TestAnnotationBase.java | 84 +++++++++++++++++++ 15 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJakartaValidationAnnotation.java rename ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/{InitMetaValidationAnnotation.java => InitMetaJavaxValidationAnnotation.java} (85%) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 16fa6d698..46be98f0b 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -293,6 +293,6 @@ ebean.tenant.schemaProvider ebean.updateAllPropertiesInBatch ebean.updateChangesOnly ebean.updatesDeleteMissingChildren -ebean.useJavaxValidationNotNull +ebean.useValidationNotNull ebean.useJtaTransactionManager diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 0d8e67b00..1edc0a655 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -109,6 +109,12 @@ 1.1.0.Final true + + jakarta.validation + jakarta.validation-api + 3.0.0 + true + javax.servlet diff --git a/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java b/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java index 821905405..764fa86a1 100644 --- a/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/ClassLoadConfig.java @@ -40,6 +40,13 @@ public class ClassLoadConfig { return isPresent("javax.validation.constraints.NotNull"); } + /** + * Return true if jakarta validation annotations like Size and NotNull are present. + */ + public boolean isJakartaValidationAnnotationsPresent() { + return isPresent("jakarta.validation.constraints.NotNull"); + } + /** * Return true if javax PostConstruct annotation is present (maybe not in java9). * If not we don't support PostConstruct lifecycle events. diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java index 7ea8e56e9..036a39040 100644 --- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java +++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java @@ -489,7 +489,7 @@ public class DatabaseConfig { * Should the javax.validation.constraints.NotNull enforce a notNull column in DB. * If set to false, use io.ebean.annotation.NotNull or Column(nullable=true). */ - private boolean useJavaxValidationNotNull = true; + private boolean useValidationNotNull = true; /** * Generally we want to perform L2 cache notification in the background and not impact @@ -2809,7 +2809,7 @@ public class DatabaseConfig { enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions); notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground); useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager); - useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull); + useValidationNotNull = p.getBoolean("useValidationNotNull", useValidationNotNull); autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource); idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic); @@ -3056,20 +3056,21 @@ public class DatabaseConfig { /** * Returns if we use javax.validation.constraints.NotNull */ - public boolean isUseJavaxValidationNotNull() { - return useJavaxValidationNotNull; + public boolean isUseValidationNotNull() { + return useValidationNotNull; } /** - * Controls if Ebean should ignore &x64;javax.validation.contstraints.NotNull + * Controls if Ebean should ignore &x64;javax.validation.contstraints.NotNull or + * &x64;jakarta.validation.contstraints.NotNull * with respect to generating a NOT NULL column. *

* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL. * Set this to false and the javax NotNull annotation is effectively ignored (and * we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation. */ - public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) { - this.useJavaxValidationNotNull = useJavaxValidationNotNull; + public void setUseValidationNotNull(boolean useValidationNotNull) { + this.useValidationNotNull = useValidationNotNull; } /** diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 20d9cfe6c..dcdad1b0e 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -138,6 +138,12 @@ 1.1.0.Final true + + jakarta.validation + jakarta.validation-api + 3.0.0 + true + joda-time diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index df3a46094..bccc21bcc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -1140,7 +1140,7 @@ public class DeployBeanProperty { return result; } - public List getMetaAnnotationSize() { + public List getMetaAnnotationJavaxSize() { final List size = getMetaAnnotations(Size.class); final List lists = getMetaAnnotations(Size.List.class); for (Size.List list : lists) { @@ -1149,6 +1149,15 @@ public class DeployBeanProperty { return size; } + public List getMetaAnnotationJakartaSize() { + final List size = getMetaAnnotations(jakarta.validation.constraints.Size.class); + final List lists = getMetaAnnotations(jakarta.validation.constraints.Size.List.class); + for (jakarta.validation.constraints.Size.List list : lists) { + Collections.addAll(size, list.value()); + } + return size; + } + public Formula getMetaAnnotationFormula(Platform platform) { Formula fallback = null; for (Annotation ann : metaAnnotations) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java index 6750633cb..aa38d93e6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java @@ -4,13 +4,11 @@ import io.ebean.annotation.DbForeignKey; import io.ebean.annotation.FetchPreference; import io.ebean.annotation.TenantId; import io.ebean.annotation.Where; -import io.ebean.config.BeanNotRegisteredException; import io.ebean.config.NamingConvention; import io.ebeaninternal.server.deploy.BeanDescriptorManager; import io.ebeaninternal.server.deploy.BeanTable; import io.ebeaninternal.server.deploy.PropertyForeignKey; import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; -import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn; import io.ebeaninternal.server.query.SqlJoinType; @@ -117,7 +115,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc { if (nonNull != null) { prop.setNullable(false); } - if (validationAnnotations) { + if (javaxValidationAnnotations) { NotNull notNull = get(prop, NotNull.class); if (notNull != null && isEbeanValidationGroups(notNull.groups())) { prop.setNullable(false); @@ -125,6 +123,14 @@ public class AnnotationAssocOnes extends AnnotationAssoc { prop.getTableJoin().setType(SqlJoinType.INNER); } } + if (jakartaValidationAnnotations) { + jakarta.validation.constraints.NotNull notNull = get(prop, jakarta.validation.constraints.NotNull.class); + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { + prop.setNullable(false); + // overrides optional attribute of ManyToOne etc + prop.getTableJoin().setType(SqlJoinType.INNER); + } + } // check for manually defined joins BeanTable beanTable = prop.getBeanTable(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index aafbfa182..413929098 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -258,19 +258,38 @@ public class AnnotationFields extends AnnotationParser { } private void initValidation(DeployBeanProperty prop) { - NotNull notNull = get(prop, NotNull.class); - if (notNull != null && isEbeanValidationGroups(notNull.groups())) { - // Not null on all validation groups so enable - // DDL generation of Not Null Constraint - prop.setNullable(false); + if (javaxValidationAnnotations) { + NotNull notNull = get(prop, NotNull.class); + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { + // Not null on all validation groups so enable + // DDL generation of Not Null Constraint + prop.setNullable(false); + } + } + if (jakartaValidationAnnotations) { + jakarta.validation.constraints.NotNull notNull = get(prop, jakarta.validation.constraints.NotNull.class); + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { + // Not null on all validation groups so enable + // DDL generation of Not Null Constraint + prop.setNullable(false); + } } if (!prop.isLob()) { // take the max size of all @Size annotations int maxSize = -1; - for (Size size : prop.getMetaAnnotationSize()) { - if (size.max() < Integer.MAX_VALUE) { - maxSize = Math.max(maxSize, size.max()); + if (javaxValidationAnnotations) { + for (Size size : prop.getMetaAnnotationJavaxSize()) { + if (size.max() < Integer.MAX_VALUE) { + maxSize = Math.max(maxSize, size.max()); + } + } + } + if (jakartaValidationAnnotations) { + for (jakarta.validation.constraints.Size size : prop.getMetaAnnotationJakartaSize()) { + if (size.max() < Integer.MAX_VALUE) { + maxSize = Math.max(maxSize, size.max()); + } } } if (maxSize != -1) { @@ -280,7 +299,7 @@ public class AnnotationFields extends AnnotationParser { } private void initTenantId(DeployBeanProperty prop) { - if (validationAnnotations) { + if (javaxValidationAnnotations || jakartaValidationAnnotations) { initValidation(prop); } if (has(prop, TenantId.class)) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java index 64851135d..8a27981b4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationParser.java @@ -26,14 +26,17 @@ public abstract class AnnotationParser extends AnnotationBase { final Class beanType; - final boolean validationAnnotations; + final boolean javaxValidationAnnotations; + + final boolean jakartaValidationAnnotations; final ReadAnnotationConfig readConfig; AnnotationParser(DeployBeanInfo info, ReadAnnotationConfig readConfig) { super(info.getUtil()); this.readConfig = readConfig; - this.validationAnnotations = readConfig.isJavaxValidationAnnotations(); + this.javaxValidationAnnotations = readConfig.isJavaxValidationAnnotations(); + this.jakartaValidationAnnotations = readConfig.isJakartaValidationAnnotations(); this.info = info; this.beanType = info.getDescriptor().getBeanType(); this.descriptor = info.getDescriptor(); @@ -132,7 +135,7 @@ public abstract class AnnotationParser extends AnnotationBase { * can be applied to DDL generation. */ boolean isEbeanValidationGroups(Class[] groups) { - if (!util.isUseJavaxValidationNotNull()) { + if (!util.isUseValidationNotNull()) { return false; } return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index 3a0f8d826..65c7cf45e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -57,7 +57,7 @@ public class DeployUtil { private final Encryptor bytesEncryptor; - private final boolean useJavaxValidationNotNull; + private final boolean useValidationNotNull; public DeployUtil(TypeManager typeMgr, DatabaseConfig config) { this.typeManager = typeMgr; @@ -67,7 +67,7 @@ public class DeployUtil { this.encryptKeyManager = config.getEncryptKeyManager(); Encryptor be = config.getEncryptor(); this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor(); - this.useJavaxValidationNotNull = config.isUseJavaxValidationNotNull(); + this.useValidationNotNull = config.isUseValidationNotNull(); } public TypeManager getTypeManager() { @@ -286,8 +286,8 @@ public class DeployUtil { return type.equals(String.class); } - boolean isUseJavaxValidationNotNull() { - return useJavaxValidationNotNull; + boolean isUseValidationNotNull() { + return useValidationNotNull; } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJakartaValidationAnnotation.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJakartaValidationAnnotation.java new file mode 100644 index 000000000..41ea9a8c7 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJakartaValidationAnnotation.java @@ -0,0 +1,11 @@ +package io.ebeaninternal.server.deploy.parse; + +import jakarta.validation.constraints.Size; + +class InitMetaJakartaValidationAnnotation { + + static void init(ReadAnnotationConfig readConfig) { + readConfig.addMetaAnnotation(Size.class); + readConfig.addMetaAnnotation(Size.List.class); + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaValidationAnnotation.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJavaxValidationAnnotation.java similarity index 85% rename from ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaValidationAnnotation.java rename to ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJavaxValidationAnnotation.java index 9d5ac9e30..495d26ff5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaValidationAnnotation.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/InitMetaJavaxValidationAnnotation.java @@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy.parse; import javax.validation.constraints.Size; -class InitMetaValidationAnnotation { +class InitMetaJavaxValidationAnnotation { static void init(ReadAnnotationConfig readConfig) { readConfig.addMetaAnnotation(Size.class); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java index bf96849b0..d7d46e9ce 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotationConfig.java @@ -21,6 +21,7 @@ class ReadAnnotationConfig { private final boolean disableL2Cache; private final boolean eagerFetchLobs; private final boolean javaxValidationAnnotations; + private final boolean jakartaValidationAnnotations; private final boolean jacksonAnnotations; private final boolean idGeneratorAutomatic; @@ -34,6 +35,7 @@ class ReadAnnotationConfig { this.eagerFetchLobs = config.isEagerFetchLobs(); this.idGeneratorAutomatic = config.isIdGeneratorAutomatic(); this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent(); + this.jakartaValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJakartaValidationAnnotationsPresent(); this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent(); this.metaAnnotations.add(Column.class); this.metaAnnotations.add(Formula.class); @@ -75,6 +77,10 @@ class ReadAnnotationConfig { return javaxValidationAnnotations; } + boolean isJakartaValidationAnnotations() { + return jakartaValidationAnnotations; + } + boolean isJacksonAnnotations() { return jacksonAnnotations; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java index 86a6a15f0..796a975aa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/ReadAnnotations.java @@ -15,7 +15,10 @@ public class ReadAnnotations { public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) { this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, config); if (readConfig.isJavaxValidationAnnotations()) { - InitMetaValidationAnnotation.init(readConfig); + InitMetaJavaxValidationAnnotation.init(readConfig); + } + if (readConfig.isJakartaValidationAnnotations()) { + InitMetaJakartaValidationAnnotation.init(readConfig); } if (readConfig.isJacksonAnnotations()) { InitMetaJacksonAnnotation.init(readConfig); diff --git a/ebean-core/src/test/java/org/tests/basic/TestAnnotationBase.java b/ebean-core/src/test/java/org/tests/basic/TestAnnotationBase.java index aa0e0e926..c5781174d 100644 --- a/ebean-core/src/test/java/org/tests/basic/TestAnnotationBase.java +++ b/ebean-core/src/test/java/org/tests/basic/TestAnnotationBase.java @@ -144,6 +144,69 @@ public class TestAnnotationBase extends BaseTestCase { } } + @Entity + public static class TestJakartaAnnotationBaseEntity extends MappedBaseEntity { + @Where(clause = "SELECT 'mysql' from 1", platforms = Platform.MYSQL) + @Where(clause = "SELECT 'h2' from 1", platforms = H2) + @Where(clause = "SELECT 'other' from 1") + private String direct; + + @MetaTest + private String meta; + + @MetaTest + @Where(clause = "SELECT 'oracle' from 1", platforms = Platform.ORACLE) + private String mixed; + + @jakarta.validation.constraints.Size.List({ + @jakarta.validation.constraints.Size(max = 10, message = "max length for you is 10"), + @jakarta.validation.constraints.Size(min = 1), + @jakarta.validation.constraints.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; + + private String null3; + + 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 SecurityException { @@ -152,6 +215,13 @@ public class TestAnnotationBase extends BaseTestCase { assertEquals(40, bp.getDbLength()); } + @Test + public void testFindJakartaMaxSize() throws SecurityException { + BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestJakartaAnnotationBaseEntity.class); + BeanProperty bp = descriptor.findProperty("constraintAnnotation"); + assertEquals(40, bp.getDbLength()); + } + @Test public void annotationClassIndexes() throws SecurityException { BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class); @@ -173,6 +243,20 @@ public class TestAnnotationBase extends BaseTestCase { assertTrue(bp.isNullable()); } + @Test + public void testJakartaNotNullWithGroup() throws SecurityException { + BeanDescriptor descriptor = spiEbeanServer().getBeanDescriptor(TestJakartaAnnotationBaseEntity.class); + + BeanProperty bp = descriptor.findProperty("null1"); + assertFalse(bp.isNullable()); + + bp = descriptor.findProperty("null2"); + assertTrue(bp.isNullable()); + + bp = descriptor.findProperty("null3"); + assertTrue(bp.isNullable()); + } + @Test public void testFindAnnotation() throws NoSuchFieldException, SecurityException {