package org.hibernate.cfg.annotations;
import java.lang.annotation.Annotation;
import java.util.Map;
import javax.persistence.EmbeddedId;
import javax.persistence.Id;
import javax.persistence.Lob;
import org.hibernate.AnnotationException;
import org.hibernate.HibernateException;
import org.hibernate.annotations.AttributeAccessor;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.OptimisticLock;
import org.hibernate.annotations.ValueGenerationType;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.AccessType;
import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.cfg.BinderHelper;
import org.hibernate.cfg.Ejb3Column;
import org.hibernate.cfg.InheritanceState;
import org.hibernate.cfg.PropertyHolder;
import org.hibernate.cfg.PropertyPreloadedData;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.tuple.AnnotationValueGeneration;
import org.hibernate.tuple.GenerationTiming;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.ValueGenerator;
import org.jboss.logging.Logger;
public class PropertyBinder {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, PropertyBinder.class.getName());
private MetadataBuildingContext buildingContext;
private String name;
private String returnedClassName;
private boolean lazy;
private String lazyGroup;
private AccessType accessType;
private Ejb3Column[] columns;
private PropertyHolder holder;
private Value value;
private boolean insertable = true;
private boolean updatable = true;
private String cascade;
private SimpleValueBinder simpleValueBinder;
private XClass declaringClass;
private boolean declaringClassSet;
private boolean embedded;
private EntityBinder entityBinder;
private boolean isXToMany;
private String referencedEntityName;
public void setReferencedEntityName(String referencedEntityName) {
this.referencedEntityName = referencedEntityName;
}
public void setEmbedded(boolean embedded) {
this.embedded = embedded;
}
public void setEntityBinder(EntityBinder entityBinder) {
this.entityBinder = entityBinder;
}
private XProperty property;
private XClass returnedClass;
private boolean isId;
private Map<XClass, InheritanceState> inheritanceStatePerClass;
private Property mappingProperty;
public void setInsertable(boolean insertable) {
this.insertable = insertable;
}
public void setUpdatable(boolean updatable) {
this.updatable = updatable;
}
public void setName(String name) {
this.name = name;
}
public void setReturnedClassName(String returnedClassName) {
this.returnedClassName = returnedClassName;
}
public void setLazy(boolean lazy) {
this.lazy = lazy;
}
public void setLazyGroup(String lazyGroup) {
this.lazyGroup = lazyGroup;
}
public void setAccessType(AccessType accessType) {
this.accessType = accessType;
}
public void setColumns(Ejb3Column[] columns) {
insertable = columns[0].isInsertable();
updatable = columns[0].isUpdatable();
this.columns = columns;
}
public void setHolder(PropertyHolder holder) {
this.holder = holder;
}
public void setValue(Value value) {
this.value = value;
}
public void setCascade(String cascadeStrategy) {
this.cascade = cascadeStrategy;
}
public void setBuildingContext(MetadataBuildingContext buildingContext) {
this.buildingContext = buildingContext;
}
public void setDeclaringClass(XClass declaringClass) {
this.declaringClass = declaringClass;
this.declaringClassSet = true;
}
private void validateBind() {
if ( property.isAnnotationPresent( Immutable.class ) ) {
throw new AnnotationException(
"@Immutable on property not allowed. " +
"Only allowed on entity level or on a collection."
);
}
if ( !declaringClassSet ) {
throw new AssertionFailure( "declaringClass has not been set before a bind" );
}
}
private void validateMake() {
}
private Property makePropertyAndValue() {
validateBind();
LOG.debugf( "MetadataSourceProcessor property %s with lazy=%s", name, lazy );
final String containerClassName = holder.getClassName();
holder.startingProperty( property );
simpleValueBinder = new SimpleValueBinder();
simpleValueBinder.setBuildingContext( buildingContext );
simpleValueBinder.setPropertyName( name );
simpleValueBinder.setReturnedClassName( returnedClassName );
simpleValueBinder.setColumns( columns );
simpleValueBinder.setPersistentClassName( containerClassName );
simpleValueBinder.setType(
property,
returnedClass,
containerClassName,
holder.resolveAttributeConverterDescriptor( property )
);
simpleValueBinder.setReferencedEntityName( referencedEntityName );
simpleValueBinder.setAccessType( accessType );
SimpleValue propertyValue = simpleValueBinder.make();
setValue( propertyValue );
return makeProperty();
}
public Property makePropertyAndBind() {
return bind( makeProperty() );
}
public Property makePropertyValueAndBind() {
return bind( makePropertyAndValue() );
}
public void setXToMany(boolean xToMany) {
this.isXToMany = xToMany;
}
private Property bind(Property prop) {
if (isId) {
final RootClass rootClass = ( RootClass ) holder.getPersistentClass();
if ( isXToMany || entityBinder.wrapIdsInEmbeddedComponents() ) {
Component identifier = (Component) rootClass.getIdentifier();
if (identifier == null) {
identifier = AnnotationBinder.createComponent(
holder,
new PropertyPreloadedData(null, null, null),
true,
false,
buildingContext
);
rootClass.setIdentifier( identifier );
identifier.setNullValue( "undefined" );
rootClass.setEmbeddedIdentifier( true );
rootClass.setIdentifierMapper( identifier );
}
identifier.addProperty( prop );
}
else {
rootClass.setIdentifier( ( KeyValue ) getValue() );
if (embedded) {
rootClass.setEmbeddedIdentifier( true );
}
else {
rootClass.setIdentifierProperty( prop );
final org.hibernate.mapping.MappedSuperclass superclass = BinderHelper.getMappedSuperclassOrNull(
declaringClass,
inheritanceStatePerClass,
buildingContext
);
if (superclass != null) {
superclass.setDeclaredIdentifierProperty(prop);
}
else {
rootClass.setDeclaredIdentifierProperty( prop );
}
}
}
}
else {
holder.addProperty( prop, columns, declaringClass );
}
return prop;
}
public Property makeProperty() {
validateMake();
LOG.debugf( "Building property %s", name );
Property prop = new Property();
prop.setName( name );
prop.setValue( value );
prop.setLazy( lazy );
prop.setLazyGroup( lazyGroup );
prop.setCascade( cascade );
prop.setPropertyAccessorName( accessType.getType() );
if ( property != null ) {
prop.setValueGenerationStrategy( determineValueGenerationStrategy( property ) );
if ( property.isAnnotationPresent( AttributeAccessor.class ) ) {
final AttributeAccessor accessor = property.getAnnotation( AttributeAccessor.class );
prop.setPropertyAccessorName( accessor.value() );
}
}
NaturalId naturalId = property != null ? property.getAnnotation( NaturalId.class ) : null;
if ( naturalId != null ) {
if ( ! entityBinder.isRootEntity() ) {
throw new AnnotationException( "@NaturalId only valid on root entity (or its @MappedSuperclasses)" );
}
if ( ! naturalId.mutable() ) {
updatable = false;
}
prop.setNaturalIdentifier( true );
}
Lob lob = property != null ? property.getAnnotation( Lob.class ) : null;
prop.setLob( lob != null );
prop.setInsertable( insertable );
prop.setUpdateable( updatable );
if ( Collection.class.isInstance( value ) ) {
prop.setOptimisticLocked( ( (Collection) value ).isOptimisticLocked() );
}
else {
final OptimisticLock lockAnn = property != null
? property.getAnnotation( OptimisticLock.class )
: null;
if ( lockAnn != null ) {
if ( lockAnn.excluded() && (
property.isAnnotationPresent( javax.persistence.Version.class )
|| property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( EmbeddedId.class ) ) ) {
throw new AnnotationException(
"@OptimisticLock.exclude=true incompatible with @Id, @EmbeddedId and @Version: "
+ StringHelper.qualify( holder.getPath(), name )
);
}
}
final boolean isOwnedValue = !isToOneValue( value ) || insertable;
final boolean includeInOptimisticLockChecks = ( lockAnn != null )
? ! lockAnn.excluded()
: isOwnedValue;
prop.setOptimisticLocked( includeInOptimisticLockChecks );
}
LOG.tracev( "Cascading {0} with {1}", name, cascade );
this.mappingProperty = prop;
return prop;
}
private ValueGeneration determineValueGenerationStrategy(XProperty property) {
ValueGeneration valueGeneration = getValueGenerationFromAnnotations( property );
if ( valueGeneration == null ) {
return NoValueGeneration.INSTANCE;
}
final GenerationTiming when = valueGeneration.getGenerationTiming();
if ( valueGeneration.getValueGenerator() == null ) {
insertable = false;
if ( when == GenerationTiming.ALWAYS ) {
updatable = false;
}
}
return valueGeneration;
}
private ValueGeneration getValueGenerationFromAnnotations(XProperty property) {
AnnotationValueGeneration<?> valueGeneration = null;
for ( Annotation annotation : property.getAnnotations() ) {
AnnotationValueGeneration<?> candidate = getValueGenerationFromAnnotation( property, annotation );
if ( candidate != null ) {
if ( valueGeneration != null ) {
throw new AnnotationException(
"Only one generator annotation is allowed:" + StringHelper.qualify(
holder.getPath(),
name
)
);
}
else {
valueGeneration = candidate;
}
}
}
return valueGeneration;
}
private <A extends Annotation> AnnotationValueGeneration<A> getValueGenerationFromAnnotation(
XProperty property,
A annotation) {
ValueGenerationType generatorAnnotation = annotation.annotationType()
.getAnnotation( ValueGenerationType.class );
if ( generatorAnnotation == null ) {
return null;
}
Class<? extends AnnotationValueGeneration<?>> generationType = generatorAnnotation.generatedBy();
AnnotationValueGeneration<A> valueGeneration = instantiateAndInitializeValueGeneration(
annotation, generationType, property
);
if ( annotation.annotationType() == Generated.class &&
property.isAnnotationPresent( javax.persistence.Version.class ) &&
valueGeneration.getGenerationTiming() == GenerationTiming.INSERT ) {
throw new AnnotationException(
"@Generated(INSERT) on a @Version property not allowed, use ALWAYS (or NEVER): "
+ StringHelper.qualify( holder.getPath(), name )
);
}
return valueGeneration;
}
private <A extends Annotation> AnnotationValueGeneration<A> instantiateAndInitializeValueGeneration(
A annotation,
Class<? extends AnnotationValueGeneration<?>> generationType,
XProperty property) {
try {
@SuppressWarnings( "unchecked" )
AnnotationValueGeneration<A> valueGeneration = (AnnotationValueGeneration<A>) generationType.newInstance();
valueGeneration.initialize(
annotation,
buildingContext.getBootstrapContext().getReflectionManager().toClass( property.getType() )
);
return valueGeneration;
}
catch (HibernateException e) {
throw e;
}
catch (Exception e) {
throw new AnnotationException(
"Exception occurred during processing of generator annotation:" + StringHelper.qualify(
holder.getPath(),
name
), e
);
}
}
private static class NoValueGeneration implements ValueGeneration {
public static final NoValueGeneration INSTANCE = new NoValueGeneration();
@Override
public GenerationTiming getGenerationTiming() {
return GenerationTiming.NEVER;
}
@Override
public ValueGenerator<?> getValueGenerator() {
return null;
}
@Override
public boolean referenceColumnInSql() {
return true;
}
@Override
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}
}
private boolean isToOneValue(Value value) {
return ToOne.class.isInstance( value );
}
public void setProperty(XProperty property) {
this.property = property;
}
public void setReturnedClass(XClass returnedClass) {
this.returnedClass = returnedClass;
}
public SimpleValueBinder getSimpleValueBinder() {
return simpleValueBinder;
}
public Value getValue() {
return value;
}
public void setId(boolean id) {
this.isId = id;
}
public void setInheritanceStatePerClass(Map<XClass, InheritanceState> inheritanceStatePerClass) {
this.inheritanceStatePerClass = inheritanceStatePerClass;
}
}