diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/GeneratorType.java b/hibernate-core/src/main/java/org/hibernate/annotations/GeneratorType.java new file mode 100644 index 0000000000..fbd2a16217 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/GeneratorType.java @@ -0,0 +1,59 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.ValueGenerator; +import org.hibernate.tuple.VmValueGeneration; + +/** + * Marks a property as generated, specifying the {@link ValueGenerator} type to be used for generating the value. It is + * the responsibility of the client to ensure that a generator type is specified which matches the data type of the + * annotated property. + * + * @author Gunnar Morling + */ +@ValueGenerationType( generatedBy = VmValueGeneration.class ) +@Retention( RetentionPolicy.RUNTIME ) +@Target( value = { ElementType.FIELD, ElementType.METHOD } ) +public @interface GeneratorType { + + /** + * The value generator type + * + * @return the value generator type + */ + Class extends ValueGenerator>> type(); + + /** + * When to generate the value, either only during insert or during insert and update of the hosting entity. + * + * @return the time of generation + */ + GenerationTime when() default GenerationTime.ALWAYS; +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java b/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java new file mode 100644 index 0000000000..f2139419f8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/ValueGenerationType.java @@ -0,0 +1,60 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.tuple.AnnotationValueGeneration; + +/** + * Marks an annotation type as a generator annotation type. + *
+ * Adding a generator annotation to an entity property causes the value of the property to be generated upon insert + * or update of the owning entity. Not more than one generator annotation may be placed on a given property. + *
+ * Each generator annotation type is associated with a {@link AnnotationValueGeneration} which implements the strategy + * for generating the value. Generator annotation types may define arbitrary custom attributes, e.g. allowing the + * client to configure the generation timing (if applicable) or other settings taking an effect on the value generation. + * The corresponding implementation can retrieve these settings from the annotation instance passed to + * {@link AnnotationValueGeneration#initialize(java.lang.annotation.Annotation, Class)}. + *
+ * Custom generator annotation types must have retention policy {@link RetentionPolicy#RUNTIME}.
+
+ * @author Gunnar Morling
+ */
+@Target( value = ElementType.ANNOTATION_TYPE )
+@Retention( RetentionPolicy.RUNTIME )
+public @interface ValueGenerationType {
+
+ /**
+ * The type of value generation associated with the annotated value generator annotation type. The referenced
+ * generation type must be parameterized with the type of the given generator annotation.
+ *
+ * @return the value generation type
+ */
+ Class extends AnnotationValueGeneration>> generatedBy();
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
index 8ab07549c6..f1734d340c 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PropertyBinder.java
@@ -23,6 +23,7 @@
*/
package org.hibernate.cfg.annotations;
+import java.lang.annotation.Annotation;
import java.util.Map;
import javax.persistence.EmbeddedId;
@@ -30,11 +31,13 @@ import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.AnnotationException;
+import org.hibernate.HibernateException;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.OptimisticLock;
+import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
@@ -56,10 +59,10 @@ import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
+import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.ValueGenerator;
-
import org.jboss.logging.Logger;
/**
@@ -278,7 +281,7 @@ public class PropertyBinder {
if ( property != null ) {
prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) );
}
-
+
NaturalId naturalId = property != null ? property.getAnnotation( NaturalId.class ) : null;
if ( naturalId != null ) {
if ( ! entityBinder.isRootEntity() ) {
@@ -289,11 +292,11 @@ public class PropertyBinder {
}
prop.setNaturalIdentifier( true );
}
-
+
// HHH-4635 -- needed for dialect-specific property ordering
Lob lob = property != null ? property.getAnnotation( Lob.class ) : null;
prop.setLob( lob != null );
-
+
prop.setInsertable( insertable );
prop.setUpdateable( updatable );
@@ -330,15 +333,24 @@ public class PropertyBinder {
}
private ValueGeneration determineValueGenerationStrategy(XProperty property) {
- // for now, we just handle the legacy '@Generated' annotation
- Generated generatedAnnotation = property.getAnnotation( Generated.class );
- if ( generatedAnnotation == null
- || generatedAnnotation.value() == null
- || generatedAnnotation.value() == GenerationTime.NEVER ) {
+ ValueGeneration annotationValueGeneration = getValueGenerationFromAnnotations( property );
+ ValueGeneration legacyValueGeneration = getLegacyValueGeneration( property );
+
+ if ( annotationValueGeneration == null && legacyValueGeneration == null ) {
return NoValueGeneration.INSTANCE;
}
+ else if ( annotationValueGeneration != null && legacyValueGeneration != null ) {
+ throw new AnnotationException(
+ "@Generated and a generator annotation must not be specified at the same time:" + StringHelper.qualify(
+ holder.getPath(),
+ name
+ )
+ );
+ }
- final GenerationTiming when = generatedAnnotation.value().getEquivalent();
+ final GenerationTiming when = annotationValueGeneration != null ?
+ annotationValueGeneration.getGenerationTiming() :
+ legacyValueGeneration.getGenerationTiming();
if ( property.isAnnotationPresent( javax.persistence.Version.class ) && when == GenerationTiming.INSERT ) {
throw new AnnotationException(
@@ -347,12 +359,103 @@ public class PropertyBinder {
);
}
- insertable = false;
- if ( when == GenerationTiming.ALWAYS ) {
- updatable = false;
+ if ( legacyValueGeneration != null ) {
+ insertable = false;
+ if ( when == GenerationTiming.ALWAYS ) {
+ updatable = false;
+ }
}
- return new LegacyValueGeneration( when );
+ return annotationValueGeneration != null ? annotationValueGeneration : legacyValueGeneration;
+ }
+
+ private ValueGeneration getLegacyValueGeneration(XProperty property) {
+ Generated generatedAnnotation = property.getAnnotation( Generated.class );
+
+ if ( generatedAnnotation != null && generatedAnnotation.value() != null && generatedAnnotation.value() != GenerationTime.NEVER ) {
+ return new LegacyValueGeneration( generatedAnnotation.value().getEquivalent() );
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the value generation strategy for the given property, if any.
+ */
+ private ValueGeneration getValueGenerationFromAnnotations(XProperty property) {
+ AnnotationValueGeneration> valueGeneration = null;
+
+ for ( Annotation annotation : property.getAnnotations() ) {
+ AnnotationValueGeneration> candidate = getValueGenerationFromAnnotation( property, annotation );
+
+ if ( candidate != null ) {
+ if ( valueGeneration != null ) {
+ throw new AnnotationException(
+ "Only one generator annotation is allowed:" + StringHelper.qualify(
+ holder.getPath(),
+ name
+ )
+ );
+ }
+ else {
+ valueGeneration = candidate;
+ }
+ }
+ }
+
+ return valueGeneration;
+ }
+
+ /**
+ * In case the given annotation is a value generator annotation, the corresponding value generation strategy to be
+ * applied to the given property is returned, {@code null} otherwise.
+ */
+ private AnnotationValueGeneration getValueGenerationFromAnnotation(
+ XProperty property,
+ A annotation) {
+ ValueGenerationType generatorAnnotation = annotation.annotationType()
+ .getAnnotation( ValueGenerationType.class );
+
+ if ( generatorAnnotation == null ) {
+ return null;
+ }
+
+ Class extends AnnotationValueGeneration>> generationType = generatorAnnotation.generatedBy();
+ return instantiateAndInitializeValueGeneration(
+ annotation, generationType, property
+ );
+ }
+
+ /**
+ * Instantiates the given generator annotation type, initializing it with the given instance of the corresponding
+ * generator annotation and the property's type.
+ */
+ private AnnotationValueGeneration instantiateAndInitializeValueGeneration(
+ A annotation,
+ Class extends AnnotationValueGeneration>> generationType,
+ XProperty property) {
+
+ try {
+ // This will cause a CCE in case the generation type doesn't match the annotation type; As this would be a
+ // programming error of the generation type developer and thus should show up during testing, we don't
+ // check this explicitly; If required, this could be done e.g. using ClassMate
+ @SuppressWarnings( "unchecked" )
+ AnnotationValueGeneration valueGeneration = (AnnotationValueGeneration) generationType.newInstance();
+ valueGeneration.initialize( annotation, mappings.getReflectionManager().toClass(property.getType() ) );
+
+ return valueGeneration;
+ }
+ catch (HibernateException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new AnnotationException(
+ "Exception occurred during processing of generator annotation:" + StringHelper.qualify(
+ holder.getPath(),
+ name
+ ), e
+ );
+ }
}
private static class NoValueGeneration implements ValueGeneration {
diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java
index 9a7f6811cc..a1b289b540 100644
--- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java
@@ -296,13 +296,13 @@ public final class ReflectHelper {
* @return The default constructor.
* @throws PropertyNotFoundException Indicates there was not publicly accessible, no-arg constructor (todo : why PropertyNotFoundException???)
*/
- public static Constructor getDefaultConstructor(Class clazz) throws PropertyNotFoundException {
+ public static