diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java new file mode 100644 index 0000000000..4e2a630952 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenAnnotationReader.java @@ -0,0 +1,3084 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cfg.annotations.reflection.internal; + +import java.beans.Introspector; +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.AssociationOverride; +import javax.persistence.AssociationOverrides; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Basic; +import javax.persistence.Cacheable; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ColumnResult; +import javax.persistence.ConstructorResult; +import javax.persistence.Convert; +import javax.persistence.Converts; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.EntityResult; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.ExcludeDefaultListeners; +import javax.persistence.ExcludeSuperclassListeners; +import javax.persistence.FetchType; +import javax.persistence.FieldResult; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Index; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.MapKey; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.MapKeyJoinColumns; +import javax.persistence.MapKeyTemporal; +import javax.persistence.MappedSuperclass; +import javax.persistence.MapsId; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedNativeQuery; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.NamedStoredProcedureQueries; +import javax.persistence.NamedStoredProcedureQuery; +import javax.persistence.NamedSubgraph; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.OrderBy; +import javax.persistence.OrderColumn; +import javax.persistence.ParameterMode; +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PostRemove; +import javax.persistence.PostUpdate; +import javax.persistence.PrePersist; +import javax.persistence.PreRemove; +import javax.persistence.PreUpdate; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.PrimaryKeyJoinColumns; +import javax.persistence.QueryHint; +import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; +import javax.persistence.SequenceGenerator; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.SqlResultSetMappings; +import javax.persistence.StoredProcedureParameter; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; +import javax.persistence.Version; + +import org.hibernate.AnnotationException; +import org.hibernate.annotations.Any; +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.Columns; +import org.hibernate.annotations.ManyToAny; +import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor; +import org.hibernate.annotations.common.annotationfactory.AnnotationFactory; +import org.hibernate.annotations.common.reflection.AnnotationReader; +import org.hibernate.annotations.common.reflection.ReflectionUtil; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.cfg.annotations.reflection.PersistentAttributeFilter; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; + +import org.dom4j.Attribute; +import org.dom4j.Element; + +/** + * Encapsulates the overriding of Java annotations from an EJB 3.0 descriptor (orm.xml, ...). + * + * @author Paolo Perrotta + * @author Davide Marchignoli + * @author Emmanuel Bernard + * @author Hardy Ferentschik + */ +@SuppressWarnings("unchecked") +public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( JPAXMLOverriddenAnnotationReader.class ); + + private static final String SCHEMA_VALIDATION = "Activate schema validation for more information"; + private static final String WORD_SEPARATOR = "-"; + + private enum PropertyType { + PROPERTY, + FIELD, + METHOD + } + + private static final Map annotationToXml; + + static { + annotationToXml = new HashMap<>(); + annotationToXml.put( Entity.class, "entity" ); + annotationToXml.put( MappedSuperclass.class, "mapped-superclass" ); + annotationToXml.put( Embeddable.class, "embeddable" ); + annotationToXml.put( Table.class, "table" ); + annotationToXml.put( SecondaryTable.class, "secondary-table" ); + annotationToXml.put( SecondaryTables.class, "secondary-table" ); + annotationToXml.put( PrimaryKeyJoinColumn.class, "primary-key-join-column" ); + annotationToXml.put( PrimaryKeyJoinColumns.class, "primary-key-join-column" ); + annotationToXml.put( IdClass.class, "id-class" ); + annotationToXml.put( Inheritance.class, "inheritance" ); + annotationToXml.put( DiscriminatorValue.class, "discriminator-value" ); + annotationToXml.put( DiscriminatorColumn.class, "discriminator-column" ); + annotationToXml.put( SequenceGenerator.class, "sequence-generator" ); + annotationToXml.put( TableGenerator.class, "table-generator" ); + annotationToXml.put( NamedEntityGraph.class, "named-entity-graph" ); + annotationToXml.put( NamedEntityGraphs.class, "named-entity-graph" ); + annotationToXml.put( NamedQuery.class, "named-query" ); + annotationToXml.put( NamedQueries.class, "named-query" ); + annotationToXml.put( NamedNativeQuery.class, "named-native-query" ); + annotationToXml.put( NamedNativeQueries.class, "named-native-query" ); + annotationToXml.put( NamedStoredProcedureQuery.class, "named-stored-procedure-query" ); + annotationToXml.put( NamedStoredProcedureQueries.class, "named-stored-procedure-query" ); + annotationToXml.put( SqlResultSetMapping.class, "sql-result-set-mapping" ); + annotationToXml.put( SqlResultSetMappings.class, "sql-result-set-mapping" ); + annotationToXml.put( ExcludeDefaultListeners.class, "exclude-default-listeners" ); + annotationToXml.put( ExcludeSuperclassListeners.class, "exclude-superclass-listeners" ); + annotationToXml.put( AccessType.class, "access" ); + annotationToXml.put( AttributeOverride.class, "attribute-override" ); + annotationToXml.put( AttributeOverrides.class, "attribute-override" ); + annotationToXml.put( AttributeOverride.class, "association-override" ); + annotationToXml.put( AttributeOverrides.class, "association-override" ); + annotationToXml.put( AttributeOverride.class, "map-key-attribute-override" ); + annotationToXml.put( AttributeOverrides.class, "map-key-attribute-override" ); + annotationToXml.put( Id.class, "id" ); + annotationToXml.put( EmbeddedId.class, "embedded-id" ); + annotationToXml.put( GeneratedValue.class, "generated-value" ); + annotationToXml.put( Column.class, "column" ); + annotationToXml.put( Columns.class, "column" ); + annotationToXml.put( Temporal.class, "temporal" ); + annotationToXml.put( Lob.class, "lob" ); + annotationToXml.put( Enumerated.class, "enumerated" ); + annotationToXml.put( Version.class, "version" ); + annotationToXml.put( Transient.class, "transient" ); + annotationToXml.put( Basic.class, "basic" ); + annotationToXml.put( Embedded.class, "embedded" ); + annotationToXml.put( ManyToOne.class, "many-to-one" ); + annotationToXml.put( OneToOne.class, "one-to-one" ); + annotationToXml.put( OneToMany.class, "one-to-many" ); + annotationToXml.put( ManyToMany.class, "many-to-many" ); + annotationToXml.put( Any.class, "any" ); + annotationToXml.put( ManyToAny.class, "many-to-any" ); + annotationToXml.put( JoinTable.class, "join-table" ); + annotationToXml.put( JoinColumn.class, "join-column" ); + annotationToXml.put( JoinColumns.class, "join-column" ); + annotationToXml.put( MapKey.class, "map-key" ); + annotationToXml.put( OrderBy.class, "order-by" ); + annotationToXml.put( EntityListeners.class, "entity-listeners" ); + annotationToXml.put( PrePersist.class, "pre-persist" ); + annotationToXml.put( PreRemove.class, "pre-remove" ); + annotationToXml.put( PreUpdate.class, "pre-update" ); + annotationToXml.put( PostPersist.class, "post-persist" ); + annotationToXml.put( PostRemove.class, "post-remove" ); + annotationToXml.put( PostUpdate.class, "post-update" ); + annotationToXml.put( PostLoad.class, "post-load" ); + annotationToXml.put( CollectionTable.class, "collection-table" ); + annotationToXml.put( MapKeyClass.class, "map-key-class" ); + annotationToXml.put( MapKeyTemporal.class, "map-key-temporal" ); + annotationToXml.put( MapKeyEnumerated.class, "map-key-enumerated" ); + annotationToXml.put( MapKeyColumn.class, "map-key-column" ); + annotationToXml.put( MapKeyJoinColumn.class, "map-key-join-column" ); + annotationToXml.put( MapKeyJoinColumns.class, "map-key-join-column" ); + annotationToXml.put( OrderColumn.class, "order-column" ); + annotationToXml.put( Cacheable.class, "cacheable" ); + annotationToXml.put( Index.class, "index" ); + annotationToXml.put( ForeignKey.class, "foreign-key" ); + annotationToXml.put( Convert.class, "convert" ); + annotationToXml.put( Converts.class, "convert" ); + annotationToXml.put( ConstructorResult.class, "constructor-result" ); + } + + private final XMLContext xmlContext; + private final ClassLoaderAccess classLoaderAccess; + private final AnnotatedElement element; + private final String className; + private final String propertyName; + private final PropertyType propertyType; + private transient Annotation[] annotations; + private transient Map annotationsMap; + private transient List elementsForProperty; + private AccessibleObject mirroredAttribute; + + /** + * @deprecated Use {@link #JPAXMLOverriddenAnnotationReader(AnnotatedElement, XMLContext, BootstrapContext)} instead. + */ + public JPAXMLOverriddenAnnotationReader( + AnnotatedElement el, + XMLContext xmlContext, + ClassLoaderAccess classLoaderAccess) { + this.element = el; + this.xmlContext = xmlContext; + this.classLoaderAccess = classLoaderAccess; + + if ( el instanceof Class ) { + Class clazz = (Class) el; + className = clazz.getName(); + propertyName = null; + propertyType = null; + } + else if ( el instanceof Field ) { + Field field = (Field) el; + className = field.getDeclaringClass().getName(); + propertyName = field.getName(); + propertyType = PropertyType.FIELD; + String expectedGetter = "get" + Character.toUpperCase( propertyName.charAt( 0 ) ) + propertyName.substring( + 1 + ); + try { + mirroredAttribute = field.getDeclaringClass().getDeclaredMethod( expectedGetter ); + } + catch ( NoSuchMethodException e ) { + //no method + } + } + else if ( el instanceof Method ) { + Method method = (Method) el; + className = method.getDeclaringClass().getName(); + String methodName = method.getName(); + + // YUCK! The null here is the 'boundType', we'd rather get the TypeEnvironment() + if ( ReflectionUtil.isProperty( method, null, PersistentAttributeFilter.INSTANCE ) ) { + if ( methodName.startsWith( "get" ) ) { + propertyName = Introspector.decapitalize( methodName.substring( "get".length() ) ); + } + else if ( methodName.startsWith( "is" ) ) { + propertyName = Introspector.decapitalize( methodName.substring( "is".length() ) ); + } + else { + throw new RuntimeException( "Method " + methodName + " is not a property getter" ); + } + propertyType = PropertyType.PROPERTY; + try { + mirroredAttribute = method.getDeclaringClass().getDeclaredField( propertyName ); + } + catch ( NoSuchFieldException e ) { + //no method + } + } + else { + propertyName = methodName; + propertyType = PropertyType.METHOD; + } + } + else { + className = null; + propertyName = null; + propertyType = null; + } + } + + public JPAXMLOverriddenAnnotationReader( + AnnotatedElement el, + XMLContext xmlContext, + BootstrapContext bootstrapContext) { + this( el, xmlContext, bootstrapContext.getClassLoaderAccess() ); + } + + + public T getAnnotation(Class annotationType) { + initAnnotations(); + return (T) annotationsMap.get( annotationType ); + } + + public boolean isAnnotationPresent(Class annotationType) { + initAnnotations(); + return annotationsMap.containsKey( annotationType ); + } + + public Annotation[] getAnnotations() { + initAnnotations(); + return annotations; + } + + /* + * The idea is to create annotation proxies for the xml configuration elements. Using this proxy annotations together + * with the {@link JPAMetadataProvider} allows to handle xml configuration the same way as annotation configuration. + */ + private void initAnnotations() { + if ( annotations == null ) { + XMLContext.Default defaults = xmlContext.getDefault( className ); + if ( className != null && propertyName == null ) { + //is a class + Element tree = xmlContext.getXMLTree( className ); + Annotation[] annotations = getPhysicalAnnotations(); + List annotationList = new ArrayList<>( annotations.length + 5 ); + annotationsMap = new HashMap<>( annotations.length + 5 ); + for ( Annotation annotation : annotations ) { + if ( !annotationToXml.containsKey( annotation.annotationType() ) ) { + //unknown annotations are left over + annotationList.add( annotation ); + } + } + addIfNotNull( annotationList, getEntity( tree, defaults ) ); + addIfNotNull( annotationList, getMappedSuperclass( tree, defaults ) ); + addIfNotNull( annotationList, getEmbeddable( tree, defaults ) ); + addIfNotNull( annotationList, getTable( tree, defaults ) ); + addIfNotNull( annotationList, getSecondaryTables( tree, defaults ) ); + addIfNotNull( annotationList, getPrimaryKeyJoinColumns( tree, defaults, true ) ); + addIfNotNull( annotationList, getIdClass( tree, defaults ) ); + addIfNotNull( annotationList, getCacheable( tree, defaults ) ); + addIfNotNull( annotationList, getInheritance( tree, defaults ) ); + addIfNotNull( annotationList, getDiscriminatorValue( tree, defaults ) ); + addIfNotNull( annotationList, getDiscriminatorColumn( tree, defaults ) ); + addIfNotNull( annotationList, getSequenceGenerator( tree, defaults ) ); + addIfNotNull( annotationList, getTableGenerator( tree, defaults ) ); + addIfNotNull( annotationList, getNamedQueries( tree, defaults ) ); + addIfNotNull( annotationList, getNamedNativeQueries( tree, defaults ) ); + addIfNotNull( annotationList, getNamedStoredProcedureQueries( tree, defaults ) ); + addIfNotNull( annotationList, getNamedEntityGraphs( tree, defaults ) ); + addIfNotNull( annotationList, getSqlResultSetMappings( tree, defaults ) ); + addIfNotNull( annotationList, getExcludeDefaultListeners( tree, defaults ) ); + addIfNotNull( annotationList, getExcludeSuperclassListeners( tree, defaults ) ); + addIfNotNull( annotationList, getAccessType( tree, defaults ) ); + addIfNotNull( annotationList, getAttributeOverrides( tree, defaults, true ) ); + addIfNotNull( annotationList, getAssociationOverrides( tree, defaults, true ) ); + addIfNotNull( annotationList, getEntityListeners( tree, defaults ) ); + addIfNotNull( annotationList, getConverts( tree, defaults ) ); + + this.annotations = annotationList.toArray( new Annotation[annotationList.size()] ); + for ( Annotation ann : this.annotations ) { + annotationsMap.put( ann.annotationType(), ann ); + } + checkForOrphanProperties( tree ); + } + else if ( className != null ) { //&& propertyName != null ) { //always true but less confusing + Element tree = xmlContext.getXMLTree( className ); + Annotation[] annotations = getPhysicalAnnotations(); + List annotationList = new ArrayList<>( annotations.length + 5 ); + annotationsMap = new HashMap<>( annotations.length + 5 ); + for ( Annotation annotation : annotations ) { + if ( !annotationToXml.containsKey( annotation.annotationType() ) ) { + //unknown annotations are left over + annotationList.add( annotation ); + } + } + preCalculateElementsForProperty( tree ); + Transient transientAnn = getTransient( defaults ); + if ( transientAnn != null ) { + annotationList.add( transientAnn ); + } + else { + if ( defaults.canUseJavaAnnotations() ) { + Annotation annotation = getPhysicalAnnotation( Access.class ); + addIfNotNull( annotationList, annotation ); + } + getId( annotationList, defaults ); + getEmbeddedId( annotationList, defaults ); + getEmbedded( annotationList, defaults ); + getBasic( annotationList, defaults ); + getVersion( annotationList, defaults ); + getAssociation( ManyToOne.class, annotationList, defaults ); + getAssociation( OneToOne.class, annotationList, defaults ); + getAssociation( OneToMany.class, annotationList, defaults ); + getAssociation( ManyToMany.class, annotationList, defaults ); + getAssociation( Any.class, annotationList, defaults ); + getAssociation( ManyToAny.class, annotationList, defaults ); + getElementCollection( annotationList, defaults ); + addIfNotNull( annotationList, getSequenceGenerator( elementsForProperty, defaults ) ); + addIfNotNull( annotationList, getTableGenerator( elementsForProperty, defaults ) ); + addIfNotNull( annotationList, getConvertsForAttribute( elementsForProperty, defaults ) ); + } + processEventAnnotations( annotationList, defaults ); + //FIXME use annotationsMap rather than annotationList this will be faster since the annotation type is usually known at put() time + this.annotations = annotationList.toArray( new Annotation[annotationList.size()] ); + for ( Annotation ann : this.annotations ) { + annotationsMap.put( ann.annotationType(), ann ); + } + } + else { + this.annotations = getPhysicalAnnotations(); + annotationsMap = new HashMap<>( annotations.length + 5 ); + for ( Annotation ann : this.annotations ) { + annotationsMap.put( ann.annotationType(), ann ); + } + } + } + } + + private Annotation getConvertsForAttribute(List elementsForProperty, XMLContext.Default defaults) { + // NOTE : we use a map here to make sure that an xml and annotation referring to the same attribute + // properly overrides. Very sparse map, yes, but easy setup. + // todo : revisit this + // although bear in mind that this code is no longer used in 5.0... + + final Map convertAnnotationsMap = new HashMap<>(); + + for ( Element element : elementsForProperty ) { + final boolean isBasic = "basic".equals( element.getName() ); + final boolean isEmbedded = "embedded".equals( element.getName() ); + final boolean isElementCollection = "element-collection".equals(element.getName()); + + final boolean canHaveConverts = isBasic || isEmbedded || isElementCollection; + + if ( !canHaveConverts ) { + continue; + } + + final String attributeNamePrefix = isBasic ? null : propertyName; + applyXmlDefinedConverts( element, defaults, attributeNamePrefix, convertAnnotationsMap ); + } + + // NOTE : per section 12.2.3.16 of the spec is additive, although only if "metadata-complete" is not + // specified in the XML + + if ( defaults.canUseJavaAnnotations() ) { + // todo : note sure how to best handle attributeNamePrefix here + applyPhysicalConvertAnnotations( propertyName, convertAnnotationsMap ); + } + + if ( !convertAnnotationsMap.isEmpty() ) { + final AnnotationDescriptor groupingDescriptor = new AnnotationDescriptor( Converts.class ); + groupingDescriptor.setValue( "value", convertAnnotationsMap.values().toArray( new Convert[convertAnnotationsMap.size()]) ); + return AnnotationFactory.create( groupingDescriptor ); + } + + return null; + } + + private Converts getConverts(Element tree, XMLContext.Default defaults) { + // NOTE : we use a map here to make sure that an xml and annotation referring to the same attribute + // properly overrides. Bit sparse, but easy... + final Map convertAnnotationsMap = new HashMap<>(); + + if ( tree != null ) { + applyXmlDefinedConverts( tree, defaults, null, convertAnnotationsMap ); + } + + // NOTE : per section 12.2.3.16 of the spec is additive, although only if "metadata-complete" is not + // specified in the XML + + if ( defaults.canUseJavaAnnotations() ) { + applyPhysicalConvertAnnotations( null, convertAnnotationsMap ); + } + + if ( !convertAnnotationsMap.isEmpty() ) { + final AnnotationDescriptor groupingDescriptor = new AnnotationDescriptor( Converts.class ); + groupingDescriptor.setValue( "value", convertAnnotationsMap.values().toArray( new Convert[convertAnnotationsMap.size()]) ); + return AnnotationFactory.create( groupingDescriptor ); + } + + return null; + } + + private void applyXmlDefinedConverts( + Element containingElement, + XMLContext.Default defaults, + String attributeNamePrefix, + Map convertAnnotationsMap) { + final List convertElements = containingElement.elements( "convert" ); + for ( Element convertElement : convertElements ) { + final AnnotationDescriptor convertAnnotationDescriptor = new AnnotationDescriptor( Convert.class ); + copyStringAttribute( convertAnnotationDescriptor, convertElement, "attribute-name", false ); + copyBooleanAttribute( convertAnnotationDescriptor, convertElement, "disable-conversion" ); + + final Attribute converterClassAttr = convertElement.attribute( "converter" ); + if ( converterClassAttr != null ) { + final String converterClassName = XMLContext.buildSafeClassName( + converterClassAttr.getValue(), + defaults + ); + try { + final Class converterClass = classLoaderAccess.classForName( converterClassName ); + convertAnnotationDescriptor.setValue( "converter", converterClass ); + } + catch (ClassLoadingException e) { + throw new AnnotationException( "Unable to find specified converter class id-class: " + converterClassName, e ); + } + } + final Convert convertAnnotation = AnnotationFactory.create( convertAnnotationDescriptor ); + final String qualifiedAttributeName = qualifyConverterAttributeName( + attributeNamePrefix, + convertAnnotation.attributeName() + ); + convertAnnotationsMap.put( qualifiedAttributeName, convertAnnotation ); + } + + } + + private String qualifyConverterAttributeName(String attributeNamePrefix, String specifiedAttributeName) { + String qualifiedAttributeName; + if ( StringHelper.isNotEmpty( specifiedAttributeName ) ) { + if ( StringHelper.isNotEmpty( attributeNamePrefix ) ) { + qualifiedAttributeName = attributeNamePrefix + '.' + specifiedAttributeName; + } + else { + qualifiedAttributeName = specifiedAttributeName; + } + } + else { + qualifiedAttributeName = ""; + } + return qualifiedAttributeName; + } + + private void applyPhysicalConvertAnnotations( + String attributeNamePrefix, + Map convertAnnotationsMap) { + final Convert physicalAnnotation = getPhysicalAnnotation( Convert.class ); + if ( physicalAnnotation != null ) { + // only add if no XML element named a converter for this attribute + final String qualifiedAttributeName = qualifyConverterAttributeName( attributeNamePrefix, physicalAnnotation.attributeName() ); + if ( ! convertAnnotationsMap.containsKey( qualifiedAttributeName ) ) { + convertAnnotationsMap.put( qualifiedAttributeName, physicalAnnotation ); + } + } + final Converts physicalGroupingAnnotation = getPhysicalAnnotation( Converts.class ); + if ( physicalGroupingAnnotation != null ) { + for ( Convert convertAnnotation : physicalGroupingAnnotation.value() ) { + // again, only add if no XML element named a converter for this attribute + final String qualifiedAttributeName = qualifyConverterAttributeName( attributeNamePrefix, convertAnnotation.attributeName() ); + if ( ! convertAnnotationsMap.containsKey( qualifiedAttributeName ) ) { + convertAnnotationsMap.put( qualifiedAttributeName, convertAnnotation ); + } + } + } + } + + private void checkForOrphanProperties(Element tree) { + Class clazz; + try { + clazz = classLoaderAccess.classForName( className ); + } + catch ( ClassLoadingException e ) { + return; //a primitive type most likely + } + Element element = tree != null ? tree.element( "attributes" ) : null; + //put entity.attributes elements + if ( element != null ) { + //precompute the list of properties + //TODO is it really useful... + Set properties = new HashSet<>(); + for ( Field field : clazz.getFields() ) { + properties.add( field.getName() ); + } + for ( Method method : clazz.getMethods() ) { + String name = method.getName(); + if ( name.startsWith( "get" ) ) { + properties.add( Introspector.decapitalize( name.substring( "get".length() ) ) ); + } + else if ( name.startsWith( "is" ) ) { + properties.add( Introspector.decapitalize( name.substring( "is".length() ) ) ); + } + } + for ( Element subelement : (List) element.elements() ) { + String propertyName = subelement.attributeValue( "name" ); + if ( !properties.contains( propertyName ) ) { + LOG.propertyNotFound( StringHelper.qualify( className, propertyName ) ); + } + } + } + } + + /** + * Adds {@code annotation} to the list (only if it's not null) and then returns it. + * + * @param annotationList The list of annotations. + * @param annotation The annotation to add to the list. + * + * @return The annotation which was added to the list or {@code null}. + */ + private Annotation addIfNotNull(List annotationList, Annotation annotation) { + if ( annotation != null ) { + annotationList.add( annotation ); + } + return annotation; + } + + //TODO mutualize the next 2 methods + private Annotation getTableGenerator(List elementsForProperty, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + Element subelement = element != null ? element.element( annotationToXml.get( TableGenerator.class ) ) : null; + if ( subelement != null ) { + return buildTableGeneratorAnnotation( subelement, defaults ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( TableGenerator.class ); + } + else { + return null; + } + } + + private Annotation getSequenceGenerator(List elementsForProperty, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + Element subelement = element != null ? element.element( annotationToXml.get( SequenceGenerator.class ) ) : null; + if ( subelement != null ) { + return buildSequenceGeneratorAnnotation( subelement ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( SequenceGenerator.class ); + } + else { + return null; + } + } + + private void processEventAnnotations(List annotationList, XMLContext.Default defaults) { + boolean eventElement = false; + for ( Element element : elementsForProperty ) { + String elementName = element.getName(); + if ( "pre-persist".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PrePersist.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "pre-remove".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PreRemove.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "pre-update".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PreUpdate.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "post-persist".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PostPersist.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "post-remove".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PostRemove.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "post-update".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PostUpdate.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + else if ( "post-load".equals( elementName ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PostLoad.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + eventElement = true; + } + } + if ( !eventElement && defaults.canUseJavaAnnotations() ) { + Annotation ann = getPhysicalAnnotation( PrePersist.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PreRemove.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PreUpdate.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PostPersist.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PostRemove.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PostUpdate.class ); + addIfNotNull( annotationList, ann ); + ann = getPhysicalAnnotation( PostLoad.class ); + addIfNotNull( annotationList, ann ); + } + } + + private EntityListeners getEntityListeners(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( "entity-listeners" ) : null; + if ( element != null ) { + List entityListenerClasses = new ArrayList<>(); + for ( Element subelement : (List) element.elements( "entity-listener" ) ) { + String className = subelement.attributeValue( "class" ); + try { + entityListenerClasses.add( + classLoaderAccess.classForName( + XMLContext.buildSafeClassName( className, defaults ) + ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( + "Unable to find " + element.getPath() + ".class: " + className, e + ); + } + } + AnnotationDescriptor ad = new AnnotationDescriptor( EntityListeners.class ); + ad.setValue( "value", entityListenerClasses.toArray( new Class[entityListenerClasses.size()] ) ); + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( EntityListeners.class ); + } + else { + return null; + } + } + + private JoinTable overridesDefaultsInJoinTable(Annotation annotation, XMLContext.Default defaults) { + //no element but might have some default or some annotation + boolean defaultToJoinTable = !( isPhysicalAnnotationPresent( JoinColumn.class ) + || isPhysicalAnnotationPresent( JoinColumns.class ) ); + final Class annotationClass = annotation.annotationType(); + defaultToJoinTable = defaultToJoinTable && + ( ( annotationClass == ManyToMany.class && StringHelper.isEmpty( ( (ManyToMany) annotation ).mappedBy() ) ) + || ( annotationClass == OneToMany.class && StringHelper.isEmpty( ( (OneToMany) annotation ).mappedBy() ) ) + || ( annotationClass == ElementCollection.class ) + ); + final Class annotationType = JoinTable.class; + if ( defaultToJoinTable + && ( StringHelper.isNotEmpty( defaults.getCatalog() ) + || StringHelper.isNotEmpty( defaults.getSchema() ) ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + if ( defaults.canUseJavaAnnotations() ) { + JoinTable table = getPhysicalAnnotation( annotationType ); + if ( table != null ) { + ad.setValue( "name", table.name() ); + ad.setValue( "schema", table.schema() ); + ad.setValue( "catalog", table.catalog() ); + ad.setValue( "uniqueConstraints", table.uniqueConstraints() ); + ad.setValue( "joinColumns", table.joinColumns() ); + ad.setValue( "inverseJoinColumns", table.inverseJoinColumns() ); + } + } + if ( StringHelper.isEmpty( (String) ad.valueOf( "schema" ) ) + && StringHelper.isNotEmpty( defaults.getSchema() ) ) { + ad.setValue( "schema", defaults.getSchema() ); + } + if ( StringHelper.isEmpty( (String) ad.valueOf( "catalog" ) ) + && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + ad.setValue( "catalog", defaults.getCatalog() ); + } + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( annotationType ); + } + else { + return null; + } + } + + private Annotation overridesDefaultCascadePersist(Annotation annotation, XMLContext.Default defaults) { + if ( Boolean.TRUE.equals( defaults.getCascadePersist() ) ) { + final Class annotationType = annotation.annotationType(); + + if ( annotationType == ManyToOne.class ) { + ManyToOne manyToOne = (ManyToOne) annotation; + List cascades = new ArrayList<>( Arrays.asList( manyToOne.cascade() ) ); + if ( !cascades.contains( CascadeType.ALL ) && !cascades.contains( CascadeType.PERSIST ) ) { + cascades.add( CascadeType.PERSIST ); + } + else { + return annotation; + } + + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + ad.setValue( "cascade", cascades.toArray( new CascadeType[] {} ) ); + ad.setValue( "targetEntity", manyToOne.targetEntity() ); + ad.setValue( "fetch", manyToOne.fetch() ); + ad.setValue( "optional", manyToOne.optional() ); + + return AnnotationFactory.create( ad ); + } + else if ( annotationType == OneToOne.class ) { + OneToOne oneToOne = (OneToOne) annotation; + List cascades = new ArrayList<>( Arrays.asList( oneToOne.cascade() ) ); + if ( !cascades.contains( CascadeType.ALL ) && !cascades.contains( CascadeType.PERSIST ) ) { + cascades.add( CascadeType.PERSIST ); + } + else { + return annotation; + } + + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + ad.setValue( "cascade", cascades.toArray( new CascadeType[] {} ) ); + ad.setValue( "targetEntity", oneToOne.targetEntity() ); + ad.setValue( "fetch", oneToOne.fetch() ); + ad.setValue( "optional", oneToOne.optional() ); + ad.setValue( "mappedBy", oneToOne.mappedBy() ); + ad.setValue( "orphanRemoval", oneToOne.orphanRemoval() ); + + return AnnotationFactory.create( ad ); + } + } + return annotation; + } + + private void getJoinTable(List annotationList, Element tree, XMLContext.Default defaults) { + addIfNotNull( annotationList, buildJoinTable( tree, defaults ) ); + } + + /* + * no partial overriding possible + */ + private JoinTable buildJoinTable(Element tree, XMLContext.Default defaults) { + Element subelement = tree == null ? null : tree.element( "join-table" ); + final Class annotationType = JoinTable.class; + if ( subelement == null ) { + return null; + } + //ignore java annotation, an element is defined + AnnotationDescriptor annotation = new AnnotationDescriptor( annotationType ); + copyStringAttribute( annotation, subelement, "name", false ); + copyStringAttribute( annotation, subelement, "catalog", false ); + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + copyStringAttribute( annotation, subelement, "schema", false ); + if ( StringHelper.isNotEmpty( defaults.getSchema() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); + annotation.setValue( "joinColumns", getJoinColumns( subelement, false ) ); + annotation.setValue( "inverseJoinColumns", getJoinColumns( subelement, true ) ); + return AnnotationFactory.create( annotation ); + } + + /** + * As per section 12.2 of the JPA 2.0 specification, the association + * subelements (many-to-one, one-to-many, one-to-one, many-to-many, + * element-collection) completely override the mapping for the specified + * field or property. Thus, any methods which might in some contexts merge + * with annotations must not do so in this context. + * + * @see #getElementCollection(List, XMLContext.Default) + */ + private void getAssociation( + Class annotationType, List annotationList, XMLContext.Default defaults + ) { + String xmlName = annotationToXml.get( annotationType ); + for ( Element element : elementsForProperty ) { + if ( xmlName.equals( element.getName() ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( annotationType ); + addTargetClass( element, ad, "target-entity", defaults ); + getFetchType( ad, element ); + getCascades( ad, element, defaults ); + getJoinTable( annotationList, element, defaults ); + buildJoinColumns( annotationList, element ); + Annotation annotation = getPrimaryKeyJoinColumns( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + copyBooleanAttribute( ad, element, "optional" ); + copyBooleanAttribute( ad, element, "orphan-removal" ); + copyStringAttribute( ad, element, "mapped-by", false ); + getOrderBy( annotationList, element ); + getMapKey( annotationList, element ); + getMapKeyClass( annotationList, element, defaults ); + getMapKeyColumn( annotationList, element ); + getOrderColumn( annotationList, element ); + getMapKeyTemporal( annotationList, element ); + getMapKeyEnumerated( annotationList, element ); + annotation = getMapKeyAttributeOverrides( element, defaults ); + addIfNotNull( annotationList, annotation ); + buildMapKeyJoinColumns( annotationList, element ); + getAssociationId( annotationList, element ); + getMapsId( annotationList, element ); + annotationList.add( AnnotationFactory.create( ad ) ); + getAccessType( annotationList, element ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + Annotation annotation = getPhysicalAnnotation( annotationType ); + if ( annotation != null ) { + annotation = overridesDefaultCascadePersist( annotation, defaults ); + annotationList.add( annotation ); + annotation = overridesDefaultsInJoinTable( annotation, defaults ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( JoinColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( JoinColumns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( PrimaryKeyJoinColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( PrimaryKeyJoinColumns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKey.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( OrderBy.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Lob.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Enumerated.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Columns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyClass.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyTemporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyEnumerated.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyJoinColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyJoinColumns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( OrderColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Cascade.class ); + addIfNotNull( annotationList, annotation ); + } + else if ( isPhysicalAnnotationPresent( ElementCollection.class ) ) { //JPA2 + annotation = overridesDefaultsInJoinTable( getPhysicalAnnotation( ElementCollection.class ), defaults ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKey.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( OrderBy.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Lob.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Enumerated.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( OrderColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyClass.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyTemporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyEnumerated.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyJoinColumn.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( MapKeyJoinColumns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( CollectionTable.class ); + addIfNotNull( annotationList, annotation ); + } + } + } + + private void buildMapKeyJoinColumns(List annotationList, Element element) { + MapKeyJoinColumn[] joinColumns = getMapKeyJoinColumns( element ); + if ( joinColumns.length > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapKeyJoinColumns.class ); + ad.setValue( "value", joinColumns ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private MapKeyJoinColumn[] getMapKeyJoinColumns(Element element) { + List subelements = element != null ? element.elements( "map-key-join-column" ) : null; + List joinColumns = new ArrayList<>(); + if ( subelements != null ) { + for ( Element subelement : subelements ) { + AnnotationDescriptor column = new AnnotationDescriptor( MapKeyJoinColumn.class ); + copyStringAttribute( column, subelement, "name", false ); + copyStringAttribute( column, subelement, "referenced-column-name", false ); + copyBooleanAttribute( column, subelement, "unique" ); + copyBooleanAttribute( column, subelement, "nullable" ); + copyBooleanAttribute( column, subelement, "insertable" ); + copyBooleanAttribute( column, subelement, "updatable" ); + copyStringAttribute( column, subelement, "column-definition", false ); + copyStringAttribute( column, subelement, "table", false ); + joinColumns.add( AnnotationFactory.create( column ) ); + } + } + return joinColumns.toArray( new MapKeyJoinColumn[joinColumns.size()] ); + } + + private AttributeOverrides getMapKeyAttributeOverrides(Element tree, XMLContext.Default defaults) { + List attributes = buildAttributeOverrides( tree, "map-key-attribute-override" ); + return mergeAttributeOverrides( defaults, attributes, false ); + } + + private Cacheable getCacheable(Element element, XMLContext.Default defaults){ + if ( element != null ) { + String attValue = element.attributeValue( "cacheable" ); + if ( attValue != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Cacheable.class ); + ad.setValue( "value", Boolean.valueOf( attValue ) ); + return AnnotationFactory.create( ad ); + } + } + if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( Cacheable.class ); + } + else { + return null; + } + } + /** + * Adds a @MapKeyEnumerated annotation to the specified annotationList if the specified element + * contains a map-key-enumerated sub-element. This should only be the case for + * element-collection, many-to-many, or one-to-many associations. + */ + private void getMapKeyEnumerated(List annotationList, Element element) { + Element subelement = element != null ? element.element( "map-key-enumerated" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapKeyEnumerated.class ); + EnumType value = EnumType.valueOf( subelement.getTextTrim() ); + ad.setValue( "value", value ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + /** + * Adds a @MapKeyTemporal annotation to the specified annotationList if the specified element + * contains a map-key-temporal sub-element. This should only be the case for element-collection, + * many-to-many, or one-to-many associations. + */ + private void getMapKeyTemporal(List annotationList, Element element) { + Element subelement = element != null ? element.element( "map-key-temporal" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapKeyTemporal.class ); + TemporalType value = TemporalType.valueOf( subelement.getTextTrim() ); + ad.setValue( "value", value ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + /** + * Adds an @OrderColumn annotation to the specified annotationList if the specified element + * contains an order-column sub-element. This should only be the case for element-collection, + * many-to-many, or one-to-many associations. + */ + private void getOrderColumn(List annotationList, Element element) { + Element subelement = element != null ? element.element( "order-column" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( OrderColumn.class ); + copyStringAttribute( ad, subelement, "name", false ); + copyBooleanAttribute( ad, subelement, "nullable" ); + copyBooleanAttribute( ad, subelement, "insertable" ); + copyBooleanAttribute( ad, subelement, "updatable" ); + copyStringAttribute( ad, subelement, "column-definition", false ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + /** + * Adds a @MapsId annotation to the specified annotationList if the specified element has the + * maps-id attribute set. This should only be the case for many-to-one or one-to-one + * associations. + */ + private void getMapsId(List annotationList, Element element) { + String attrVal = element.attributeValue( "maps-id" ); + if ( attrVal != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapsId.class ); + ad.setValue( "value", attrVal ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + /** + * Adds an @Id annotation to the specified annotationList if the specified element has the id + * attribute set to true. This should only be the case for many-to-one or one-to-one + * associations. + */ + private void getAssociationId(List annotationList, Element element) { + String attrVal = element.attributeValue( "id" ); + if ( "true".equals( attrVal ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Id.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void addTargetClass(Element element, AnnotationDescriptor ad, String nodeName, XMLContext.Default defaults) { + String className = element.attributeValue( nodeName ); + if ( className != null ) { + Class clazz; + try { + clazz = classLoaderAccess.classForName( XMLContext.buildSafeClassName( className, defaults ) ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( + "Unable to find " + element.getPath() + " " + nodeName + ": " + className, e + ); + } + ad.setValue( getJavaAttributeNameFromXMLOne( nodeName ), clazz ); + } + } + + /** + * As per sections 12.2.3.23.9, 12.2.4.8.9 and 12.2.5.3.6 of the JPA 2.0 + * specification, the element-collection subelement completely overrides the + * mapping for the specified field or property. Thus, any methods which + * might in some contexts merge with annotations must not do so in this + * context. + */ + private void getElementCollection(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "element-collection".equals( element.getName() ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( ElementCollection.class ); + addTargetClass( element, ad, "target-class", defaults ); + getFetchType( ad, element ); + getOrderBy( annotationList, element ); + getOrderColumn( annotationList, element ); + getMapKey( annotationList, element ); + getMapKeyClass( annotationList, element, defaults ); + getMapKeyTemporal( annotationList, element ); + getMapKeyEnumerated( annotationList, element ); + getMapKeyColumn( annotationList, element ); + buildMapKeyJoinColumns( annotationList, element ); + Annotation annotation = getColumn( element.element( "column" ), false, element ); + addIfNotNull( annotationList, annotation ); + getTemporal( annotationList, element ); + getEnumerated( annotationList, element ); + getLob( annotationList, element ); + //Both map-key-attribute-overrides and attribute-overrides + //translate into AttributeOverride annotations, which need + //need to be wrapped in the same AttributeOverrides annotation. + List attributes = new ArrayList<>(); + attributes.addAll( buildAttributeOverrides( element, "map-key-attribute-override" ) ); + attributes.addAll( buildAttributeOverrides( element, "attribute-override" ) ); + annotation = mergeAttributeOverrides( defaults, attributes, false ); + addIfNotNull( annotationList, annotation ); + annotation = getAssociationOverrides( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + getCollectionTable( annotationList, element, defaults ); + annotationList.add( AnnotationFactory.create( ad ) ); + getAccessType( annotationList, element ); + } + } + } + + private void getOrderBy(List annotationList, Element element) { + Element subelement = element != null ? element.element( "order-by" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( OrderBy.class ); + copyStringElement( subelement, ad, "value" ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getMapKey(List annotationList, Element element) { + Element subelement = element != null ? element.element( "map-key" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapKey.class ); + copyStringAttribute( ad, subelement, "name", false ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getMapKeyColumn(List annotationList, Element element) { + Element subelement = element != null ? element.element( "map-key-column" ) : null; + if ( subelement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( MapKeyColumn.class ); + copyStringAttribute( ad, subelement, "name", false ); + copyBooleanAttribute( ad, subelement, "unique" ); + copyBooleanAttribute( ad, subelement, "nullable" ); + copyBooleanAttribute( ad, subelement, "insertable" ); + copyBooleanAttribute( ad, subelement, "updatable" ); + copyStringAttribute( ad, subelement, "column-definition", false ); + copyStringAttribute( ad, subelement, "table", false ); + copyIntegerAttribute( ad, subelement, "length" ); + copyIntegerAttribute( ad, subelement, "precision" ); + copyIntegerAttribute( ad, subelement, "scale" ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getMapKeyClass(List annotationList, Element element, XMLContext.Default defaults) { + String nodeName = "map-key-class"; + Element subelement = element != null ? element.element( nodeName ) : null; + if ( subelement != null ) { + String mapKeyClassName = subelement.attributeValue( "class" ); + AnnotationDescriptor ad = new AnnotationDescriptor( MapKeyClass.class ); + if ( StringHelper.isNotEmpty( mapKeyClassName ) ) { + Class clazz; + try { + clazz = classLoaderAccess.classForName( + XMLContext.buildSafeClassName( mapKeyClassName, defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( + "Unable to find " + element.getPath() + " " + nodeName + ": " + mapKeyClassName, e + ); + } + ad.setValue( "value", clazz ); + } + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getCollectionTable(List annotationList, Element element, XMLContext.Default defaults) { + Element subelement = element != null ? element.element( "collection-table" ) : null; + if ( subelement != null ) { + AnnotationDescriptor annotation = new AnnotationDescriptor( CollectionTable.class ); + copyStringAttribute( annotation, subelement, "name", false ); + copyStringAttribute( annotation, subelement, "catalog", false ); + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + copyStringAttribute( annotation, subelement, "schema", false ); + if ( StringHelper.isNotEmpty( defaults.getSchema() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + JoinColumn[] joinColumns = getJoinColumns( subelement, false ); + if ( joinColumns.length > 0 ) { + annotation.setValue( "joinColumns", joinColumns ); + } + buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); + annotationList.add( AnnotationFactory.create( annotation ) ); + } + } + + private void buildJoinColumns(List annotationList, Element element) { + JoinColumn[] joinColumns = getJoinColumns( element, false ); + if ( joinColumns.length > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( JoinColumns.class ); + ad.setValue( "value", joinColumns ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getCascades(AnnotationDescriptor ad, Element element, XMLContext.Default defaults) { + List elements = element != null ? element.elements( "cascade" ) : new ArrayList<>( 0 ); + List cascades = new ArrayList<>(); + for ( Element subelement : elements ) { + if ( subelement.element( "cascade-all" ) != null ) { + cascades.add( CascadeType.ALL ); + } + if ( subelement.element( "cascade-persist" ) != null ) { + cascades.add( CascadeType.PERSIST ); + } + if ( subelement.element( "cascade-merge" ) != null ) { + cascades.add( CascadeType.MERGE ); + } + if ( subelement.element( "cascade-remove" ) != null ) { + cascades.add( CascadeType.REMOVE ); + } + if ( subelement.element( "cascade-refresh" ) != null ) { + cascades.add( CascadeType.REFRESH ); + } + if ( subelement.element( "cascade-detach" ) != null ) { + cascades.add( CascadeType.DETACH ); + } + } + if ( Boolean.TRUE.equals( defaults.getCascadePersist() ) + && !cascades.contains( CascadeType.ALL ) && !cascades.contains( CascadeType.PERSIST ) ) { + cascades.add( CascadeType.PERSIST ); + } + if ( cascades.size() > 0 ) { + ad.setValue( "cascade", cascades.toArray( new CascadeType[cascades.size()] ) ); + } + } + + private void getEmbedded(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "embedded".equals( element.getName() ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Embedded.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + Annotation annotation = getAttributeOverrides( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + annotation = getAssociationOverrides( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + getAccessType( annotationList, element ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + Annotation annotation = getPhysicalAnnotation( Embedded.class ); + if ( annotation != null ) { + annotationList.add( annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + } + } + } + + private Transient getTransient(XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "transient".equals( element.getName() ) ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Transient.class ); + return AnnotationFactory.create( ad ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( Transient.class ); + } + else { + return null; + } + } + + private void getVersion(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "version".equals( element.getName() ) ) { + Annotation annotation = buildColumns( element ); + addIfNotNull( annotationList, annotation ); + getTemporal( annotationList, element ); + AnnotationDescriptor basic = new AnnotationDescriptor( Version.class ); + annotationList.add( AnnotationFactory.create( basic ) ); + getAccessType( annotationList, element ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + //we have nothing, so Java annotations might occur + Annotation annotation = getPhysicalAnnotation( Version.class ); + if ( annotation != null ) { + annotationList.add( annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Columns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + } + } + } + + private void getBasic(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "basic".equals( element.getName() ) ) { + Annotation annotation = buildColumns( element ); + addIfNotNull( annotationList, annotation ); + getAccessType( annotationList, element ); + getTemporal( annotationList, element ); + getLob( annotationList, element ); + getEnumerated( annotationList, element ); + AnnotationDescriptor basic = new AnnotationDescriptor( Basic.class ); + getFetchType( basic, element ); + copyBooleanAttribute( basic, element, "optional" ); + annotationList.add( AnnotationFactory.create( basic ) ); + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + //no annotation presence constraint, basic is the default + Annotation annotation = getPhysicalAnnotation( Basic.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Lob.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Enumerated.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Columns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + } + } + + private void getEnumerated(List annotationList, Element element) { + Element subElement = element != null ? element.element( "enumerated" ) : null; + if ( subElement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Enumerated.class ); + String enumerated = subElement.getTextTrim(); + if ( "ORDINAL".equalsIgnoreCase( enumerated ) ) { + ad.setValue( "value", EnumType.ORDINAL ); + } + else if ( "STRING".equalsIgnoreCase( enumerated ) ) { + ad.setValue( "value", EnumType.STRING ); + } + else if ( StringHelper.isNotEmpty( enumerated ) ) { + throw new AnnotationException( "Unknown EnumType: " + enumerated + ". " + SCHEMA_VALIDATION ); + } + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getLob(List annotationList, Element element) { + Element subElement = element != null ? element.element( "lob" ) : null; + if ( subElement != null ) { + annotationList.add( AnnotationFactory.create( new AnnotationDescriptor( Lob.class ) ) ); + } + } + + private void getFetchType(AnnotationDescriptor descriptor, Element element) { + String fetchString = element != null ? element.attributeValue( "fetch" ) : null; + if ( fetchString != null ) { + if ( "eager".equalsIgnoreCase( fetchString ) ) { + descriptor.setValue( "fetch", FetchType.EAGER ); + } + else if ( "lazy".equalsIgnoreCase( fetchString ) ) { + descriptor.setValue( "fetch", FetchType.LAZY ); + } + } + } + + private void getEmbeddedId(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "embedded-id".equals( element.getName() ) ) { + if ( isProcessingId( defaults ) ) { + Annotation annotation = getAttributeOverrides( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + annotation = getAssociationOverrides( element, defaults, false ); + addIfNotNull( annotationList, annotation ); + AnnotationDescriptor ad = new AnnotationDescriptor( EmbeddedId.class ); + annotationList.add( AnnotationFactory.create( ad ) ); + getAccessType( annotationList, element ); + } + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + Annotation annotation = getPhysicalAnnotation( EmbeddedId.class ); + if ( annotation != null ) { + annotationList.add( annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Columns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( GeneratedValue.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( TableGenerator.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( SequenceGenerator.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + } + } + } + + private void preCalculateElementsForProperty(Element tree) { + elementsForProperty = new ArrayList<>(); + Element element = tree != null ? tree.element( "attributes" ) : null; + //put entity.attributes elements + if ( element != null ) { + for ( Element subelement : (List) element.elements() ) { + if ( propertyName.equals( subelement.attributeValue( "name" ) ) ) { + elementsForProperty.add( subelement ); + } + } + } + //add pre-* etc from entity and pure entity listener classes + if ( tree != null ) { + for ( Element subelement : (List) tree.elements() ) { + if ( propertyName.equals( subelement.attributeValue( "method-name" ) ) ) { + elementsForProperty.add( subelement ); + } + } + } + } + + private void getId(List annotationList, XMLContext.Default defaults) { + for ( Element element : elementsForProperty ) { + if ( "id".equals( element.getName() ) ) { + boolean processId = isProcessingId( defaults ); + if ( processId ) { + Annotation annotation = buildColumns( element ); + addIfNotNull( annotationList, annotation ); + annotation = buildGeneratedValue( element ); + addIfNotNull( annotationList, annotation ); + getTemporal( annotationList, element ); + //FIXME: fix the priority of xml over java for generator names + annotation = getTableGenerator( element, defaults ); + addIfNotNull( annotationList, annotation ); + annotation = getSequenceGenerator( element, defaults ); + addIfNotNull( annotationList, annotation ); + AnnotationDescriptor id = new AnnotationDescriptor( Id.class ); + annotationList.add( AnnotationFactory.create( id ) ); + getAccessType( annotationList, element ); + } + } + } + if ( elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations() ) { + Annotation annotation = getPhysicalAnnotation( Id.class ); + if ( annotation != null ) { + annotationList.add( annotation ); + annotation = getPhysicalAnnotation( Column.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Columns.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( GeneratedValue.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( Temporal.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( TableGenerator.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( SequenceGenerator.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AttributeOverrides.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverride.class ); + addIfNotNull( annotationList, annotation ); + annotation = getPhysicalAnnotation( AssociationOverrides.class ); + addIfNotNull( annotationList, annotation ); + } + } + } + + private boolean isProcessingId(XMLContext.Default defaults) { + boolean isExplicit = defaults.getAccess() != null; + boolean correctAccess = + ( PropertyType.PROPERTY.equals( propertyType ) && AccessType.PROPERTY.equals( defaults.getAccess() ) ) + || ( PropertyType.FIELD.equals( propertyType ) && AccessType.FIELD + .equals( defaults.getAccess() ) ); + boolean hasId = defaults.canUseJavaAnnotations() + && ( isPhysicalAnnotationPresent( Id.class ) || isPhysicalAnnotationPresent( EmbeddedId.class ) ); + //if ( properAccessOnMetadataComplete || properOverridingOnMetadataNonComplete ) { + boolean mirrorAttributeIsId = defaults.canUseJavaAnnotations() && + ( mirroredAttribute != null && + ( mirroredAttribute.isAnnotationPresent( Id.class ) + || mirroredAttribute.isAnnotationPresent( EmbeddedId.class ) ) ); + boolean propertyIsDefault = PropertyType.PROPERTY.equals( propertyType ) + && !mirrorAttributeIsId; + return correctAccess || ( !isExplicit && hasId ) || ( !isExplicit && propertyIsDefault ); + } + + private Columns buildColumns(Element element) { + List subelements = element.elements( "column" ); + List columns = new ArrayList<>( subelements.size() ); + for ( Element subelement : subelements ) { + columns.add( getColumn( subelement, false, element ) ); + } + if ( columns.size() > 0 ) { + AnnotationDescriptor columnsDescr = new AnnotationDescriptor( Columns.class ); + columnsDescr.setValue( "columns", columns.toArray( new Column[columns.size()] ) ); + return AnnotationFactory.create( columnsDescr ); + } + else { + return null; + } + } + + private GeneratedValue buildGeneratedValue(Element element) { + Element subElement = element != null ? element.element( "generated-value" ) : null; + if ( subElement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( GeneratedValue.class ); + String strategy = subElement.attributeValue( "strategy" ); + if ( "TABLE".equalsIgnoreCase( strategy ) ) { + ad.setValue( "strategy", GenerationType.TABLE ); + } + else if ( "SEQUENCE".equalsIgnoreCase( strategy ) ) { + ad.setValue( "strategy", GenerationType.SEQUENCE ); + } + else if ( "IDENTITY".equalsIgnoreCase( strategy ) ) { + ad.setValue( "strategy", GenerationType.IDENTITY ); + } + else if ( "AUTO".equalsIgnoreCase( strategy ) ) { + ad.setValue( "strategy", GenerationType.AUTO ); + } + else if ( StringHelper.isNotEmpty( strategy ) ) { + throw new AnnotationException( "Unknown GenerationType: " + strategy + ". " + SCHEMA_VALIDATION ); + } + copyStringAttribute( ad, subElement, "generator", false ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private void getTemporal(List annotationList, Element element) { + Element subElement = element != null ? element.element( "temporal" ) : null; + if ( subElement != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Temporal.class ); + String temporal = subElement.getTextTrim(); + if ( "DATE".equalsIgnoreCase( temporal ) ) { + ad.setValue( "value", TemporalType.DATE ); + } + else if ( "TIME".equalsIgnoreCase( temporal ) ) { + ad.setValue( "value", TemporalType.TIME ); + } + else if ( "TIMESTAMP".equalsIgnoreCase( temporal ) ) { + ad.setValue( "value", TemporalType.TIMESTAMP ); + } + else if ( StringHelper.isNotEmpty( temporal ) ) { + throw new AnnotationException( "Unknown TemporalType: " + temporal + ". " + SCHEMA_VALIDATION ); + } + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getAccessType(List annotationList, Element element) { + if ( element == null ) { + return; + } + String access = element.attributeValue( "access" ); + if ( access != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Access.class ); + AccessType type; + try { + type = AccessType.valueOf( access ); + } + catch ( IllegalArgumentException e ) { + throw new AnnotationException( access + " is not a valid access type. Check you xml confguration." ); + } + + if ( ( AccessType.PROPERTY.equals( type ) && this.element instanceof Method ) || + ( AccessType.FIELD.equals( type ) && this.element instanceof Field ) ) { + return; + } + + ad.setValue( "value", type ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + /** + * @param mergeWithAnnotations Whether to use Java annotations for this + * element, if present and not disabled by the XMLContext defaults. + * In some contexts (such as an element-collection mapping) merging + * with annotations is never allowed. + */ + private AssociationOverrides getAssociationOverrides(Element tree, XMLContext.Default defaults, boolean mergeWithAnnotations) { + List attributes = buildAssociationOverrides( tree, defaults ); + if ( mergeWithAnnotations && defaults.canUseJavaAnnotations() ) { + AssociationOverride annotation = getPhysicalAnnotation( AssociationOverride.class ); + addAssociationOverrideIfNeeded( annotation, attributes ); + AssociationOverrides annotations = getPhysicalAnnotation( AssociationOverrides.class ); + if ( annotations != null ) { + for ( AssociationOverride current : annotations.value() ) { + addAssociationOverrideIfNeeded( current, attributes ); + } + } + } + if ( attributes.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( AssociationOverrides.class ); + ad.setValue( "value", attributes.toArray( new AssociationOverride[attributes.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private List buildAssociationOverrides(Element element, XMLContext.Default defaults) { + List subelements = element == null ? null : element.elements( "association-override" ); + List overrides = new ArrayList<>(); + if ( subelements != null && subelements.size() > 0 ) { + for ( Element current : subelements ) { + AnnotationDescriptor override = new AnnotationDescriptor( AssociationOverride.class ); + copyStringAttribute( override, current, "name", true ); + override.setValue( "joinColumns", getJoinColumns( current, false ) ); + JoinTable joinTable = buildJoinTable( current, defaults ); + if ( joinTable != null ) { + override.setValue( "joinTable", joinTable ); + } + overrides.add( AnnotationFactory.create( override ) ); + } + } + return overrides; + } + + private JoinColumn[] getJoinColumns(Element element, boolean isInverse) { + List subelements = element != null ? + element.elements( isInverse ? "inverse-join-column" : "join-column" ) : + null; + List joinColumns = new ArrayList<>(); + if ( subelements != null ) { + for ( Element subelement : subelements ) { + AnnotationDescriptor column = new AnnotationDescriptor( JoinColumn.class ); + copyStringAttribute( column, subelement, "name", false ); + copyStringAttribute( column, subelement, "referenced-column-name", false ); + copyBooleanAttribute( column, subelement, "unique" ); + copyBooleanAttribute( column, subelement, "nullable" ); + copyBooleanAttribute( column, subelement, "insertable" ); + copyBooleanAttribute( column, subelement, "updatable" ); + copyStringAttribute( column, subelement, "column-definition", false ); + copyStringAttribute( column, subelement, "table", false ); + joinColumns.add( AnnotationFactory.create( column ) ); + } + } + return joinColumns.toArray( new JoinColumn[joinColumns.size()] ); + } + + private void addAssociationOverrideIfNeeded(AssociationOverride annotation, List overrides) { + if ( annotation != null ) { + String overrideName = annotation.name(); + boolean present = false; + for ( AssociationOverride current : overrides ) { + if ( current.name().equals( overrideName ) ) { + present = true; + break; + } + } + if ( !present ) { + overrides.add( annotation ); + } + } + } + + /** + * @param mergeWithAnnotations Whether to use Java annotations for this + * element, if present and not disabled by the XMLContext defaults. + * In some contexts (such as an association mapping) merging with + * annotations is never allowed. + */ + private AttributeOverrides getAttributeOverrides(Element tree, XMLContext.Default defaults, boolean mergeWithAnnotations) { + List attributes = buildAttributeOverrides( tree, "attribute-override" ); + return mergeAttributeOverrides( defaults, attributes, mergeWithAnnotations ); + } + + /** + * @param mergeWithAnnotations Whether to use Java annotations for this + * element, if present and not disabled by the XMLContext defaults. + * In some contexts (such as an association mapping) merging with + * annotations is never allowed. + */ + private AttributeOverrides mergeAttributeOverrides(XMLContext.Default defaults, List attributes, boolean mergeWithAnnotations) { + if ( mergeWithAnnotations && defaults.canUseJavaAnnotations() ) { + AttributeOverride annotation = getPhysicalAnnotation( AttributeOverride.class ); + addAttributeOverrideIfNeeded( annotation, attributes ); + AttributeOverrides annotations = getPhysicalAnnotation( AttributeOverrides.class ); + if ( annotations != null ) { + for ( AttributeOverride current : annotations.value() ) { + addAttributeOverrideIfNeeded( current, attributes ); + } + } + } + if ( attributes.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( AttributeOverrides.class ); + ad.setValue( "value", attributes.toArray( new AttributeOverride[attributes.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private List buildAttributeOverrides(Element element, String nodeName) { + List subelements = element == null ? null : element.elements( nodeName ); + return buildAttributeOverrides( subelements, nodeName ); + } + + private List buildAttributeOverrides(List subelements, String nodeName) { + List overrides = new ArrayList<>(); + if ( subelements != null && subelements.size() > 0 ) { + for ( Element current : subelements ) { + if ( !current.getName().equals( nodeName ) ) { + continue; + } + AnnotationDescriptor override = new AnnotationDescriptor( AttributeOverride.class ); + copyStringAttribute( override, current, "name", true ); + Element column = current.element( "column" ); + override.setValue( "column", getColumn( column, true, current ) ); + overrides.add( AnnotationFactory.create( override ) ); + } + } + return overrides; + } + + private Column getColumn(Element element, boolean isMandatory, Element current) { + //Element subelement = element != null ? element.element( "column" ) : null; + if ( element != null ) { + AnnotationDescriptor column = new AnnotationDescriptor( Column.class ); + copyStringAttribute( column, element, "name", false ); + copyBooleanAttribute( column, element, "unique" ); + copyBooleanAttribute( column, element, "nullable" ); + copyBooleanAttribute( column, element, "insertable" ); + copyBooleanAttribute( column, element, "updatable" ); + copyStringAttribute( column, element, "column-definition", false ); + copyStringAttribute( column, element, "table", false ); + copyIntegerAttribute( column, element, "length" ); + copyIntegerAttribute( column, element, "precision" ); + copyIntegerAttribute( column, element, "scale" ); + return (Column) AnnotationFactory.create( column ); + } + else { + if ( isMandatory ) { + throw new AnnotationException( current.getPath() + ".column is mandatory. " + SCHEMA_VALIDATION ); + } + return null; + } + } + + private void addAttributeOverrideIfNeeded(AttributeOverride annotation, List overrides) { + if ( annotation != null ) { + String overrideName = annotation.name(); + boolean present = false; + for ( AttributeOverride current : overrides ) { + if ( current.name().equals( overrideName ) ) { + present = true; + break; + } + } + if ( !present ) { + overrides.add( annotation ); + } + } + } + + private Access getAccessType(Element tree, XMLContext.Default defaults) { + String access = tree == null ? null : tree.attributeValue( "access" ); + if ( access != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Access.class ); + AccessType type; + try { + type = AccessType.valueOf( access ); + } + catch ( IllegalArgumentException e ) { + throw new AnnotationException( access + " is not a valid access type. Check you xml confguration." ); + } + ad.setValue( "value", type ); + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() && isPhysicalAnnotationPresent( Access.class ) ) { + return getPhysicalAnnotation( Access.class ); + } + else if ( defaults.getAccess() != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Access.class ); + ad.setValue( "value", defaults.getAccess() ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private ExcludeSuperclassListeners getExcludeSuperclassListeners(Element tree, XMLContext.Default defaults) { + return (ExcludeSuperclassListeners) getMarkerAnnotation( ExcludeSuperclassListeners.class, tree, defaults ); + } + + private ExcludeDefaultListeners getExcludeDefaultListeners(Element tree, XMLContext.Default defaults) { + return (ExcludeDefaultListeners) getMarkerAnnotation( ExcludeDefaultListeners.class, tree, defaults ); + } + + private Annotation getMarkerAnnotation( + Class clazz, Element element, XMLContext.Default defaults + ) { + Element subelement = element == null ? null : element.element( annotationToXml.get( clazz ) ); + if ( subelement != null ) { + return AnnotationFactory.create( new AnnotationDescriptor( clazz ) ); + } + else if ( defaults.canUseJavaAnnotations() ) { + //TODO wonder whether it should be excluded so that user can undone it + return getPhysicalAnnotation( clazz ); + } + else { + return null; + } + } + + private SqlResultSetMappings getSqlResultSetMappings(Element tree, XMLContext.Default defaults) { + List results = buildSqlResultsetMappings( tree, defaults, classLoaderAccess ); + if ( defaults.canUseJavaAnnotations() ) { + SqlResultSetMapping annotation = getPhysicalAnnotation( SqlResultSetMapping.class ); + addSqlResultsetMappingIfNeeded( annotation, results ); + SqlResultSetMappings annotations = getPhysicalAnnotation( SqlResultSetMappings.class ); + if ( annotations != null ) { + for ( SqlResultSetMapping current : annotations.value() ) { + addSqlResultsetMappingIfNeeded( current, results ); + } + } + } + if ( results.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( SqlResultSetMappings.class ); + ad.setValue( "value", results.toArray( new SqlResultSetMapping[results.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + public static List buildNamedEntityGraph( + Element element, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + if ( element == null ) { + return new ArrayList<>(); + } + List namedEntityGraphList = new ArrayList<>(); + List namedEntityGraphElements = element.elements( "named-entity-graph" ); + for ( Element subElement : namedEntityGraphElements ) { + AnnotationDescriptor ann = new AnnotationDescriptor( NamedEntityGraph.class ); + copyStringAttribute( ann, subElement, "name", false ); + copyBooleanAttribute( ann, subElement, "include-all-attributes" ); + bindNamedAttributeNodes( subElement, ann ); + + List subgraphNodes = subElement.elements( "subgraph" ); + List subclassSubgraphNodes = subElement.elements( "subclass-subgraph" ); + if(!subclassSubgraphNodes.isEmpty()) { + subgraphNodes.addAll( subclassSubgraphNodes ); + } + bindNamedSubgraph( defaults, ann, subgraphNodes, classLoaderAccess ); + namedEntityGraphList.add( AnnotationFactory.create( ann ) ); + } + //TODO + return namedEntityGraphList; + } + + private static void bindNamedSubgraph( + XMLContext.Default defaults, + AnnotationDescriptor ann, + List subgraphNodes, + ClassLoaderAccess classLoaderAccess) { + List annSubgraphNodes = new ArrayList<>( ); + for(Element subgraphNode : subgraphNodes){ + AnnotationDescriptor annSubgraphNode = new AnnotationDescriptor( NamedSubgraph.class ); + copyStringAttribute( annSubgraphNode, subgraphNode, "name", true ); + String clazzName = subgraphNode.attributeValue( "class" ); + Class clazz; + try { + clazz = classLoaderAccess.classForName( + XMLContext.buildSafeClassName( clazzName, defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); + } + annSubgraphNode.setValue( "type", clazz ); + bindNamedAttributeNodes(subgraphNode, annSubgraphNode); + annSubgraphNodes.add( AnnotationFactory.create( annSubgraphNode ) ); + } + + ann.setValue( "subgraphs", annSubgraphNodes.toArray( new NamedSubgraph[annSubgraphNodes.size()] ) ); + } + + private static void bindNamedAttributeNodes(Element subElement, AnnotationDescriptor ann) { + List namedAttributeNodes = subElement.elements("named-attribute-node"); + List annNamedAttributeNodes = new ArrayList<>( ); + for(Element namedAttributeNode : namedAttributeNodes){ + AnnotationDescriptor annNamedAttributeNode = new AnnotationDescriptor( NamedAttributeNode.class ); + copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "value", "name", true ); + copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "subgraph", false ); + copyStringAttribute( annNamedAttributeNode, namedAttributeNode, "key-subgraph", false ); + annNamedAttributeNodes.add( AnnotationFactory.create( annNamedAttributeNode ) ); + } + ann.setValue( "attributeNodes", annNamedAttributeNodes.toArray( new NamedAttributeNode[annNamedAttributeNodes.size()] ) ); + } + + public static List buildNamedStoreProcedureQueries( + Element element, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + if ( element == null ) { + return new ArrayList<>(); + } + List namedStoredProcedureElements = element.elements( "named-stored-procedure-query" ); + List namedStoredProcedureQueries = new ArrayList<>(); + for ( Object obj : namedStoredProcedureElements ) { + Element subElement = (Element) obj; + AnnotationDescriptor ann = new AnnotationDescriptor( NamedStoredProcedureQuery.class ); + copyStringAttribute( ann, subElement, "name", true ); + copyStringAttribute( ann, subElement, "procedure-name", true ); + + List elements = subElement.elements( "parameter" ); + List storedProcedureParameters = new ArrayList<>(); + + for ( Element parameterElement : elements ) { + AnnotationDescriptor parameterDescriptor = new AnnotationDescriptor( StoredProcedureParameter.class ); + copyStringAttribute( parameterDescriptor, parameterElement, "name", false ); + String modeValue = parameterElement.attributeValue( "mode" ); + if ( modeValue == null ) { + parameterDescriptor.setValue( "mode", ParameterMode.IN ); + } + else { + parameterDescriptor.setValue( "mode", ParameterMode.valueOf( modeValue.toUpperCase(Locale.ROOT) ) ); + } + String clazzName = parameterElement.attributeValue( "class" ); + Class clazz; + try { + clazz = classLoaderAccess.classForName( + XMLContext.buildSafeClassName( clazzName, defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); + } + parameterDescriptor.setValue( "type", clazz ); + storedProcedureParameters.add( AnnotationFactory.create( parameterDescriptor ) ); + } + + ann.setValue( + "parameters", + storedProcedureParameters.toArray( new StoredProcedureParameter[storedProcedureParameters.size()] ) + ); + + elements = subElement.elements( "result-class" ); + List returnClasses = new ArrayList<>(); + for ( Element classElement : elements ) { + String clazzName = classElement.getTextTrim(); + Class clazz; + try { + clazz = classLoaderAccess.classForName( + XMLContext.buildSafeClassName( clazzName, defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); + } + returnClasses.add( clazz ); + } + ann.setValue( "resultClasses", returnClasses.toArray( new Class[returnClasses.size()] ) ); + + + elements = subElement.elements( "result-set-mapping" ); + List resultSetMappings = new ArrayList<>(); + for ( Element resultSetMappingElement : elements ) { + resultSetMappings.add( resultSetMappingElement.getTextTrim() ); + } + ann.setValue( "resultSetMappings", resultSetMappings.toArray( new String[resultSetMappings.size()] ) ); + elements = subElement.elements( "hint" ); + buildQueryHints( elements, ann ); + namedStoredProcedureQueries.add( AnnotationFactory.create( ann ) ); + } + return namedStoredProcedureQueries; + + } + + public static List buildSqlResultsetMappings( + Element element, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + final List builtResultSetMappings = new ArrayList<>(); + if ( element == null ) { + return builtResultSetMappings; + } + + // iterate over each element + for ( Object resultSetMappingElementObject : element.elements( "sql-result-set-mapping" ) ) { + final Element resultSetMappingElement = (Element) resultSetMappingElementObject; + + final AnnotationDescriptor resultSetMappingAnnotation = new AnnotationDescriptor( SqlResultSetMapping.class ); + copyStringAttribute( resultSetMappingAnnotation, resultSetMappingElement, "name", true ); + + // iterate over the sub-elements, which should include: + // * + // * + // * + + List entityResultAnnotations = null; + List columnResultAnnotations = null; + List constructorResultAnnotations = null; + + for ( Object resultElementObject : resultSetMappingElement.elements() ) { + final Element resultElement = (Element) resultElementObject; + + if ( "entity-result".equals( resultElement.getName() ) ) { + if ( entityResultAnnotations == null ) { + entityResultAnnotations = new ArrayList<>(); + } + // process the + entityResultAnnotations.add( buildEntityResult( resultElement, defaults, classLoaderAccess ) ); + } + else if ( "column-result".equals( resultElement.getName() ) ) { + if ( columnResultAnnotations == null ) { + columnResultAnnotations = new ArrayList<>(); + } + columnResultAnnotations.add( buildColumnResult( resultElement, defaults, classLoaderAccess ) ); + } + else if ( "constructor-result".equals( resultElement.getName() ) ) { + if ( constructorResultAnnotations == null ) { + constructorResultAnnotations = new ArrayList<>(); + } + constructorResultAnnotations.add( buildConstructorResult( resultElement, defaults, classLoaderAccess ) ); + } + else { + // most likely the this code used to handle. I have left the code here, + // but commented it out for now. I'll just log a warning for now. + LOG.debug( "Encountered unrecognized sql-result-set-mapping sub-element : " + resultElement.getName() ); + +// String clazzName = subelement.attributeValue( "result-class" ); +// if ( StringHelper.isNotEmpty( clazzName ) ) { +// Class clazz; +// try { +// clazz = ReflectHelper.classForName( +// XMLContext.buildSafeClassName( clazzName, defaults ), +// JPAOverriddenAnnotationReader.class +// ); +// } +// catch ( ClassNotFoundException e ) { +// throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); +// } +// ann.setValue( "resultClass", clazz ); +// } + } + } + + if ( entityResultAnnotations != null && !entityResultAnnotations.isEmpty() ) { + resultSetMappingAnnotation.setValue( + "entities", + entityResultAnnotations.toArray( new EntityResult[entityResultAnnotations.size()] ) + ); + } + if ( columnResultAnnotations != null && !columnResultAnnotations.isEmpty() ) { + resultSetMappingAnnotation.setValue( + "columns", + columnResultAnnotations.toArray( new ColumnResult[columnResultAnnotations.size()] ) + ); + } + if ( constructorResultAnnotations != null && !constructorResultAnnotations.isEmpty() ) { + resultSetMappingAnnotation.setValue( + "classes", + constructorResultAnnotations.toArray( new ConstructorResult[constructorResultAnnotations.size()] ) + ); + } + + + // this was part of the old code too, but could never figure out what it is supposed to do... + // copyStringAttribute( ann, subelement, "result-set-mapping", false ); + + builtResultSetMappings.add( AnnotationFactory.create( resultSetMappingAnnotation ) ); + } + + return builtResultSetMappings; + } + + private static EntityResult buildEntityResult( + Element entityResultElement, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + final AnnotationDescriptor entityResultDescriptor = new AnnotationDescriptor( EntityResult.class ); + + final Class entityClass = resolveClassReference( entityResultElement.attributeValue( "entity-class" ), defaults, classLoaderAccess ); + entityResultDescriptor.setValue( "entityClass", entityClass ); + + copyStringAttribute( entityResultDescriptor, entityResultElement, "discriminator-column", false ); + + // process the sub-elements + List fieldResultAnnotations = new ArrayList<>(); + for ( Element fieldResult : (List) entityResultElement.elements( "field-result" ) ) { + AnnotationDescriptor fieldResultDescriptor = new AnnotationDescriptor( FieldResult.class ); + copyStringAttribute( fieldResultDescriptor, fieldResult, "name", true ); + copyStringAttribute( fieldResultDescriptor, fieldResult, "column", true ); + fieldResultAnnotations.add( AnnotationFactory.create( fieldResultDescriptor ) ); + } + entityResultDescriptor.setValue( + "fields", fieldResultAnnotations.toArray( new FieldResult[fieldResultAnnotations.size()] ) + ); + return AnnotationFactory.create( entityResultDescriptor ); + } + + private static Class resolveClassReference( + String className, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + if ( className == null ) { + throw new AnnotationException( " without entity-class. " + SCHEMA_VALIDATION ); + } + try { + return classLoaderAccess.classForName( + XMLContext.buildSafeClassName( className, defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( "Unable to find specified class: " + className, e ); + } + } + + private static ColumnResult buildColumnResult( + Element columnResultElement, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { +// AnnotationDescriptor columnResultDescriptor = new AnnotationDescriptor( ColumnResult.class ); +// copyStringAttribute( columnResultDescriptor, columnResultElement, "name", true ); +// return AnnotationFactory.create( columnResultDescriptor ); + + AnnotationDescriptor columnResultDescriptor = new AnnotationDescriptor( ColumnResult.class ); + copyStringAttribute( columnResultDescriptor, columnResultElement, "name", true ); + final String columnTypeName = columnResultElement.attributeValue( "class" ); + if ( StringHelper.isNotEmpty( columnTypeName ) ) { + columnResultDescriptor.setValue( "type", resolveClassReference( columnTypeName, defaults, classLoaderAccess ) ); + } + return AnnotationFactory.create( columnResultDescriptor ); + } + + private static ConstructorResult buildConstructorResult( + Element constructorResultElement, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + AnnotationDescriptor constructorResultDescriptor = new AnnotationDescriptor( ConstructorResult.class ); + + final Class entityClass = resolveClassReference( constructorResultElement.attributeValue( "target-class" ), defaults, classLoaderAccess ); + constructorResultDescriptor.setValue( "targetClass", entityClass ); + + List columnResultAnnotations = new ArrayList<>(); + for ( Element columnResultElement : (List) constructorResultElement.elements( "column" ) ) { + columnResultAnnotations.add( buildColumnResult( columnResultElement, defaults, classLoaderAccess ) ); + } + constructorResultDescriptor.setValue( + "columns", + columnResultAnnotations.toArray( new ColumnResult[ columnResultAnnotations.size() ] ) + ); + + return AnnotationFactory.create( constructorResultDescriptor ); + } + + private void addSqlResultsetMappingIfNeeded(SqlResultSetMapping annotation, List resultsets) { + if ( annotation != null ) { + String resultsetName = annotation.name(); + boolean present = false; + for ( SqlResultSetMapping current : resultsets ) { + if ( current.name().equals( resultsetName ) ) { + present = true; + break; + } + } + if ( !present ) { + resultsets.add( annotation ); + } + } + } + + private NamedQueries getNamedQueries(Element tree, XMLContext.Default defaults) { + //TODO avoid the Proxy Creation (@NamedQueries) when possible + List queries = (List) buildNamedQueries( tree, false, defaults, classLoaderAccess ); + if ( defaults.canUseJavaAnnotations() ) { + NamedQuery annotation = getPhysicalAnnotation( NamedQuery.class ); + addNamedQueryIfNeeded( annotation, queries ); + NamedQueries annotations = getPhysicalAnnotation( NamedQueries.class ); + if ( annotations != null ) { + for ( NamedQuery current : annotations.value() ) { + addNamedQueryIfNeeded( current, queries ); + } + } + } + if ( queries.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( NamedQueries.class ); + ad.setValue( "value", queries.toArray( new NamedQuery[queries.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private void addNamedQueryIfNeeded(NamedQuery annotation, List queries) { + if ( annotation != null ) { + String queryName = annotation.name(); + boolean present = false; + for ( NamedQuery current : queries ) { + if ( current.name().equals( queryName ) ) { + present = true; + break; + } + } + if ( !present ) { + queries.add( annotation ); + } + } + } + + private NamedEntityGraphs getNamedEntityGraphs(Element tree, XMLContext.Default defaults) { + List queries = buildNamedEntityGraph( tree, defaults, classLoaderAccess ); + if ( defaults.canUseJavaAnnotations() ) { + NamedEntityGraph annotation = getPhysicalAnnotation( NamedEntityGraph.class ); + addNamedEntityGraphIfNeeded( annotation, queries ); + NamedEntityGraphs annotations = getPhysicalAnnotation( NamedEntityGraphs.class ); + if ( annotations != null ) { + for ( NamedEntityGraph current : annotations.value() ) { + addNamedEntityGraphIfNeeded( current, queries ); + } + } + } + if ( queries.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( NamedEntityGraphs.class ); + ad.setValue( "value", queries.toArray( new NamedEntityGraph[queries.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private void addNamedEntityGraphIfNeeded(NamedEntityGraph annotation, List queries) { + if ( annotation != null ) { + String queryName = annotation.name(); + boolean present = false; + for ( NamedEntityGraph current : queries ) { + if ( current.name().equals( queryName ) ) { + present = true; + break; + } + } + if ( !present ) { + queries.add( annotation ); + } + } + + } + + private NamedStoredProcedureQueries getNamedStoredProcedureQueries(Element tree, XMLContext.Default defaults) { + List queries = buildNamedStoreProcedureQueries( tree, defaults, classLoaderAccess ); + if ( defaults.canUseJavaAnnotations() ) { + NamedStoredProcedureQuery annotation = getPhysicalAnnotation( NamedStoredProcedureQuery.class ); + addNamedStoredProcedureQueryIfNeeded( annotation, queries ); + NamedStoredProcedureQueries annotations = getPhysicalAnnotation( NamedStoredProcedureQueries.class ); + if ( annotations != null ) { + for ( NamedStoredProcedureQuery current : annotations.value() ) { + addNamedStoredProcedureQueryIfNeeded( current, queries ); + } + } + } + if ( queries.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( NamedStoredProcedureQueries.class ); + ad.setValue( "value", queries.toArray( new NamedStoredProcedureQuery[queries.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private void addNamedStoredProcedureQueryIfNeeded(NamedStoredProcedureQuery annotation, List queries) { + if ( annotation != null ) { + String queryName = annotation.name(); + boolean present = false; + for ( NamedStoredProcedureQuery current : queries ) { + if ( current.name().equals( queryName ) ) { + present = true; + break; + } + } + if ( !present ) { + queries.add( annotation ); + } + } + } + + + private NamedNativeQueries getNamedNativeQueries( + Element tree, + XMLContext.Default defaults) { + List queries = (List) buildNamedQueries( tree, true, defaults, classLoaderAccess ); + if ( defaults.canUseJavaAnnotations() ) { + NamedNativeQuery annotation = getPhysicalAnnotation( NamedNativeQuery.class ); + addNamedNativeQueryIfNeeded( annotation, queries ); + NamedNativeQueries annotations = getPhysicalAnnotation( NamedNativeQueries.class ); + if ( annotations != null ) { + for ( NamedNativeQuery current : annotations.value() ) { + addNamedNativeQueryIfNeeded( current, queries ); + } + } + } + if ( queries.size() > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( NamedNativeQueries.class ); + ad.setValue( "value", queries.toArray( new NamedNativeQuery[queries.size()] ) ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private void addNamedNativeQueryIfNeeded(NamedNativeQuery annotation, List queries) { + if ( annotation != null ) { + String queryName = annotation.name(); + boolean present = false; + for ( NamedNativeQuery current : queries ) { + if ( current.name().equals( queryName ) ) { + present = true; + break; + } + } + if ( !present ) { + queries.add( annotation ); + } + } + } + + private static void buildQueryHints(List elements, AnnotationDescriptor ann){ + List queryHints = new ArrayList<>( elements.size() ); + for ( Element hint : elements ) { + AnnotationDescriptor hintDescriptor = new AnnotationDescriptor( QueryHint.class ); + String value = hint.attributeValue( "name" ); + if ( value == null ) { + throw new AnnotationException( " without name. " + SCHEMA_VALIDATION ); + } + hintDescriptor.setValue( "name", value ); + value = hint.attributeValue( "value" ); + if ( value == null ) { + throw new AnnotationException( " without value. " + SCHEMA_VALIDATION ); + } + hintDescriptor.setValue( "value", value ); + queryHints.add( AnnotationFactory.create( hintDescriptor ) ); + } + ann.setValue( "hints", queryHints.toArray( new QueryHint[queryHints.size()] ) ); + } + + public static List buildNamedQueries( + Element element, + boolean isNative, + XMLContext.Default defaults, + ClassLoaderAccess classLoaderAccess) { + if ( element == null ) { + return new ArrayList(); + } + List namedQueryElementList = isNative ? + element.elements( "named-native-query" ) : + element.elements( "named-query" ); + List namedQueries = new ArrayList(); + for ( Object aNamedQueryElementList : namedQueryElementList ) { + Element subelement = (Element) aNamedQueryElementList; + AnnotationDescriptor ann = new AnnotationDescriptor( + isNative ? NamedNativeQuery.class : NamedQuery.class + ); + copyStringAttribute( ann, subelement, "name", false ); + Element queryElt = subelement.element( "query" ); + if ( queryElt == null ) { + throw new AnnotationException( "No element found." + SCHEMA_VALIDATION ); + } + copyStringElement( queryElt, ann, "query" ); + List elements = subelement.elements( "hint" ); + buildQueryHints( elements, ann ); + String clazzName = subelement.attributeValue( "result-class" ); + if ( StringHelper.isNotEmpty( clazzName ) ) { + Class clazz; + try { + clazz = classLoaderAccess.classForName( + XMLContext.buildSafeClassName( clazzName, defaults ) + ); + } + catch (ClassLoadingException e) { + throw new AnnotationException( "Unable to find entity-class: " + clazzName, e ); + } + ann.setValue( "resultClass", clazz ); + } + copyStringAttribute( ann, subelement, "result-set-mapping", false ); + namedQueries.add( AnnotationFactory.create( ann ) ); + } + return namedQueries; + } + + private TableGenerator getTableGenerator(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( annotationToXml.get( TableGenerator.class ) ) : null; + if ( element != null ) { + return buildTableGeneratorAnnotation( element, defaults ); + } + else if ( defaults.canUseJavaAnnotations() && isPhysicalAnnotationPresent( TableGenerator.class ) ) { + TableGenerator tableAnn = getPhysicalAnnotation( TableGenerator.class ); + if ( StringHelper.isNotEmpty( defaults.getSchema() ) + || StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + AnnotationDescriptor annotation = new AnnotationDescriptor( TableGenerator.class ); + annotation.setValue( "name", tableAnn.name() ); + annotation.setValue( "table", tableAnn.table() ); + annotation.setValue( "catalog", tableAnn.table() ); + if ( StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) + && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + annotation.setValue( "schema", tableAnn.table() ); + if ( StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) + && StringHelper.isNotEmpty( defaults.getSchema() ) ) { + annotation.setValue( "catalog", defaults.getSchema() ); + } + annotation.setValue( "pkColumnName", tableAnn.pkColumnName() ); + annotation.setValue( "valueColumnName", tableAnn.valueColumnName() ); + annotation.setValue( "pkColumnValue", tableAnn.pkColumnValue() ); + annotation.setValue( "initialValue", tableAnn.initialValue() ); + annotation.setValue( "allocationSize", tableAnn.allocationSize() ); + annotation.setValue( "uniqueConstraints", tableAnn.uniqueConstraints() ); + return AnnotationFactory.create( annotation ); + } + else { + return tableAnn; + } + } + else { + return null; + } + } + + public static TableGenerator buildTableGeneratorAnnotation(Element element, XMLContext.Default defaults) { + AnnotationDescriptor ad = new AnnotationDescriptor( TableGenerator.class ); + copyStringAttribute( ad, element, "name", false ); + copyStringAttribute( ad, element, "table", false ); + copyStringAttribute( ad, element, "catalog", false ); + copyStringAttribute( ad, element, "schema", false ); + copyStringAttribute( ad, element, "pk-column-name", false ); + copyStringAttribute( ad, element, "value-column-name", false ); + copyStringAttribute( ad, element, "pk-column-value", false ); + copyIntegerAttribute( ad, element, "initial-value" ); + copyIntegerAttribute( ad, element, "allocation-size" ); + buildUniqueConstraints( ad, element ); + if ( StringHelper.isEmpty( (String) ad.valueOf( "schema" ) ) + && StringHelper.isNotEmpty( defaults.getSchema() ) ) { + ad.setValue( "schema", defaults.getSchema() ); + } + if ( StringHelper.isEmpty( (String) ad.valueOf( "catalog" ) ) + && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + ad.setValue( "catalog", defaults.getCatalog() ); + } + return AnnotationFactory.create( ad ); + } + + private SequenceGenerator getSequenceGenerator(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( annotationToXml.get( SequenceGenerator.class ) ) : null; + if ( element != null ) { + return buildSequenceGeneratorAnnotation( element ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( SequenceGenerator.class ); + } + else { + return null; + } + } + + public static SequenceGenerator buildSequenceGeneratorAnnotation(Element element) { + if ( element != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( SequenceGenerator.class ); + copyStringAttribute( ad, element, "name", false ); + copyStringAttribute( ad, element, "sequence-name", false ); + copyIntegerAttribute( ad, element, "initial-value" ); + copyIntegerAttribute( ad, element, "allocation-size" ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private DiscriminatorColumn getDiscriminatorColumn(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( "discriminator-column" ) : null; + if ( element != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( DiscriminatorColumn.class ); + copyStringAttribute( ad, element, "name", false ); + copyStringAttribute( ad, element, "column-definition", false ); + String value = element.attributeValue( "discriminator-type" ); + DiscriminatorType type = DiscriminatorType.STRING; + if ( value != null ) { + if ( "STRING".equals( value ) ) { + type = DiscriminatorType.STRING; + } + else if ( "CHAR".equals( value ) ) { + type = DiscriminatorType.CHAR; + } + else if ( "INTEGER".equals( value ) ) { + type = DiscriminatorType.INTEGER; + } + else { + throw new AnnotationException( + "Unknown DiscriminatorType in XML: " + value + " (" + SCHEMA_VALIDATION + ")" + ); + } + } + ad.setValue( "discriminatorType", type ); + copyIntegerAttribute( ad, element, "length" ); + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( DiscriminatorColumn.class ); + } + else { + return null; + } + } + + private DiscriminatorValue getDiscriminatorValue(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( "discriminator-value" ) : null; + if ( element != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( DiscriminatorValue.class ); + copyStringElement( element, ad, "value" ); + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( DiscriminatorValue.class ); + } + else { + return null; + } + } + + private Inheritance getInheritance(Element tree, XMLContext.Default defaults) { + Element element = tree != null ? tree.element( "inheritance" ) : null; + if ( element != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Inheritance.class ); + Attribute attr = element.attribute( "strategy" ); + InheritanceType strategy = InheritanceType.SINGLE_TABLE; + if ( attr != null ) { + String value = attr.getValue(); + if ( "SINGLE_TABLE".equals( value ) ) { + strategy = InheritanceType.SINGLE_TABLE; + } + else if ( "JOINED".equals( value ) ) { + strategy = InheritanceType.JOINED; + } + else if ( "TABLE_PER_CLASS".equals( value ) ) { + strategy = InheritanceType.TABLE_PER_CLASS; + } + else { + throw new AnnotationException( + "Unknown InheritanceType in XML: " + value + " (" + SCHEMA_VALIDATION + ")" + ); + } + } + ad.setValue( "strategy", strategy ); + return AnnotationFactory.create( ad ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( Inheritance.class ); + } + else { + return null; + } + } + + private IdClass getIdClass(Element tree, XMLContext.Default defaults) { + Element element = tree == null ? null : tree.element( "id-class" ); + if ( element != null ) { + Attribute attr = element.attribute( "class" ); + if ( attr != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( IdClass.class ); + Class clazz; + try { + clazz = classLoaderAccess.classForName( XMLContext.buildSafeClassName( attr.getValue(), defaults ) + ); + } + catch ( ClassLoadingException e ) { + throw new AnnotationException( "Unable to find id-class: " + attr.getValue(), e ); + } + ad.setValue( "value", clazz ); + return AnnotationFactory.create( ad ); + } + else { + throw new AnnotationException( "id-class without class. " + SCHEMA_VALIDATION ); + } + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( IdClass.class ); + } + else { + return null; + } + } + + /** + * @param mergeWithAnnotations Whether to use Java annotations for this + * element, if present and not disabled by the XMLContext defaults. + * In some contexts (such as an association mapping) merging with + * annotations is never allowed. + */ + private PrimaryKeyJoinColumns getPrimaryKeyJoinColumns(Element element, XMLContext.Default defaults, boolean mergeWithAnnotations) { + PrimaryKeyJoinColumn[] columns = buildPrimaryKeyJoinColumns( element ); + if ( mergeWithAnnotations ) { + if ( columns.length == 0 && defaults.canUseJavaAnnotations() ) { + PrimaryKeyJoinColumn annotation = getPhysicalAnnotation( PrimaryKeyJoinColumn.class ); + if ( annotation != null ) { + columns = new PrimaryKeyJoinColumn[] { annotation }; + } + else { + PrimaryKeyJoinColumns annotations = getPhysicalAnnotation( PrimaryKeyJoinColumns.class ); + columns = annotations != null ? annotations.value() : columns; + } + } + } + if ( columns.length > 0 ) { + AnnotationDescriptor ad = new AnnotationDescriptor( PrimaryKeyJoinColumns.class ); + ad.setValue( "value", columns ); + return AnnotationFactory.create( ad ); + } + else { + return null; + } + } + + private Entity getEntity(Element tree, XMLContext.Default defaults) { + if ( tree == null ) { + return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation( Entity.class ) : null; + } + else { + if ( "entity".equals( tree.getName() ) ) { + AnnotationDescriptor entity = new AnnotationDescriptor( Entity.class ); + copyStringAttribute( entity, tree, "name", false ); + if ( defaults.canUseJavaAnnotations() + && StringHelper.isEmpty( (String) entity.valueOf( "name" ) ) ) { + Entity javaAnn = getPhysicalAnnotation( Entity.class ); + if ( javaAnn != null ) { + entity.setValue( "name", javaAnn.name() ); + } + } + return AnnotationFactory.create( entity ); + } + else { + return null; //this is not an entity + } + } + } + + private MappedSuperclass getMappedSuperclass(Element tree, XMLContext.Default defaults) { + if ( tree == null ) { + return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation( MappedSuperclass.class ) : null; + } + else { + if ( "mapped-superclass".equals( tree.getName() ) ) { + AnnotationDescriptor entity = new AnnotationDescriptor( MappedSuperclass.class ); + return AnnotationFactory.create( entity ); + } + else { + return null; //this is not an entity + } + } + } + + private Embeddable getEmbeddable(Element tree, XMLContext.Default defaults) { + if ( tree == null ) { + return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation( Embeddable.class ) : null; + } + else { + if ( "embeddable".equals( tree.getName() ) ) { + AnnotationDescriptor entity = new AnnotationDescriptor( Embeddable.class ); + return AnnotationFactory.create( entity ); + } + else { + return null; //this is not an entity + } + } + } + + private Table getTable(Element tree, XMLContext.Default defaults) { + Element subelement = tree == null ? null : tree.element( "table" ); + if ( subelement == null ) { + //no element but might have some default or some annotation + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + || StringHelper.isNotEmpty( defaults.getSchema() ) ) { + AnnotationDescriptor annotation = new AnnotationDescriptor( Table.class ); + if ( defaults.canUseJavaAnnotations() ) { + Table table = getPhysicalAnnotation( Table.class ); + if ( table != null ) { + annotation.setValue( "name", table.name() ); + annotation.setValue( "schema", table.schema() ); + annotation.setValue( "catalog", table.catalog() ); + annotation.setValue( "uniqueConstraints", table.uniqueConstraints() ); + annotation.setValue( "indexes", table.indexes() ); + } + } + if ( StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) + && StringHelper.isNotEmpty( defaults.getSchema() ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + if ( StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) + && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + return AnnotationFactory.create( annotation ); + } + else if ( defaults.canUseJavaAnnotations() ) { + return getPhysicalAnnotation( Table.class ); + } + else { + return null; + } + } + else { + //ignore java annotation, an element is defined + AnnotationDescriptor annotation = new AnnotationDescriptor( Table.class ); + copyStringAttribute( annotation, subelement, "name", false ); + copyStringAttribute( annotation, subelement, "catalog", false ); + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + copyStringAttribute( annotation, subelement, "schema", false ); + if ( StringHelper.isNotEmpty( defaults.getSchema() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + buildUniqueConstraints( annotation, subelement ); + buildIndex( annotation, subelement ); + return AnnotationFactory.create( annotation ); + } + } + + private SecondaryTables getSecondaryTables(Element tree, XMLContext.Default defaults) { + List elements = tree == null ? + new ArrayList<>() : + (List) tree.elements( "secondary-table" ); + List secondaryTables = new ArrayList<>( 3 ); + for ( Element element : elements ) { + AnnotationDescriptor annotation = new AnnotationDescriptor( SecondaryTable.class ); + copyStringAttribute( annotation, element, "name", false ); + copyStringAttribute( annotation, element, "catalog", false ); + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + copyStringAttribute( annotation, element, "schema", false ); + if ( StringHelper.isNotEmpty( defaults.getSchema() ) + && StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + buildUniqueConstraints( annotation, element ); + buildIndex( annotation, element ); + annotation.setValue( "pkJoinColumns", buildPrimaryKeyJoinColumns( element ) ); + secondaryTables.add( AnnotationFactory.create( annotation ) ); + } + /* + * You can't have both secondary tables in XML and Java, + * since there would be no way to "remove" a secondary table + */ + if ( secondaryTables.size() == 0 && defaults.canUseJavaAnnotations() ) { + SecondaryTable secTableAnn = getPhysicalAnnotation( SecondaryTable.class ); + overridesDefaultInSecondaryTable( secTableAnn, defaults, secondaryTables ); + SecondaryTables secTablesAnn = getPhysicalAnnotation( SecondaryTables.class ); + if ( secTablesAnn != null ) { + for ( SecondaryTable table : secTablesAnn.value() ) { + overridesDefaultInSecondaryTable( table, defaults, secondaryTables ); + } + } + } + if ( secondaryTables.size() > 0 ) { + AnnotationDescriptor descriptor = new AnnotationDescriptor( SecondaryTables.class ); + descriptor.setValue( "value", secondaryTables.toArray( new SecondaryTable[secondaryTables.size()] ) ); + return AnnotationFactory.create( descriptor ); + } + else { + return null; + } + } + + private void overridesDefaultInSecondaryTable( + SecondaryTable secTableAnn, XMLContext.Default defaults, List secondaryTables + ) { + if ( secTableAnn != null ) { + //handle default values + if ( StringHelper.isNotEmpty( defaults.getCatalog() ) + || StringHelper.isNotEmpty( defaults.getSchema() ) ) { + AnnotationDescriptor annotation = new AnnotationDescriptor( SecondaryTable.class ); + annotation.setValue( "name", secTableAnn.name() ); + annotation.setValue( "schema", secTableAnn.schema() ); + annotation.setValue( "catalog", secTableAnn.catalog() ); + annotation.setValue( "uniqueConstraints", secTableAnn.uniqueConstraints() ); + annotation.setValue( "pkJoinColumns", secTableAnn.pkJoinColumns() ); + if ( StringHelper.isEmpty( (String) annotation.valueOf( "schema" ) ) + && StringHelper.isNotEmpty( defaults.getSchema() ) ) { + annotation.setValue( "schema", defaults.getSchema() ); + } + if ( StringHelper.isEmpty( (String) annotation.valueOf( "catalog" ) ) + && StringHelper.isNotEmpty( defaults.getCatalog() ) ) { + annotation.setValue( "catalog", defaults.getCatalog() ); + } + secondaryTables.add( AnnotationFactory.create( annotation ) ); + } + else { + secondaryTables.add( secTableAnn ); + } + } + } + private static void buildIndex(AnnotationDescriptor annotation, Element element){ + List indexElementList = element.elements( "index" ); + Index[] indexes = new Index[indexElementList.size()]; + for(int i=0;i columnNamesElements = subelement.elements( "column-name" ); + String[] columnNames = new String[columnNamesElements.size()]; + int columnNameIndex = 0; + Iterator it = columnNamesElements.listIterator(); + while ( it.hasNext() ) { + Element columnNameElt = (Element) it.next(); + columnNames[columnNameIndex++] = columnNameElt.getTextTrim(); + } + AnnotationDescriptor ucAnn = new AnnotationDescriptor( UniqueConstraint.class ); + copyStringAttribute( ucAnn, subelement, "name", false ); + ucAnn.setValue( "columnNames", columnNames ); + uniqueConstraints[ucIndex++] = AnnotationFactory.create( ucAnn ); + } + annotation.setValue( "uniqueConstraints", uniqueConstraints ); + } + + private PrimaryKeyJoinColumn[] buildPrimaryKeyJoinColumns(Element element) { + if ( element == null ) { + return new PrimaryKeyJoinColumn[] { }; + } + List pkJoinColumnElementList = element.elements( "primary-key-join-column" ); + PrimaryKeyJoinColumn[] pkJoinColumns = new PrimaryKeyJoinColumn[pkJoinColumnElementList.size()]; + int index = 0; + Iterator pkIt = pkJoinColumnElementList.listIterator(); + while ( pkIt.hasNext() ) { + Element subelement = (Element) pkIt.next(); + AnnotationDescriptor pkAnn = new AnnotationDescriptor( PrimaryKeyJoinColumn.class ); + copyStringAttribute( pkAnn, subelement, "name", false ); + copyStringAttribute( pkAnn, subelement, "referenced-column-name", false ); + copyStringAttribute( pkAnn, subelement, "column-definition", false ); + pkJoinColumns[index++] = AnnotationFactory.create( pkAnn ); + } + return pkJoinColumns; + } + + /** + * Copy a string attribute from an XML element to an annotation descriptor. The name of the annotation attribute is + * computed from the name of the XML attribute by {@link #getJavaAttributeNameFromXMLOne(String)}. + * + * @param annotation annotation descriptor where to copy to the attribute. + * @param element XML element from where to copy the attribute. + * @param attributeName name of the XML attribute to copy. + * @param mandatory whether the attribute is mandatory. + */ + private static void copyStringAttribute( + final AnnotationDescriptor annotation, final Element element, + final String attributeName, final boolean mandatory) { + copyStringAttribute( + annotation, + element, + getJavaAttributeNameFromXMLOne( attributeName ), + attributeName, + mandatory + ); + } + + /** + * Copy a string attribute from an XML element to an annotation descriptor. The name of the annotation attribute is + * explicitly given. + * + * @param annotation annotation where to copy to the attribute. + * @param element XML element from where to copy the attribute. + * @param annotationAttributeName name of the annotation attribute where to copy. + * @param attributeName name of the XML attribute to copy. + * @param mandatory whether the attribute is mandatory. + */ + private static void copyStringAttribute( + final AnnotationDescriptor annotation, final Element element, + final String annotationAttributeName, final String attributeName, boolean mandatory) { + String attribute = element.attributeValue( attributeName ); + if ( attribute != null ) { + annotation.setValue( annotationAttributeName, attribute ); + } + else { + if ( mandatory ) { + throw new AnnotationException( + element.getName() + "." + attributeName + " is mandatory in XML overriding. " + SCHEMA_VALIDATION + ); + } + } + } + + private static void copyIntegerAttribute(AnnotationDescriptor annotation, Element element, String attributeName) { + String attribute = element.attributeValue( attributeName ); + if ( attribute != null ) { + String annotationAttributeName = getJavaAttributeNameFromXMLOne( attributeName ); + annotation.setValue( annotationAttributeName, attribute ); + try { + int length = Integer.parseInt( attribute ); + annotation.setValue( annotationAttributeName, length ); + } + catch ( NumberFormatException e ) { + throw new AnnotationException( + element.getPath() + attributeName + " not parseable: " + attribute + " (" + SCHEMA_VALIDATION + ")" + ); + } + } + } + + private static String getJavaAttributeNameFromXMLOne(String attributeName) { + StringBuilder annotationAttributeName = new StringBuilder( attributeName ); + int index = annotationAttributeName.indexOf( WORD_SEPARATOR ); + while ( index != -1 ) { + annotationAttributeName.deleteCharAt( index ); + annotationAttributeName.setCharAt( + index, Character.toUpperCase( annotationAttributeName.charAt( index ) ) + ); + index = annotationAttributeName.indexOf( WORD_SEPARATOR ); + } + return annotationAttributeName.toString(); + } + + private static void copyStringElement(Element element, AnnotationDescriptor ad, String annotationAttribute) { + String discr = element.getTextTrim(); + ad.setValue( annotationAttribute, discr ); + } + + private static void copyBooleanAttribute(AnnotationDescriptor descriptor, Element element, String attribute) { + String attributeValue = element.attributeValue( attribute ); + if ( StringHelper.isNotEmpty( attributeValue ) ) { + String javaAttribute = getJavaAttributeNameFromXMLOne( attribute ); + descriptor.setValue( javaAttribute, Boolean.parseBoolean( attributeValue ) ); + } + } + + private T getPhysicalAnnotation(Class annotationType) { + return element.getAnnotation( annotationType ); + } + + private boolean isPhysicalAnnotationPresent(Class annotationType) { + return element.isAnnotationPresent( annotationType ); + } + + private Annotation[] getPhysicalAnnotations() { + return element.getAnnotations(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java new file mode 100644 index 0000000000..6991140d35 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/JPAXMLOverriddenMetadataProvider.java @@ -0,0 +1,230 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cfg.annotations.reflection.internal; + +import java.lang.reflect.AnnotatedElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.EntityListeners; +import javax.persistence.NamedNativeQuery; +import javax.persistence.NamedQuery; +import javax.persistence.NamedStoredProcedureQuery; +import javax.persistence.SequenceGenerator; +import javax.persistence.SqlResultSetMapping; +import javax.persistence.TableGenerator; + +import org.hibernate.annotations.common.reflection.AnnotationReader; +import org.hibernate.annotations.common.reflection.MetadataProvider; +import org.hibernate.annotations.common.reflection.java.JavaMetadataProvider; +import org.hibernate.boot.internal.ClassLoaderAccessImpl; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.boot.spi.ClassLoaderAccessDelegateImpl; +import org.hibernate.boot.spi.MetadataBuildingOptions; + +import org.dom4j.Element; + +/** + * MetadataProvider aware of the JPA Deployment descriptor (orm.xml, ...). + * + * @author Emmanuel Bernard + */ +@SuppressWarnings("unchecked") +public final class JPAXMLOverriddenMetadataProvider implements MetadataProvider { + + private static final MetadataProvider STATELESS_BASE_DELEGATE = new JavaMetadataProvider(); + + private final ClassLoaderAccess classLoaderAccess; + private final XMLContext xmlContext; + + /** + * We allow fully disabling XML sources so to improve the efficiency of + * the boot process for those not using it. + */ + private final boolean xmlMappingEnabled; + + private Map defaults; + private Map cache; + + /** + * @deprecated Use {@link JPAXMLOverriddenMetadataProvider#JPAXMLOverriddenMetadataProvider(BootstrapContext)} instead. + */ + @Deprecated + public JPAXMLOverriddenMetadataProvider(final MetadataBuildingOptions metadataBuildingOptions) { + this( new ClassLoaderAccessDelegateImpl() { + ClassLoaderAccess delegate; + + @Override + protected ClassLoaderAccess getDelegate() { + if ( delegate == null ) { + delegate = new ClassLoaderAccessImpl( + metadataBuildingOptions.getTempClassLoader(), + metadataBuildingOptions.getServiceRegistry().getService( ClassLoaderService.class ) + ); + } + return delegate; + } + }, + metadataBuildingOptions.isXmlMappingEnabled() ); + } + + public JPAXMLOverriddenMetadataProvider(BootstrapContext bootstrapContext) { + this( bootstrapContext.getClassLoaderAccess(), + bootstrapContext.getMetadataBuildingOptions().isXmlMappingEnabled() ); + } + + JPAXMLOverriddenMetadataProvider(ClassLoaderAccess classLoaderAccess, boolean xmlMetadataEnabled) { + this.classLoaderAccess = classLoaderAccess; + this.xmlContext = new XMLContext( classLoaderAccess ); + this.xmlMappingEnabled = xmlMetadataEnabled; + } + + //all of the above can be safely rebuilt from XMLContext: only XMLContext this object is serialized + @Override + public AnnotationReader getAnnotationReader(AnnotatedElement annotatedElement) { + if ( cache == null ) { + cache = new HashMap<>(50 ); + } + AnnotationReader reader = cache.get( annotatedElement ); + if (reader == null) { + if ( xmlContext.hasContext() ) { + reader = new JPAXMLOverriddenAnnotationReader( annotatedElement, xmlContext, classLoaderAccess ); + } + else { + reader = STATELESS_BASE_DELEGATE.getAnnotationReader( annotatedElement ); + } + cache.put( annotatedElement, reader ); + } + return reader; + } + + // @Override + // FIXME this method was introduced in HCANN 5.1.1: we can't mark it as @Override yet, + // but it's expected to be invoked when we're running with the right HCANN version. + public void reset() { + //It's better to remove the HashMap, as it could grow rather large: + //when doing a clear() the internal buckets array is not scaled down. + this.cache = null; + } + + @Override + public Map getDefaults() { + if ( xmlMappingEnabled == false ) { + return Collections.emptyMap(); + } + else { + if ( defaults == null ) { + defaults = new HashMap<>(); + XMLContext.Default xmlDefaults = xmlContext.getDefault( null ); + + defaults.put( "schema", xmlDefaults.getSchema() ); + defaults.put( "catalog", xmlDefaults.getCatalog() ); + defaults.put( "delimited-identifier", xmlDefaults.getDelimitedIdentifier() ); + defaults.put( "cascade-persist", xmlDefaults.getCascadePersist() ); + List entityListeners = new ArrayList(); + for ( String className : xmlContext.getDefaultEntityListeners() ) { + try { + entityListeners.add( classLoaderAccess.classForName( className ) ); + } + catch ( ClassLoadingException e ) { + throw new IllegalStateException( "Default entity listener class not found: " + className ); + } + } + defaults.put( EntityListeners.class, entityListeners ); + for ( Element element : xmlContext.getAllDocuments() ) { + @SuppressWarnings( "unchecked" ) + List elements = element.elements( "sequence-generator" ); + List sequenceGenerators = ( List ) defaults.get( SequenceGenerator.class ); + if ( sequenceGenerators == null ) { + sequenceGenerators = new ArrayList<>(); + defaults.put( SequenceGenerator.class, sequenceGenerators ); + } + for ( Element subelement : elements ) { + sequenceGenerators.add( JPAXMLOverriddenAnnotationReader.buildSequenceGeneratorAnnotation( subelement ) ); + } + + elements = element.elements( "table-generator" ); + List tableGenerators = ( List ) defaults.get( TableGenerator.class ); + if ( tableGenerators == null ) { + tableGenerators = new ArrayList<>(); + defaults.put( TableGenerator.class, tableGenerators ); + } + for ( Element subelement : elements ) { + tableGenerators.add( + JPAXMLOverriddenAnnotationReader.buildTableGeneratorAnnotation( + subelement, xmlDefaults + ) + ); + } + + List namedQueries = ( List ) defaults.get( NamedQuery.class ); + if ( namedQueries == null ) { + namedQueries = new ArrayList<>(); + defaults.put( NamedQuery.class, namedQueries ); + } + List currentNamedQueries = JPAXMLOverriddenAnnotationReader.buildNamedQueries( + element, + false, + xmlDefaults, + classLoaderAccess + ); + namedQueries.addAll( currentNamedQueries ); + + List namedNativeQueries = ( List ) defaults.get( NamedNativeQuery.class ); + if ( namedNativeQueries == null ) { + namedNativeQueries = new ArrayList<>(); + defaults.put( NamedNativeQuery.class, namedNativeQueries ); + } + List currentNamedNativeQueries = JPAXMLOverriddenAnnotationReader.buildNamedQueries( + element, + true, + xmlDefaults, + classLoaderAccess + ); + namedNativeQueries.addAll( currentNamedNativeQueries ); + + List sqlResultSetMappings = ( List ) defaults.get( + SqlResultSetMapping.class + ); + if ( sqlResultSetMappings == null ) { + sqlResultSetMappings = new ArrayList<>(); + defaults.put( SqlResultSetMapping.class, sqlResultSetMappings ); + } + List currentSqlResultSetMappings = JPAXMLOverriddenAnnotationReader.buildSqlResultsetMappings( + element, + xmlDefaults, + classLoaderAccess + ); + sqlResultSetMappings.addAll( currentSqlResultSetMappings ); + + List namedStoredProcedureQueries = (List)defaults.get( NamedStoredProcedureQuery.class ); + if(namedStoredProcedureQueries==null){ + namedStoredProcedureQueries = new ArrayList<>( ); + defaults.put( NamedStoredProcedureQuery.class, namedStoredProcedureQueries ); + } + List currentNamedStoredProcedureQueries = JPAXMLOverriddenAnnotationReader + .buildNamedStoreProcedureQueries( + element, + xmlDefaults, + classLoaderAccess + ); + namedStoredProcedureQueries.addAll( currentNamedStoredProcedureQueries ); + } + } + return defaults; + } + } + + public XMLContext getXMLContext() { + return xmlContext; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java new file mode 100644 index 0000000000..9dc7cdc537 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/reflection/internal/XMLContext.java @@ -0,0 +1,359 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.cfg.annotations.reflection.internal; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.AccessType; +import javax.persistence.AttributeConverter; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.AttributeConverterInfo; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.cfg.AttributeConverterDefinition; +import org.hibernate.cfg.annotations.reflection.AttributeConverterDefinitionCollector; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; + +import org.dom4j.Document; +import org.dom4j.Element; + +/** + * A helper for consuming orm.xml mappings. + * + * @author Emmanuel Bernard + * @author Brett Meyer + */ +public class XMLContext implements Serializable { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( XMLContext.class ); + + private final ClassLoaderAccess classLoaderAccess; + + private Default globalDefaults; + private Map classOverriding = new HashMap<>(); + private Map defaultsOverriding = new HashMap<>(); + private List defaultElements = new ArrayList<>(); + private List defaultEntityListeners = new ArrayList<>(); + private boolean hasContext = false; + + /** + * @deprecated Use {@link XMLContext#XMLContext(BootstrapContext)} instead. + */ + @Deprecated + public XMLContext(ClassLoaderAccess classLoaderAccess) { + this.classLoaderAccess = classLoaderAccess; + } + + public XMLContext(BootstrapContext bootstrapContext) { + this.classLoaderAccess = bootstrapContext.getClassLoaderAccess(); + } + + /** + * @param doc The xml document to add + * @return Add an xml document to this context and return the list of added class names. + */ + @SuppressWarnings( "unchecked" ) + public List addDocument(Document doc) { + hasContext = true; + List addedClasses = new ArrayList<>(); + Element root = doc.getRootElement(); + //global defaults + Element metadata = root.element( "persistence-unit-metadata" ); + if ( metadata != null ) { + if ( globalDefaults == null ) { + globalDefaults = new Default(); + globalDefaults.setMetadataComplete( + metadata.element( "xml-mapping-metadata-complete" ) != null ? + Boolean.TRUE : + null + ); + Element defaultElement = metadata.element( "persistence-unit-defaults" ); + if ( defaultElement != null ) { + Element unitElement = defaultElement.element( "schema" ); + globalDefaults.setSchema( unitElement != null ? unitElement.getTextTrim() : null ); + unitElement = defaultElement.element( "catalog" ); + globalDefaults.setCatalog( unitElement != null ? unitElement.getTextTrim() : null ); + unitElement = defaultElement.element( "access" ); + setAccess( unitElement, globalDefaults ); + unitElement = defaultElement.element( "cascade-persist" ); + globalDefaults.setCascadePersist( unitElement != null ? Boolean.TRUE : null ); + unitElement = defaultElement.element( "delimited-identifiers" ); + globalDefaults.setDelimitedIdentifiers( unitElement != null ? Boolean.TRUE : null ); + defaultEntityListeners.addAll( addEntityListenerClasses( defaultElement, null, addedClasses ) ); + } + } + else { + LOG.duplicateMetadata(); + } + } + + //entity mapping default + Default entityMappingDefault = new Default(); + Element unitElement = root.element( "package" ); + String packageName = unitElement != null ? unitElement.getTextTrim() : null; + entityMappingDefault.setPackageName( packageName ); + unitElement = root.element( "schema" ); + entityMappingDefault.setSchema( unitElement != null ? unitElement.getTextTrim() : null ); + unitElement = root.element( "catalog" ); + entityMappingDefault.setCatalog( unitElement != null ? unitElement.getTextTrim() : null ); + unitElement = root.element( "access" ); + setAccess( unitElement, entityMappingDefault ); + defaultElements.add( root ); + + setLocalAttributeConverterDefinitions( root.elements( "converter" ) ); + + List entities = root.elements( "entity" ); + addClass( entities, packageName, entityMappingDefault, addedClasses ); + + entities = root.elements( "mapped-superclass" ); + addClass( entities, packageName, entityMappingDefault, addedClasses ); + + entities = root.elements( "embeddable" ); + addClass( entities, packageName, entityMappingDefault, addedClasses ); + return addedClasses; + } + + private void setAccess(Element unitElement, Default defaultType) { + if ( unitElement != null ) { + String access = unitElement.getTextTrim(); + setAccess( access, defaultType ); + } + } + + private void setAccess( String access, Default defaultType) { + AccessType type; + if ( access != null ) { + try { + type = AccessType.valueOf( access ); + } + catch ( IllegalArgumentException e ) { + throw new AnnotationException( "Invalid access type " + access + " (check your xml configuration)" ); + } + defaultType.setAccess( type ); + } + } + + private void addClass(List entities, String packageName, Default defaults, List addedClasses) { + for (Element element : entities) { + String className = buildSafeClassName( element.attributeValue( "class" ), packageName ); + if ( classOverriding.containsKey( className ) ) { + //maybe switch it to warn? + throw new IllegalStateException( "Duplicate XML entry for " + className ); + } + addedClasses.add( className ); + classOverriding.put( className, element ); + Default localDefault = new Default(); + localDefault.override( defaults ); + String metadataCompleteString = element.attributeValue( "metadata-complete" ); + if ( metadataCompleteString != null ) { + localDefault.setMetadataComplete( Boolean.parseBoolean( metadataCompleteString ) ); + } + String access = element.attributeValue( "access" ); + setAccess( access, localDefault ); + defaultsOverriding.put( className, localDefault ); + + LOG.debugf( "Adding XML overriding information for %s", className ); + addEntityListenerClasses( element, packageName, addedClasses ); + } + } + + private List addEntityListenerClasses(Element element, String packageName, List addedClasses) { + List localAddedClasses = new ArrayList<>(); + Element listeners = element.element( "entity-listeners" ); + if ( listeners != null ) { + @SuppressWarnings( "unchecked" ) + List elements = listeners.elements( "entity-listener" ); + for (Element listener : elements) { + String listenerClassName = buildSafeClassName( listener.attributeValue( "class" ), packageName ); + if ( classOverriding.containsKey( listenerClassName ) ) { + //maybe switch it to warn? + if ( "entity-listener".equals( classOverriding.get( listenerClassName ).getName() ) ) { + LOG.duplicateListener( listenerClassName ); + continue; + } + throw new IllegalStateException("Duplicate XML entry for " + listenerClassName); + } + localAddedClasses.add( listenerClassName ); + classOverriding.put( listenerClassName, listener ); + } + } + LOG.debugf( "Adding XML overriding information for listeners: %s", localAddedClasses ); + addedClasses.addAll( localAddedClasses ); + return localAddedClasses; + } + + @SuppressWarnings("unchecked") + private void setLocalAttributeConverterDefinitions(List converterElements) { + for ( Element converterElement : converterElements ) { + final String className = converterElement.attributeValue( "class" ); + final String autoApplyAttribute = converterElement.attributeValue( "auto-apply" ); + final boolean autoApply = autoApplyAttribute != null && Boolean.parseBoolean( autoApplyAttribute ); + + try { + final Class attributeConverterClass = classLoaderAccess.classForName( + className + ); + attributeConverterInfoList.add( + new AttributeConverterDefinition( attributeConverterClass.newInstance(), autoApply ) + ); + } + catch (ClassLoadingException e) { + throw new AnnotationException( "Unable to locate specified AttributeConverter implementation class : " + className, e ); + } + catch (Exception e) { + throw new AnnotationException( "Unable to instantiate specified AttributeConverter implementation class : " + className, e ); + } + } + } + + public static String buildSafeClassName(String className, String defaultPackageName) { + if ( className.indexOf( '.' ) < 0 && StringHelper.isNotEmpty( defaultPackageName ) ) { + className = StringHelper.qualify( defaultPackageName, className ); + } + return className; + } + + public static String buildSafeClassName(String className, XMLContext.Default defaults) { + return buildSafeClassName( className, defaults.getPackageName() ); + } + + public Default getDefault(String className) { + Default xmlDefault = new Default(); + xmlDefault.override( globalDefaults ); + if ( className != null ) { + Default entityMappingOverriding = defaultsOverriding.get( className ); + xmlDefault.override( entityMappingOverriding ); + } + return xmlDefault; + } + + public Element getXMLTree(String className ) { + return classOverriding.get( className ); + } + + public List getAllDocuments() { + return defaultElements; + } + + public boolean hasContext() { + return hasContext; + } + + private List attributeConverterInfoList = new ArrayList<>(); + + public void applyDiscoveredAttributeConverters(AttributeConverterDefinitionCollector collector) { + for ( AttributeConverterInfo info : attributeConverterInfoList ) { + collector.addAttributeConverter( info ); + } + attributeConverterInfoList.clear(); + } + + public static class Default implements Serializable { + private AccessType access; + private String packageName; + private String schema; + private String catalog; + private Boolean metadataComplete; + private Boolean cascadePersist; + private Boolean delimitedIdentifier; + + public AccessType getAccess() { + return access; + } + + protected void setAccess(AccessType access) { + this.access = access; + } + + public String getCatalog() { + return catalog; + } + + protected void setCatalog(String catalog) { + this.catalog = catalog; + } + + public String getPackageName() { + return packageName; + } + + protected void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getSchema() { + return schema; + } + + protected void setSchema(String schema) { + this.schema = schema; + } + + public Boolean getMetadataComplete() { + return metadataComplete; + } + + public boolean canUseJavaAnnotations() { + return metadataComplete == null || !metadataComplete; + } + + protected void setMetadataComplete(Boolean metadataComplete) { + this.metadataComplete = metadataComplete; + } + + public Boolean getCascadePersist() { + return cascadePersist; + } + + void setCascadePersist(Boolean cascadePersist) { + this.cascadePersist = cascadePersist; + } + + public void override(Default globalDefault) { + if ( globalDefault != null ) { + if ( globalDefault.getAccess() != null ) { + access = globalDefault.getAccess(); + } + if ( globalDefault.getPackageName() != null ) { + packageName = globalDefault.getPackageName(); + } + if ( globalDefault.getSchema() != null ) { + schema = globalDefault.getSchema(); + } + if ( globalDefault.getCatalog() != null ) { + catalog = globalDefault.getCatalog(); + } + if ( globalDefault.getDelimitedIdentifier() != null ) { + delimitedIdentifier = globalDefault.getDelimitedIdentifier(); + } + if ( globalDefault.getMetadataComplete() != null ) { + metadataComplete = globalDefault.getMetadataComplete(); + } + //TODO fix that in stone if cascade-persist is set already? + if ( globalDefault.getCascadePersist() != null ) cascadePersist = globalDefault.getCascadePersist(); + } + } + + public void setDelimitedIdentifiers(Boolean delimitedIdentifier) { + this.delimitedIdentifier = delimitedIdentifier; + } + + public Boolean getDelimitedIdentifier() { + return delimitedIdentifier; + } + } + + public List getDefaultEntityListeners() { + return defaultEntityListeners; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java index 2eeb459eae..1ee923a43e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/ElementCollectionConverterTest.java @@ -6,6 +6,8 @@ */ package org.hibernate.test.annotations.reflection; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; + import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -13,9 +15,15 @@ import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; -@TestForIssue( jiraKey = "HHH-11924") +@TestForIssue(jiraKey = {"HHH-11924", "HHH-14529"}) public class ElementCollectionConverterTest extends BaseCoreFunctionalTestCase { + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + // FIXME HHH-14529 configure the BootstrapServiceRegistry to use JAXB for orm.xml mappings + super.prepareBootstrapRegistryBuilder( builder ); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java new file mode 100644 index 0000000000..7024aad124 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAXMLOverriddenAnnotationReaderTest.java @@ -0,0 +1,432 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.reflection; + +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; +import org.hibernate.annotations.Columns; +import org.hibernate.cfg.EJB3DTDEntityResolver; +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; +import org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader; +import org.hibernate.cfg.annotations.reflection.internal.XMLContext; +import org.hibernate.internal.util.xml.ErrorLogger; +import org.hibernate.internal.util.xml.XMLHelper; + +import org.hibernate.testing.boot.BootstrapContextImpl; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; +import org.junit.Test; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotSupportedException; + +import javax.persistence.*; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.Assert.*; + +/** + * Tests the new {@link JPAXMLOverriddenAnnotationReader}, + * which will be replacing {@link JPAOverriddenAnnotationReader}. + * {@link JPAOverriddenAnnotationReader} is still the default implementation, + * but we want to switch to {@link JPAXMLOverriddenAnnotationReader} + * as soon as it will be practical. + * + * @see LegacyJPAOverriddenAnnotationReaderTest + * @author Emmanuel Bernard + */ +@TestForIssue(jiraKey = "HHH-14529") +public class JPAXMLOverriddenAnnotationReaderTest extends BaseUnitTestCase { + @Test + public void testMappedSuperclassAnnotations() throws Exception { + XMLContext context = buildContext( + "org/hibernate/test/annotations/reflection/metadata-complete.xml" + ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( Organization.class, context, BootstrapContextImpl.INSTANCE ); + assertTrue( reader.isAnnotationPresent( MappedSuperclass.class ) ); + } + + @Test + public void testEntityRelatedAnnotations() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Entity.class ) ); + assertEquals( + "Default value in xml entity should not override @Entity.name", "JavaAdministration", + reader.getAnnotation( Entity.class ).name() + ); + assertNotNull( reader.getAnnotation( Table.class ) ); + assertEquals( "@Table not overridden", "tbl_admin", reader.getAnnotation( Table.class ).name() ); + assertEquals( "Default schema not overridden", "myschema", reader.getAnnotation( Table.class ).schema() ); + assertEquals( + "Proper @Table.uniqueConstraints", 2, + reader.getAnnotation( Table.class ).uniqueConstraints()[0].columnNames().length + ); + String columnName = reader.getAnnotation( Table.class ).uniqueConstraints()[0].columnNames()[0]; + assertTrue( + "Proper @Table.uniqueConstraints", "firstname".equals( columnName ) || "lastname".equals( columnName ) + ); + assertNull( "Both Java and XML used", reader.getAnnotation( SecondaryTable.class ) ); + assertNotNull( "XML does not work", reader.getAnnotation( SecondaryTables.class ) ); + SecondaryTable[] tables = reader.getAnnotation( SecondaryTables.class ).value(); + assertEquals( 1, tables.length ); + assertEquals( "admin2", tables[0].name() ); + assertEquals( "unique constraints ignored", 1, tables[0].uniqueConstraints().length ); + assertEquals( "pk join column ignored", 1, tables[0].pkJoinColumns().length ); + assertEquals( "pk join column ignored", "admin_id", tables[0].pkJoinColumns()[0].name() ); + assertNotNull( "Sequence Overriding not working", reader.getAnnotation( SequenceGenerator.class ) ); + assertEquals( + "wrong sequence name", "seqhilo", reader.getAnnotation( SequenceGenerator.class ).sequenceName() + ); + assertEquals( "default fails", 50, reader.getAnnotation( SequenceGenerator.class ).allocationSize() ); + assertNotNull( "TableOverriding not working", reader.getAnnotation( TableGenerator.class ) ); + assertEquals( "wrong tble name", "tablehilo", reader.getAnnotation( TableGenerator.class ).table() ); + assertEquals( "no schema overriding", "myschema", reader.getAnnotation( TableGenerator.class ).schema() ); + + reader = new JPAXMLOverriddenAnnotationReader( Match.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Table.class ) ); + assertEquals( + "Java annotation not taken into account", "matchtable", reader.getAnnotation( Table.class ).name() + ); + assertEquals( + "Java annotation not taken into account", "matchschema", reader.getAnnotation( Table.class ).schema() + ); + assertEquals( "Overriding not taken into account", "mycatalog", reader.getAnnotation( Table.class ).catalog() ); + assertNotNull( "SecondaryTable swallowed", reader.getAnnotation( SecondaryTables.class ) ); + assertEquals( + "Default schema not taken into account", "myschema", + reader.getAnnotation( SecondaryTables.class ).value()[0].schema() + ); + assertNotNull( reader.getAnnotation( Inheritance.class ) ); + assertEquals( + "inheritance strategy not overriden", InheritanceType.JOINED, + reader.getAnnotation( Inheritance.class ).strategy() + ); + assertNotNull( "NamedQuery not overriden", reader.getAnnotation( NamedQueries.class ) ); + assertEquals( "No deduplication", 3, reader.getAnnotation( NamedQueries.class ).value().length ); + assertEquals( + "deduplication kept the Java version", 1, + reader.getAnnotation( NamedQueries.class ).value()[1].hints().length + ); + assertEquals( + "org.hibernate.timeout", reader.getAnnotation( NamedQueries.class ).value()[1].hints()[0].name() + ); + assertNotNull( "NamedNativeQuery not overriden", reader.getAnnotation( NamedNativeQueries.class ) ); + assertEquals( "No deduplication", 3, reader.getAnnotation( NamedNativeQueries.class ).value().length ); + assertEquals( + "deduplication kept the Java version", 1, + reader.getAnnotation( NamedNativeQueries.class ).value()[1].hints().length + ); + assertEquals( + "org.hibernate.timeout", reader.getAnnotation( NamedNativeQueries.class ).value()[1].hints()[0].name() + ); + assertNotNull( reader.getAnnotation( SqlResultSetMappings.class ) ); + assertEquals( + "competitor1Point", reader.getAnnotation( SqlResultSetMappings.class ).value()[0].columns()[0].name() + ); + assertEquals( + "competitor1Point", + reader.getAnnotation( SqlResultSetMappings.class ).value()[0].entities()[0].fields()[0].column() + ); + assertNotNull( reader.getAnnotation( ExcludeSuperclassListeners.class ) ); + assertNotNull( reader.getAnnotation( ExcludeDefaultListeners.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( Competition.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( MappedSuperclass.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( TennisMatch.class, context, BootstrapContextImpl.INSTANCE ); + assertNull( "Mutualize PKJC into PKJCs", reader.getAnnotation( PrimaryKeyJoinColumn.class ) ); + assertNotNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); + assertEquals( + "PrimaryKeyJoinColumn overrden", "id", + reader.getAnnotation( PrimaryKeyJoinColumns.class ).value()[0].name() + ); + assertNotNull( reader.getAnnotation( AttributeOverrides.class ) ); + assertEquals( "Wrong deduplication", 3, reader.getAnnotation( AttributeOverrides.class ).value().length ); + assertEquals( + "Wrong priority (XML vs java annotations)", "fld_net", + reader.getAnnotation( AttributeOverrides.class ).value()[0].column().name() + ); + assertEquals( + "Column mapping", 2, reader.getAnnotation( AttributeOverrides.class ).value()[1].column().scale() + ); + assertEquals( + "Column mapping", true, reader.getAnnotation( AttributeOverrides.class ).value()[1].column().unique() + ); + assertNotNull( reader.getAnnotation( AssociationOverrides.class ) ); + assertEquals( "no XML processing", 1, reader.getAnnotation( AssociationOverrides.class ).value().length ); + assertEquals( + "wrong xml processing", "id", + reader.getAnnotation( AssociationOverrides.class ).value()[0].joinColumns()[0].referencedColumnName() + ); + + + reader = new JPAXMLOverriddenAnnotationReader( SocialSecurityPhysicalAccount.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( IdClass.class ) ); + assertEquals( "id-class not used", SocialSecurityNumber.class, reader.getAnnotation( IdClass.class ).value() ); + assertEquals( + "discriminator-value not used", "Physical", reader.getAnnotation( DiscriminatorValue.class ).value() + ); + assertNotNull( "discriminator-column not used", reader.getAnnotation( DiscriminatorColumn.class ) ); + assertEquals( + "discriminator-column.name default value broken", "DTYPE", + reader.getAnnotation( DiscriminatorColumn.class ).name() + ); + assertEquals( + "discriminator-column.length broken", 34, reader.getAnnotation( DiscriminatorColumn.class ).length() + ); + } + + @Test + public void testEntityRelatedAnnotationsMetadataComplete() throws Exception { + XMLContext context = buildContext( + "org/hibernate/test/annotations/reflection/metadata-complete.xml" + ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Entity.class ) ); + assertEquals( + "Metadata complete should ignore java annotations", "", reader.getAnnotation( Entity.class ).name() + ); + assertNotNull( reader.getAnnotation( Table.class ) ); + assertEquals( "@Table should not be used", "", reader.getAnnotation( Table.class ).name() ); + assertEquals( "Default schema not overriden", "myschema", reader.getAnnotation( Table.class ).schema() ); + + reader = new JPAXMLOverriddenAnnotationReader( Match.class, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Table.class ) ); + assertEquals( "@Table should not be used", "", reader.getAnnotation( Table.class ).name() ); + assertEquals( "Overriding not taken into account", "myschema", reader.getAnnotation( Table.class ).schema() ); + assertEquals( "Overriding not taken into account", "mycatalog", reader.getAnnotation( Table.class ).catalog() ); + assertNull( "Ignore Java annotation", reader.getAnnotation( SecondaryTable.class ) ); + assertNull( "Ignore Java annotation", reader.getAnnotation( SecondaryTables.class ) ); + assertNull( "Ignore Java annotation", reader.getAnnotation( Inheritance.class ) ); + assertNull( reader.getAnnotation( NamedQueries.class ) ); + assertNull( reader.getAnnotation( NamedNativeQueries.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( TennisMatch.class, context, BootstrapContextImpl.INSTANCE ); + assertNull( reader.getAnnotation( PrimaryKeyJoinColumn.class ) ); + assertNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( Competition.class, context, BootstrapContextImpl.INSTANCE ); + assertNull( reader.getAnnotation( MappedSuperclass.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( SocialSecurityMoralAccount.class, context, BootstrapContextImpl.INSTANCE ); + assertNull( reader.getAnnotation( IdClass.class ) ); + assertNull( reader.getAnnotation( DiscriminatorValue.class ) ); + assertNull( reader.getAnnotation( DiscriminatorColumn.class ) ); + assertNull( reader.getAnnotation( SequenceGenerator.class ) ); + assertNull( reader.getAnnotation( TableGenerator.class ) ); + } + + @Test + public void testIdRelatedAnnotations() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + Method method = Administration.class.getDeclaredMethod( "getId" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertNull( reader.getAnnotation( Id.class ) ); + assertNull( reader.getAnnotation( Column.class ) ); + Field field = Administration.class.getDeclaredField( "id" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Id.class ) ); + assertNotNull( reader.getAnnotation( GeneratedValue.class ) ); + assertEquals( GenerationType.SEQUENCE, reader.getAnnotation( GeneratedValue.class ).strategy() ); + assertEquals( "generator", reader.getAnnotation( GeneratedValue.class ).generator() ); + assertNotNull( reader.getAnnotation( SequenceGenerator.class ) ); + assertEquals( "seq", reader.getAnnotation( SequenceGenerator.class ).sequenceName() ); + assertNotNull( reader.getAnnotation( Columns.class ) ); + assertEquals( 1, reader.getAnnotation( Columns.class ).columns().length ); + assertEquals( "fld_id", reader.getAnnotation( Columns.class ).columns()[0].name() ); + assertNotNull( reader.getAnnotation( Temporal.class ) ); + assertEquals( TemporalType.DATE, reader.getAnnotation( Temporal.class ).value() ); + + context = buildContext( + "org/hibernate/test/annotations/reflection/metadata-complete.xml" + ); + method = Administration.class.getDeclaredMethod( "getId" ); + reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( + "Default access type when not defined in metadata complete should be property", + reader.getAnnotation( Id.class ) + ); + field = Administration.class.getDeclaredField( "id" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNull( + "Default access type when not defined in metadata complete should be property", + reader.getAnnotation( Id.class ) + ); + + method = BusTrip.class.getDeclaredMethod( "getId" ); + reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertNull( reader.getAnnotation( EmbeddedId.class ) ); + field = BusTrip.class.getDeclaredField( "id" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( EmbeddedId.class ) ); + assertNotNull( reader.getAnnotation( AttributeOverrides.class ) ); + assertEquals( 1, reader.getAnnotation( AttributeOverrides.class ).value().length ); + } + + @Test + public void testBasicRelatedAnnotations() throws Exception { + XMLContext context = buildContext( + "org/hibernate/test/annotations/reflection/metadata-complete.xml" + ); + Field field = BusTrip.class.getDeclaredField( "status" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Enumerated.class ) ); + assertEquals( EnumType.STRING, reader.getAnnotation( Enumerated.class ).value() ); + assertEquals( false, reader.getAnnotation( Basic.class ).optional() ); + field = BusTrip.class.getDeclaredField( "serial" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Lob.class ) ); + assertEquals( "serialbytes", reader.getAnnotation( Columns.class ).columns()[0].name() ); + field = BusTrip.class.getDeclaredField( "terminusTime" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Temporal.class ) ); + assertEquals( TemporalType.TIMESTAMP, reader.getAnnotation( Temporal.class ).value() ); + assertEquals( FetchType.LAZY, reader.getAnnotation( Basic.class ).fetch() ); + + field = BusTripPk.class.getDeclaredField( "busDriver" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.isAnnotationPresent( Basic.class ) ); + } + + @Test + public void testVersionRelatedAnnotations() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + Method method = Administration.class.getDeclaredMethod( "getVersion" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Version.class ) ); + + Field field = Match.class.getDeclaredField( "version" ); + assertNotNull( reader.getAnnotation( Version.class ) ); + } + + @Test + public void testTransientAndEmbeddedRelatedAnnotations() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + + Field field = Administration.class.getDeclaredField( "transientField" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Transient.class ) ); + assertNull( reader.getAnnotation( Basic.class ) ); + + field = Match.class.getDeclaredField( "playerASSN" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( Embedded.class ) ); + } + + @Test + public void testAssociationRelatedAnnotations() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + + Field field = Administration.class.getDeclaredField( "defaultBusTrip" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( OneToOne.class ) ); + assertNull( reader.getAnnotation( JoinColumns.class ) ); + assertNotNull( reader.getAnnotation( PrimaryKeyJoinColumns.class ) ); + assertEquals( "pk", reader.getAnnotation( PrimaryKeyJoinColumns.class ).value()[0].name() ); + assertEquals( 5, reader.getAnnotation( OneToOne.class ).cascade().length ); + assertEquals( FetchType.LAZY, reader.getAnnotation( OneToOne.class ).fetch() ); + assertEquals( "test", reader.getAnnotation( OneToOne.class ).mappedBy() ); + + context = buildContext( + "org/hibernate/test/annotations/reflection/metadata-complete.xml" + ); + field = BusTrip.class.getDeclaredField( "players" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( OneToMany.class ) ); + assertNotNull( reader.getAnnotation( JoinColumns.class ) ); + assertEquals( 2, reader.getAnnotation( JoinColumns.class ).value().length ); + assertEquals( "driver", reader.getAnnotation( JoinColumns.class ).value()[0].name() ); + assertNotNull( reader.getAnnotation( MapKey.class ) ); + assertEquals( "name", reader.getAnnotation( MapKey.class ).name() ); + + field = BusTrip.class.getDeclaredField( "roads" ); + reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( ManyToMany.class ) ); + assertNotNull( reader.getAnnotation( JoinTable.class ) ); + assertEquals( "bus_road", reader.getAnnotation( JoinTable.class ).name() ); + assertEquals( 2, reader.getAnnotation( JoinTable.class ).joinColumns().length ); + assertEquals( 1, reader.getAnnotation( JoinTable.class ).inverseJoinColumns().length ); + assertEquals( 2, reader.getAnnotation( JoinTable.class ).uniqueConstraints()[0].columnNames().length ); + assertNotNull( reader.getAnnotation( OrderBy.class ) ); + assertEquals( "maxSpeed", reader.getAnnotation( OrderBy.class ).value() ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11924") + public void testElementCollectionConverter() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + + Field field = Company.class.getDeclaredField( "organizations" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( field, context, BootstrapContextImpl.INSTANCE ); + assertNotNull( reader.getAnnotation( ElementCollection.class ) ); + assertNotNull( reader.getAnnotation( Converts.class ) ); + assertNotNull( reader.getAnnotation( Converts.class ).value() ); + assertTrue( reader.getAnnotation( Converts.class ).value().length == 1 ); + assertEquals(OrganizationConverter.class, reader.getAnnotation( Converts.class ).value()[0].converter()); + } + + @Test + public void testEntityListeners() throws Exception { + XMLContext context = buildContext( "org/hibernate/test/annotations/reflection/orm.xml" ); + + Method method = Administration.class.getDeclaredMethod( "calculate" ); + JPAXMLOverriddenAnnotationReader reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertTrue( reader.isAnnotationPresent( PrePersist.class ) ); + + reader = new JPAXMLOverriddenAnnotationReader( Administration.class, context, BootstrapContextImpl.INSTANCE ); + assertTrue( reader.isAnnotationPresent( EntityListeners.class ) ); + assertEquals( 1, reader.getAnnotation( EntityListeners.class ).value().length ); + assertEquals( LogListener.class, reader.getAnnotation( EntityListeners.class ).value()[0] ); + + method = LogListener.class.getDeclaredMethod( "noLog", Object.class ); + reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertTrue( reader.isAnnotationPresent( PostLoad.class ) ); + + method = LogListener.class.getDeclaredMethod( "log", Object.class ); + reader = new JPAXMLOverriddenAnnotationReader( method, context, BootstrapContextImpl.INSTANCE ); + assertTrue( reader.isAnnotationPresent( PrePersist.class ) ); + assertFalse( reader.isAnnotationPresent( PostPersist.class ) ); + + assertEquals( 1, context.getDefaultEntityListeners().size() ); + assertEquals( OtherLogListener.class.getName(), context.getDefaultEntityListeners().get( 0 ) ); + } + + private XMLContext buildContext(String ormfile) throws SAXException, DocumentException, IOException { + XMLHelper xmlHelper = new XMLHelper(); + InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( ormfile ); + assertNotNull( "ORM.xml not found: " + ormfile, is ); + XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); + ErrorLogger errorLogger = new ErrorLogger(); + SAXReader saxReader = xmlHelper.createSAXReader( errorLogger, EJB3DTDEntityResolver.INSTANCE ); + //saxReader.setValidation( false ); + try { + saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true ); + } + catch ( SAXNotSupportedException e ) { + saxReader.setValidation( false ); + } + org.dom4j.Document doc; + try { + doc = saxReader.read( new InputSource( new BufferedInputStream( is ) ) ); + } + finally { + is.close(); + } + if ( errorLogger.hasErrors() ) { + System.out.println( errorLogger.getErrors().get( 0 ) ); + } + assertFalse( errorLogger.hasErrors() ); + context.addDocument( doc ); + return context; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyElementCollectionConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyElementCollectionConverterTest.java new file mode 100644 index 0000000000..70d559357b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyElementCollectionConverterTest.java @@ -0,0 +1,71 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.reflection; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * Tests the legacy {@link JPAOverriddenAnnotationReader}, + * which will be replaced with {@link org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader}. + * {@link JPAOverriddenAnnotationReader} is still the default implementation, + * but we want to switch to {@link org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader} + * as soon as it will be practical. + * + * @see JPAXMLOverriddenAnnotationReaderTest + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +@TestForIssue( jiraKey = "HHH-11924") +public class LegacyElementCollectionConverterTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Company.class, + }; + } + + @Override + protected String[] getXmlFiles() { + return new String[] { "org/hibernate/test/annotations/reflection/element-collection-converter-orm.xml" }; + } + + + @Test + public void testConverterIsAppliedToElementCollection() { + doInHibernate( this::sessionFactory, session -> { + Company company = new Company(); + company.setId( 1L ); + + Organization org1 = new Organization(); + org1.setOrganizationId( "ACME" ); + + company.getOrganizations().add( org1 ); + + session.persist( company ); + } ); + + doInHibernate( this::sessionFactory, session -> { + String organizationId = (String) session + .createNativeQuery( "select organizations from Company_organizations" ) + .getSingleResult(); + assertEquals( "ORG-ACME", organizationId ); + + Company company = session.find( Company.class, 1L ); + + assertEquals( 1, company.getOrganizations().size() ); + assertEquals( "ACME" , company.getOrganizations().get( 0 ).getOrganizationId()); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyJPAOverriddenAnnotationReaderTest.java similarity index 88% rename from hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java rename to hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyJPAOverriddenAnnotationReaderTest.java index c79a60e708..57561467cc 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/JPAOverriddenAnnotationReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyJPAOverriddenAnnotationReaderTest.java @@ -6,8 +6,61 @@ */ package org.hibernate.test.annotations.reflection; -import org.dom4j.DocumentException; -import org.dom4j.io.SAXReader; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import javax.persistence.AssociationOverrides; +import javax.persistence.AttributeOverrides; +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Converts; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorValue; +import javax.persistence.ElementCollection; +import javax.persistence.Embedded; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.ExcludeDefaultListeners; +import javax.persistence.ExcludeSuperclassListeners; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.MapKey; +import javax.persistence.MappedSuperclass; +import javax.persistence.NamedNativeQueries; +import javax.persistence.NamedQueries; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.OrderBy; +import javax.persistence.PostLoad; +import javax.persistence.PostPersist; +import javax.persistence.PrePersist; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.PrimaryKeyJoinColumns; +import javax.persistence.SecondaryTable; +import javax.persistence.SecondaryTables; +import javax.persistence.SequenceGenerator; +import javax.persistence.SqlResultSetMappings; +import javax.persistence.Table; +import javax.persistence.TableGenerator; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; +import javax.persistence.Version; + import org.hibernate.annotations.Columns; import org.hibernate.cfg.EJB3DTDEntityResolver; import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; @@ -15,28 +68,37 @@ import org.hibernate.cfg.annotations.reflection.XMLContext; import org.hibernate.internal.util.xml.ErrorLogger; import org.hibernate.internal.util.xml.XMLHelper; -import org.hibernate.testing.boot.BootstrapContextImpl; -import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.boot.BootstrapContextImpl; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; +import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; + +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXNotSupportedException; -import javax.persistence.*; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; /** + * Tests the legacy {@link JPAOverriddenAnnotationReader}, + * which will be replaced with {@link org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader}. + * {@link JPAOverriddenAnnotationReader} is still the default implementation, + * but we want to switch to {@link org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader} + * as soon as it will be practical. + * + * @see JPAXMLOverriddenAnnotationReaderTest * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. */ -public class JPAOverriddenAnnotationReaderTest extends BaseUnitTestCase { +@Deprecated +public class LegacyJPAOverriddenAnnotationReaderTest extends BaseUnitTestCase { @Test public void testMappedSuperclassAnnotations() throws Exception { XMLContext context = buildContext( diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyXMLContextTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyXMLContextTest.java new file mode 100644 index 0000000000..38ade26b4c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/LegacyXMLContextTest.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.reflection; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.hibernate.cfg.EJB3DTDEntityResolver; +import org.hibernate.cfg.annotations.reflection.XMLContext; +import org.hibernate.internal.util.xml.ErrorLogger; +import org.hibernate.internal.util.xml.XMLHelper; + +import org.hibernate.testing.boot.BootstrapContextImpl; +import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; +import org.junit.Assert; +import org.junit.Test; + +import org.dom4j.io.SAXReader; +import org.xml.sax.InputSource; +import org.xml.sax.SAXNotSupportedException; + +/** + * Tests the legacy {@link XMLContext}, + * which will be replaced with {@link org.hibernate.cfg.annotations.reflection.internal.XMLContext}. + * {@link XMLContext} is still the default implementation, + * but we want to switch to {@link org.hibernate.cfg.annotations.reflection.internal.XMLContext} + * as soon as it will be practical. + * + * @see XMLContextTest + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link XMLContext}. + */ +public class LegacyXMLContextTest { + @Test + public void testAll() throws Exception { + final XMLHelper xmlHelper = new XMLHelper(); + final XMLContext context = new XMLContext( BootstrapContextImpl.INSTANCE ); + + InputStream is = ClassLoaderServiceTestingImpl.INSTANCE.locateResourceStream( + "org/hibernate/test/annotations/reflection/orm.xml" + ); + Assert.assertNotNull( "ORM.xml not found", is ); + + final ErrorLogger errorLogger = new ErrorLogger(); + final SAXReader saxReader = xmlHelper.createSAXReader( errorLogger, EJB3DTDEntityResolver.INSTANCE ); + + try { + saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true ); + } + catch ( SAXNotSupportedException e ) { + saxReader.setValidation( false ); + } + org.dom4j.Document doc; + try { + doc = saxReader.read( new InputSource( new BufferedInputStream( is ) ) ); + } + finally { + try { + is.close(); + } + catch ( IOException ioe ) { + //log.warn( "Could not close input stream", ioe ); + } + } + Assert.assertFalse( errorLogger.hasErrors() ); + context.addDocument( doc ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java index 235fe820e4..fb68e62a46 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/reflection/XMLContextTest.java @@ -17,16 +17,24 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXNotSupportedException; import org.hibernate.cfg.EJB3DTDEntityResolver; -import org.hibernate.cfg.annotations.reflection.XMLContext; +import org.hibernate.cfg.annotations.reflection.internal.XMLContext; import org.hibernate.internal.util.xml.ErrorLogger; import org.hibernate.internal.util.xml.XMLHelper; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.boot.BootstrapContextImpl; import org.hibernate.testing.boot.ClassLoaderServiceTestingImpl; /** + * Tests the new {@link XMLContext}, + * which will be replacing {@link org.hibernate.cfg.annotations.reflection.XMLContext}. + * {@link org.hibernate.cfg.annotations.reflection.XMLContext} is still the default implementation, + * but we want to switch to {@link XMLContext} + * as soon as it will be practical. + * * @author Emmanuel Bernard */ +@TestForIssue(jiraKey = "HHH-14529") public class XMLContextTest { @Test public void testAll() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlElementCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlElementCollectionTest.java index 235b7e70a5..73d749f4f0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlElementCollectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlElementCollectionTest.java @@ -34,12 +34,14 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.UniqueConstraint; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlElementCollectionTest extends Ejb3XmlTestCase { @Test public void testNoChildren() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToManyTest.java index 01f6170000..7f37ff4363 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToManyTest.java @@ -29,12 +29,14 @@ import javax.persistence.OrderColumn; import javax.persistence.TemporalType; import javax.persistence.UniqueConstraint; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlManyToManyTest extends Ejb3XmlTestCase { @Test public void testNoChildren() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToOneTest.java index b0e3c43b2a..dfe16a7468 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlManyToOneTest.java @@ -18,12 +18,14 @@ import javax.persistence.ManyToOne; import javax.persistence.MapsId; import javax.persistence.UniqueConstraint; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlManyToOneTest extends Ejb3XmlTestCase { @Test public void testNoJoins() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToManyTest.java index bca6d21c01..c1eda4f9d2 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToManyTest.java @@ -29,12 +29,14 @@ import javax.persistence.OrderColumn; import javax.persistence.TemporalType; import javax.persistence.UniqueConstraint; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlOneToManyTest extends Ejb3XmlTestCase { @Test public void testNoChildren() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToOneTest.java index fb2c0f70b1..e513889c0f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlOneToOneTest.java @@ -20,12 +20,14 @@ import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.PrimaryKeyJoinColumns; import javax.persistence.UniqueConstraint; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlOneToOneTest extends Ejb3XmlTestCase { @Test public void testNoChildren() throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTest.java index 2a4964ca0c..89bb787312 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTest.java @@ -12,6 +12,7 @@ import java.util.List; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.dialect.CockroachDB192Dialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.PostgreSQLDialect; @@ -19,6 +20,7 @@ import org.hibernate.dialect.TeradataDialect; import org.hibernate.persister.collection.BasicCollectionPersister; import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -28,7 +30,15 @@ import static org.junit.Assert.assertNotNull; /** * @author Emmanuel Bernard */ +@TestForIssue(jiraKey = "HHH-14529") public class Ejb3XmlTest extends BaseCoreFunctionalTestCase { + + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + // FIXME HHH-14529 configure the BootstrapServiceRegistry to use JAXB for orm.xml mappings + super.prepareBootstrapRegistryBuilder( builder ); + } + @Test @SkipForDialect(value = {PostgreSQL81Dialect.class, PostgreSQLDialect.class, CockroachDB192Dialect.class}, comment = "postgresql jdbc driver does not implement the setQueryTimeout method") diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java index 3c96ba4df1..c4ebbed1d6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/Ejb3XmlTestCase.java @@ -13,8 +13,8 @@ import java.lang.reflect.AnnotatedElement; import org.dom4j.Document; import org.dom4j.io.SAXReader; -import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; -import org.hibernate.cfg.annotations.reflection.XMLContext; +import org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenAnnotationReader; +import org.hibernate.cfg.annotations.reflection.internal.XMLContext; import org.hibernate.testing.boot.BootstrapContextImpl; import org.hibernate.testing.junit4.BaseUnitTestCase; @@ -28,8 +28,8 @@ import static org.junit.Assert.assertTrue; * XML to JPA annotations. The configuration is built within each test, and no * database is used. Thus, no schema generation or cleanup will be performed. */ -abstract class Ejb3XmlTestCase extends BaseUnitTestCase { - protected JPAOverriddenAnnotationReader reader; +public abstract class Ejb3XmlTestCase extends BaseUnitTestCase { + protected JPAXMLOverriddenAnnotationReader reader; protected void assertAnnotationPresent(Class annotationType) { assertTrue( @@ -45,11 +45,11 @@ abstract class Ejb3XmlTestCase extends BaseUnitTestCase { ); } - protected JPAOverriddenAnnotationReader getReader(Class entityClass, String fieldName, String ormResourceName) + protected JPAXMLOverriddenAnnotationReader getReader(Class entityClass, String fieldName, String ormResourceName) throws Exception { AnnotatedElement el = getAnnotatedElement( entityClass, fieldName ); XMLContext xmlContext = getContext( ormResourceName ); - return new JPAOverriddenAnnotationReader( el, xmlContext, BootstrapContextImpl.INSTANCE ); + return new JPAXMLOverriddenAnnotationReader( el, xmlContext, BootstrapContextImpl.INSTANCE ); } protected AnnotatedElement getAnnotatedElement(Class entityClass, String fieldName) throws Exception { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlElementCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlElementCollectionTest.java new file mode 100644 index 0000000000..7d46e662bf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlElementCollectionTest.java @@ -0,0 +1,716 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.AssociationOverride; +import javax.persistence.AssociationOverrides; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.Lob; +import javax.persistence.MapKey; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.MapKeyJoinColumns; +import javax.persistence.MapKeyTemporal; +import javax.persistence.OrderBy; +import javax.persistence.OrderColumn; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlElementCollectionTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlElementCollectionTest extends LegacyEjb3XmlTestCase { + @Test + public void testNoChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "element-collection.orm1.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( Column.class ); + assertAnnotationNotPresent( Temporal.class ); + assertAnnotationNotPresent( Enumerated.class ); + assertAnnotationNotPresent( Lob.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationNotPresent( AttributeOverrides.class ); + assertAnnotationNotPresent( AssociationOverride.class ); + assertAnnotationNotPresent( AssociationOverrides.class ); + assertAnnotationNotPresent( CollectionTable.class ); + assertAnnotationNotPresent( Access.class ); + ElementCollection relAnno = reader.getAnnotation( ElementCollection.class ); + assertEquals( FetchType.LAZY, relAnno.fetch() ); + assertEquals( void.class, relAnno.targetClass() ); + } + + @Test + public void testOrderBy() throws Exception { + reader = getReader( Entity2.class, "field1", "element-collection.orm2.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertEquals( + "col1 ASC, col2 DESC", reader.getAnnotation( OrderBy.class ) + .value() + ); + } + + @Test + public void testOrderColumnNoAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "element-collection.orm3.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "", orderColumnAnno.columnDefinition() ); + assertEquals( "", orderColumnAnno.name() ); + assertTrue( orderColumnAnno.insertable() ); + assertTrue( orderColumnAnno.nullable() ); + assertTrue( orderColumnAnno.updatable() ); + } + + @Test + public void testOrderColumnAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "element-collection.orm4.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "int", orderColumnAnno.columnDefinition() ); + assertEquals( "col1", orderColumnAnno.name() ); + assertFalse( orderColumnAnno.insertable() ); + assertFalse( orderColumnAnno.nullable() ); + assertFalse( orderColumnAnno.updatable() ); + } + + @Test + public void testMapKeyNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm5.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm6.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "field2", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyClass() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm7.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + Entity2.class, reader.getAnnotation( MapKeyClass.class ) + .value() + ); + } + + @Test + public void testMapKeyTemporal() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm8.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + TemporalType.DATE, reader.getAnnotation( + MapKeyTemporal.class + ).value() + ); + } + + @Test + public void testMapKeyEnumerated() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm9.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + EnumType.STRING, reader.getAnnotation( + MapKeyEnumerated.class + ).value() + ); + } + + /** + * When there's a single map key attribute override, we still wrap it with + * an AttributeOverrides annotation. + */ + @Test + public void testSingleMapKeyAttributeOverride() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm10.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 1, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "col1", overrides[0].column().name() ); + } + + @Test + public void testMultipleMapKeyAttributeOverrides() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm11.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "", overrides[0].column().name() ); + assertFalse( overrides[0].column().unique() ); + assertTrue( overrides[0].column().nullable() ); + assertTrue( overrides[0].column().insertable() ); + assertTrue( overrides[0].column().updatable() ); + assertEquals( "", overrides[0].column().columnDefinition() ); + assertEquals( "", overrides[0].column().table() ); + assertEquals( 255, overrides[0].column().length() ); + assertEquals( 0, overrides[0].column().precision() ); + assertEquals( 0, overrides[0].column().scale() ); + assertEquals( "field2", overrides[1].name() ); + assertEquals( "col1", overrides[1].column().name() ); + assertTrue( overrides[1].column().unique() ); + assertFalse( overrides[1].column().nullable() ); + assertFalse( overrides[1].column().insertable() ); + assertFalse( overrides[1].column().updatable() ); + assertEquals( "int", overrides[1].column().columnDefinition() ); + assertEquals( "table1", overrides[1].column().table() ); + assertEquals( 50, overrides[1].column().length() ); + assertEquals( 2, overrides[1].column().precision() ); + assertEquals( 1, overrides[1].column().scale() ); + } + + @Test + public void testMapKeyColumnNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm12.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "", keyColAnno.columnDefinition() ); + assertEquals( "", keyColAnno.name() ); + assertEquals( "", keyColAnno.table() ); + assertFalse( keyColAnno.nullable() ); + assertTrue( keyColAnno.insertable() ); + assertFalse( keyColAnno.unique() ); + assertTrue( keyColAnno.updatable() ); + assertEquals( 255, keyColAnno.length() ); + assertEquals( 0, keyColAnno.precision() ); + assertEquals( 0, keyColAnno.scale() ); + } + + @Test + public void testMapKeyColumnAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm13.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "int", keyColAnno.columnDefinition() ); + assertEquals( "col1", keyColAnno.name() ); + assertEquals( "table1", keyColAnno.table() ); + assertTrue( keyColAnno.nullable() ); + assertFalse( keyColAnno.insertable() ); + assertTrue( keyColAnno.unique() ); + assertFalse( keyColAnno.updatable() ); + assertEquals( 50, keyColAnno.length() ); + assertEquals( 2, keyColAnno.precision() ); + assertEquals( 1, keyColAnno.scale() ); + } + + /** + * When there's a single map key join column, we still wrap it with a + * MapKeyJoinColumns annotation. + */ + @Test + public void testSingleMapKeyJoinColumn() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm14.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + } + + @Test + public void testMultipleMapKeyJoinColumns() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm15.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertFalse( joinColumns[0].unique() ); + assertFalse( joinColumns[0].nullable() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertTrue( joinColumns[1].unique() ); + assertTrue( joinColumns[1].nullable() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertEquals( "table1", joinColumns[1].table() ); + } + + @Test + public void testColumnNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm16.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( Column.class ); + Column column = reader.getAnnotation( Column.class ); + assertEquals( "", column.name() ); + assertFalse( column.unique() ); + assertTrue( column.nullable() ); + assertTrue( column.insertable() ); + assertTrue( column.updatable() ); + assertEquals( "", column.columnDefinition() ); + assertEquals( "", column.table() ); + assertEquals( 255, column.length() ); + assertEquals( 0, column.precision() ); + assertEquals( 0, column.scale() ); + } + + @Test + public void testColumnAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm17.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( Column.class ); + Column column = reader.getAnnotation( Column.class ); + assertEquals( "col1", column.name() ); + assertTrue( column.unique() ); + assertFalse( column.nullable() ); + assertFalse( column.insertable() ); + assertFalse( column.updatable() ); + assertEquals( "int", column.columnDefinition() ); + assertEquals( "table1", column.table() ); + assertEquals( 50, column.length() ); + assertEquals( 2, column.precision() ); + assertEquals( 1, column.scale() ); + } + + @Test + public void testTemporal() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm18.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( Temporal.class ); + assertAnnotationNotPresent( Enumerated.class ); + assertAnnotationNotPresent( Lob.class ); + assertEquals( + TemporalType.DATE, reader.getAnnotation( + Temporal.class + ).value() + ); + } + + @Test + public void testEnumerated() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm19.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( Temporal.class ); + assertAnnotationPresent( Enumerated.class ); + assertAnnotationNotPresent( Lob.class ); + assertEquals( + EnumType.STRING, reader.getAnnotation( + Enumerated.class + ).value() + ); + } + + @Test + public void testLob() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm20.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( Temporal.class ); + assertAnnotationNotPresent( Enumerated.class ); + assertAnnotationPresent( Lob.class ); + } + + /** + * When there's a single attribute override, we still wrap it with an + * AttributeOverrides annotation. + */ + @Test + public void testSingleAttributeOverride() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm21.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 1, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "col1", overrides[0].column().name() ); + } + + @Test + public void testMultipleAttributeOverrides() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm22.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "", overrides[0].column().name() ); + assertFalse( overrides[0].column().unique() ); + assertTrue( overrides[0].column().nullable() ); + assertTrue( overrides[0].column().insertable() ); + assertTrue( overrides[0].column().updatable() ); + assertEquals( "", overrides[0].column().columnDefinition() ); + assertEquals( "", overrides[0].column().table() ); + assertEquals( 255, overrides[0].column().length() ); + assertEquals( 0, overrides[0].column().precision() ); + assertEquals( 0, overrides[0].column().scale() ); + assertEquals( "field2", overrides[1].name() ); + assertEquals( "col1", overrides[1].column().name() ); + assertTrue( overrides[1].column().unique() ); + assertFalse( overrides[1].column().nullable() ); + assertFalse( overrides[1].column().insertable() ); + assertFalse( overrides[1].column().updatable() ); + assertEquals( "int", overrides[1].column().columnDefinition() ); + assertEquals( "table1", overrides[1].column().table() ); + assertEquals( 50, overrides[1].column().length() ); + assertEquals( 2, overrides[1].column().precision() ); + assertEquals( 1, overrides[1].column().scale() ); + } + + /** + * Tests that map-key-attribute-override and attribute-override elements + * both end up in the AttributeOverrides annotation. + */ + @Test + public void testMixedAttributeOverrides() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm23.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "col1", overrides[0].column().name() ); + assertEquals( "field2", overrides[1].name() ); + assertEquals( "col2", overrides[1].column().name() ); + } + + /** + * When there's a single association override, we still wrap it with an + * AssociationOverrides annotation. + */ + @Test + public void testSingleAssociationOverride() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm24.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( AssociationOverride.class ); + assertAnnotationPresent( AssociationOverrides.class ); + AssociationOverrides overridesAnno = reader.getAnnotation( AssociationOverrides.class ); + AssociationOverride[] overrides = overridesAnno.value(); + assertEquals( 1, overrides.length ); + assertEquals( "association1", overrides[0].name() ); + assertEquals( 0, overrides[0].joinColumns().length ); + assertEquals( "", overrides[0].joinTable().name() ); + } + + @Test + public void testMultipleAssociationOverridesJoinColumns() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm25.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( AssociationOverride.class ); + assertAnnotationPresent( AssociationOverrides.class ); + AssociationOverrides overridesAnno = reader.getAnnotation( AssociationOverrides.class ); + AssociationOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + //First, an association using join table + assertEquals( "association1", overrides[0].name() ); + assertEquals( 0, overrides[0].joinColumns().length ); + + JoinTable joinTableAnno = overrides[0].joinTable(); + assertEquals( "catalog1", joinTableAnno.catalog() ); + assertEquals( "table1", joinTableAnno.name() ); + assertEquals( "schema1", joinTableAnno.schema() ); + + //JoinColumns + JoinColumn[] joinColumns = joinTableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + //InverseJoinColumns + JoinColumn[] inverseJoinColumns = joinTableAnno.inverseJoinColumns(); + assertEquals( 2, inverseJoinColumns.length ); + assertEquals( "", inverseJoinColumns[0].name() ); + assertEquals( "", inverseJoinColumns[0].referencedColumnName() ); + assertEquals( "", inverseJoinColumns[0].table() ); + assertEquals( "", inverseJoinColumns[0].columnDefinition() ); + assertTrue( inverseJoinColumns[0].insertable() ); + assertTrue( inverseJoinColumns[0].updatable() ); + assertTrue( inverseJoinColumns[0].nullable() ); + assertFalse( inverseJoinColumns[0].unique() ); + assertEquals( "col3", inverseJoinColumns[1].name() ); + assertEquals( "col4", inverseJoinColumns[1].referencedColumnName() ); + assertEquals( "table3", inverseJoinColumns[1].table() ); + assertEquals( "int", inverseJoinColumns[1].columnDefinition() ); + assertFalse( inverseJoinColumns[1].insertable() ); + assertFalse( inverseJoinColumns[1].updatable() ); + assertFalse( inverseJoinColumns[1].nullable() ); + assertTrue( inverseJoinColumns[1].unique() ); + + //UniqueConstraints + UniqueConstraint[] uniqueConstraints = joinTableAnno + .uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col5", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col6", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col7", uniqueConstraints[1].columnNames()[1] ); + + //Second, an association using join columns + assertEquals( "association2", overrides[1].name() ); + + //JoinColumns + joinColumns = overrides[1].joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col8", joinColumns[1].name() ); + assertEquals( "col9", joinColumns[1].referencedColumnName() ); + assertEquals( "table4", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + } + + @Test + public void testCollectionTableNoChildren() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm26.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( CollectionTable.class ); + CollectionTable tableAnno = reader.getAnnotation( CollectionTable.class ); + assertEquals( "", tableAnno.name() ); + assertEquals( "", tableAnno.catalog() ); + assertEquals( "", tableAnno.schema() ); + assertEquals( 0, tableAnno.joinColumns().length ); + assertEquals( 0, tableAnno.uniqueConstraints().length ); + } + + @Test + public void testCollectionTableAllChildren() throws Exception { + reader = getReader( Entity3.class, "field1", "element-collection.orm27.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationPresent( CollectionTable.class ); + CollectionTable tableAnno = reader.getAnnotation( CollectionTable.class ); + assertEquals( "table1", tableAnno.name() ); + assertEquals( "catalog1", tableAnno.catalog() ); + assertEquals( "schema1", tableAnno.schema() ); + + //JoinColumns + JoinColumn[] joinColumns = tableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + //UniqueConstraints + UniqueConstraint[] uniqueConstraints = tableAnno.uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col3", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col4", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col5", uniqueConstraints[1].columnNames()[1] ); + } + + @Test + public void testAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "element-collection.orm28.xml" ); + assertAnnotationPresent( ElementCollection.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( Column.class ); + assertAnnotationNotPresent( Temporal.class ); + assertAnnotationNotPresent( Enumerated.class ); + assertAnnotationNotPresent( Lob.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationNotPresent( AttributeOverrides.class ); + assertAnnotationNotPresent( AssociationOverride.class ); + assertAnnotationNotPresent( AssociationOverrides.class ); + assertAnnotationNotPresent( CollectionTable.class ); + assertAnnotationPresent( Access.class ); + ElementCollection relAnno = reader.getAnnotation( ElementCollection.class ); + assertEquals( FetchType.EAGER, relAnno.fetch() ); + assertEquals( Entity3.class, relAnno.targetClass() ); + assertEquals( + AccessType.PROPERTY, reader.getAnnotation( Access.class ) + .value() + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToManyTest.java new file mode 100644 index 0000000000..e80c00e08e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToManyTest.java @@ -0,0 +1,508 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.CascadeType; +import javax.persistence.EnumType; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKey; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.MapKeyJoinColumns; +import javax.persistence.MapKeyTemporal; +import javax.persistence.OrderBy; +import javax.persistence.OrderColumn; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlManyToManyTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlManyToManyTest extends LegacyEjb3XmlTestCase { + @Test + public void testNoChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm1.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationNotPresent( Access.class ); + ManyToMany relAnno = reader.getAnnotation( ManyToMany.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.LAZY, relAnno.fetch() ); + assertEquals( "", relAnno.mappedBy() ); + assertEquals( void.class, relAnno.targetEntity() ); + } + + @Test + public void testOrderBy() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm2.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertEquals( + "col1 ASC, col2 DESC", reader.getAnnotation( OrderBy.class ) + .value() + ); + } + + @Test + public void testOrderColumnNoAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm3.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "", orderColumnAnno.columnDefinition() ); + assertEquals( "", orderColumnAnno.name() ); + assertTrue( orderColumnAnno.insertable() ); + assertTrue( orderColumnAnno.nullable() ); + assertTrue( orderColumnAnno.updatable() ); + } + + @Test + public void testOrderColumnAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm4.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "int", orderColumnAnno.columnDefinition() ); + assertEquals( "col1", orderColumnAnno.name() ); + assertFalse( orderColumnAnno.insertable() ); + assertFalse( orderColumnAnno.nullable() ); + assertFalse( orderColumnAnno.updatable() ); + } + + @Test + public void testMapKeyNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm5.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm6.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "field2", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyClass() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm7.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + Entity2.class, reader.getAnnotation( MapKeyClass.class ) + .value() + ); + } + + @Test + public void testMapKeyTemporal() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm8.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + TemporalType.DATE, reader.getAnnotation( + MapKeyTemporal.class + ).value() + ); + } + + @Test + public void testMapKeyEnumerated() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm9.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + EnumType.STRING, reader.getAnnotation( + MapKeyEnumerated.class + ).value() + ); + } + + /** + * When there's a single map key attribute override, we still wrap it with + * an AttributeOverrides annotation. + */ + @Test + public void testSingleMapKeyAttributeOverride() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm10.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 1, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "col1", overrides[0].column().name() ); + } + + @Test + public void testMultipleMapKeyAttributeOverrides() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm11.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "", overrides[0].column().name() ); + assertFalse( overrides[0].column().unique() ); + assertTrue( overrides[0].column().nullable() ); + assertTrue( overrides[0].column().insertable() ); + assertTrue( overrides[0].column().updatable() ); + assertEquals( "", overrides[0].column().columnDefinition() ); + assertEquals( "", overrides[0].column().table() ); + assertEquals( 255, overrides[0].column().length() ); + assertEquals( 0, overrides[0].column().precision() ); + assertEquals( 0, overrides[0].column().scale() ); + assertEquals( "field2", overrides[1].name() ); + assertEquals( "col1", overrides[1].column().name() ); + assertTrue( overrides[1].column().unique() ); + assertFalse( overrides[1].column().nullable() ); + assertFalse( overrides[1].column().insertable() ); + assertFalse( overrides[1].column().updatable() ); + assertEquals( "int", overrides[1].column().columnDefinition() ); + assertEquals( "table1", overrides[1].column().table() ); + assertEquals( 50, overrides[1].column().length() ); + assertEquals( 2, overrides[1].column().precision() ); + assertEquals( 1, overrides[1].column().scale() ); + } + + @Test + public void testMapKeyColumnNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm12.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "", keyColAnno.columnDefinition() ); + assertEquals( "", keyColAnno.name() ); + assertEquals( "", keyColAnno.table() ); + assertFalse( keyColAnno.nullable() ); + assertTrue( keyColAnno.insertable() ); + assertFalse( keyColAnno.unique() ); + assertTrue( keyColAnno.updatable() ); + assertEquals( 255, keyColAnno.length() ); + assertEquals( 0, keyColAnno.precision() ); + assertEquals( 0, keyColAnno.scale() ); + } + + @Test + public void testMapKeyColumnAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm13.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "int", keyColAnno.columnDefinition() ); + assertEquals( "col1", keyColAnno.name() ); + assertEquals( "table1", keyColAnno.table() ); + assertTrue( keyColAnno.nullable() ); + assertFalse( keyColAnno.insertable() ); + assertTrue( keyColAnno.unique() ); + assertFalse( keyColAnno.updatable() ); + assertEquals( 50, keyColAnno.length() ); + assertEquals( 2, keyColAnno.precision() ); + assertEquals( 1, keyColAnno.scale() ); + } + + /** + * When there's a single map key join column, we still wrap it with a + * MapKeyJoinColumns annotation. + */ + @Test + public void testSingleMapKeyJoinColumn() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm14.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + } + + @Test + public void testMultipleMapKeyJoinColumns() throws Exception { + reader = getReader( Entity3.class, "field1", "many-to-many.orm15.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertFalse( joinColumns[0].unique() ); + assertFalse( joinColumns[0].nullable() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertTrue( joinColumns[1].unique() ); + assertTrue( joinColumns[1].nullable() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertEquals( "table1", joinColumns[1].table() ); + } + + @Test + public void testJoinTableNoChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm16.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "", joinTableAnno.catalog() ); + assertEquals( "", joinTableAnno.name() ); + assertEquals( "", joinTableAnno.schema() ); + assertEquals( 0, joinTableAnno.joinColumns().length ); + assertEquals( 0, joinTableAnno.inverseJoinColumns().length ); + assertEquals( 0, joinTableAnno.uniqueConstraints().length ); + } + + @Test + public void testJoinTableAllChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm17.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "cat1", joinTableAnno.catalog() ); + assertEquals( "table1", joinTableAnno.name() ); + assertEquals( "schema1", joinTableAnno.schema() ); + + // JoinColumns + JoinColumn[] joinColumns = joinTableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + // InverseJoinColumns + JoinColumn[] inverseJoinColumns = joinTableAnno.inverseJoinColumns(); + assertEquals( 2, inverseJoinColumns.length ); + assertEquals( "", inverseJoinColumns[0].name() ); + assertEquals( "", inverseJoinColumns[0].referencedColumnName() ); + assertEquals( "", inverseJoinColumns[0].table() ); + assertEquals( "", inverseJoinColumns[0].columnDefinition() ); + assertTrue( inverseJoinColumns[0].insertable() ); + assertTrue( inverseJoinColumns[0].updatable() ); + assertTrue( inverseJoinColumns[0].nullable() ); + assertFalse( inverseJoinColumns[0].unique() ); + assertEquals( "col3", inverseJoinColumns[1].name() ); + assertEquals( "col4", inverseJoinColumns[1].referencedColumnName() ); + assertEquals( "table3", inverseJoinColumns[1].table() ); + assertEquals( "int", inverseJoinColumns[1].columnDefinition() ); + assertFalse( inverseJoinColumns[1].insertable() ); + assertFalse( inverseJoinColumns[1].updatable() ); + assertFalse( inverseJoinColumns[1].nullable() ); + assertTrue( inverseJoinColumns[1].unique() ); + + // UniqueConstraints + UniqueConstraint[] uniqueConstraints = joinTableAnno + .uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col5", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col6", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col7", uniqueConstraints[1].columnNames()[1] ); + } + + @Test + public void testCascadeAll() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm18.xml" ); + assertAnnotationPresent( ManyToMany.class ); + ManyToMany relAnno = reader.getAnnotation( ManyToMany.class ); + assertEquals( 1, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + } + + @Test + public void testCascadeSomeWithDefaultPersist() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm19.xml" ); + assertAnnotationPresent( ManyToMany.class ); + ManyToMany relAnno = reader.getAnnotation( ManyToMany.class ); + assertEquals( 4, relAnno.cascade().length ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[0] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[1] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[2] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[3] ); + } + + /** + * Make sure that it doesn't break the handler when {@link CascadeType#ALL} + * is specified in addition to a default cascade-persist or individual + * cascade settings. + */ + @Test + public void testCascadeAllPlusMore() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm20.xml" ); + assertAnnotationPresent( ManyToMany.class ); + ManyToMany relAnno = reader.getAnnotation( ManyToMany.class ); + assertEquals( 6, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[1] ); + assertEquals( CascadeType.MERGE, relAnno.cascade()[2] ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[3] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[4] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[5] ); + } + + @Test + public void testAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "many-to-many.orm21.xml" ); + assertAnnotationPresent( ManyToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationPresent( Access.class ); + ManyToMany relAnno = reader.getAnnotation( ManyToMany.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.EAGER, relAnno.fetch() ); + assertEquals( "field2", relAnno.mappedBy() ); + assertEquals( Entity3.class, relAnno.targetEntity() ); + assertEquals( + AccessType.PROPERTY, reader.getAnnotation( Access.class ) + .value() + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToOneTest.java new file mode 100644 index 0000000000..e665be4f2e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlManyToOneTest.java @@ -0,0 +1,245 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CascadeType; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlManyToOneTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlManyToOneTest extends LegacyEjb3XmlTestCase { + @Test + public void testNoJoins() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm1.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationNotPresent( Id.class ); + assertAnnotationNotPresent( MapsId.class ); + assertAnnotationNotPresent( Access.class ); + ManyToOne relAnno = reader.getAnnotation( ManyToOne.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.EAGER, relAnno.fetch() ); + assertTrue( relAnno.optional() ); + assertEquals( void.class, relAnno.targetEntity() ); + } + + /** + * When there's a single join column, we still wrap it with a JoinColumns + * annotation. + */ + @Test + public void testSingleJoinColumn() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm2.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + assertEquals( "col2", joinColumns[0].referencedColumnName() ); + assertEquals( "table1", joinColumns[0].table() ); + } + + @Test + public void testMultipleJoinColumns() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm3.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table1", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + } + + @Test + public void testJoinTableNoChildren() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm4.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationPresent( JoinTable.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "", joinTableAnno.catalog() ); + assertEquals( "", joinTableAnno.name() ); + assertEquals( "", joinTableAnno.schema() ); + assertEquals( 0, joinTableAnno.joinColumns().length ); + assertEquals( 0, joinTableAnno.inverseJoinColumns().length ); + assertEquals( 0, joinTableAnno.uniqueConstraints().length ); + } + + @Test + public void testJoinTableAllChildren() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm5.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationPresent( JoinTable.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "cat1", joinTableAnno.catalog() ); + assertEquals( "table1", joinTableAnno.name() ); + assertEquals( "schema1", joinTableAnno.schema() ); + + // JoinColumns + JoinColumn[] joinColumns = joinTableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + // InverseJoinColumns + JoinColumn[] inverseJoinColumns = joinTableAnno.inverseJoinColumns(); + assertEquals( 2, inverseJoinColumns.length ); + assertEquals( "", inverseJoinColumns[0].name() ); + assertEquals( "", inverseJoinColumns[0].referencedColumnName() ); + assertEquals( "", inverseJoinColumns[0].table() ); + assertEquals( "", inverseJoinColumns[0].columnDefinition() ); + assertTrue( inverseJoinColumns[0].insertable() ); + assertTrue( inverseJoinColumns[0].updatable() ); + assertTrue( inverseJoinColumns[0].nullable() ); + assertFalse( inverseJoinColumns[0].unique() ); + assertEquals( "col3", inverseJoinColumns[1].name() ); + assertEquals( "col4", inverseJoinColumns[1].referencedColumnName() ); + assertEquals( "table3", inverseJoinColumns[1].table() ); + assertEquals( "int", inverseJoinColumns[1].columnDefinition() ); + assertFalse( inverseJoinColumns[1].insertable() ); + assertFalse( inverseJoinColumns[1].updatable() ); + assertFalse( inverseJoinColumns[1].nullable() ); + assertTrue( inverseJoinColumns[1].unique() ); + + // UniqueConstraints + UniqueConstraint[] uniqueConstraints = joinTableAnno + .uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col5", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col6", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col7", uniqueConstraints[1].columnNames()[1] ); + } + + @Test + public void testAllAttributes() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm6.xml" ); + assertAnnotationPresent( ManyToOne.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationPresent( Id.class ); + assertAnnotationPresent( MapsId.class ); + assertAnnotationPresent( Access.class ); + ManyToOne relAnno = reader.getAnnotation( ManyToOne.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.LAZY, relAnno.fetch() ); + assertFalse( relAnno.optional() ); + assertEquals( Entity3.class, relAnno.targetEntity() ); + assertEquals( "col1", reader.getAnnotation( MapsId.class ).value() ); + assertEquals( + AccessType.PROPERTY, reader.getAnnotation( Access.class ) + .value() + ); + } + + @Test + public void testCascadeAll() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm7.xml" ); + assertAnnotationPresent( ManyToOne.class ); + ManyToOne relAnno = reader.getAnnotation( ManyToOne.class ); + assertEquals( 1, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + } + + @Test + public void testCascadeSomeWithDefaultPersist() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm8.xml" ); + assertAnnotationPresent( ManyToOne.class ); + ManyToOne relAnno = reader.getAnnotation( ManyToOne.class ); + assertEquals( 4, relAnno.cascade().length ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[0] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[1] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[2] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[3] ); + } + + /** + * Make sure that it doesn't break the handler when {@link CascadeType#ALL} + * is specified in addition to a default cascade-persist or individual + * cascade settings. + */ + @Test + public void testCascadeAllPlusMore() throws Exception { + reader = getReader( Entity1.class, "field1", "many-to-one.orm9.xml" ); + assertAnnotationPresent( ManyToOne.class ); + ManyToOne relAnno = reader.getAnnotation( ManyToOne.class ); + assertEquals( 6, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[1] ); + assertEquals( CascadeType.MERGE, relAnno.cascade()[2] ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[3] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[4] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[5] ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToManyTest.java new file mode 100644 index 0000000000..b531fabecf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToManyTest.java @@ -0,0 +1,561 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.CascadeType; +import javax.persistence.EnumType; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.MapKey; +import javax.persistence.MapKeyClass; +import javax.persistence.MapKeyColumn; +import javax.persistence.MapKeyEnumerated; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.MapKeyJoinColumns; +import javax.persistence.MapKeyTemporal; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.OrderColumn; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlOneToManyTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlOneToManyTest extends LegacyEjb3XmlTestCase { + @Test + public void testNoChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm1.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( Access.class ); + OneToMany relAnno = reader.getAnnotation( OneToMany.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.LAZY, relAnno.fetch() ); + assertEquals( "", relAnno.mappedBy() ); + assertFalse( relAnno.orphanRemoval() ); + assertEquals( void.class, relAnno.targetEntity() ); + } + + @Test + public void testOrderBy() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm2.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertEquals( + "col1 ASC, col2 DESC", reader.getAnnotation( OrderBy.class ) + .value() + ); + } + + @Test + public void testOrderColumnNoAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm3.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "", orderColumnAnno.columnDefinition() ); + assertEquals( "", orderColumnAnno.name() ); + assertTrue( orderColumnAnno.insertable() ); + assertTrue( orderColumnAnno.nullable() ); + assertTrue( orderColumnAnno.updatable() ); + } + + @Test + public void testOrderColumnAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm4.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationPresent( OrderColumn.class ); + OrderColumn orderColumnAnno = reader.getAnnotation( OrderColumn.class ); + assertEquals( "int", orderColumnAnno.columnDefinition() ); + assertEquals( "col1", orderColumnAnno.name() ); + assertFalse( orderColumnAnno.insertable() ); + assertFalse( orderColumnAnno.nullable() ); + assertFalse( orderColumnAnno.updatable() ); + } + + @Test + public void testMapKeyNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm5.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm6.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( "field2", reader.getAnnotation( MapKey.class ).name() ); + } + + @Test + public void testMapKeyClass() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm7.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + Entity2.class, reader.getAnnotation( MapKeyClass.class ) + .value() + ); + } + + @Test + public void testMapKeyTemporal() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm8.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + TemporalType.DATE, reader.getAnnotation( + MapKeyTemporal.class + ).value() + ); + } + + @Test + public void testMapKeyEnumerated() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm9.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertEquals( + EnumType.STRING, reader.getAnnotation( + MapKeyEnumerated.class + ).value() + ); + } + + /** + * When there's a single map key attribute override, we still wrap it with + * an AttributeOverrides annotation. + */ + @Test + public void testSingleMapKeyAttributeOverride() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm10.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 1, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "col1", overrides[0].column().name() ); + } + + @Test + public void testMultipleMapKeyAttributeOverrides() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm11.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( AttributeOverride.class ); + assertAnnotationPresent( AttributeOverrides.class ); + AttributeOverrides overridesAnno = reader + .getAnnotation( AttributeOverrides.class ); + AttributeOverride[] overrides = overridesAnno.value(); + assertEquals( 2, overrides.length ); + assertEquals( "field1", overrides[0].name() ); + assertEquals( "", overrides[0].column().name() ); + assertFalse( overrides[0].column().unique() ); + assertTrue( overrides[0].column().nullable() ); + assertTrue( overrides[0].column().insertable() ); + assertTrue( overrides[0].column().updatable() ); + assertEquals( "", overrides[0].column().columnDefinition() ); + assertEquals( "", overrides[0].column().table() ); + assertEquals( 255, overrides[0].column().length() ); + assertEquals( 0, overrides[0].column().precision() ); + assertEquals( 0, overrides[0].column().scale() ); + assertEquals( "field2", overrides[1].name() ); + assertEquals( "col1", overrides[1].column().name() ); + assertTrue( overrides[1].column().unique() ); + assertFalse( overrides[1].column().nullable() ); + assertFalse( overrides[1].column().insertable() ); + assertFalse( overrides[1].column().updatable() ); + assertEquals( "int", overrides[1].column().columnDefinition() ); + assertEquals( "table1", overrides[1].column().table() ); + assertEquals( 50, overrides[1].column().length() ); + assertEquals( 2, overrides[1].column().precision() ); + assertEquals( 1, overrides[1].column().scale() ); + } + + @Test + public void testMapKeyColumnNoAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm12.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "", keyColAnno.columnDefinition() ); + assertEquals( "", keyColAnno.name() ); + assertEquals( "", keyColAnno.table() ); + assertFalse( keyColAnno.nullable() ); + assertTrue( keyColAnno.insertable() ); + assertFalse( keyColAnno.unique() ); + assertTrue( keyColAnno.updatable() ); + assertEquals( 255, keyColAnno.length() ); + assertEquals( 0, keyColAnno.precision() ); + assertEquals( 0, keyColAnno.scale() ); + } + + @Test + public void testMapKeyColumnAllAttributes() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm13.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyColumn keyColAnno = reader.getAnnotation( MapKeyColumn.class ); + assertEquals( "int", keyColAnno.columnDefinition() ); + assertEquals( "col1", keyColAnno.name() ); + assertEquals( "table1", keyColAnno.table() ); + assertTrue( keyColAnno.nullable() ); + assertFalse( keyColAnno.insertable() ); + assertTrue( keyColAnno.unique() ); + assertFalse( keyColAnno.updatable() ); + assertEquals( 50, keyColAnno.length() ); + assertEquals( 2, keyColAnno.precision() ); + assertEquals( 1, keyColAnno.scale() ); + } + + /** + * When there's a single map key join column, we still wrap it with a + * MapKeyJoinColumns annotation. + */ + @Test + public void testSingleMapKeyJoinColumn() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm14.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + } + + @Test + public void testMultipleMapKeyJoinColumns() throws Exception { + reader = getReader( Entity3.class, "field1", "one-to-many.orm15.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + MapKeyJoinColumns joinColumnsAnno = reader + .getAnnotation( MapKeyJoinColumns.class ); + MapKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertFalse( joinColumns[0].unique() ); + assertFalse( joinColumns[0].nullable() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertTrue( joinColumns[1].unique() ); + assertTrue( joinColumns[1].nullable() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertEquals( "table1", joinColumns[1].table() ); + } + + @Test + public void testJoinTableNoChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm16.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "", joinTableAnno.catalog() ); + assertEquals( "", joinTableAnno.name() ); + assertEquals( "", joinTableAnno.schema() ); + assertEquals( 0, joinTableAnno.joinColumns().length ); + assertEquals( 0, joinTableAnno.inverseJoinColumns().length ); + assertEquals( 0, joinTableAnno.uniqueConstraints().length ); + } + + @Test + public void testJoinTableAllChildren() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm17.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "cat1", joinTableAnno.catalog() ); + assertEquals( "table1", joinTableAnno.name() ); + assertEquals( "schema1", joinTableAnno.schema() ); + + // JoinColumns + JoinColumn[] joinColumns = joinTableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + // InverseJoinColumns + JoinColumn[] inverseJoinColumns = joinTableAnno.inverseJoinColumns(); + assertEquals( 2, inverseJoinColumns.length ); + assertEquals( "", inverseJoinColumns[0].name() ); + assertEquals( "", inverseJoinColumns[0].referencedColumnName() ); + assertEquals( "", inverseJoinColumns[0].table() ); + assertEquals( "", inverseJoinColumns[0].columnDefinition() ); + assertTrue( inverseJoinColumns[0].insertable() ); + assertTrue( inverseJoinColumns[0].updatable() ); + assertTrue( inverseJoinColumns[0].nullable() ); + assertFalse( inverseJoinColumns[0].unique() ); + assertEquals( "col3", inverseJoinColumns[1].name() ); + assertEquals( "col4", inverseJoinColumns[1].referencedColumnName() ); + assertEquals( "table3", inverseJoinColumns[1].table() ); + assertEquals( "int", inverseJoinColumns[1].columnDefinition() ); + assertFalse( inverseJoinColumns[1].insertable() ); + assertFalse( inverseJoinColumns[1].updatable() ); + assertFalse( inverseJoinColumns[1].nullable() ); + assertTrue( inverseJoinColumns[1].unique() ); + + // UniqueConstraints + UniqueConstraint[] uniqueConstraints = joinTableAnno + .uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col5", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col6", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col7", uniqueConstraints[1].columnNames()[1] ); + } + + /** + * When there's a single join column, we still wrap it with a JoinColumns + * annotation. + */ + @Test + public void testSingleJoinColumn() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm18.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + assertEquals( "col2", joinColumns[0].referencedColumnName() ); + assertEquals( "table1", joinColumns[0].table() ); + } + + @Test + public void testMultipleJoinColumns() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm19.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table1", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + } + + @Test + public void testCascadeAll() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm20.xml" ); + assertAnnotationPresent( OneToMany.class ); + OneToMany relAnno = reader.getAnnotation( OneToMany.class ); + assertEquals( 1, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + } + + @Test + public void testCascadeSomeWithDefaultPersist() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm21.xml" ); + assertAnnotationPresent( OneToMany.class ); + OneToMany relAnno = reader.getAnnotation( OneToMany.class ); + assertEquals( 4, relAnno.cascade().length ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[0] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[1] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[2] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[3] ); + } + + /** + * Make sure that it doesn't break the handler when {@link CascadeType#ALL} + * is specified in addition to a default cascade-persist or individual + * cascade settings. + */ + @Test + public void testCascadeAllPlusMore() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm22.xml" ); + assertAnnotationPresent( OneToMany.class ); + OneToMany relAnno = reader.getAnnotation( OneToMany.class ); + assertEquals( 6, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[1] ); + assertEquals( CascadeType.MERGE, relAnno.cascade()[2] ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[3] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[4] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[5] ); + } + + @Test + public void testAllAttributes() throws Exception { + reader = getReader( Entity2.class, "field1", "one-to-many.orm23.xml" ); + assertAnnotationPresent( OneToMany.class ); + assertAnnotationNotPresent( OrderBy.class ); + assertAnnotationNotPresent( OrderColumn.class ); + assertAnnotationNotPresent( MapKey.class ); + assertAnnotationNotPresent( MapKeyClass.class ); + assertAnnotationNotPresent( MapKeyTemporal.class ); + assertAnnotationNotPresent( MapKeyEnumerated.class ); + assertAnnotationNotPresent( MapKeyColumn.class ); + assertAnnotationNotPresent( MapKeyJoinColumns.class ); + assertAnnotationNotPresent( MapKeyJoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( Access.class ); + OneToMany relAnno = reader.getAnnotation( OneToMany.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.EAGER, relAnno.fetch() ); + assertEquals( "field2", relAnno.mappedBy() ); + assertTrue( relAnno.orphanRemoval() ); + assertEquals( Entity3.class, relAnno.targetEntity() ); + assertEquals( + AccessType.PROPERTY, reader.getAnnotation( Access.class ) + .value() + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToOneTest.java new file mode 100644 index 0000000000..45c5e5e844 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlOneToOneTest.java @@ -0,0 +1,309 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CascadeType; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.JoinTable; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.PrimaryKeyJoinColumns; +import javax.persistence.UniqueConstraint; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlOneToOneTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlOneToOneTest extends LegacyEjb3XmlTestCase { + @Test + public void testNoChildren() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm1.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( MapsId.class ); + assertAnnotationNotPresent( Id.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationNotPresent( Access.class ); + OneToOne relAnno = reader.getAnnotation( OneToOne.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.EAGER, relAnno.fetch() ); + assertEquals( "", relAnno.mappedBy() ); + assertTrue( relAnno.optional() ); + assertFalse( relAnno.orphanRemoval() ); + assertEquals( void.class, relAnno.targetEntity() ); + } + + /** + * When there's a single primary key join column, we still wrap it with + * a PrimaryKeyJoinColumns annotation. + */ + @Test + public void testSinglePrimaryKeyJoinColumn() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm2.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationPresent( PrimaryKeyJoinColumns.class ); + PrimaryKeyJoinColumns joinColumnsAnno = + reader.getAnnotation( PrimaryKeyJoinColumns.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + PrimaryKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + assertEquals( "col2", joinColumns[0].referencedColumnName() ); + assertEquals( "int", joinColumns[0].columnDefinition() ); + } + + @Test + public void testMultiplePrimaryKeyJoinColumn() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm3.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + PrimaryKeyJoinColumns joinColumnsAnno = + reader.getAnnotation( PrimaryKeyJoinColumns.class ); + PrimaryKeyJoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + } + + /** + * When there's a single join column, we still wrap it with a JoinColumns + * annotation. + */ + @Test + public void testSingleJoinColumn() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm4.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 1, joinColumns.length ); + assertEquals( "col1", joinColumns[0].name() ); + assertEquals( "col2", joinColumns[0].referencedColumnName() ); + assertEquals( "table1", joinColumns[0].table() ); + } + + @Test + public void testMultipleJoinColumns() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm5.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinTable.class ); + JoinColumns joinColumnsAnno = reader.getAnnotation( JoinColumns.class ); + JoinColumn[] joinColumns = joinColumnsAnno.value(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table1", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + } + + @Test + public void testJoinTableNoChildren() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm6.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "", joinTableAnno.catalog() ); + assertEquals( "", joinTableAnno.name() ); + assertEquals( "", joinTableAnno.schema() ); + assertEquals( 0, joinTableAnno.joinColumns().length ); + assertEquals( 0, joinTableAnno.inverseJoinColumns().length ); + assertEquals( 0, joinTableAnno.uniqueConstraints().length ); + } + + @Test + public void testJoinTableAllChildren() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm7.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationPresent( JoinTable.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + JoinTable joinTableAnno = reader.getAnnotation( JoinTable.class ); + assertEquals( "cat1", joinTableAnno.catalog() ); + assertEquals( "table1", joinTableAnno.name() ); + assertEquals( "schema1", joinTableAnno.schema() ); + + // JoinColumns + JoinColumn[] joinColumns = joinTableAnno.joinColumns(); + assertEquals( 2, joinColumns.length ); + assertEquals( "", joinColumns[0].name() ); + assertEquals( "", joinColumns[0].referencedColumnName() ); + assertEquals( "", joinColumns[0].table() ); + assertEquals( "", joinColumns[0].columnDefinition() ); + assertTrue( joinColumns[0].insertable() ); + assertTrue( joinColumns[0].updatable() ); + assertTrue( joinColumns[0].nullable() ); + assertFalse( joinColumns[0].unique() ); + assertEquals( "col1", joinColumns[1].name() ); + assertEquals( "col2", joinColumns[1].referencedColumnName() ); + assertEquals( "table2", joinColumns[1].table() ); + assertEquals( "int", joinColumns[1].columnDefinition() ); + assertFalse( joinColumns[1].insertable() ); + assertFalse( joinColumns[1].updatable() ); + assertFalse( joinColumns[1].nullable() ); + assertTrue( joinColumns[1].unique() ); + + // InverseJoinColumns + JoinColumn[] inverseJoinColumns = joinTableAnno.inverseJoinColumns(); + assertEquals( 2, inverseJoinColumns.length ); + assertEquals( "", inverseJoinColumns[0].name() ); + assertEquals( "", inverseJoinColumns[0].referencedColumnName() ); + assertEquals( "", inverseJoinColumns[0].table() ); + assertEquals( "", inverseJoinColumns[0].columnDefinition() ); + assertTrue( inverseJoinColumns[0].insertable() ); + assertTrue( inverseJoinColumns[0].updatable() ); + assertTrue( inverseJoinColumns[0].nullable() ); + assertFalse( inverseJoinColumns[0].unique() ); + assertEquals( "col3", inverseJoinColumns[1].name() ); + assertEquals( "col4", inverseJoinColumns[1].referencedColumnName() ); + assertEquals( "table3", inverseJoinColumns[1].table() ); + assertEquals( "int", inverseJoinColumns[1].columnDefinition() ); + assertFalse( inverseJoinColumns[1].insertable() ); + assertFalse( inverseJoinColumns[1].updatable() ); + assertFalse( inverseJoinColumns[1].nullable() ); + assertTrue( inverseJoinColumns[1].unique() ); + + // UniqueConstraints + UniqueConstraint[] uniqueConstraints = joinTableAnno + .uniqueConstraints(); + assertEquals( 2, uniqueConstraints.length ); + assertEquals( "", uniqueConstraints[0].name() ); + assertEquals( 1, uniqueConstraints[0].columnNames().length ); + assertEquals( "col5", uniqueConstraints[0].columnNames()[0] ); + assertEquals( "uq1", uniqueConstraints[1].name() ); + assertEquals( 2, uniqueConstraints[1].columnNames().length ); + assertEquals( "col6", uniqueConstraints[1].columnNames()[0] ); + assertEquals( "col7", uniqueConstraints[1].columnNames()[1] ); + } + + @Test + public void testCascadeAll() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm8.xml" ); + assertAnnotationPresent( OneToOne.class ); + OneToOne relAnno = reader.getAnnotation( OneToOne.class ); + assertEquals( 1, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + } + + @Test + public void testCascadeSomeWithDefaultPersist() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm9.xml" ); + assertAnnotationPresent( OneToOne.class ); + OneToOne relAnno = reader.getAnnotation( OneToOne.class ); + assertEquals( 4, relAnno.cascade().length ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[0] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[1] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[2] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[3] ); + } + + /** + * Make sure that it doesn't break the handler when {@link CascadeType#ALL} + * is specified in addition to a default cascade-persist or individual + * cascade settings. + */ + @Test + public void testCascadeAllPlusMore() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm10.xml" ); + assertAnnotationPresent( OneToOne.class ); + OneToOne relAnno = reader.getAnnotation( OneToOne.class ); + assertEquals( 6, relAnno.cascade().length ); + assertEquals( CascadeType.ALL, relAnno.cascade()[0] ); + assertEquals( CascadeType.PERSIST, relAnno.cascade()[1] ); + assertEquals( CascadeType.MERGE, relAnno.cascade()[2] ); + assertEquals( CascadeType.REMOVE, relAnno.cascade()[3] ); + assertEquals( CascadeType.REFRESH, relAnno.cascade()[4] ); + assertEquals( CascadeType.DETACH, relAnno.cascade()[5] ); + } + + @Test + public void testAllAttributes() throws Exception { + reader = getReader( Entity1.class, "field1", "one-to-one.orm11.xml" ); + assertAnnotationPresent( OneToOne.class ); + assertAnnotationPresent( MapsId.class ); + assertAnnotationPresent( Id.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumn.class ); + assertAnnotationNotPresent( PrimaryKeyJoinColumns.class ); + assertAnnotationNotPresent( JoinColumns.class ); + assertAnnotationNotPresent( JoinColumn.class ); + assertAnnotationNotPresent( JoinTable.class ); + assertAnnotationPresent( Access.class ); + OneToOne relAnno = reader.getAnnotation( OneToOne.class ); + assertEquals( 0, relAnno.cascade().length ); + assertEquals( FetchType.LAZY, relAnno.fetch() ); + assertEquals( "field2", relAnno.mappedBy() ); + assertFalse( relAnno.optional() ); + assertTrue( relAnno.orphanRemoval() ); + assertEquals( Entity3.class, relAnno.targetEntity() ); + assertEquals( + AccessType.PROPERTY, reader.getAnnotation( Access.class ) + .value() + ); + assertEquals( + "field3", reader.getAnnotation( MapsId.class ) + .value() + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTest.java new file mode 100644 index 0000000000..dae470e238 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTest.java @@ -0,0 +1,148 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import java.util.Date; +import java.util.List; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; +import org.hibernate.dialect.CockroachDB192Dialect; +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.TeradataDialect; +import org.hibernate.persister.collection.BasicCollectionPersister; + +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +public class LegacyEjb3XmlTest extends BaseCoreFunctionalTestCase { + @Test + @SkipForDialect(value = {PostgreSQL81Dialect.class, PostgreSQLDialect.class, CockroachDB192Dialect.class}, + comment = "postgresql jdbc driver does not implement the setQueryTimeout method") + @SkipForDialect(value = TeradataDialect.class, + jiraKey = "HHH-8190", + comment = "uses Teradata reserved word - year") + public void testEjb3Xml() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + CarModel model = new CarModel(); + model.setYear( new Date() ); + Manufacturer manufacturer = new Manufacturer(); + //s.persist( manufacturer ); + model.setManufacturer( manufacturer ); + manufacturer.getModels().add( model ); + s.persist( model ); + s.flush(); + s.clear(); + + model.setYear( new Date() ); + manufacturer = (Manufacturer) s.get( Manufacturer.class, manufacturer.getId() ); + @SuppressWarnings("unchecked") + List cars = s.getNamedQuery( "allModelsPerManufacturer" ) + .setParameter( "manufacturer", manufacturer ) + .list(); + assertEquals( 1, cars.size() ); + for ( Model car : cars ) { + assertNotNull( car.getManufacturer() ); + s.delete( manufacturer ); + s.delete( car ); + } + tx.rollback(); + s.close(); + } + + @Test + public void testXMLEntityHandled() throws Exception { + Session s = openSession(); + s.getTransaction().begin(); + Lighter l = new Lighter(); + l.name = "Blue"; + l.power = "400F"; + s.persist( l ); + s.flush(); + s.getTransaction().rollback(); + s.close(); + } + + @Test + public void testXmlDefaultOverriding() throws Exception { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + Manufacturer manufacturer = new Manufacturer(); + s.persist( manufacturer ); + s.flush(); + s.clear(); + + assertEquals( 1, s.getNamedQuery( "manufacturer.findAll" ).list().size() ); + tx.rollback(); + s.close(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMapXMLSupport() throws Exception { + Session s = openSession(); + SessionFactory sf = s.getSessionFactory(); + Transaction tx = s.beginTransaction(); + + // Verify that we can persist an object with a couple Map mappings + VicePresident vpSales = new VicePresident(); + vpSales.name = "Dwight"; + Company company = new Company(); + company.conferenceRoomExtensions.put( "8932", "x1234" ); + company.organization.put( "sales", vpSales ); + s.persist( company ); + s.flush(); + s.clear(); + + // For the element-collection, check that the orm.xml entries are honored. + // This includes: map-key-column/column/collection-table/join-column + BasicCollectionPersister confRoomMeta = (BasicCollectionPersister) sf.getCollectionMetadata( Company.class.getName() + ".conferenceRoomExtensions" ); + assertEquals( "company_id", confRoomMeta.getKeyColumnNames()[0] ); + assertEquals( "phone_extension", confRoomMeta.getElementColumnNames()[0] ); + assertEquals( "room_number", confRoomMeta.getIndexColumnNames()[0] ); + assertEquals( "phone_extension_lookup", confRoomMeta.getTableName() ); + tx.rollback(); + s.close(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + CarModel.class, + Manufacturer.class, + Model.class, + Light.class + //Lighter.class xml only entuty + }; + } + + @Override + protected String[] getXmlFiles() { + return new String[] { + "org/hibernate/test/annotations/xml/ejb3/orm.xml", + "org/hibernate/test/annotations/xml/ejb3/orm2.xml", + "org/hibernate/test/annotations/xml/ejb3/orm3.xml", + "org/hibernate/test/annotations/xml/ejb3/orm4.xml" + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTestCase.java new file mode 100644 index 0000000000..e22dfea26e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyEjb3XmlTestCase.java @@ -0,0 +1,82 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; + +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; +import org.hibernate.cfg.annotations.reflection.XMLContext; + +import org.hibernate.testing.boot.BootstrapContextImpl; +import org.hibernate.testing.junit4.BaseUnitTestCase; + +import org.dom4j.Document; +import org.dom4j.io.SAXReader; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test superclass to provide utility methods for testing the mapping of JPA + * XML to JPA annotations. The configuration is built within each test, and no + * database is used. Thus, no schema generation or cleanup will be performed. + *

+ * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.Ejb3XmlTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@Deprecated +abstract class LegacyEjb3XmlTestCase extends BaseUnitTestCase { + protected JPAOverriddenAnnotationReader reader; + + protected void assertAnnotationPresent(Class annotationType) { + assertTrue( + "Expected annotation " + annotationType.getSimpleName() + " was not present", + reader.isAnnotationPresent( annotationType ) + ); + } + + protected void assertAnnotationNotPresent(Class annotationType) { + assertFalse( + "Unexpected annotation " + annotationType.getSimpleName() + " was present", + reader.isAnnotationPresent( annotationType ) + ); + } + + protected JPAOverriddenAnnotationReader getReader(Class entityClass, String fieldName, String ormResourceName) + throws Exception { + AnnotatedElement el = getAnnotatedElement( entityClass, fieldName ); + XMLContext xmlContext = getContext( ormResourceName ); + return new JPAOverriddenAnnotationReader( el, xmlContext, BootstrapContextImpl.INSTANCE ); + } + + protected AnnotatedElement getAnnotatedElement(Class entityClass, String fieldName) throws Exception { + return entityClass.getDeclaredField( fieldName ); + } + + protected XMLContext getContext(String resourceName) throws Exception { + InputStream is = getClass().getResourceAsStream( resourceName ); + assertNotNull( "Could not load resource " + resourceName, is ); + return getContext( is ); + } + + protected XMLContext getContext(InputStream is) throws Exception { + XMLContext xmlContext = new XMLContext( BootstrapContextImpl.INSTANCE ); + SAXReader reader = new SAXReader(); + reader.setFeature( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false ); + reader.setFeature( "http://xml.org/sax/features/external-general-entities", false ); + reader.setFeature( "http://xml.org/sax/features/external-parameter-entities", false ); + Document doc = reader.read( is ); + xmlContext.addDocument( doc ); + return xmlContext; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyNonExistentOrmVersionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyNonExistentOrmVersionTest.java new file mode 100644 index 0000000000..74509b879c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyNonExistentOrmVersionTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import org.hibernate.InvalidMappingException; +import org.hibernate.boot.MetadataSources; +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; +import org.hibernate.internal.util.xml.UnsupportedOrmXsdVersionException; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.NonExistentOrmVersionTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@TestForIssue(jiraKey = "HHH-6271") +public class LegacyNonExistentOrmVersionTest extends BaseUnitTestCase { + @Test + public void testNonExistentOrmVersion() { + try { + new MetadataSources() + .addResource( "org/hibernate/test/annotations/xml/ejb3/orm5.xml" ) + .buildMetadata(); + fail( "Expecting failure due to unsupported xsd version" ); + } + catch ( InvalidMappingException expected ) { + } + catch ( UnsupportedOrmXsdVersionException expected ) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyOrmVersion1SupportedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyOrmVersion1SupportedTest.java new file mode 100644 index 0000000000..763b79f605 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyOrmVersion1SupportedTest.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.xml.ErrorLogger; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Rule; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.OrmVersion1SupportedTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@TestForIssue(jiraKey = "HHH-6271") +public class LegacyOrmVersion1SupportedTest extends BaseCoreFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( + CoreMessageLogger.class, + ErrorLogger.class.getName() + ) + ); + + @Test + public void testOrm1Support() { + Triggerable triggerable = logInspection.watchForLogMessages( "HHH00196" ); + + Session s = openSession(); + Transaction tx = s.beginTransaction(); + Light light = new Light(); + light.name = "the light at the end of the tunnel"; + s.persist( light ); + s.flush(); + s.clear(); + + assertEquals( 1, s.getNamedQuery( "find.the.light" ).list().size() ); + tx.rollback(); + s.close(); + + assertFalse( triggerable.wasTriggered() ); + } + + @Override + protected String[] getXmlFiles() { + return new String[] { "org/hibernate/test/annotations/xml/ejb3/orm2.xml" }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyPreParsedOrmXmlTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyPreParsedOrmXmlTest.java new file mode 100644 index 0000000000..9d3e4654b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/LegacyPreParsedOrmXmlTest.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.annotations.xml.ejb3; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; + +import org.hibernate.boot.jaxb.spi.Binding; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.annotations.xml.ejb3.PreParsedOrmXmlTest.NonAnnotatedEntity; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Equivalent to {@link org.hibernate.test.annotations.xml.ejb3.PreParsedOrmXmlTest} + * for the legacy {@link JPAOverriddenAnnotationReader}. + * + * @author Emmanuel Bernard + * @deprecated This test will be removed in Hibernate ORM 6, along with the legacy {@link JPAOverriddenAnnotationReader}. + */ +@TestForIssue(jiraKey = "HHH-14530") +public class LegacyPreParsedOrmXmlTest extends BaseCoreFunctionalTestCase { + + @Override + protected void addMappings(Configuration configuration) { + super.addMappings( configuration ); + try (InputStream xmlStream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream( "org/hibernate/test/annotations/xml/ejb3/pre-parsed-orm.xml" )) { + Binding parsed = configuration.getXmlMappingBinderAccess().bind( xmlStream ); + configuration.addXmlMapping( parsed ); + } + catch (IOException e) { + throw new UncheckedIOException( e ); + } + } + + @Test + public void testPreParsedOrmXml() { + // Just check that the entity can be persisted, which means the mapping file was taken into account + NonAnnotatedEntity persistedEntity = new NonAnnotatedEntity( "someName" ); + inTransaction( s -> s.persist( persistedEntity ) ); + inTransaction( s -> { + NonAnnotatedEntity retrievedEntity = s.find( NonAnnotatedEntity.class, persistedEntity.getId() ); + assertThat( retrievedEntity ).extracting( NonAnnotatedEntity::getName ) + .isEqualTo( persistedEntity.getName() ); + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java index 986c9c077e..63e9df91b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/NonExistentOrmVersionTest.java @@ -8,6 +8,7 @@ package org.hibernate.test.annotations.xml.ejb3; import org.hibernate.InvalidMappingException; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.internal.util.xml.UnsupportedOrmXsdVersionException; import org.hibernate.testing.TestForIssue; @@ -16,12 +17,13 @@ import org.junit.Test; import static org.junit.Assert.fail; -@TestForIssue(jiraKey = "HHH-6271") +@TestForIssue(jiraKey = {"HHH-6271", "HHH-14529"}) public class NonExistentOrmVersionTest extends BaseUnitTestCase { @Test public void testNonExistentOrmVersion() { + // FIXME HHH-14529 configure the BootstrapServiceRegistry to use JAXB for orm.xml mappings try { - new MetadataSources() + new MetadataSources( new BootstrapServiceRegistryBuilder().build() ) .addResource( "org/hibernate/test/annotations/xml/ejb3/orm5.xml" ) .buildMetadata(); fail( "Expecting failure due to unsupported xsd version" ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/OrmVersion1SupportedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/OrmVersion1SupportedTest.java index 03658267dd..f56ccd780e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/OrmVersion1SupportedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/OrmVersion1SupportedTest.java @@ -8,6 +8,7 @@ package org.hibernate.test.annotations.xml.ejb3; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.xml.ErrorLogger; @@ -23,9 +24,15 @@ import org.jboss.logging.Logger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -@TestForIssue(jiraKey = "HHH-6271") +@TestForIssue(jiraKey = {"HHH-6271", "HHH-14529"}) public class OrmVersion1SupportedTest extends BaseCoreFunctionalTestCase { + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + // FIXME HHH-14529 configure the BootstrapServiceRegistry to use JAXB for orm.xml mappings + super.prepareBootstrapRegistryBuilder( builder ); + } + @Rule public LoggerInspectionRule logInspection = new LoggerInspectionRule( Logger.getMessageLogger( diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/PreParsedOrmXmlTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/PreParsedOrmXmlTest.java index 04640901d8..0fa294fe03 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/PreParsedOrmXmlTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/xml/ejb3/PreParsedOrmXmlTest.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.io.UncheckedIOException; import org.hibernate.boot.jaxb.spi.Binding; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.cfg.Configuration; import org.hibernate.testing.TestForIssue; @@ -19,9 +20,15 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -@TestForIssue(jiraKey = "HHH-14530") +@TestForIssue(jiraKey = {"HHH-14530", "HHH-14529"}) public class PreParsedOrmXmlTest extends BaseCoreFunctionalTestCase { + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + // FIXME HHH-14529 configure the BootstrapServiceRegistry to use JAXB for orm.xml mappings + super.prepareBootstrapRegistryBuilder( builder ); + } + @Override protected void addMappings(Configuration configuration) { super.addMappings( configuration );