org.hibernate.type.CollectionType Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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.type;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.collection.internal.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.sql.results.spi.LoadingCollectionEntry;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
/**
* A type that handles Hibernate PersistentCollections (including arrays).
*
* @author Gavin King
*/
public abstract class CollectionType extends AbstractType implements AssociationType {
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, CollectionType.class.getName());
private static final Object NOT_NULL_COLLECTION = new MarkerObject( "NOT NULL COLLECTION" );
public static final Object UNFETCHED_COLLECTION = new MarkerObject( "UNFETCHED COLLECTION" );
private final String role;
private final String foreignKeyPropertyName;
// the need for the persister if very hot in many use cases: cache it in a field
// TODO initialize it at constructor time
private volatile CollectionPersister persister;
public CollectionType(TypeConfiguration typeConfiguration, String role, String foreignKeyPropertyName) {
this.role = role;
this.foreignKeyPropertyName = foreignKeyPropertyName;
}
public String getRole() {
return role;
}
public Object indexOf(Object collection, Object element) {
throw new UnsupportedOperationException( "generic collections don't have indexes" );
}
public boolean contains(Object collection, Object childObject, SharedSessionContractImplementor session) {
// we do not have to worry about queued additions to uninitialized
// collections, since they can only occur for inverse collections!
Iterator elems = getElementsIterator( collection, session );
while ( elems.hasNext() ) {
Object element = elems.next();
// worrying about proxies is perhaps a little bit of overkill here...
if ( element instanceof HibernateProxy ) {
LazyInitializer li = ( (HibernateProxy) element ).getHibernateLazyInitializer();
if ( !li.isUninitialized() ) {
element = li.getImplementation();
}
}
if ( element == childObject ) {
return true;
}
}
return false;
}
@Override
public boolean isCollectionType() {
return true;
}
@Override
public final boolean isEqual(Object x, Object y) {
return x == y
|| ( x instanceof PersistentCollection && isEqual( (PersistentCollection) x, y ) )
|| ( y instanceof PersistentCollection && isEqual( (PersistentCollection) y, x ) );
}
private boolean isEqual(PersistentCollection x, Object y) {
return x.wasInitialized() && ( x.isWrapper( y ) || x.isDirectlyProvidedCollection( y ) );
}
@Override
public int compare(Object x, Object y) {
return 0; // collections cannot be compared
}
@Override
public int getHashCode(Object x) {
throw new UnsupportedOperationException( "cannot doAfterTransactionCompletion lookups on collections" );
}
/**
* Instantiate an uninitialized collection wrapper or holder. Callers MUST add the holder to the
* persistence context!
*
* @param session The session from which the request is originating.
* @param persister The underlying collection persister (metadata)
* @param key The owner key.
* @return The instantiated collection.
*/
public abstract PersistentCollection instantiate(SharedSessionContractImplementor session, CollectionPersister persister, Object key);
@Override
public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws SQLException {
return nullSafeGet( rs, new String[] { name }, session, owner );
}
@Override
public Object nullSafeGet(ResultSet rs, String[] name, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
return resolve( null, session, owner );
}
@Override
public final void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable,
SharedSessionContractImplementor session) throws HibernateException, SQLException {
//NOOP
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException {
}
@Override
public int[] sqlTypes(Mapping session) throws MappingException {
return ArrayHelper.EMPTY_INT_ARRAY;
}
@Override
public Size[] dictatedSizes(Mapping mapping) throws MappingException {
return new Size[] { LEGACY_DICTATED_SIZE };
}
@Override
public Size[] defaultSizes(Mapping mapping) throws MappingException {
return new Size[] { LEGACY_DEFAULT_SIZE };
}
@Override
public int getColumnSpan(Mapping session) throws MappingException {
return 0;
}
@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory)
throws HibernateException {
if ( value == null ) {
return "null";
}
if ( !getReturnedClass().isInstance( value ) && !PersistentCollection.class.isInstance( value ) ) {
// its most likely the collection-key
final CollectionPersister persister = getPersister( factory );
if ( persister.getKeyType().getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + getPersister( factory ).getKeyType().toLoggableString( value, factory );
}
else {
// although it could also be the collection-id
if ( persister.getIdentifierType() != null
&& persister.getIdentifierType().getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + getPersister( factory ).getIdentifierType().toLoggableString( value, factory );
}
}
}
return renderLoggableString( value, factory );
}
protected String renderLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
if ( !Hibernate.isInitialized( value ) ) {
return "";
}
final List list = new ArrayList<>();
Type elemType = getElementType( factory );
Iterator itr = getElementsIterator( value );
while ( itr.hasNext() ) {
Object element = itr.next();
if ( element == LazyPropertyInitializer.UNFETCHED_PROPERTY || !Hibernate.isInitialized( element ) ) {
list.add( "" );
}
else {
list.add( elemType.toLoggableString( element, factory ) );
}
}
return list.toString();
}
@Override
public Object deepCopy(Object value, SessionFactoryImplementor factory)
throws HibernateException {
return value;
}
@Override
public String getName() {
return getReturnedClass().getName() + '(' + getRole() + ')';
}
/**
* Get an iterator over the element set of the collection, which may not yet be wrapped
*
* @param collection The collection to be iterated
* @param session The session from which the request is originating.
* @return The iterator.
*/
public Iterator getElementsIterator(Object collection, SharedSessionContractImplementor session) {
return getElementsIterator( collection );
}
/**
* Get an iterator over the element set of the collection in POJO mode
*
* @param collection The collection to be iterated
* @return The iterator.
*/
protected Iterator getElementsIterator(Object collection) {
return ( (Collection) collection ).iterator();
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
//remember the uk value
//This solution would allow us to eliminate the owner arg to disassemble(), but
//what if the collection was null, and then later had elements added? seems unsafe
//session.getPersistenceContext().getCollectionEntry( (PersistentCollection) value ).getKey();
final Object key = getKeyOfOwner(owner, session);
if ( key == null ) {
return null;
}
else {
return getPersister(session)
.getKeyType()
.disassemble( key, session, owner );
}
}
@Override
public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
//we must use the "remembered" uk value, since it is
//not available from the EntityEntry during assembly
if (cached==null) {
return null;
}
else {
final Serializable key = (Serializable) getPersister(session)
.getKeyType()
.assemble( cached, session, owner);
return resolveKey( key, session, owner, null );
}
}
/**
* Is the owning entity versioned?
*
* @param session The session from which the request is originating.
* @return True if the collection owner is versioned; false otherwise.
* @throws org.hibernate.MappingException Indicates our persister could not be located.
*/
private boolean isOwnerVersioned(SharedSessionContractImplementor session) throws MappingException {
return getPersister( session ).getOwnerEntityPersister().isVersioned();
}
/**
* Get our underlying collection persister (using the session to access the
* factory).
*
* @param session The session from which the request is originating.
* @return The underlying collection persister
*/
private CollectionPersister getPersister(SharedSessionContractImplementor session) {
CollectionPersister p = this.persister;
if ( p != null ) {
return p;
}
else {
return getPersister( session.getFactory() );
}
}
private CollectionPersister getPersister(SessionFactoryImplementor factory) {
CollectionPersister p = this.persister;
if ( p != null ) {
return p;
}
else {
synchronized ( this ) {
p = this.persister;
if ( p != null ) {
return p;
}
else {
p = factory.getMetamodel().collectionPersister( role );
this.persister = p;
return p;
}
}
}
}
@Override
public boolean isDirty(Object old, Object current, SharedSessionContractImplementor session)
throws HibernateException {
// collections don't dirty an unversioned parent entity
// TODO: I don't really like this implementation; it would be better if
// this was handled by searchForDirtyCollections()
return super.isDirty( old, current, session );
// return false;
}
@Override
public boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session)
throws HibernateException {
return isDirty(old, current, session);
}
/**
* Wrap the naked collection instance in a wrapper, or instantiate a
* holder. Callers MUST add the holder to the persistence context!
*
* @param session The session from which the request is originating.
* @param collection The bare collection to be wrapped.
* @return The wrapped collection.
*/
public abstract PersistentCollection wrap(SharedSessionContractImplementor session, Object collection);
/**
* Note: return true because this type is castable to AssociationType. Not because
* all collections are associations.
*/
@Override
public boolean isAssociationType() {
return true;
}
@Override
public ForeignKeyDirection getForeignKeyDirection() {
return ForeignKeyDirection.TO_PARENT;
}
/**
* Get the key value from the owning entity instance, usually the identifier, but might be some
* other unique key, in the case of property-ref
*
* @param owner The collection owner
* @param session The session from which the request is originating.
* @return The collection owner's key
*/
public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
final PersistenceContext pc = session.getPersistenceContextInternal();
EntityEntry entityEntry = pc.getEntry( owner );
if ( entityEntry == null ) {
// This just handles a particular case of component
// projection, perhaps get rid of it and throw an exception
return null;
}
if ( foreignKeyPropertyName == null ) {
return entityEntry.getId();
}
else {
// TODO: at the point where we are resolving collection references, we don't
// know if the uk value has been resolved (depends if it was earlier or
// later in the mapping document) - now, we could try and use e.getStatus()
// to decide to semiResolve(), trouble is that initializeEntity() reuses
// the same array for resolved and hydrated values
Object id;
if ( entityEntry.getLoadedState() != null ) {
id = entityEntry.getLoadedValue( foreignKeyPropertyName );
}
else {
id = entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName );
}
// NOTE VERY HACKISH WORKAROUND!!
// TODO: Fix this so it will work for non-POJO entity mode
Type keyType = getPersister( session ).getKeyType();
Class returnedClass = keyType.getReturnedClass();
if ( !returnedClass.isInstance( id ) ) {
id = keyType.semiResolve(
entityEntry.getLoadedValue( foreignKeyPropertyName ),
session,
owner
);
}
return (Serializable) id;
}
}
/**
* Get the id value from the owning entity key, usually the same as the key, but might be some
* other property, in the case of property-ref
*
* @param key The collection owner key
* @param session The session from which the request is originating.
* @return The collection owner's id, if it can be obtained from the key;
* otherwise, null is returned
*/
public Object getIdOfOwnerOrNull(Object key, SharedSessionContractImplementor session) {
Object ownerId = null;
if ( foreignKeyPropertyName == null ) {
ownerId = key;
}
else {
final CollectionPersister persister = getPersister( session );
Type keyType = persister.getKeyType();
EntityPersister ownerPersister = persister.getOwnerEntityPersister();
// TODO: Fix this so it will work for non-POJO entity mode
Class ownerMappedClass = ownerPersister.getMappedClass();
if ( ownerMappedClass.isAssignableFrom( keyType.getReturnedClass() ) &&
keyType.getReturnedClass().isInstance( key ) ) {
// the key is the owning entity itself, so get the ID from the key
ownerId = ownerPersister.getIdentifier( key, session );
}
else {
// TODO: check if key contains the owner ID
}
}
return ownerId;
}
@Override
public Object hydrate(ResultSet rs, String[] name, SharedSessionContractImplementor session, Object owner) {
// can't just return null here, since that would
// cause an owning component to become null
return NOT_NULL_COLLECTION;
}
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
return resolve( value, session, owner, null );
}
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException {
return resolveKey( getKeyOfOwner( owner, session ), session, owner, overridingEager );
}
private Object resolveKey(Object key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) {
// if (key==null) throw new AssertionFailure("owner identifier unknown when re-assembling
// collection reference");
return key == null ? null : // TODO: can this case really occur??
getCollection( key, session, owner, overridingEager );
}
@Override
public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
throw new UnsupportedOperationException(
"collection mappings may not form part of a property-ref" );
}
public boolean isArrayType() {
return false;
}
@Override
public boolean useLHSPrimaryKey() {
return foreignKeyPropertyName == null;
}
@Override
public String getRHSUniqueKeyPropertyName() {
return null;
}
@Override
public Joinable getAssociatedJoinable(SessionFactoryImplementor factory)
throws MappingException {
return (Joinable) factory.getCollectionPersister( role );
}
@Override
public boolean isModified(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException {
return false;
}
@Override
public String getAssociatedEntityName(SessionFactoryImplementor factory)
throws MappingException {
try {
QueryableCollection collectionPersister = (QueryableCollection) factory
.getCollectionPersister( role );
if ( !collectionPersister.getElementType().isEntityType() ) {
throw new MappingException(
"collection was not an association: " +
collectionPersister.getRole()
);
}
return collectionPersister.getElementPersister().getEntityName();
}
catch (ClassCastException cce) {
throw new MappingException( "collection role is not queryable " + role );
}
}
/**
* Replace the elements of a collection with the elements of another collection.
*
* @param original The 'source' of the replacement elements (where we copy from)
* @param target The target of the replacement elements (where we copy to)
* @param owner The owner of the collection being merged
* @param copyCache The map of elements already replaced.
* @param session The session from which the merge event originated.
* @return The merged collection.
*/
public Object replaceElements(
Object original,
Object target,
Object owner,
Map copyCache,
SharedSessionContractImplementor session) {
// TODO: does not work for EntityMode.DOM4J yet!
java.util.Collection result = ( java.util.Collection ) target;
result.clear();
// copy elements into newly empty target collection
Type elemType = getElementType( session.getFactory() );
Iterator iter = ( (java.util.Collection) original ).iterator();
while ( iter.hasNext() ) {
result.add( elemType.replace( iter.next(), null, session, owner, copyCache ) );
}
// if the original is a PersistentCollection, and that original
// was not flagged as dirty, then reset the target's dirty flag
// here after the copy operation.
//
// One thing to be careful of here is a "bare" original collection
// in which case we should never ever ever reset the dirty flag
// on the target because we simply do not know...
if ( original instanceof PersistentCollection ) {
if ( result instanceof PersistentCollection ) {
final PersistentCollection originalPersistentCollection = (PersistentCollection) original;
final PersistentCollection resultPersistentCollection = (PersistentCollection) result;
preserveSnapshot( originalPersistentCollection, resultPersistentCollection, elemType, owner, copyCache, session );
if ( ! originalPersistentCollection.isDirty() ) {
resultPersistentCollection.clearDirty();
}
}
}
return result;
}
private void preserveSnapshot(
PersistentCollection original,
PersistentCollection result,
Type elemType,
Object owner,
Map copyCache,
SharedSessionContractImplementor session) {
Serializable originalSnapshot = original.getStoredSnapshot();
Serializable resultSnapshot = result.getStoredSnapshot();
Serializable targetSnapshot;
if ( originalSnapshot instanceof List ) {
targetSnapshot = new ArrayList(
( (List) originalSnapshot ).size() );
for ( Object obj : (List) originalSnapshot ) {
( (List) targetSnapshot ).add( elemType.replace( obj, null, session, owner, copyCache ) );
}
}
else if ( originalSnapshot instanceof Map ) {
if ( originalSnapshot instanceof SortedMap ) {
targetSnapshot = new TreeMap( ( (SortedMap) originalSnapshot ).comparator() );
}
else {
targetSnapshot = new HashMap(
CollectionHelper.determineProperSizing( ( (Map) originalSnapshot ).size() ),
CollectionHelper.LOAD_FACTOR
);
}
for ( Map.Entry