package org.hibernate.action.internal;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.hibernate.PropertyValueException;
import org.hibernate.TransientPropertyValueException;
import org.hibernate.engine.internal.NonNullableTransientDependencies;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.pretty.MessageHelper;
import org.jboss.logging.Logger;
public class UnresolvedEntityInsertActions {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
UnresolvedEntityInsertActions.class.getName()
);
private static final int INIT_SIZE = 5;
private final Map<AbstractEntityInsertAction,NonNullableTransientDependencies> dependenciesByAction = new IdentityHashMap<>( INIT_SIZE );
private final Map<Object,Set<AbstractEntityInsertAction>> dependentActionsByTransientEntity = new IdentityHashMap<>( INIT_SIZE );
public void addUnresolvedEntityInsertAction(AbstractEntityInsertAction insert, NonNullableTransientDependencies dependencies) {
if ( dependencies == null || dependencies.isEmpty() ) {
throw new IllegalArgumentException(
"Attempt to add an unresolved insert action that has no non-nullable transient entities."
);
}
if ( LOG.isTraceEnabled() ) {
LOG.tracev(
"Adding insert with non-nullable, transient entities; insert=[{0}], dependencies=[{1}]",
insert,
dependencies.toLoggableString( insert.getSession() )
);
}
dependenciesByAction.put( insert, dependencies );
addDependenciesByTransientEntity( insert, dependencies );
}
public Iterable<AbstractEntityInsertAction> getDependentEntityInsertActions() {
return dependenciesByAction.keySet();
}
public void checkNoUnresolvedActionsAfterOperation() throws PropertyValueException {
if ( isEmpty() ) {
LOG.trace( "No entity insert actions have non-nullable, transient entity dependencies." );
}
else {
final AbstractEntityInsertAction firstDependentAction =
dependenciesByAction.keySet().iterator().next();
logCannotResolveNonNullableTransientDependencies( firstDependentAction.getSession() );
final NonNullableTransientDependencies nonNullableTransientDependencies =
dependenciesByAction.get( firstDependentAction );
final Object firstTransientDependency =
nonNullableTransientDependencies.getNonNullableTransientEntities().iterator().next();
final String firstPropertyPath =
nonNullableTransientDependencies.getNonNullableTransientPropertyPaths( firstTransientDependency ).iterator().next();
throw new TransientPropertyValueException(
"Not-null property references a transient value - transient instance must be saved before current operation",
firstDependentAction.getSession().guessEntityName( firstTransientDependency ),
firstDependentAction.getEntityName(),
firstPropertyPath
);
}
}
private void logCannotResolveNonNullableTransientDependencies(SharedSessionContractImplementor session) {
for ( Map.Entry<Object,Set<AbstractEntityInsertAction>> entry : dependentActionsByTransientEntity.entrySet() ) {
final Object transientEntity = entry.getKey();
final String transientEntityName = session.guessEntityName( transientEntity );
final Serializable transientEntityId = session.getFactory().getMetamodel().entityPersister( transientEntityName ).getIdentifier( transientEntity, session );
final String transientEntityString = MessageHelper.infoString( transientEntityName, transientEntityId );
final Set<String> dependentEntityStrings = new TreeSet<>();
final Set<String> nonNullableTransientPropertyPaths = new TreeSet<>();
for ( AbstractEntityInsertAction dependentAction : entry.getValue() ) {
dependentEntityStrings.add( MessageHelper.infoString( dependentAction.getEntityName(), dependentAction.getId() ) );
for ( String path : dependenciesByAction.get( dependentAction ).getNonNullableTransientPropertyPaths( transientEntity ) ) {
final String fullPath = dependentAction.getEntityName() + '.' + path;
nonNullableTransientPropertyPaths.add( fullPath );
}
}
LOG.cannotResolveNonNullableTransientDependencies(
transientEntityString,
dependentEntityStrings,
nonNullableTransientPropertyPaths
);
}
}
public boolean isEmpty() {
return dependenciesByAction.isEmpty();
}
@SuppressWarnings({ "unchecked" })
private void addDependenciesByTransientEntity(AbstractEntityInsertAction insert, NonNullableTransientDependencies dependencies) {
for ( Object transientEntity : dependencies.getNonNullableTransientEntities() ) {
Set<AbstractEntityInsertAction> dependentActions = dependentActionsByTransientEntity.get( transientEntity );
if ( dependentActions == null ) {
dependentActions = new IdentitySet();
dependentActionsByTransientEntity.put( transientEntity, dependentActions );
}
dependentActions.add( insert );
}
}
@SuppressWarnings({ "unchecked" })
public Set<AbstractEntityInsertAction> resolveDependentActions(Object managedEntity, SessionImplementor session) {
final EntityEntry entityEntry = session.getPersistenceContext().getEntry( managedEntity );
if ( entityEntry.getStatus() != Status.MANAGED && entityEntry.getStatus() != Status.READ_ONLY ) {
throw new IllegalArgumentException( "EntityEntry did not have status MANAGED or READ_ONLY: " + entityEntry );
}
final boolean traceEnabled = LOG.isTraceEnabled();
final Set<AbstractEntityInsertAction> dependentActions = dependentActionsByTransientEntity.remove( managedEntity );
if ( dependentActions == null ) {
if ( traceEnabled ) {
LOG.tracev(
"No unresolved entity inserts that depended on [{0}]",
MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() )
);
}
return Collections.emptySet();
}
final Set<AbstractEntityInsertAction> resolvedActions = new IdentitySet( );
if ( traceEnabled ) {
LOG.tracev(
"Unresolved inserts before resolving [{0}]: [{1}]",
MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() ),
toString()
);
}
for ( AbstractEntityInsertAction dependentAction : dependentActions ) {
if ( traceEnabled ) {
LOG.tracev(
"Resolving insert [{0}] dependency on [{1}]",
MessageHelper.infoString( dependentAction.getEntityName(), dependentAction.getId() ),
MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() )
);
}
final NonNullableTransientDependencies dependencies = dependenciesByAction.get( dependentAction );
dependencies.resolveNonNullableTransientEntity( managedEntity );
if ( dependencies.isEmpty() ) {
if ( traceEnabled ) {
LOG.tracev(
"Resolving insert [{0}] (only depended on [{1}])",
dependentAction,
MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() )
);
}
dependenciesByAction.remove( dependentAction );
resolvedActions.add( dependentAction );
}
}
if ( traceEnabled ) {
LOG.tracev(
"Unresolved inserts after resolving [{0}]: [{1}]",
MessageHelper.infoString( entityEntry.getEntityName(), entityEntry.getId() ),
toString()
);
}
return resolvedActions;
}
public void clear() {
dependenciesByAction.clear();
dependentActionsByTransientEntity.clear();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder( getClass().getSimpleName() )
.append( '[' );
for ( Map.Entry<AbstractEntityInsertAction,NonNullableTransientDependencies> entry : dependenciesByAction.entrySet() ) {
final AbstractEntityInsertAction insert = entry.getKey();
final NonNullableTransientDependencies dependencies = entry.getValue();
sb.append( "[insert=" )
.append( insert )
.append( " dependencies=[" )
.append( dependencies.toLoggableString( insert.getSession() ) )
.append( "]" );
}
sb.append( ']');
return sb.toString();
}
public void serialize(ObjectOutputStream oos) throws IOException {
final int queueSize = dependenciesByAction.size();
LOG.tracev( "Starting serialization of [{0}] unresolved insert entries", queueSize );
oos.writeInt( queueSize );
for ( AbstractEntityInsertAction unresolvedAction : dependenciesByAction.keySet() ) {
oos.writeObject( unresolvedAction );
}
}
public static UnresolvedEntityInsertActions deserialize(
ObjectInputStream ois,
SessionImplementor session) throws IOException, ClassNotFoundException {
final UnresolvedEntityInsertActions rtn = new UnresolvedEntityInsertActions();
final int queueSize = ois.readInt();
LOG.tracev( "Starting deserialization of [{0}] unresolved insert entries", queueSize );
for ( int i = 0; i < queueSize; i++ ) {
final AbstractEntityInsertAction unresolvedAction = (AbstractEntityInsertAction) ois.readObject();
unresolvedAction.afterDeserialize( session );
rtn.addUnresolvedEntityInsertAction(
unresolvedAction,
unresolvedAction.findNonNullableTransientEntities()
);
}
return rtn;
}
}