HHH-9495 - @Convert support for collections
This commit is contained in:
parent
8284b9ae2c
commit
f45b37da8e
|
@ -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'"
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue