HHH-9495 - @Convert support for collections

This commit is contained in:
Steve Ebersole 2015-03-20 15:08:03 -05:00
parent 8284b9ae2c
commit f45b37da8e
2 changed files with 249 additions and 21 deletions

View File

@ -42,6 +42,7 @@ import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.MapKeyType;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.source.spi.AttributePath;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
@ -143,6 +144,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
}
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
// the @Convert did not name an attribute...
if ( canElementBeConverted && canKeyBeConverted ) {
throw new IllegalStateException(
"@Convert placed on Map attribute [" + collection.getRole()
@ -158,30 +160,46 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
// if neither, we should not be here...
}
else {
if ( canElementBeConverted && canKeyBeConverted ) {
// the @Convert named an attribute...
final String keyPath = removePrefix( info.getAttributeName(), "key" );
final String elementPath = removePrefix( info.getAttributeName(), "value" );
if ( canElementBeConverted && canKeyBeConverted && keyPath == null && elementPath == null ) {
// specified attributeName needs to have 'key.' or 'value.' prefix
if ( info.getAttributeName().startsWith( "key." ) ) {
keyAttributeConversionInfoMap.put(
info.getAttributeName().substring( 4 ),
info
);
}
else if ( info.getAttributeName().startsWith( "value." ) ) {
elementAttributeConversionInfoMap.put(
info.getAttributeName().substring( 6 ),
info
);
}
else {
throw new IllegalStateException(
"@Convert placed on Map attribute [" + collection.getRole()
+ "] must define attributeName of 'key' or 'value'"
);
}
throw new IllegalStateException(
"@Convert placed on Map attribute [" + collection.getRole()
+ "] must define attributeName of 'key' or 'value'"
);
}
if ( keyPath != null ) {
keyAttributeConversionInfoMap.put( keyPath, info );
}
else if ( elementPath != null ) {
elementAttributeConversionInfoMap.put( elementPath, info );
}
}
}
/**
* Check if path has the given prefix and remove it.
*
* @param path Path.
* @param prefix Prefix.
* @return Path without prefix, or null, if path did not have the prefix.
*/
private String removePrefix(String path, String prefix) {
if ( path.equals(prefix) ) {
return "";
}
if (path.startsWith(prefix + ".")) {
return path.substring( prefix.length() + 1 );
}
return null;
}
@Override
protected String normalizeCompositePath(String attributeName) {
return attributeName;
@ -219,8 +237,17 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
@Override
protected AttributeConversionInfo locateAttributeConversionInfo(String path) {
// todo : implement
return null;
final String key = removePrefix( path, "key" );
if ( key != null ) {
return keyAttributeConversionInfoMap.get( key );
}
final String element = removePrefix( path, "element" );
if ( element != null ) {
return elementAttributeConversionInfoMap.get( element );
}
return elementAttributeConversionInfoMap.get( path );
}
public String getClassName() {

View File

@ -0,0 +1,201 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.type.converter;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.AttributeConverter;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Converts;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* Test for {@link org.hibernate.cfg.CollectionPropertyHolder}.
*
* Tests that {@link javax.persistence.AttributeConverter}s are considered correctly for {@link javax.persistence.ElementCollection}.
*
* @author Markus Heiden
* @author Steve Ebersole
*/
@TestForIssue( jiraKey = "HHH-9495" )
public class ElementCollectionTests extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { TheEntity.class };
}
@Test
public void testSimpleConvertUsage() throws MalformedURLException {
// first some assertions of the metamodel
PersistentClass entityBinding = metadata().getEntityBinding( TheEntity.class.getName() );
assertNotNull( entityBinding );
Property setAttributeBinding = entityBinding.getProperty( "set" );
Collection setBinding = (Collection) setAttributeBinding.getValue();
assertTyping( AttributeConverterTypeAdapter.class, setBinding.getElement().getType() );
Property mapAttributeBinding = entityBinding.getProperty( "map" );
IndexedCollection mapBinding = (IndexedCollection) mapAttributeBinding.getValue();
assertTyping( AttributeConverterTypeAdapter.class, mapBinding.getIndex().getType() );
assertTyping( AttributeConverterTypeAdapter.class, mapBinding.getElement().getType() );
// now lets try to use the model, integration-testing-style!
TheEntity entity = new TheEntity(1);
Session s = openSession();
s.beginTransaction();
s.save( entity );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
TheEntity retrieved = (TheEntity) s.load( TheEntity.class, 1 );
assertEquals( 1, retrieved.getSet().size() );
assertEquals(new ValueType("set_value"), retrieved.getSet().iterator().next());
assertEquals(1, retrieved.getMap().size());
assertEquals(new ValueType("map_value"), retrieved.getMap().get(new ValueType("map_key")));
s.delete( retrieved );
s.getTransaction().commit();
s.close();
}
/**
* Non-serializable value type.
*/
public static class ValueType {
private final String value;
public ValueType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
return o instanceof ValueType &&
value.equals(((ValueType) o).value);
}
@Override
public int hashCode() {
return value.hashCode();
}
}
/**
* Converter for {@link ValueType}.
*/
public static class ValueTypeConverter implements AttributeConverter<ValueType, String> {
@Override
public String convertToDatabaseColumn(ValueType type) {
return type.getValue();
}
@Override
public ValueType convertToEntityAttribute(String type) {
return new ValueType(type);
}
}
/**
* Entity holding element collections.
*/
@Entity( name = "TheEntity" )
@Table(name = "entity")
public static class TheEntity {
@Id
public Integer id;
/**
* Element set with converter.
*/
@Convert( converter = ValueTypeConverter.class )
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "entity_set", joinColumns = @JoinColumn(name = "entity_id", nullable = false))
@Column(name = "value", nullable = false)
public Set<ValueType> set = new HashSet<ValueType>();
/**
* Element map with converters.
*/
@Converts({
@Convert(attributeName = "key", converter = ValueTypeConverter.class),
@Convert(attributeName = "value", converter = ValueTypeConverter.class)
})
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "entity_map", joinColumns = @JoinColumn(name = "entity_id", nullable = false))
@MapKeyColumn(name = "key", nullable = false)
@Column(name = "value", nullable = false)
public Map<ValueType, ValueType> map = new HashMap<ValueType, ValueType>();
public TheEntity() {
}
public TheEntity(Integer id) {
this.id = id;
this.set.add(new ValueType("set_value"));
this.map.put( new ValueType( "map_key" ), new ValueType( "map_value" ) );
}
public Set<ValueType> getSet() {
return set;
}
public Map<ValueType, ValueType> getMap() {
return map;
}
}
}