HHH-9241 - Allow declaring non-java.util Collection interfaces
This commit is contained in:
parent
bc6289e0b8
commit
5b76256e96
|
@ -33,6 +33,18 @@ public @interface CollectionType {
|
|||
*/
|
||||
String type();
|
||||
|
||||
/**
|
||||
* Specifies the class to use the semantics of.
|
||||
*
|
||||
* For example, specifying {@link java.util.Set} will use Set semantics.
|
||||
*
|
||||
* When not specified, will be inferred from the interfaces on the property
|
||||
* as long as it extends a standard {@link java.util.Collection} or {@link java.util.Map}.
|
||||
*
|
||||
* @return the class to use the semantics of.
|
||||
*/
|
||||
Class<?> semantics() default void.class;
|
||||
|
||||
/**
|
||||
* Specifies configuration information for the type. Note that if the named type is a
|
||||
* {@link org.hibernate.usertype.UserCollectionType}, it must also implement
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
*/
|
||||
package org.hibernate.cfg.annotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
@ -29,6 +32,7 @@ import javax.persistence.OneToMany;
|
|||
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.FetchMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.Cache;
|
||||
|
@ -88,6 +92,7 @@ import org.hibernate.criterion.Junction;
|
|||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||
import org.hibernate.mapping.Any;
|
||||
|
@ -119,6 +124,14 @@ import static org.hibernate.cfg.BinderHelper.toAliasTableMap;
|
|||
public abstract class CollectionBinder {
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, CollectionBinder.class.getName());
|
||||
|
||||
private static final List<Class<?>> INFERRED_CLASS_PRIORITY = Collections.unmodifiableList( Arrays.asList(
|
||||
java.util.List.class,
|
||||
java.util.SortedSet.class,
|
||||
java.util.Set.class,
|
||||
java.util.SortedMap.class,
|
||||
java.util.Map.class,
|
||||
java.util.Collection.class) );
|
||||
|
||||
private MetadataBuildingContext buildingContext;
|
||||
|
||||
protected Collection collection;
|
||||
|
@ -256,6 +269,7 @@ public abstract class CollectionBinder {
|
|||
boolean isHibernateExtensionMapping,
|
||||
MetadataBuildingContext buildingContext) {
|
||||
final CollectionBinder result;
|
||||
|
||||
if ( property.isArray() ) {
|
||||
if ( property.getElementClass().isPrimitive() ) {
|
||||
result = new PrimitiveArrayBinder();
|
||||
|
@ -266,62 +280,39 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
else if ( property.isCollection() ) {
|
||||
//TODO consider using an XClass
|
||||
Class returnedClass = property.getCollectionClass();
|
||||
if ( java.util.Set.class.equals( returnedClass ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Set do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
result = new SetBinder( false );
|
||||
final Class<?> returnedClass = property.getCollectionClass();
|
||||
|
||||
final CollectionBinder basicBinder = getBinderFromBasicCollectionType(
|
||||
returnedClass,
|
||||
property,
|
||||
entityName,
|
||||
isIndexed
|
||||
);
|
||||
if ( basicBinder != null ) {
|
||||
result = basicBinder;
|
||||
}
|
||||
else if ( java.util.SortedSet.class.equals( returnedClass ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Set do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
result = new SetBinder( true );
|
||||
}
|
||||
else if ( java.util.Map.class.equals( returnedClass ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Map do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
result = new MapBinder( false );
|
||||
}
|
||||
else if ( java.util.SortedMap.class.equals( returnedClass ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Map do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
result = new MapBinder( true );
|
||||
}
|
||||
else if ( java.util.Collection.class.equals( returnedClass ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
result = new IdBagBinder();
|
||||
else if ( property.isAnnotationPresent( CollectionType.class ) ) {
|
||||
Class<?> semanticsClass = property.getAnnotation( CollectionType.class ).semantics();
|
||||
|
||||
if ( semanticsClass != void.class ) {
|
||||
result = getBinderFromBasicCollectionType( semanticsClass, property, entityName, isIndexed );
|
||||
}
|
||||
else {
|
||||
result = new BagBinder();
|
||||
}
|
||||
}
|
||||
else if ( java.util.List.class.equals( returnedClass ) ) {
|
||||
if ( isIndexed ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException(
|
||||
"List do not support @CollectionId and @OrderColumn (or @IndexColumn) at the same time: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
result = new ListBinder();
|
||||
}
|
||||
else if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
result = new IdBagBinder();
|
||||
}
|
||||
else {
|
||||
result = new BagBinder();
|
||||
final Class<?> inferredClass = inferCollectionClassFromSubclass( returnedClass );
|
||||
result = inferredClass != null ? getBinderFromBasicCollectionType(
|
||||
inferredClass,
|
||||
property,
|
||||
entityName,
|
||||
isIndexed
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = null;
|
||||
}
|
||||
if ( result == null ) {
|
||||
throw new AnnotationException(
|
||||
returnedClass.getName() + " collection not yet supported: "
|
||||
returnedClass.getName() + " collection type not supported for property: "
|
||||
+ StringHelper.qualify( entityName, property.getName() )
|
||||
);
|
||||
}
|
||||
|
@ -354,6 +345,72 @@ public abstract class CollectionBinder {
|
|||
return result;
|
||||
}
|
||||
|
||||
private static CollectionBinder getBinderFromBasicCollectionType(Class<?> clazz, XProperty property,
|
||||
String entityName, boolean isIndexed) {
|
||||
if ( java.util.Set.class.equals( clazz) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class) ) {
|
||||
throw new AnnotationException("Set do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
return new SetBinder( false );
|
||||
}
|
||||
else if ( java.util.SortedSet.class.equals( clazz ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Set do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
return new SetBinder( true );
|
||||
}
|
||||
else if ( java.util.Map.class.equals( clazz ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Map do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
return new MapBinder( false );
|
||||
}
|
||||
else if ( java.util.SortedMap.class.equals( clazz ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException( "Map do not support @CollectionId: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
return new MapBinder( true );
|
||||
}
|
||||
else if ( java.util.Collection.class.equals( clazz ) ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
return new IdBagBinder();
|
||||
}
|
||||
else {
|
||||
return new BagBinder();
|
||||
}
|
||||
}
|
||||
else if ( java.util.List.class.equals( clazz ) ) {
|
||||
if ( isIndexed ) {
|
||||
if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
throw new AnnotationException(
|
||||
"List do not support @CollectionId and @OrderColumn (or @IndexColumn) at the same time: "
|
||||
+ StringHelper.qualify( entityName, property.getName() ) );
|
||||
}
|
||||
return new ListBinder();
|
||||
}
|
||||
else if ( property.isAnnotationPresent( CollectionId.class ) ) {
|
||||
return new IdBagBinder();
|
||||
}
|
||||
else {
|
||||
return new BagBinder();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Class<?> inferCollectionClassFromSubclass(Class<?> clazz) {
|
||||
for ( Class<?> priorityClass : INFERRED_CLASS_PRIORITY ) {
|
||||
if ( priorityClass.isAssignableFrom( clazz ) ) {
|
||||
return priorityClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setMappedBy(String mappedBy) {
|
||||
this.mappedBy = mappedBy;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
public class Email {
|
||||
private Long id;
|
||||
private String address;
|
||||
|
||||
Email() {
|
||||
}
|
||||
|
||||
public Email(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue( strategy = GenerationType.AUTO )
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String type) {
|
||||
this.address = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if ( !(that instanceof Email ) ) return false;
|
||||
Email p = (Email) that;
|
||||
return this.address.equals(p.address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) ${year}, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class UserCollectionTypeAnnotationsVariantTest extends UserCollectionTypeTest {
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { User.class, Email.class };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) ${year}, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class UserCollectionTypeHbmVariantTest extends UserCollectionTypeTest {
|
||||
@Override
|
||||
public String[] getMappings() {
|
||||
return new String[] { "collection/custom/declaredtype/UserPermissions.hbm.xml" };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OrderColumn;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.annotations.CollectionType;
|
||||
import org.hibernate.annotations.TypeDef;
|
||||
import org.hibernate.collection.internal.PersistentList;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.usertype.UserCollectionType;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Max Rydahl Andersen
|
||||
* @author David Weinberg
|
||||
*/
|
||||
public abstract class UserCollectionTypeTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected String getCacheConcurrencyStrategy() {
|
||||
return "nonstrict-read-write";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicOperation() {
|
||||
Session s = openSession();
|
||||
Transaction t = s.beginTransaction();
|
||||
User u = new User("max");
|
||||
u.getEmailAddresses().add( new Email("max@hibernate.org") );
|
||||
u.getEmailAddresses().add( new Email("max.andersen@jboss.com") );
|
||||
s.persist(u);
|
||||
t.commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
t = s.beginTransaction();
|
||||
User u2 = (User) s.createCriteria(User.class).uniqueResult();
|
||||
assertTrue( Hibernate.isInitialized( u2.getEmailAddresses() ) );
|
||||
assertEquals( u2.getEmailAddresses().size(), 2 );
|
||||
assertNotNull( u2.getEmailAddresses().head());
|
||||
t.commit();
|
||||
s.close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom collection class that implements a simple method just for illustration.
|
||||
* We extend a java.util.Collection class which is required for annotations-based entities, but not xml-based.
|
||||
*
|
||||
* @author David Weinberg
|
||||
*/
|
||||
public static class HeadList<X> extends ArrayList<X> implements IHeadList<X> {
|
||||
|
||||
@Override
|
||||
public X head() {
|
||||
return isEmpty() ? null : get( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeadListType implements UserCollectionType {
|
||||
|
||||
@Override
|
||||
public PersistentCollection instantiate(SharedSessionContractImplementor session, CollectionPersister persister) throws HibernateException {
|
||||
return new PersistentHeadList(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentCollection wrap(SharedSessionContractImplementor session, Object collection) {
|
||||
return new PersistentHeadList( session, (IHeadList) collection );
|
||||
}
|
||||
|
||||
public Iterator getElementsIterator(Object collection) {
|
||||
return ( (IHeadList) collection ).iterator();
|
||||
}
|
||||
|
||||
public boolean contains(Object collection, Object entity) {
|
||||
return ( (IHeadList) collection ).contains(entity);
|
||||
}
|
||||
|
||||
public Object indexOf(Object collection, Object entity) {
|
||||
int l = ( (IHeadList) collection ).indexOf(entity);
|
||||
if(l<0) {
|
||||
return null;
|
||||
} else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object replaceElements(
|
||||
Object original,
|
||||
Object target,
|
||||
CollectionPersister persister,
|
||||
Object owner,
|
||||
Map copyCache,
|
||||
SharedSessionContractImplementor session) throws HibernateException {
|
||||
IHeadList result = (IHeadList) target;
|
||||
result.clear();
|
||||
result.addAll( (HeadList) original );
|
||||
return result;
|
||||
}
|
||||
|
||||
public Object instantiate(int anticipatedSize) {
|
||||
return new HeadList();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public interface IHeadList<X> extends List<X> {
|
||||
X head();
|
||||
}
|
||||
|
||||
public static class PersistentHeadList extends PersistentList implements IHeadList {
|
||||
|
||||
public PersistentHeadList(SharedSessionContractImplementor session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
public PersistentHeadList(SharedSessionContractImplementor session, IHeadList list) {
|
||||
super(session, list);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object head() {
|
||||
return ((IHeadList) list).head();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "UC_BSC_USER")
|
||||
@TypeDef( name = "HeadListType", typeClass = HeadListType.class )
|
||||
public static class User {
|
||||
private String userName;
|
||||
private IHeadList<Email> emailAddresses = new HeadList<Email>();
|
||||
private Map sessionData = new HashMap();
|
||||
|
||||
User() {
|
||||
|
||||
}
|
||||
public User(String name) {
|
||||
userName = name;
|
||||
}
|
||||
|
||||
@Id
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@OneToMany( fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true )
|
||||
@CollectionType( type = "HeadListType" )
|
||||
@JoinColumn( name = "userName" )
|
||||
@OrderColumn( name = "displayOrder" )
|
||||
public IHeadList<Email> getEmailAddresses() { //can declare a custom interface type
|
||||
return emailAddresses;
|
||||
}
|
||||
public void setEmailAddresses(IHeadList<Email> emailAddresses) {
|
||||
this.emailAddresses = emailAddresses;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public Map getSessionData() {
|
||||
return sessionData;
|
||||
}
|
||||
public void setSessionData(Map sessionData) {
|
||||
this.sessionData = sessionData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE hibernate-mapping PUBLIC
|
||||
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
||||
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
|
||||
|
||||
<!--
|
||||
This mapping is a basic example of how to write a UserCollectionType.
|
||||
-->
|
||||
|
||||
<hibernate-mapping package="org.hibernate.test.collection.custom.declaredtype">
|
||||
|
||||
<import class="Permission"/>
|
||||
|
||||
<class name="org.hibernate.test.collection.custom.declaredtype.UserCollectionTypeTest$User" table="UC_BSC_USER">
|
||||
<id name="userName"/>
|
||||
<list name="emailAddresses" fetch="join" cascade="all, delete-orphan" collection-type="org.hibernate.test.collection.custom.declaredtype.UserCollectionTypeTest$HeadListType">
|
||||
<key column="userName"/>
|
||||
<list-index column="displayOrder" base="1"/>
|
||||
<one-to-many class="Email"/>
|
||||
</list>
|
||||
</class>
|
||||
|
||||
<class name="Email">
|
||||
<id name="id">
|
||||
<generator class="native"/>
|
||||
</id>
|
||||
<property name="address"/>
|
||||
</class>
|
||||
|
||||
</hibernate-mapping>
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) ${year}, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
import org.hibernate.annotations.CollectionType;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "UC_BSC_USER")
|
||||
public class UserWithUnimplementedCollection {
|
||||
private String userName;
|
||||
private AtomicReference<Email> emailAddresses = new AtomicReference<Email>();
|
||||
private Map sessionData = new HashMap();
|
||||
|
||||
UserWithUnimplementedCollection() {
|
||||
|
||||
}
|
||||
public UserWithUnimplementedCollection(String name) {
|
||||
userName = name;
|
||||
}
|
||||
|
||||
@Id
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@OneToMany( fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true )
|
||||
@CollectionType( type = "org.hibernate.test.collection.custom.declaredtype.UserCollectionTypeTest.HeadListType" )
|
||||
@JoinColumn( name = "userName" )
|
||||
@OrderColumn( name = "displayOrder" )
|
||||
public AtomicReference<Email> getEmailAddresses() { //can declare a custom interface type
|
||||
return emailAddresses;
|
||||
}
|
||||
public void setEmailAddresses(AtomicReference<Email> emailAddresses) {
|
||||
this.emailAddresses = emailAddresses;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public Map getSessionData() {
|
||||
return sessionData;
|
||||
}
|
||||
public void setSessionData(Map sessionData) {
|
||||
this.sessionData = sessionData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype;
|
||||
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Max Rydahl Andersen
|
||||
* @author David Weinberg Negative test when specifying a type that can't be mapped as a collection
|
||||
*/
|
||||
public class UserWithUnimplementedCollectionTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[]{ UserWithUnimplementedCollection.class, Email.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCacheConcurrencyStrategy() {
|
||||
return "nonstrict-read-write";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildSessionFactory() {
|
||||
try {
|
||||
super.buildSessionFactory();
|
||||
fail( "Expected exception" );
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue( e instanceof AnnotationException );
|
||||
assertEquals(
|
||||
"Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: org.hibernate.test.collection.custom.declaredtype.UserWithUnimplementedCollection.emailAddresses",
|
||||
e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionFactoryFailsToBeCreated() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.collection.custom.declaredtype.explicitsemantics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OrderColumn;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.annotations.CollectionType;
|
||||
import org.hibernate.annotations.TypeDef;
|
||||
import org.hibernate.collection.internal.PersistentSet;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.persister.collection.CollectionPersister;
|
||||
import org.hibernate.usertype.UserCollectionType;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Max Rydahl Andersen
|
||||
* @author David Weinberg
|
||||
*/
|
||||
public class UserCollectionTypeTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[]{ User.class, Email.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCacheConcurrencyStrategy() {
|
||||
return "nonstrict-read-write";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicOperation() {
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
User u = new User( "max" );
|
||||
u.getEmailAddresses().add( new Email( "max@hibernate.org" ) );
|
||||
u.getEmailAddresses().add( new Email( "max.andersen@jboss.com" ) );
|
||||
session.persist( u );
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
User u2 = (User) session.createCriteria( User.class ).uniqueResult();
|
||||
assertTrue( Hibernate.isInitialized( u2.getEmailAddresses() ) );
|
||||
assertEquals( u2.getEmailAddresses().size(), 2 );
|
||||
assertNotNull( u2.getEmailAddresses().head() );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity(name = "Email")
|
||||
public static class Email {
|
||||
|
||||
private Long id;
|
||||
private String address;
|
||||
|
||||
Email() {
|
||||
}
|
||||
|
||||
public Email(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String type) {
|
||||
this.address = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if ( !( that instanceof Email ) )
|
||||
return false;
|
||||
Email p = (Email) that;
|
||||
return this.address.equals( p.address );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "User")
|
||||
@Table(name = "UC_BSC_USER")
|
||||
@TypeDef(name = "HeadSetListType", typeClass = HeadSetListType.class)
|
||||
public static class User {
|
||||
|
||||
private String userName;
|
||||
|
||||
private IHeadSetList<Email> emailAddresses = new HeadSetList<Email>();
|
||||
|
||||
private Map sessionData = new HashMap();
|
||||
|
||||
User() {
|
||||
|
||||
}
|
||||
|
||||
public User(String name) {
|
||||
userName = name;
|
||||
}
|
||||
|
||||
@Id
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@CollectionType(type = "HeadSetListType", semantics = Set.class)
|
||||
@JoinColumn(name = "userName")
|
||||
@OrderColumn(name = "displayOrder")
|
||||
public IHeadSetList<Email> getEmailAddresses() { // can declare a custom interface type
|
||||
return emailAddresses;
|
||||
}
|
||||
|
||||
public void setEmailAddresses(IHeadSetList<Email> emailAddresses) {
|
||||
this.emailAddresses = emailAddresses;
|
||||
}
|
||||
|
||||
@Transient
|
||||
public Map getSessionData() {
|
||||
return sessionData;
|
||||
}
|
||||
|
||||
public void setSessionData(Map sessionData) {
|
||||
this.sessionData = sessionData;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HeadSetListType implements UserCollectionType {
|
||||
|
||||
@Override
|
||||
public PersistentCollection instantiate(SharedSessionContractImplementor session, CollectionPersister persister)
|
||||
throws HibernateException {
|
||||
return new PersistentHeadList( session );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentCollection wrap(SharedSessionContractImplementor session, Object collection) {
|
||||
return new PersistentHeadList( session, (IHeadSetList) collection );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator getElementsIterator(Object collection) {
|
||||
return ( (IHeadSetList) collection ).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object collection, Object entity) {
|
||||
return ( (IHeadSetList) collection ).contains( entity );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object indexOf(Object collection, Object entity) {
|
||||
int l = ( (IHeadSetList) collection ).indexOf( entity );
|
||||
if ( l < 0 ) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object replaceElements(
|
||||
Object original,
|
||||
Object target,
|
||||
CollectionPersister persister,
|
||||
Object owner,
|
||||
Map copyCache,
|
||||
SharedSessionContractImplementor session) throws HibernateException {
|
||||
IHeadSetList result = (IHeadSetList) target;
|
||||
result.clear();
|
||||
result.addAll( (HeadSetList) original );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiate(int anticipatedSize) {
|
||||
return new HeadSetList();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IHeadSetList<X> extends Set<X>, List<X> {
|
||||
|
||||
X head();
|
||||
|
||||
@Override
|
||||
default Spliterator<X> spliterator() {
|
||||
return Spliterators.spliterator( this, Spliterator.DISTINCT );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom collection class that has both List and Set interfaces, but only really implements set for persistence
|
||||
* (e.g. extends PersistentSet). Without setting the semantics on the CollectionType annotation, List semantics
|
||||
* would be inferred, and that would not match the implemented methods in PersistentSet and would fail. HeadSetList
|
||||
* is very much a toy collection type.
|
||||
*
|
||||
* @author David Weinberg
|
||||
*/
|
||||
public static class HeadSetList<X> extends ArrayList<X> implements IHeadSetList<X> {
|
||||
|
||||
@Override
|
||||
public X head() {
|
||||
return isEmpty() ? null : get( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
public static class PersistentHeadList extends PersistentSet implements IHeadSetList {
|
||||
|
||||
public PersistentHeadList(SharedSessionContractImplementor session) {
|
||||
super( session );
|
||||
}
|
||||
|
||||
public PersistentHeadList(SharedSessionContractImplementor session, IHeadSetList list) {
|
||||
super( session, list );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object head() {
|
||||
return ( (IHeadSetList) set ).head();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection c) {
|
||||
return set.addAll( c );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(int index) {
|
||||
Iterator iterator = iterator();
|
||||
Object next = null;
|
||||
for ( int i = 0; i <= index; i++ ) {
|
||||
next = iterator.next();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object set(int index, Object element) {
|
||||
remove( index );
|
||||
return add( element );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, Object element) {
|
||||
add( element );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(int index) {
|
||||
return remove( get( index ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
throw new UnsupportedOperationException( "Toy class" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
throw new UnsupportedOperationException( "Toy class" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator listIterator() {
|
||||
throw new UnsupportedOperationException( "Toy class" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator listIterator(int index) {
|
||||
throw new UnsupportedOperationException( "Toy class" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public List subList(int fromIndex, int toIndex) {
|
||||
throw new UnsupportedOperationException( "Toy class" );
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue