mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Merge pull request #847 from FOCONIS/feature/platform-dependent-annotations
platform dependent @Formula and @Where annotations
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
/**
|
||||
* special validation group for @NotNull annotation to enforce <code>NOT NULL</code> generation on DDL.
|
||||
* Normally if you put the {@link NotNull} annotation on a property, Ebean will only generate a
|
||||
* <code>NOT NULL</code> in DDL if you do not change the validation-groups!
|
||||
*/
|
||||
public interface EbeanDDL {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
* }</pre>
|
||||
*/
|
||||
String join() default "";
|
||||
|
||||
Class<? extends DatabasePlatform>[] platforms() default {};
|
||||
|
||||
/**
|
||||
* Repeatable support for {@link Formula}.
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface List {
|
||||
|
||||
Formula[] value() default {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
* </p>
|
||||
*/
|
||||
String clause();
|
||||
|
||||
/**
|
||||
* The platform where this annotation is active. Default: any platform
|
||||
*/
|
||||
Class<? extends DatabasePlatform>[] platforms() default {};
|
||||
|
||||
/**
|
||||
* Repeatable support for {@link Where}.
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface List {
|
||||
|
||||
Where[] value() default {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JoinColumn> jcSet, BeanTable beanTable) {
|
||||
for (JoinColumn jc: jcSet) {
|
||||
addJoinColumn(order, jc, beanTable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the join columns.
|
||||
*/
|
||||
|
||||
@@ -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<JoinColumn> 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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* Looks first at the field and then at the getter method.
|
||||
* </p>
|
||||
* <p>
|
||||
* If a <code>repeatable</code> 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.
|
||||
* </p>
|
||||
* <p>
|
||||
*/
|
||||
protected <T extends Annotation> T get(DeployBeanProperty prop, Class<T> 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 <T extends Annotation> Set<T> getAll(DeployBeanProperty prop, Class<T> annClass) {
|
||||
Set<T> 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.
|
||||
* <p>
|
||||
@@ -69,7 +105,7 @@ public abstract class AnnotationBase {
|
||||
protected <T extends Annotation> T find(DeployBeanProperty prop, Class<T> 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 {
|
||||
* <p>
|
||||
* <strong>Warning</strong>: 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 extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> 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 extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType,
|
||||
Class<? extends DatabasePlatform> databasePlatform) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> 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 extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> 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}.
|
||||
* <p>
|
||||
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
|
||||
* <p>
|
||||
* <strong>Warning</strong>: 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 <A extends Annotation> Set<A> findAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> ret = new LinkedHashSet<A>();
|
||||
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<Annotation>());
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm avoiding endless recursion by tracking which
|
||||
* annotations have already been visited.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends Annotation> void findMetaAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType, Set<A> ret, Set<Annotation> 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<Annotation, Method> valueMethods = new ConcurrentHashMap<Annotation, Method>();
|
||||
private static final Method nullMethod = getNullMethod();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the <code>value()</code> method for a possible containerAnnotation.
|
||||
* Method is retuned only, if its signature is <code>array of containingType</code>.
|
||||
*/
|
||||
private static <A extends Annotation> Method getRepeatableValueMethod(
|
||||
Annotation containerAnnotation, Class<A> 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 <code>Set<T> anns</code> for this platform.
|
||||
* To distinguish between platforms, annotation type <code>T</code> must define
|
||||
* a method withthis signature:
|
||||
* <p>
|
||||
* <code>Class<? extends DatabasePlatform>[] platforms() default {};</code>
|
||||
* </p>
|
||||
* The finding rules are:
|
||||
* <ol>
|
||||
* <li>Check if T has method "platforms" if not, return <code>ann[0]</code></code>
|
||||
* <li>find the annotation that is defined for <code>databasePlatform</code></li>
|
||||
* <li>otherwise return the annotation for default platform (platforms = {})</li>
|
||||
* <li>return null
|
||||
* </ol>
|
||||
* (This mechanism is currently used by {@link Where} and {@link Formula})
|
||||
*/
|
||||
public static <T extends Annotation> T getPlatformMatchingAnnotation(Set<T> anns, Class<? extends DatabasePlatform> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Index> 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<NamedQuery> namedQueries = AnnotationBase.findAnnotations(cls,NamedQuery.class);
|
||||
for (NamedQuery namedQuery : namedQueries) {
|
||||
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Index> 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());
|
||||
|
||||
@@ -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<AttributeOverride> attrOverrides = getAll(prop, AttributeOverride.class);
|
||||
if (!attrOverrides.isEmpty()) {
|
||||
HashMap<String, String> 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<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TestAnnotationBaseEntity> descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class);
|
||||
BeanProperty bp = descriptor.findBeanProperty("constraintAnnotation");
|
||||
assertEquals(40, bp.getDbLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotNullWithGroup() throws NoSuchFieldException, SecurityException {
|
||||
BeanDescriptor<TestAnnotationBaseEntity> 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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user