Skip to content

Commit

Permalink
HHH-9495 - @convert support for collections
Browse files Browse the repository at this point in the history
(cherry picked from commit f45b37d)

Conflicts:
	hibernate-core/src/main/java/org/hibernate/cfg/CollectionPropertyHolder.java
  • Loading branch information
sebersole committed Mar 20, 2015
1 parent 33e490e commit cedcdae
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 21 deletions.
Expand Up @@ -138,6 +138,7 @@ private void applyLocalConvert(
}

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()
Expand All @@ -153,30 +154,46 @@ else if ( canElementBeConverted ) {
// 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;
Expand Down Expand Up @@ -214,8 +231,17 @@ else if ( canKeyBeConverted ) {

@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() {
Expand Down
@@ -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;
}
}
}

0 comments on commit cedcdae

Please sign in to comment.