HHH-8159 - Apply fixups indicated by analysis tools

This commit is contained in:
Steve Ebersole 2013-04-26 14:10:54 -05:00
parent fc02da1c12
commit 021401835c
5 changed files with 753 additions and 443 deletions

View File

@ -47,29 +47,29 @@ import org.jboss.logging.Logger;
* @author Gavin King
*/
public final class Collections {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, Collections.class.getName());
private Collections() {
}
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
Collections.class.getName()
);
/**
* record the fact that this collection was dereferenced
*
* @param coll The collection to be updated by un-reachability.
* @param session The session
*/
@SuppressWarnings( {"JavaDoc"})
public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session) {
if ( coll.getOwner()==null ) {
processNeverReferencedCollection(coll, session);
processNeverReferencedCollection( coll, session );
}
else {
processDereferencedCollection(coll, session);
processDereferencedCollection( coll, session );
}
}
private static void processDereferencedCollection(PersistentCollection coll, SessionImplementor session) {
final PersistenceContext persistenceContext = session.getPersistenceContext();
CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
final CollectionPersister loadedPersister = entry.getLoadedPersister();
if ( loadedPersister != null && LOG.isDebugEnabled() ) {
@ -82,15 +82,15 @@ public final class Collections {
}
// do a check
boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete();
if (hasOrphanDelete) {
final boolean hasOrphanDelete = loadedPersister != null && loadedPersister.hasOrphanDelete();
if ( hasOrphanDelete ) {
Serializable ownerId = loadedPersister.getOwnerEntityPersister().getIdentifier( coll.getOwner(), session );
if ( ownerId == null ) {
// the owning entity may have been deleted and its identifier unset due to
// identifier-rollback; in which case, try to look up its identifier from
// the persistence context
if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
final EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
if ( ownerEntry != null ) {
ownerId = ownerEntry.getId();
}
@ -99,15 +99,15 @@ public final class Collections {
throw new AssertionFailure( "Unable to determine collection owner identifier for orphan-delete processing" );
}
}
EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() );
Object owner = persistenceContext.getEntity(key);
final EntityKey key = session.generateEntityKey( ownerId, loadedPersister.getOwnerEntityPersister() );
final Object owner = persistenceContext.getEntity( key );
if ( owner == null ) {
throw new AssertionFailure(
"collection owner not associated with session: " +
loadedPersister.getRole()
);
}
EntityEntry e = persistenceContext.getEntry(owner);
final EntityEntry e = persistenceContext.getEntry( owner );
//only collections belonging to deleted entities are allowed to be dereferenced in the case of orphan delete
if ( e != null && e.getStatus() != Status.DELETED && e.getStatus() != Status.GONE ) {
throw new HibernateException(
@ -118,23 +118,27 @@ public final class Collections {
}
// do the work
entry.setCurrentPersister(null);
entry.setCurrentKey(null);
entry.setCurrentPersister( null );
entry.setCurrentKey( null );
prepareCollectionForUpdate( coll, entry, session.getFactory() );
}
private static void processNeverReferencedCollection(PersistentCollection coll, SessionImplementor session)
throws HibernateException {
throws HibernateException {
final PersistenceContext persistenceContext = session.getPersistenceContext();
CollectionEntry entry = persistenceContext.getCollectionEntry(coll);
final CollectionEntry entry = persistenceContext.getCollectionEntry( coll );
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Found collection with unloaded owner: %s",
LOG.debugf(
"Found collection with unloaded owner: %s",
MessageHelper.collectionInfoString(
entry.getLoadedPersister(), coll,
entry.getLoadedKey(), session ) );
entry.getLoadedPersister(),
coll,
entry.getLoadedKey(),
session
)
);
}
entry.setCurrentPersister( entry.getLoadedPersister() );
@ -154,13 +158,11 @@ public final class Collections {
*/
public static void processReachableCollection(
PersistentCollection collection,
CollectionType type,
Object entity,
SessionImplementor session) {
collection.setOwner(entity);
CollectionEntry ce = session.getPersistenceContext().getCollectionEntry(collection);
CollectionType type,
Object entity,
SessionImplementor session) {
collection.setOwner( entity );
final CollectionEntry ce = session.getPersistenceContext().getCollectionEntry( collection );
if ( ce == null ) {
// refer to comment in StatefulPersistenceContext.addCollection()
@ -175,30 +177,35 @@ public final class Collections {
if ( ce.isReached() ) {
// We've been here before
throw new HibernateException(
"Found shared references to a collection: " +
type.getRole()
"Found shared references to a collection: " + type.getRole()
);
}
ce.setReached(true);
ce.setReached( true );
SessionFactoryImplementor factory = session.getFactory();
CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
ce.setCurrentPersister(persister);
ce.setCurrentKey( type.getKeyOfOwner(entity, session) ); //TODO: better to pass the id in as an argument?
final SessionFactoryImplementor factory = session.getFactory();
final CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
ce.setCurrentPersister( persister );
//TODO: better to pass the id in as an argument?
ce.setCurrentKey( type.getKeyOfOwner( entity, session ) );
if (LOG.isDebugEnabled()) {
if (collection.wasInitialized()) LOG.debugf("Collection found: %s, was: %s (initialized)",
MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session),
MessageHelper.collectionInfoString(ce.getLoadedPersister(), collection,
ce.getLoadedKey(),
session));
else LOG.debugf("Collection found: %s, was: %s (uninitialized)",
MessageHelper.collectionInfoString(persister, collection, ce.getCurrentKey(), session),
MessageHelper.collectionInfoString(ce.getLoadedPersister(), collection, ce.getLoadedKey(), session));
}
if ( LOG.isDebugEnabled() ) {
if ( collection.wasInitialized() ) {
LOG.debugf(
"Collection found: %s, was: %s (initialized)",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
);
}
else {
LOG.debugf(
"Collection found: %s, was: %s (uninitialized)",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
);
}
}
prepareCollectionForUpdate( collection, ce, factory );
}
/**
@ -209,9 +216,8 @@ public final class Collections {
@SuppressWarnings( {"JavaDoc"})
private static void prepareCollectionForUpdate(
PersistentCollection collection,
CollectionEntry entry,
SessionFactoryImplementor factory) {
CollectionEntry entry,
SessionFactoryImplementor factory) {
if ( entry.isProcessed() ) {
throw new AssertionFailure( "collection was processed twice by flush()" );
}
@ -219,37 +225,33 @@ public final class Collections {
final CollectionPersister loadedPersister = entry.getLoadedPersister();
final CollectionPersister currentPersister = entry.getCurrentPersister();
if ( loadedPersister != null || currentPersister != null ) { // it is or was referenced _somewhere_
if ( loadedPersister != null || currentPersister != null ) {
// it is or was referenced _somewhere_
boolean ownerChanged = loadedPersister != currentPersister || // if either its role changed,
!currentPersister
.getKeyType().isEqual( // or its key changed
entry.getLoadedKey(),
entry.getCurrentKey(),
factory
);
if (ownerChanged) {
// if either its role changed, or its key changed
final boolean ownerChanged = loadedPersister != currentPersister
|| !currentPersister.getKeyType().isEqual( entry.getLoadedKey(), entry.getCurrentKey(), factory );
if ( ownerChanged ) {
// do a check
final boolean orphanDeleteAndRoleChanged = loadedPersister != null &&
currentPersister != null &&
loadedPersister.hasOrphanDelete();
final boolean orphanDeleteAndRoleChanged =
loadedPersister != null && currentPersister != null && loadedPersister.hasOrphanDelete();
if (orphanDeleteAndRoleChanged) {
throw new HibernateException(
"Don't change the reference to a collection with cascade=\"all-delete-orphan\": " +
loadedPersister.getRole()
"Don't change the reference to a collection with delete-orphan enabled : "
+ loadedPersister.getRole()
);
}
// do the work
if ( currentPersister != null ) {
entry.setDorecreate( true ); // we will need to create new entries
entry.setDorecreate( true );
}
if ( loadedPersister != null ) {
entry.setDoremove( true ); // we will need to remove ye olde entries
// we will need to remove ye olde entries
entry.setDoremove( true );
if ( entry.isDorecreate() ) {
LOG.trace( "Forcing collection initialization" );
collection.forceInitialization();
@ -260,8 +262,12 @@ public final class Collections {
// the collection's elements have changed
entry.setDoupdate( true );
}
}
}
/**
* Disallow instantiation
*/
private Collections() {
}
}

View File

@ -31,7 +31,6 @@ import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.AssertionFailure;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.ManagedEntity;
@ -56,22 +55,31 @@ public class EntityEntryContext {
private transient ManagedEntity head;
private transient ManagedEntity tail;
private transient int count = 0;
private transient int count;
private transient IdentityHashMap<Object,ManagedEntity> nonEnhancedEntityXref;
@SuppressWarnings( {"unchecked"})
private transient Map.Entry<Object,EntityEntry>[] reentrantSafeEntries = new Map.Entry[0];
private transient boolean dirty = false;
private transient boolean dirty;
/**
* Constructs a EntityEntryContext
*/
public EntityEntryContext() {
}
/**
* Adds the entity and entry to this context, associating them together
*
* @param entity The entity
* @param entityEntry The entry
*/
public void addEntityEntry(Object entity, EntityEntry entityEntry) {
// IMPORTANT!!!!!
// add is called more than once of some entities. In such cases the first
// call is simply setting up a "marker" to avoid infinite looping from reentrancy
//
// any addition (even the double one described above) should invalidate the cross-ref array
dirty = true;
@ -127,11 +135,26 @@ public class EntityEntryContext {
}
}
/**
* Does this entity exist in this context, associated with an EntityEntry?
*
* @param entity The entity to check
*
* @return {@code true} if it is associated with this context
*/
public boolean hasEntityEntry(Object entity) {
return getEntityEntry( entity ) != null;
}
/**
* Retrieve the associated EntityEntry for the entity
*
* @param entity The entity to retrieve the EntityEntry for
*
* @return The associated EntityEntry
*/
public EntityEntry getEntityEntry(Object entity) {
// essentially resolve the entity to a ManagedEntity...
final ManagedEntity managedEntity;
if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity;
@ -143,14 +166,23 @@ public class EntityEntryContext {
managedEntity = nonEnhancedEntityXref.get( entity );
}
// and get/return the EntityEntry from the ManagedEntry
return managedEntity == null
? null
: managedEntity.$$_hibernate_getEntityEntry();
}
/**
* Remove an entity from the context, returning the EntityEntry which was associated with it
*
* @param entity The entity to remove
*
* @return Tjee EntityEntry
*/
public EntityEntry removeEntityEntry(Object entity) {
dirty = true;
// again, resolve the entity to a ManagedEntity (which may not be possible for non-enhanced)...
final ManagedEntity managedEntity;
if ( ManagedEntity.class.isInstance( entity ) ) {
managedEntity = (ManagedEntity) entity;
@ -162,16 +194,18 @@ public class EntityEntryContext {
managedEntity = nonEnhancedEntityXref.remove( entity );
}
// if we could not resolve it, just return (it was not associated with this context)
if ( managedEntity == null ) {
return null;
}
// prepare for re-linking...
ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
final ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
managedEntity.$$_hibernate_setNextManagedEntity( null );
// re-link
count--;
if ( count == 0 ) {
@ -203,11 +237,20 @@ public class EntityEntryContext {
}
}
EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
// finally clean out the ManagedEntity and return the associated EntityEntry
final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
managedEntity.$$_hibernate_setEntityEntry( null );
return theEntityEntry;
}
/**
* The main bugaboo with IdentityMap that warranted this class in the first place.
*
* Return an array of all the entity/EntityEntry pairs in this context. The array is to make sure
* that the iterators built off of it are safe from concurrency/reentrancy
*
* @return The safe array
*/
public Map.Entry<Object, EntityEntry>[] reentrantSafeEntityEntries() {
if ( dirty ) {
reentrantSafeEntries = new EntityEntryCrossRefImpl[count];
@ -225,6 +268,9 @@ public class EntityEntryContext {
return reentrantSafeEntries;
}
/**
* Clear this context of all managed entities
*/
public void clear() {
dirty = true;
@ -250,6 +296,9 @@ public class EntityEntryContext {
reentrantSafeEntries = null;
}
/**
* Down-grade locks to NONE for all entities in this context
*/
public void downgradeLocks() {
if ( head == null ) {
return;
@ -263,6 +312,13 @@ public class EntityEntryContext {
}
}
/**
* JDK serialization hook for serializing
*
* @param oos The stream to write ourselves to
*
* @throws IOException Indicates an IO exception accessing the given stream
*/
public void serialize(ObjectOutputStream oos) throws IOException {
log.tracef( "Starting serialization of [%s] EntityEntry entries", count );
oos.writeInt( count );
@ -281,7 +337,17 @@ public class EntityEntryContext {
}
}
public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn) throws IOException, ClassNotFoundException {
/**
* JDK serialization hook for deserializing
*
* @param ois The stream to read ourselves from
* @param rtn The persistence context we belong to
*
* @throws IOException Indicates an IO exception accessing the given stream
* @throws ClassNotFoundException Problem reading stream data
*/
public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPersistenceContext rtn)
throws IOException, ClassNotFoundException {
final int count = ois.readInt();
log.tracef( "Starting deserialization of [%s] EntityEntry entries", count );
@ -332,6 +398,9 @@ public class EntityEntryContext {
return count;
}
/**
* The wrapper for entity classes which do not implement ManagedEntity
*/
private static class ManagedEntityImpl implements ManagedEntity {
private final Object entityInstance;
private EntityEntry entityEntry;
@ -378,6 +447,28 @@ public class EntityEntryContext {
}
}
/**
* Used in building the {@link #reentrantSafeEntityEntries()} entries
*/
public static interface EntityEntryCrossRef extends Map.Entry<Object,EntityEntry> {
/**
* The entity
*
* @return The entity
*/
public Object getEntity();
/**
* The associated EntityEntry
*
* @return The EntityEntry associated with the entity in this context
*/
public EntityEntry getEntityEntry();
}
/**
* Implementation of the EntityEntryCrossRef interface
*/
private static class EntityEntryCrossRefImpl implements EntityEntryCrossRef {
private final Object entity;
private EntityEntry entityEntry;
@ -414,9 +505,4 @@ public class EntityEntryContext {
return old;
}
}
public static interface EntityEntryCrossRef extends Map.Entry<Object,EntityEntry> {
public Object getEntity();
public EntityEntry getEntityEntry();
}
}

View File

@ -40,70 +40,81 @@ import org.hibernate.type.Type;
/**
* Algorithms related to foreign key constraint transparency
*
*
* @author Gavin King
*/
public final class ForeignKeys {
private ForeignKeys() {}
/**
* Delegate for handling nullifying ("null"ing-out) non-cascaded associations
*/
public static class Nullifier {
private final boolean isDelete;
private final boolean isEarlyInsert;
private final SessionImplementor session;
private final Object self;
/**
* Constructs a Nullifier
*
* @param self The entity
* @param isDelete Are we in the middle of a delete action?
* @param isEarlyInsert Is this an early insert (INSERT generated id strategy)?
* @param session The session
*/
public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SessionImplementor session) {
this.isDelete = isDelete;
this.isEarlyInsert = isEarlyInsert;
this.session = session;
this.self = self;
}
/**
* Nullify all references to entities that have not yet
* been inserted in the database, where the foreign key
* points toward that entity
* Nullify all references to entities that have not yet been inserted in the database, where the foreign key
* points toward that entity.
*
* @param values The entity attribute values
* @param types The entity attribute types
*/
public void nullifyTransientReferences(final Object[] values, final Type[] types)
throws HibernateException {
public void nullifyTransientReferences(final Object[] values, final Type[] types)
throws HibernateException {
for ( int i = 0; i < types.length; i++ ) {
values[i] = nullifyTransientReferences( values[i], types[i] );
}
}
/**
* Return null if the argument is an "unsaved" entity (ie.
* one with no existing database row), or the input argument
* otherwise. This is how Hibernate avoids foreign key constraint
* violations.
* Return null if the argument is an "unsaved" entity (ie. one with no existing database row), or the
* input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
*
* @param value An entity attribute value
* @param type An entity attribute type
*/
private Object nullifyTransientReferences(final Object value, final Type type)
throws HibernateException {
private Object nullifyTransientReferences(final Object value, final Type type)
throws HibernateException {
if ( value == null ) {
return null;
}
else if ( type.isEntityType() ) {
EntityType entityType = (EntityType) type;
final EntityType entityType = (EntityType) type;
if ( entityType.isOneToOne() ) {
return value;
}
else {
String entityName = entityType.getAssociatedEntityName();
return isNullifiable(entityName, value) ? null : value;
final String entityName = entityType.getAssociatedEntityName();
return isNullifiable( entityName, value ) ? null : value;
}
}
else if ( type.isAnyType() ) {
return isNullifiable(null, value) ? null : value;
return isNullifiable( null, value ) ? null : value;
}
else if ( type.isComponentType() ) {
CompositeType actype = (CompositeType) type;
Object[] subvalues = actype.getPropertyValues(value, session);
Type[] subtypes = actype.getSubtypes();
final CompositeType actype = (CompositeType) type;
final Object[] subvalues = actype.getPropertyValues( value, session );
final Type[] subtypes = actype.getSubtypes();
boolean substitute = false;
for ( int i = 0; i < subvalues.length; i++ ) {
Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
if ( replacement != subvalues[i] ) {
substitute = true;
subvalues[i] = replacement;
@ -119,20 +130,25 @@ public final class ForeignKeys {
return value;
}
}
/**
* Determine if the object already exists in the database,
* Determine if the object already exists in the database,
* using a "best guess"
*
* @param entityName The name of the entity
* @param object The entity instance
*/
private boolean isNullifiable(final String entityName, Object object)
throws HibernateException {
if (object==LazyPropertyInitializer.UNFETCHED_PROPERTY) return false; //this is kinda the best we can do...
private boolean isNullifiable(final String entityName, Object object)
throws HibernateException {
if ( object == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
// this is the best we can do...
return false;
}
if ( object instanceof HibernateProxy ) {
// if its an uninitialized proxy it can't be transient
LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
if ( li.getImplementation(session) == null ) {
final LazyInitializer li = ( (HibernateProxy) object ).getHibernateLazyInitializer();
if ( li.getImplementation( session ) == null ) {
return false;
// ie. we never have to null out a reference to
// an uninitialized proxy
@ -142,100 +158,130 @@ public final class ForeignKeys {
object = li.getImplementation();
}
}
// if it was a reference to self, don't need to nullify
// unless we are using native id generation, in which
// case we definitely need to nullify
if ( object == self ) {
return isEarlyInsert || (
isDelete &&
session.getFactory()
.getDialect()
.hasSelfReferentialForeignKeyBug()
);
return isEarlyInsert
|| ( isDelete && session.getFactory().getDialect().hasSelfReferentialForeignKeyBug() );
}
// See if the entity is already bound to this session, if not look at the
// entity identifier and assume that the entity is persistent if the
// id is not "unsaved" (that is, we rely on foreign keys to keep
// database integrity)
EntityEntry entityEntry = session.getPersistenceContext().getEntry(object);
if ( entityEntry==null ) {
return isTransient(entityName, object, null, session);
final EntityEntry entityEntry = session.getPersistenceContext().getEntry( object );
if ( entityEntry == null ) {
return isTransient( entityName, object, null, session );
}
else {
return entityEntry.isNullifiable(isEarlyInsert, session);
return entityEntry.isNullifiable( isEarlyInsert, session );
}
}
}
/**
* Is this instance persistent or detached?
* If <tt>assumed</tt> is non-null, don't hit the database to make the
* determination, instead assume that value; the client code must be
* prepared to "recover" in the case that this assumed result is incorrect.
* <p/>
* If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
* value; the client code must be prepared to "recover" in the case that this assumed result is incorrect.
*
* @param entityName The name of the entity
* @param entity The entity instance
* @param assumed The assumed return value, if avoiding database hit is desired
* @param session The session
*
* @return {@code true} if the given entity is not transient (meaning it is either detached/persistent)
*/
public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
throws HibernateException {
if (entity instanceof HibernateProxy) return true;
if ( session.getPersistenceContext().isEntryFor(entity) ) return true;
return !isTransient(entityName, entity, assumed, session);
@SuppressWarnings("SimplifiableIfStatement")
public static boolean isNotTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
if ( entity instanceof HibernateProxy ) {
return true;
}
if ( session.getPersistenceContext().isEntryFor( entity ) ) {
return true;
}
// todo : shouldnt assumed be revered here?
return !isTransient( entityName, entity, assumed, session );
}
/**
* Is this instance, which we know is not persistent, actually transient?
* If <tt>assumed</tt> is non-null, don't hit the database to make the
* determination, instead assume that value; the client code must be
* prepared to "recover" in the case that this assumed result is incorrect.
* <p/>
* If <tt>assumed</tt> is non-null, don't hit the database to make the determination, instead assume that
* value; the client code must be prepared to "recover" in the case that this assumed result is incorrect.
*
* @param entityName The name of the entity
* @param entity The entity instance
* @param assumed The assumed return value, if avoiding database hit is desired
* @param session The session
*
* @return {@code true} if the given entity is transient (unsaved)
*/
public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session)
throws HibernateException {
if (entity== LazyPropertyInitializer.UNFETCHED_PROPERTY) {
@SuppressWarnings("UnnecessaryUnboxing")
public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
// an unfetched association can only point to
// an entity that already exists in the db
return false;
}
// let the interceptor inspect the instance to decide
Boolean isUnsaved = session.getInterceptor().isTransient(entity);
if (isUnsaved!=null) return isUnsaved.booleanValue();
Boolean isUnsaved = session.getInterceptor().isTransient( entity );
if ( isUnsaved != null ) {
return isUnsaved.booleanValue();
}
// let the persister inspect the instance to decide
EntityPersister persister = session.getEntityPersister(entityName, entity);
isUnsaved = persister.isTransient(entity, session);
if (isUnsaved!=null) return isUnsaved.booleanValue();
final EntityPersister persister = session.getEntityPersister( entityName, entity );
isUnsaved = persister.isTransient( entity, session );
if ( isUnsaved != null ) {
return isUnsaved.booleanValue();
}
// we use the assumed value, if there is one, to avoid hitting
// the database
if (assumed!=null) return assumed.booleanValue();
if ( assumed != null ) {
return assumed.booleanValue();
}
// hit the database, after checking the session cache for a snapshot
Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
final Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
persister.getIdentifier( entity, session ),
persister
);
return snapshot==null;
return snapshot == null;
}
/**
* Return the identifier of the persistent or transient object, or throw
* an exception if the instance is "unsaved"
*
* Used by OneToOneType and ManyToOneType to determine what id value should
* be used for an object that may or may not be associated with the session.
* This does a "best guess" using any/all info available to use (not just the
* <p/>
* Used by OneToOneType and ManyToOneType to determine what id value should
* be used for an object that may or may not be associated with the session.
* This does a "best guess" using any/all info available to use (not just the
* EntityEntry).
*
* @param entityName The name of the entity
* @param object The entity instance
* @param session The session
*
* @return The identifier
*
* @throws TransientObjectException if the entity is transient (does not yet have an identifier)
*/
public static Serializable getEntityIdentifierIfNotUnsaved(
final String entityName,
final Object object,
final SessionImplementor session)
throws HibernateException {
final String entityName,
final Object object,
final SessionImplementor session) throws TransientObjectException {
if ( object == null ) {
return null;
}
@ -245,10 +291,10 @@ public final class ForeignKeys {
// context-entity-identifier returns null explicitly if the entity
// is not associated with the persistence context; so make some
// deeper checks...
if ( isTransient(entityName, object, Boolean.FALSE, session) ) {
if ( isTransient( entityName, object, Boolean.FALSE, session ) ) {
throw new TransientObjectException(
"object references an unsaved transient instance - save the transient instance before flushing: " +
(entityName == null ? session.guessEntityName( object ) : entityName)
(entityName == null ? session.guessEntityName( object ) : entityName)
);
}
id = session.getEntityPersister( entityName, object ).getIdentifier( object, session );
@ -265,9 +311,9 @@ public final class ForeignKeys {
* @param entityName - the entity name
* @param entity - the entity instance
* @param values - insertable properties of the object (including backrefs),
* possibly with substitutions
* possibly with substitutions
* @param isEarlyInsert - true if the entity needs to be executed as soon as possible
* (e.g., to generate an ID)
* (e.g., to generate an ID)
* @param session - the session
*
* @return the transient unsaved entity dependencies that are non-nullable,
@ -278,18 +324,16 @@ public final class ForeignKeys {
Object entity,
Object[] values,
boolean isEarlyInsert,
SessionImplementor session
) {
Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
SessionImplementor session) {
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
final EntityPersister persister = session.getEntityPersister( entityName, entity );
final String[] propertyNames = persister.getPropertyNames();
final Type[] types = persister.getPropertyTypes();
final boolean[] nullability = persister.getPropertyNullability();
NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
final NonNullableTransientDependencies nonNullableTransientEntities = new NonNullableTransientDependencies();
for ( int i = 0; i < types.length; i++ ) {
collectNonNullableTransientEntities(
nullifier,
i,
values[i],
propertyNames[i],
types[i],
@ -303,7 +347,6 @@ public final class ForeignKeys {
private static void collectNonNullableTransientEntities(
Nullifier nullifier,
int i,
Object value,
String propertyName,
Type type,
@ -311,33 +354,32 @@ public final class ForeignKeys {
SessionImplementor session,
NonNullableTransientDependencies nonNullableTransientEntities) {
if ( value == null ) {
return; // EARLY RETURN
return;
}
if ( type.isEntityType() ) {
EntityType entityType = (EntityType) type;
if ( ! isNullable &&
! entityType.isOneToOne() &&
nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) {
if ( type.isEntityType() ) {
final EntityType entityType = (EntityType) type;
if ( !isNullable
&& !entityType.isOneToOne()
&& nullifier.isNullifiable( entityType.getAssociatedEntityName(), value ) ) {
nonNullableTransientEntities.add( propertyName, value );
}
}
else if ( type.isAnyType() ) {
if ( ! isNullable &&
nullifier.isNullifiable( null, value ) ) {
if ( !isNullable && nullifier.isNullifiable( null, value ) ) {
nonNullableTransientEntities.add( propertyName, value );
}
}
else if ( type.isComponentType() ) {
CompositeType actype = (CompositeType) type;
boolean[] subValueNullability = actype.getPropertyNullability();
final CompositeType actype = (CompositeType) type;
final boolean[] subValueNullability = actype.getPropertyNullability();
if ( subValueNullability != null ) {
String[] subPropertyNames = actype.getPropertyNames();
Object[] subvalues = actype.getPropertyValues(value, session);
Type[] subtypes = actype.getSubtypes();
final String[] subPropertyNames = actype.getPropertyNames();
final Object[] subvalues = actype.getPropertyValues( value, session );
final Type[] subtypes = actype.getSubtypes();
for ( int j = 0; j < subvalues.length; j++ ) {
collectNonNullableTransientEntities(
nullifier,
j,
subvalues[j],
subPropertyNames[j],
subtypes[j],
@ -349,4 +391,11 @@ public final class ForeignKeys {
}
}
}
/**
* Disallow instantiation
*/
private ForeignKeys() {
}
}

View File

@ -36,45 +36,40 @@ import org.hibernate.type.AssociationType;
* @author Gavin King
*/
public final class JoinHelper {
private JoinHelper() {}
/**
* Get the aliased columns of the owning entity which are to
* Get the aliased columns of the owning entity which are to
* be used in the join
*/
public static String[] getAliasedLHSColumnNames(
AssociationType type,
String alias,
int property,
AssociationType type,
String alias,
int property,
OuterJoinLoadable lhsPersister,
Mapping mapping
) {
return getAliasedLHSColumnNames(type, alias, property, 0, lhsPersister, mapping);
Mapping mapping) {
return getAliasedLHSColumnNames( type, alias, property, 0, lhsPersister, mapping );
}
/**
* Get the columns of the owning entity which are to
* Get the columns of the owning entity which are to
* be used in the join
*/
public static String[] getLHSColumnNames(
AssociationType type,
int property,
AssociationType type,
int property,
OuterJoinLoadable lhsPersister,
Mapping mapping
) {
return getLHSColumnNames(type, property, 0, lhsPersister, mapping);
Mapping mapping) {
return getLHSColumnNames( type, property, 0, lhsPersister, mapping );
}
/**
* Get the aliased columns of the owning entity which are to
* Get the aliased columns of the owning entity which are to
* be used in the join
*/
public static String[] getAliasedLHSColumnNames(
AssociationType associationType,
String columnQualifier,
int propertyIndex,
int begin,
int begin,
OuterJoinLoadable lhsPersister,
Mapping mapping) {
if ( associationType.useLHSPrimaryKey() ) {
@ -90,7 +85,8 @@ public final class JoinHelper {
);
}
else {
return ( (PropertyMapping) lhsPersister ).toColumns(columnQualifier, propertyName); //bad cast
//bad cast
return ( (PropertyMapping) lhsPersister ).toColumns( columnQualifier, propertyName );
}
}
}
@ -112,41 +108,57 @@ public final class JoinHelper {
}
/**
* Get the columns of the owning entity which are to
* be used in the join
* Get the columns of the owning entity which are to be used in the join
*
* @param type The type representing the join
* @param property The property index for the association type
* @param begin ?
* @param lhsPersister The persister for the left-hand-side of the join
* @param mapping The mapping object (typically the SessionFactory)
*
* @return The columns for the left-hand-side of the join
*/
public static String[] getLHSColumnNames(
AssociationType type,
int property,
int begin,
AssociationType type,
int property,
int begin,
OuterJoinLoadable lhsPersister,
Mapping mapping
) {
Mapping mapping) {
if ( type.useLHSPrimaryKey() ) {
//return lhsPersister.getSubclassPropertyColumnNames(property);
return lhsPersister.getIdentifierColumnNames();
}
else {
String propertyName = type.getLHSPropertyName();
if (propertyName==null) {
final String propertyName = type.getLHSPropertyName();
if ( propertyName == null ) {
//slice, to get the columns for this component
//property
return ArrayHelper.slice(
property < 0
? lhsPersister.getIdentifierColumnNames()
: lhsPersister.getSubclassPropertyColumnNames(property),
begin,
type.getColumnSpan(mapping)
);
: lhsPersister.getSubclassPropertyColumnNames( property ),
begin,
type.getColumnSpan( mapping )
);
}
else {
//property-refs for associations defined on a
//component are not supported, so no need to slice
return lhsPersister.getPropertyColumnNames(propertyName);
return lhsPersister.getPropertyColumnNames( propertyName );
}
}
}
/**
* Determine the name of the table that is the left-hand-side of the join. Usually this is the
* name of the main table from the left-hand-side persister. But that is not the case with property-refs.
*
* @param type The type representing the join
* @param propertyIndex The property index for the type
* @param lhsPersister The persister for the left-hand-side of the join
*
* @return The table name
*/
public static String getLHSTableName(
AssociationType type,
int propertyIndex,
@ -155,17 +167,17 @@ public final class JoinHelper {
return lhsPersister.getTableName();
}
else {
String propertyName = type.getLHSPropertyName();
if (propertyName==null) {
final String propertyName = type.getLHSPropertyName();
if ( propertyName == null ) {
//if there is no property-ref, assume the join
//is to the subclass table (ie. the table of the
//subclass that the association belongs to)
return lhsPersister.getSubclassPropertyTableName(propertyIndex);
return lhsPersister.getSubclassPropertyTableName( propertyIndex );
}
else {
//handle a property-ref
String propertyRefTable = lhsPersister.getPropertyTableName(propertyName);
if (propertyRefTable==null) {
String propertyRefTable = lhsPersister.getPropertyTableName( propertyName );
if ( propertyRefTable == null ) {
//it is possible that the tree-walking in OuterJoinLoader can get to
//an association defined by a subclass, in which case the property-ref
//might refer to a property defined on a subclass of the current class
@ -173,25 +185,32 @@ public final class JoinHelper {
//assumes that the property-ref refers to a property of the subclass
//table that the association belongs to (a reasonable guess)
//TODO: fix this, add: OuterJoinLoadable.getSubclassPropertyTableName(String propertyName)
propertyRefTable = lhsPersister.getSubclassPropertyTableName(propertyIndex);
propertyRefTable = lhsPersister.getSubclassPropertyTableName( propertyIndex );
}
return propertyRefTable;
}
}
}
/**
* Get the columns of the associated table which are to
* be used in the join
* Get the columns of the associated table which are to be used in the join
*
* @param type The type
* @param factory The SessionFactory
*
* @return The columns for the right-hand-side of the join
*/
public static String[] getRHSColumnNames(AssociationType type, SessionFactoryImplementor factory) {
String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName();
Joinable joinable = type.getAssociatedJoinable(factory);
if (uniqueKeyPropertyName==null) {
final String uniqueKeyPropertyName = type.getRHSUniqueKeyPropertyName();
final Joinable joinable = type.getAssociatedJoinable( factory );
if ( uniqueKeyPropertyName == null ) {
return joinable.getKeyColumnNames();
}
else {
return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames(uniqueKeyPropertyName);
return ( (OuterJoinLoadable) joinable ).getPropertyColumnNames( uniqueKeyPropertyName );
}
}
private JoinHelper() {
}
}

View File

@ -25,7 +25,6 @@ package org.hibernate.engine.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -39,46 +38,368 @@ import org.hibernate.sql.QueryJoinFragment;
import org.hibernate.type.AssociationType;
/**
* A sequence of {@link Join} delegates to make it "easier" to work with joins. The "easier" part is obviously
* subjective ;)
* <p/>
* Additionally JoinSequence is a directed graph of other JoinSequence instances, as represented by the
* {@link #next} ({@link #setNext(JoinSequence)}) pointer.
*
* @author Gavin King
* @author Steve Ebersole
*
* @see JoinFragment
*/
public class JoinSequence {
private final SessionFactoryImplementor factory;
private final List<Join> joins = new ArrayList<Join>();
private boolean useThetaStyle = false;
private final StringBuilder conditions = new StringBuilder();
private final List<Join> joins = new ArrayList<Join>();
private boolean useThetaStyle;
private String rootAlias;
private Joinable rootJoinable;
private Selector selector;
private JoinSequence next;
private boolean isFromPart = false;
private boolean isFromPart;
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append( "JoinSequence{" );
if ( rootJoinable != null ) {
buf.append( rootJoinable )
.append( '[' )
.append( rootAlias )
.append( ']' );
}
for ( int i = 0; i < joins.size(); i++ ) {
buf.append( "->" ).append( joins.get( i ) );
}
return buf.append( '}' ).toString();
/**
* Constructs a JoinSequence
*
* @param factory The SessionFactory
*/
public JoinSequence(SessionFactoryImplementor factory) {
this.factory = factory;
}
public final class Join {
/**
* Retrieve a JoinSequence that represents just the FROM clause parts
*
* @return The JoinSequence that represents just the FROM clause parts
*/
public JoinSequence getFromPart() {
final JoinSequence fromPart = new JoinSequence( factory );
fromPart.joins.addAll( this.joins );
fromPart.useThetaStyle = this.useThetaStyle;
fromPart.rootAlias = this.rootAlias;
fromPart.rootJoinable = this.rootJoinable;
fromPart.selector = this.selector;
fromPart.next = this.next == null ? null : this.next.getFromPart();
fromPart.isFromPart = true;
return fromPart;
}
/**
* Create a full, although shallow, copy.
*
* @return The copy
*/
public JoinSequence copy() {
final JoinSequence copy = new JoinSequence( factory );
copy.joins.addAll( this.joins );
copy.useThetaStyle = this.useThetaStyle;
copy.rootAlias = this.rootAlias;
copy.rootJoinable = this.rootJoinable;
copy.selector = this.selector;
copy.next = this.next == null ? null : this.next.copy();
copy.isFromPart = this.isFromPart;
copy.conditions.append( this.conditions.toString() );
return copy;
}
/**
* Add a join to this sequence
*
* @param associationType The type of the association representing the join
* @param alias The RHS alias for the join
* @param joinType The type of join (INNER, etc)
* @param referencingKey The LHS columns for the join condition
*
* @return The Join memento
*
* @throws MappingException Generally indicates a problem resolving the associationType to a {@link Joinable}
*/
public JoinSequence addJoin(
AssociationType associationType,
String alias,
JoinType joinType,
String[] referencingKey) throws MappingException {
joins.add( new Join( factory, associationType, alias, joinType, referencingKey ) );
return this;
}
/**
* Generate a JoinFragment
*
* @return The JoinFragment
*
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
*/
public JoinFragment toJoinFragment() throws MappingException {
return toJoinFragment( Collections.EMPTY_MAP, true );
}
/**
* Generate a JoinFragment
*
* @param enabledFilters The filters associated with the originating session to properly define join conditions
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
* ramifications of this argument
*
* @return The JoinFragment
*
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
*/
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeExtraJoins) throws MappingException {
return toJoinFragment( enabledFilters, includeExtraJoins, null, null );
}
/**
* Generate a JoinFragment
*
* @param enabledFilters The filters associated with the originating session to properly define join conditions
* @param includeExtraJoins Should {@link #addExtraJoins} to called. Honestly I do not understand the full
* ramifications of this argument
* @param withClauseFragment The with clause (which represents additional join restrictions) fragment
* @param withClauseJoinAlias The
*
* @return The JoinFragment
*
* @throws MappingException Indicates a problem access the provided metadata, or incorrect metadata
*/
public JoinFragment toJoinFragment(
Map enabledFilters,
boolean includeExtraJoins,
String withClauseFragment,
String withClauseJoinAlias) throws MappingException {
final QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
if ( rootJoinable != null ) {
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
final String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters );
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track
// of that fact.
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
if ( includeExtraJoins ) {
//TODO: not quite sure about the full implications of this!
addExtraJoins( joinFragment, rootAlias, rootJoinable, true );
}
}
Joinable last = rootJoinable;
for ( Join join : joins ) {
final String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters );
String condition;
if ( last != null
&& isManyToManyRoot( last )
&& ((QueryableCollection) last).getElementType() == join.getAssociationType() ) {
// the current join represents the join between a many-to-many association table
// and its "target" table. Here we need to apply any additional filters
// defined specifically on the many-to-many
final String manyToManyFilter = ( (QueryableCollection) last ).getManyToManyFilterFragment(
join.getAlias(),
enabledFilters
);
condition = "".equals( manyToManyFilter )
? on
: "".equals( on ) ? manyToManyFilter : on + " and " + manyToManyFilter;
}
else {
condition = on;
}
if ( withClauseFragment != null ) {
if ( join.getAlias().equals( withClauseJoinAlias ) ) {
condition += " and " + withClauseFragment;
}
}
joinFragment.addJoin(
join.getJoinable().getTableName(),
join.getAlias(),
join.getLHSColumns(),
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
join.joinType,
condition
);
//TODO: not quite sure about the full implications of this!
if ( includeExtraJoins ) {
addExtraJoins(
joinFragment,
join.getAlias(),
join.getJoinable(),
join.joinType == JoinType.INNER_JOIN
);
}
last = join.getJoinable();
}
if ( next != null ) {
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeExtraJoins ) );
}
joinFragment.addCondition( conditions.toString() );
if ( isFromPart ) {
joinFragment.clearWherePart();
}
return joinFragment;
}
@SuppressWarnings("SimplifiableIfStatement")
private boolean isManyToManyRoot(Joinable joinable) {
if ( joinable != null && joinable.isCollection() ) {
return ( (QueryableCollection) joinable ).isManyToMany();
}
return false;
}
private void addExtraJoins(JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin) {
final boolean include = isIncluded( alias );
joinFragment.addJoins(
joinable.fromJoinFragment( alias, innerJoin, include ),
joinable.whereJoinFragment( alias, innerJoin, include )
);
}
private boolean isIncluded(String alias) {
return selector != null && selector.includeSubclasses( alias );
}
/**
* Add a condition to this sequence.
*
* @param condition The condition
*
* @return {@link this}, for method chaining
*/
public JoinSequence addCondition(String condition) {
if ( condition.trim().length() != 0 ) {
if ( !condition.startsWith( " and " ) ) {
conditions.append( " and " );
}
conditions.append( condition );
}
return this;
}
/**
* Add a condition to this sequence. Typical usage here might be:
* <pre>
* addCondition( "a", {"c1", "c2"}, "?" )
* </pre>
* to represent:
* <pre>
* "... a.c1 = ? and a.c2 = ? ..."
* </pre>
*
* @param alias The alias to apply to the columns
* @param columns The columns to add checks for
* @param condition The conditions to check against the columns
*
* @return {@link this}, for method chaining
*/
public JoinSequence addCondition(String alias, String[] columns, String condition) {
for ( String column : columns ) {
conditions.append( " and " )
.append( alias )
.append( '.' )
.append( column )
.append( condition );
}
return this;
}
/**
* Set the root of this JoinSequence. In SQL terms, this would be the driving table.
*
* @param joinable The entity/collection that is the root of this JoinSequence
* @param alias The alias associated with that joinable.
*
* @return {@link this}, for method chaining
*/
public JoinSequence setRoot(Joinable joinable, String alias) {
this.rootAlias = alias;
this.rootJoinable = joinable;
return this;
}
/**
* Sets the next join sequence
*
* @param next The next JoinSequence in the directed graph
*
* @return {@code this}, for method chaining
*/
public JoinSequence setNext(JoinSequence next) {
this.next = next;
return this;
}
/**
* Set the Selector to use to determine how subclass joins should be applied.
*
* @param selector The selector to apply
*
* @return {@code this}, for method chaining
*/
public JoinSequence setSelector(Selector selector) {
this.selector = selector;
return this;
}
/**
* Should this JoinSequence use theta-style joining (both a FROM and WHERE component) in the rendered SQL?
*
* @param useThetaStyle {@code true} indicates that theta-style joins should be used.
*
* @return {@code this}, for method chaining
*/
public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
this.useThetaStyle = useThetaStyle;
return this;
}
public boolean isThetaStyle() {
return useThetaStyle;
}
public Join getFirstJoin() {
return joins.get( 0 );
}
/**
* A subclass join selector
*/
public static interface Selector {
/**
* Should subclasses be included in the rendered join sequence?
*
* @param alias The alias
*
* @return {@code true} if the subclass joins should be included
*/
public boolean includeSubclasses(String alias);
}
/**
* Represents a join
*/
public static final class Join {
private final AssociationType associationType;
private final Joinable joinable;
private final JoinType joinType;
private final String alias;
private final String[] lhsColumns;
Join(AssociationType associationType, String alias, JoinType joinType, String[] lhsColumns)
throws MappingException {
Join(
SessionFactoryImplementor factory,
AssociationType associationType,
String alias,
JoinType joinType,
String[] lhsColumns) throws MappingException {
this.associationType = associationType;
this.joinable = associationType.getAssociatedJoinable( factory );
this.alias = alias;
@ -107,195 +428,24 @@ public class JoinSequence {
}
@Override
public String toString() {
public String toString() {
return joinable.toString() + '[' + alias + ']';
}
}
public JoinSequence(SessionFactoryImplementor factory) {
this.factory = factory;
}
public JoinSequence getFromPart() {
JoinSequence fromPart = new JoinSequence( factory );
fromPart.joins.addAll( this.joins );
fromPart.useThetaStyle = this.useThetaStyle;
fromPart.rootAlias = this.rootAlias;
fromPart.rootJoinable = this.rootJoinable;
fromPart.selector = this.selector;
fromPart.next = this.next == null ? null : this.next.getFromPart();
fromPart.isFromPart = true;
return fromPart;
}
public JoinSequence copy() {
JoinSequence copy = new JoinSequence( factory );
copy.joins.addAll( this.joins );
copy.useThetaStyle = this.useThetaStyle;
copy.rootAlias = this.rootAlias;
copy.rootJoinable = this.rootJoinable;
copy.selector = this.selector;
copy.next = this.next == null ? null : this.next.copy();
copy.isFromPart = this.isFromPart;
copy.conditions.append( this.conditions.toString() );
return copy;
}
public JoinSequence addJoin(AssociationType associationType, String alias, JoinType joinType, String[] referencingKey)
throws MappingException {
joins.add( new Join( associationType, alias, joinType, referencingKey ) );
return this;
}
public JoinFragment toJoinFragment() throws MappingException {
return toJoinFragment( Collections.EMPTY_MAP, true );
}
public JoinFragment toJoinFragment(Map enabledFilters, boolean includeExtraJoins) throws MappingException {
return toJoinFragment( enabledFilters, includeExtraJoins, null, null );
}
public JoinFragment toJoinFragment(
Map enabledFilters,
boolean includeExtraJoins,
String withClauseFragment,
String withClauseJoinAlias) throws MappingException {
QueryJoinFragment joinFragment = new QueryJoinFragment( factory.getDialect(), useThetaStyle );
@Override
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append( "JoinSequence{" );
if ( rootJoinable != null ) {
joinFragment.addCrossJoin( rootJoinable.getTableName(), rootAlias );
String filterCondition = rootJoinable.filterFragment( rootAlias, enabledFilters );
// JoinProcessor needs to know if the where clause fragment came from a dynamic filter or not so it
// can put the where clause fragment in the right place in the SQL AST. 'hasFilterCondition' keeps track
// of that fact.
joinFragment.setHasFilterCondition( joinFragment.addCondition( filterCondition ) );
if (includeExtraJoins) { //TODO: not quite sure about the full implications of this!
addExtraJoins( joinFragment, rootAlias, rootJoinable, true );
}
buf.append( rootJoinable )
.append( '[' )
.append( rootAlias )
.append( ']' );
}
Joinable last = rootJoinable;
for ( Join join: joins ) {
String on = join.getAssociationType().getOnCondition( join.getAlias(), factory, enabledFilters );
String condition = null;
if ( last != null &&
isManyToManyRoot( last ) &&
( ( QueryableCollection ) last ).getElementType() == join.getAssociationType() ) {
// the current join represents the join between a many-to-many association table
// and its "target" table. Here we need to apply any additional filters
// defined specifically on the many-to-many
String manyToManyFilter = ( ( QueryableCollection ) last )
.getManyToManyFilterFragment( join.getAlias(), enabledFilters );
condition = "".equals( manyToManyFilter )
? on
: "".equals( on )
? manyToManyFilter
: on + " and " + manyToManyFilter;
}
else {
condition = on;
}
if ( withClauseFragment != null ) {
if ( join.getAlias().equals( withClauseJoinAlias ) ) {
condition += " and " + withClauseFragment;
}
}
joinFragment.addJoin(
join.getJoinable().getTableName(),
join.getAlias(),
join.getLHSColumns(),
JoinHelper.getRHSColumnNames( join.getAssociationType(), factory ),
join.joinType,
condition
);
if (includeExtraJoins) { //TODO: not quite sure about the full implications of this!
addExtraJoins( joinFragment, join.getAlias(), join.getJoinable(), join.joinType == JoinType.INNER_JOIN );
}
last = join.getJoinable();
for ( Join join : joins ) {
buf.append( "->" ).append( join );
}
if ( next != null ) {
joinFragment.addFragment( next.toJoinFragment( enabledFilters, includeExtraJoins ) );
}
joinFragment.addCondition( conditions.toString() );
if ( isFromPart ) joinFragment.clearWherePart();
return joinFragment;
}
private boolean isManyToManyRoot(Joinable joinable) {
if ( joinable != null && joinable.isCollection() ) {
QueryableCollection persister = ( QueryableCollection ) joinable;
return persister.isManyToMany();
}
return false;
}
private boolean isIncluded(String alias) {
return selector != null && selector.includeSubclasses( alias );
}
private void addExtraJoins(JoinFragment joinFragment, String alias, Joinable joinable, boolean innerJoin) {
boolean include = isIncluded( alias );
joinFragment.addJoins( joinable.fromJoinFragment( alias, innerJoin, include ),
joinable.whereJoinFragment( alias, innerJoin, include ) );
}
public JoinSequence addCondition(String condition) {
if ( condition.trim().length() != 0 ) {
if ( !condition.startsWith( " and " ) ) conditions.append( " and " );
conditions.append( condition );
}
return this;
}
public JoinSequence addCondition(String alias, String[] columns, String condition) {
for ( int i = 0; i < columns.length; i++ ) {
conditions.append( " and " )
.append( alias )
.append( '.' )
.append( columns[i] )
.append( condition );
}
return this;
}
public JoinSequence setRoot(Joinable joinable, String alias) {
this.rootAlias = alias;
this.rootJoinable = joinable;
return this;
}
public JoinSequence setNext(JoinSequence next) {
this.next = next;
return this;
}
public JoinSequence setSelector(Selector s) {
this.selector = s;
return this;
}
public JoinSequence setUseThetaStyle(boolean useThetaStyle) {
this.useThetaStyle = useThetaStyle;
return this;
}
public boolean isThetaStyle() {
return useThetaStyle;
}
public int getJoinCount() {
return joins.size();
}
public Iterator iterateJoins() {
return joins.iterator();
}
public Join getFirstJoin() {
return joins.get( 0 );
}
public static interface Selector {
public boolean includeSubclasses(String alias);
return buf.append( '}' ).toString();
}
}