package org.hibernate.engine.internal;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.hibernate.AssertionFailure;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryExtraState;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.pretty.MessageHelper;
public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
protected final Serializable id;
protected Object[] loadedState;
protected Object version;
protected final EntityPersister persister;
protected transient EntityKey cachedEntityKey;
protected final transient Object rowId;
protected final transient PersistenceContext persistenceContext;
protected EntityEntryExtraState next;
private transient int compressedState;
@Deprecated
public AbstractEntityEntry(
final Status status,
final Object[] loadedState,
final Object rowId,
final Serializable id,
final Object version,
final LockMode lockMode,
final boolean existsInDatabase,
final EntityPersister persister,
final EntityMode entityMode,
final String tenantId,
final boolean disableVersionIncrement,
final PersistenceContext persistenceContext) {
this( status, loadedState, rowId, id, version, lockMode, existsInDatabase,
persister,disableVersionIncrement, persistenceContext
);
}
public AbstractEntityEntry(
final Status status,
final Object[] loadedState,
final Object rowId,
final Serializable id,
final Object version,
final LockMode lockMode,
final boolean existsInDatabase,
final EntityPersister persister,
final boolean disableVersionIncrement,
final PersistenceContext persistenceContext) {
setCompressedValue( EnumState.STATUS, status );
setCompressedValue( EnumState.PREVIOUS_STATUS, null );
if ( status != Status.READ_ONLY ) {
this.loadedState = loadedState;
}
this.id=id;
this.rowId=rowId;
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
this.version=version;
setCompressedValue( EnumState.LOCK_MODE, lockMode );
setCompressedValue( BooleanState.IS_BEING_REPLICATED, disableVersionIncrement );
this.persister=persister;
this.persistenceContext = persistenceContext;
}
@SuppressWarnings( {"JavaDoc"})
protected AbstractEntityEntry(
final SessionFactoryImplementor factory,
final String entityName,
final Serializable id,
final Status status,
final Status previousStatus,
final Object[] loadedState,
final Object[] deletedState,
final Object version,
final LockMode lockMode,
final boolean existsInDatabase,
final boolean isBeingReplicated,
final PersistenceContext persistenceContext) {
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
this.id = id;
setCompressedValue( EnumState.STATUS, status );
setCompressedValue( EnumState.PREVIOUS_STATUS, previousStatus );
this.loadedState = loadedState;
setDeletedState( deletedState );
this.version = version;
setCompressedValue( EnumState.LOCK_MODE, lockMode );
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, existsInDatabase );
setCompressedValue( BooleanState.IS_BEING_REPLICATED, isBeingReplicated );
this.rowId = null;
this.persistenceContext = persistenceContext;
}
@Override
public LockMode getLockMode() {
return getCompressedValue( EnumState.LOCK_MODE );
}
@Override
public void setLockMode(LockMode lockMode) {
setCompressedValue( EnumState.LOCK_MODE, lockMode );
}
@Override
public Status getStatus() {
return getCompressedValue( EnumState.STATUS );
}
private Status getPreviousStatus() {
return getCompressedValue( EnumState.PREVIOUS_STATUS );
}
@Override
public void setStatus(Status status) {
if ( status == Status.READ_ONLY ) {
loadedState = null;
}
final Status currentStatus = this.getStatus();
if ( currentStatus != status ) {
setCompressedValue( EnumState.PREVIOUS_STATUS, currentStatus );
setCompressedValue( EnumState.STATUS, status );
}
}
@Override
public Serializable getId() {
return id;
}
@Override
public Object[] getLoadedState() {
return loadedState;
}
private static final Object[] DEFAULT_DELETED_STATE = null;
@Override
public Object[] getDeletedState() {
final EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
return extra != null ? extra.getDeletedState() : DEFAULT_DELETED_STATE;
}
@Override
public void setDeletedState(Object[] deletedState) {
EntityEntryExtraStateHolder extra = getExtraState( EntityEntryExtraStateHolder.class );
if ( extra == null && deletedState == DEFAULT_DELETED_STATE ) {
return;
}
if ( extra == null ) {
extra = new EntityEntryExtraStateHolder();
addExtraState( extra );
}
extra.setDeletedState( deletedState );
}
@Override
public boolean isExistsInDatabase() {
return getCompressedValue( BooleanState.EXISTS_IN_DATABASE );
}
@Override
public Object getVersion() {
return version;
}
@Override
public EntityPersister getPersister() {
return persister;
}
@Override
public EntityKey getEntityKey() {
if ( cachedEntityKey == null ) {
if ( getId() == null ) {
throw new IllegalStateException( "cannot generate an EntityKey when id is null.");
}
cachedEntityKey = new EntityKey( getId(), getPersister() );
}
return cachedEntityKey;
}
@Override
public String getEntityName() {
return persister == null ? null : persister.getEntityName();
}
@Override
public boolean isBeingReplicated() {
return getCompressedValue( BooleanState.IS_BEING_REPLICATED );
}
@Override
public Object getRowId() {
return rowId;
}
@Override
public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) {
this.loadedState = updatedState;
setLockMode( LockMode.WRITE );
if ( getPersister().isVersioned() ) {
this.version = nextVersion;
getPersister().setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
}
if( entity instanceof SelfDirtinessTracker ) {
( (SelfDirtinessTracker) entity ).$$_hibernate_clearDirtyAttributes();
}
getPersistenceContext().getSession()
.getFactory()
.getCustomEntityDirtinessStrategy()
.resetDirty( entity, getPersister(), (Session) getPersistenceContext().getSession() );
}
@Override
public void postDelete() {
setCompressedValue( EnumState.PREVIOUS_STATUS, getStatus() );
setCompressedValue( EnumState.STATUS, Status.GONE );
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, false );
}
@Override
public void postInsert(Object[] insertedState) {
setCompressedValue( BooleanState.EXISTS_IN_DATABASE, true );
}
@Override
public boolean isNullifiable(boolean earlyInsert, SharedSessionContractImplementor session) {
if ( getStatus() == Status.SAVING ) {
return true;
}
else if ( earlyInsert ) {
return !isExistsInDatabase();
}
else {
return session.getPersistenceContext().getNullifiableEntityKeys().contains( getEntityKey() );
}
}
@Override
public Object getLoadedValue(String propertyName) {
if ( loadedState == null || propertyName == null ) {
return null;
}
else {
final int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
return loadedState[propertyIndex];
}
}
@Override
public void overwriteLoadedStateCollectionValue(String propertyName, PersistentCollection collection) {
if ( getStatus() != Status.READ_ONLY ) {
assert propertyName != null;
assert loadedState != null;
final int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
loadedState[propertyIndex] = collection;
}
}
@Override
public boolean requiresDirtyCheck(Object entity) {
return isModifiableEntity()
&& ( !isUnequivocallyNonDirty( entity ) );
}
@SuppressWarnings( {"SimplifiableIfStatement"})
private boolean isUnequivocallyNonDirty(Object entity) {
if ( entity instanceof SelfDirtinessTracker ) {
return ! persister.hasCollections() && ! ( (SelfDirtinessTracker) entity ).$$_hibernate_hasDirtyAttributes();
}
final CustomEntityDirtinessStrategy customEntityDirtinessStrategy =
getPersistenceContext().getSession().getFactory().getCustomEntityDirtinessStrategy();
if ( customEntityDirtinessStrategy.canDirtyCheck( entity, getPersister(), (Session) getPersistenceContext().getSession() ) ) {
return ! customEntityDirtinessStrategy.isDirty( entity, getPersister(), (Session) getPersistenceContext().getSession() );
}
if ( getPersister().hasMutableProperties() ) {
return false;
}
return false;
}
@Override
public boolean isModifiableEntity() {
final Status status = getStatus();
final Status previousStatus = getPreviousStatus();
return getPersister().isMutable()
&& status != Status.READ_ONLY
&& ! ( status == Status.DELETED && previousStatus == Status.READ_ONLY );
}
@Override
public void forceLocked(Object entity, Object nextVersion) {
version = nextVersion;
loadedState[ persister.getVersionProperty() ] = version;
setLockMode( LockMode.FORCE );
persister.setPropertyValue( entity, getPersister().getVersionProperty(), nextVersion );
}
@Override
public boolean isReadOnly() {
final Status status = getStatus();
if (status != Status.MANAGED && status != Status.READ_ONLY) {
throw new HibernateException("instance was not in a valid state");
}
return status == Status.READ_ONLY;
}
@Override
public void setReadOnly(boolean readOnly, Object entity) {
if ( readOnly == isReadOnly() ) {
return;
}
if ( readOnly ) {
setStatus( Status.READ_ONLY );
loadedState = null;
}
else {
if ( ! persister.isMutable() ) {
throw new IllegalStateException( "Cannot make an immutable entity modifiable." );
}
setStatus( Status.MANAGED );
loadedState = getPersister().getPropertyValues( entity );
getPersistenceContext().getNaturalIdHelper().manageLocalNaturalIdCrossReference(
persister,
id,
loadedState,
null,
CachedNaturalIdValueSource.LOAD
);
}
}
@Override
public String toString() {
return "EntityEntry" +
MessageHelper.infoString( getPersister().getEntityName(), id ) +
'(' + getStatus() + ')';
}
@Override
public void serialize(ObjectOutputStream oos) throws IOException {
final Status previousStatus = getPreviousStatus();
oos.writeObject( getEntityName() );
oos.writeObject( id );
oos.writeObject( getStatus().name() );
oos.writeObject( (previousStatus == null ? "" : previousStatus.name()) );
oos.writeObject( loadedState );
oos.writeObject( getDeletedState() );
oos.writeObject( version );
oos.writeObject( getLockMode().toString() );
oos.writeBoolean( isExistsInDatabase() );
oos.writeBoolean( isBeingReplicated() );
}
@Override
public void (EntityEntryExtraState extraState) {
if ( next == null ) {
next = extraState;
}
else {
next.addExtraState( extraState );
}
}
@Override
public <T extends EntityEntryExtraState> T (Class<T> extraStateType) {
if ( next == null ) {
return null;
}
if ( extraStateType.isAssignableFrom( next.getClass() ) ) {
return (T) next;
}
else {
return next.getExtraState( extraStateType );
}
}
public PersistenceContext getPersistenceContext(){
return persistenceContext;
}
protected <E extends Enum<E>> void setCompressedValue(EnumState<E> state, E value) {
compressedState &= state.getUnsetMask();
compressedState |= ( state.getValue( value ) << state.getOffset() );
}
protected <E extends Enum<E>> E getCompressedValue(EnumState<E> state) {
final int index = ( ( compressedState & state.getMask() ) >> state.getOffset() ) - 1;
return index == - 1 ? null : state.getEnumConstants()[index];
}
protected void setCompressedValue(BooleanState state, boolean value) {
compressedState &= state.getUnsetMask();
compressedState |= ( state.getValue( value ) << state.getOffset() );
}
protected boolean getCompressedValue(BooleanState state) {
return ( ( compressedState & state.getMask() ) >> state.getOffset() ) == 1;
}
protected static class EnumState<E extends Enum<E>> {
protected static final EnumState<LockMode> LOCK_MODE = new EnumState<LockMode>( 0, LockMode.class );
protected static final EnumState<Status> STATUS = new EnumState<Status>( 4, Status.class );
protected static final EnumState<Status> PREVIOUS_STATUS = new EnumState<Status>( 8, Status.class );
protected final int offset;
protected final E[] enumConstants;
protected final int mask;
protected final int unsetMask;
private EnumState(int offset, Class<E> enumType) {
final E[] enumConstants = enumType.getEnumConstants();
if ( enumConstants.length > 15 ) {
throw new AssertionFailure( "Cannot store enum type " + enumType.getName() + " in compressed state as"
+ " it has too many values." );
}
this.offset = offset;
this.enumConstants = enumConstants;
this.mask = 0xF << offset;
this.unsetMask = 0xFFFF & ~mask;
}
private int getValue(E value) {
return value != null ? value.ordinal() + 1 : 0;
}
private int getOffset() {
return offset;
}
private int getMask() {
return mask;
}
private int getUnsetMask() {
return unsetMask;
}
private E[] getEnumConstants() {
return enumConstants;
}
}
protected enum BooleanState {
EXISTS_IN_DATABASE(13),
IS_BEING_REPLICATED(14);
private final int offset;
private final int mask;
private final int unsetMask;
private BooleanState(int offset) {
this.offset = offset;
this.mask = 0x1 << offset;
this.unsetMask = 0xFFFF & ~mask;
}
private int getValue(boolean value) {
return value ? 1 : 0;
}
private int getOffset() {
return offset;
}
private int getMask() {
return mask;
}
private int getUnsetMask() {
return unsetMask;
}
}
}