HHH-12338 - Incorrect metamodel for basic collections

This commit is contained in:
helloztt 2017-08-19 18:05:05 +08:00 committed by Vlad Mihalcea
parent 139f354873
commit fe619ceb08
15 changed files with 515 additions and 6 deletions

View File

@ -138,14 +138,20 @@ public class MetaAttributeGenerationVisitor extends SimpleTypeVisitor6<Annotatio
accessTypeInfo.setDefaultAccessType( entity.getEntityAccessTypeInfo().getAccessType() );
}
}
if ( collection.equals( "javax.persistence.metamodel.MapAttribute" ) ) {
return createAnnotationMetaAttributeForMap( declaredType, element, collection, targetEntity );
}
else {
return new AnnotationMetaCollection(
entity, element, collection, getElementType( declaredType, targetEntity )
if ( TypeUtils.containsAnnotation( element, Constants.CONVERT ) ||
TypeUtils.containsAnnotation( element, Constants.HIBERNATE_TYPE ) ) {
return new AnnotationMetaSingleAttribute(
entity,
element,
TypeUtils.toTypeString( declaredType )
);
}
if ( collection.equals( Constants.MAP_ATTRIBUTE ) ) {
return createAnnotationMetaAttributeForMap( declaredType, element, collection, targetEntity );
}
return new AnnotationMetaCollection(
entity, element, collection, getElementType( declaredType, targetEntity )
);
}
@Override

View File

@ -30,6 +30,9 @@ public final class Constants {
public static final String MAP_KEY_CLASS = "javax.persistence.MapKeyClass";
public static final String ELEMENT_COLLECTION = "javax.persistence.ElementCollection";
public static final String ACCESS = "javax.persistence.Access";
public static final String MAP_ATTRIBUTE = "javax.persistence.metamodel.MapAttribute";
public static final String CONVERT = "javax.persistence.Convert";
public static final String HIBERNATE_TYPE = "org.hibernate.annotations.Type";
public static final Map<String, String> COLLECTIONS = new HashMap<String, String>();

View File

@ -0,0 +1,89 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import org.hibernate.jpamodelgen.test.util.CompilationTest;
import org.hibernate.jpamodelgen.test.util.TestForIssue;
import org.hibernate.jpamodelgen.test.util.WithClasses;
import org.junit.Test;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAttributeTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertListAttributeTypeInMetaModelFor;
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor;
/**
* @author helloztt
*/
public class CollectionAsBasicTypeTest extends CompilationTest {
@Test
@TestForIssue(jiraKey = "HHH-12338")
@WithClasses({Goods.class, Product.class})
public void testConvert() throws ClassNotFoundException, NoSuchFieldException {
assertMetamodelClassGeneratedFor(Product.class);
assertMetamodelClassGeneratedFor(Goods.class);
assertListAttributeTypeInMetaModelFor(
Goods.class,
"productList",
Product.class,
"ListAttribute generic type should be Product"
);
assertAttributeTypeInMetaModelFor(
Goods.class,
"tags",
Goods.class.getDeclaredField("tags").getGenericType(),
"Wrong meta model type"
);
}
@Test
@TestForIssue(jiraKey = "HHH-12338")
@WithClasses({Person.class})
public void testListType() throws ClassNotFoundException, NoSuchFieldException {
assertMetamodelClassGeneratedFor(Person.class);
assertAttributeTypeInMetaModelFor(
Person.class,
"phones",
Person.class.getDeclaredField("phones").getGenericType(),
"Wrong meta model type"
);
}
@Test
@TestForIssue(jiraKey = "HHH-12338")
@WithClasses({PersonPhone.class})
public void testListTypeWithImport() throws ClassNotFoundException, NoSuchFieldException {
assertMetamodelClassGeneratedFor(PersonPhone.class);
assertAttributeTypeInMetaModelFor(
PersonPhone.class,
"phones",
PersonPhone.class.getDeclaredField("phones").getGenericType(),
"Wrong meta model type"
);
}
@Test
@TestForIssue(jiraKey = "HHH-12338")
@WithClasses({PhoneBook.class})
public void testMapType() throws ClassNotFoundException, NoSuchFieldException {
assertMetamodelClassGeneratedFor(PhoneBook.class);
assertAttributeTypeInMetaModelFor(
PhoneBook.class,
"phones",
PhoneBook.class.getDeclaredField("phones").getGenericType(),
"Wrong meta model type"
);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
/**
* @author Vlad Mihalcea
*/
public class CommaDelimitedStringMapJavaTypeDescriptor extends AbstractTypeDescriptor<Map> {
public static final String DELIMITER = ",";
public CommaDelimitedStringMapJavaTypeDescriptor() {
super(
Map.class,
new MutableMutabilityPlan<Map>() {
@Override
protected Map deepCopyNotNull(Map value) {
return new HashMap( value );
}
}
);
}
@Override
public String toString(Map value) {
return null;
}
@Override
public Map fromString(String string) {
return null;
}
@Override
public <X> X unwrap(Map value, Class<X> type, WrapperOptions options) {
return (X) toString( value );
}
@Override
public <X> Map wrap(X value, WrapperOptions options) {
return fromString( (String) value );
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
/**
* @author Vlad Mihalcea
*/
public class CommaDelimitedStringsJavaTypeDescriptor extends AbstractTypeDescriptor<List> {
public static final String DELIMITER = ",";
public CommaDelimitedStringsJavaTypeDescriptor() {
super(
List.class,
new MutableMutabilityPlan<List>() {
@Override
protected List deepCopyNotNull(List value) {
return new ArrayList( value );
}
}
);
}
@Override
public String toString(List value) {
return ( (List<String>) value ).stream().collect( Collectors.joining( DELIMITER ) );
}
@Override
public List fromString(String string) {
List<String> values = new ArrayList<>();
Collections.addAll( values, string.split( DELIMITER ) );
return values;
}
@Override
public <X> X unwrap(List value, Class<X> type, WrapperOptions options) {
return (X) toString( value );
}
@Override
public <X> List wrap(X value, WrapperOptions options) {
return fromString( (String) value );
}
}

View File

@ -0,0 +1,31 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.List;
import java.util.Map;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
/**
* @author Vlad Mihalcea
*/
public class CommaDelimitedStringsMapType extends AbstractSingleColumnStandardBasicType<Map> {
public CommaDelimitedStringsMapType() {
super(
VarcharTypeDescriptor.INSTANCE,
new CommaDelimitedStringMapJavaTypeDescriptor()
);
}
@Override
public String getName() {
return "comma_delimited_string_map";
}
}

View File

@ -0,0 +1,30 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.List;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
/**
* @author Vlad Mihalcea
*/
public class CommaDelimitedStringsType extends AbstractSingleColumnStandardBasicType<List> {
public CommaDelimitedStringsType() {
super(
VarcharTypeDescriptor.INSTANCE,
new CommaDelimitedStringsJavaTypeDescriptor()
);
}
@Override
public String getName() {
return "comma_delimited_strings";
}
}

View File

@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.List;
/**
* @author helloztt
*/
@Entity
public class Goods {
private Long id;
private List<Product> productList;
private List<String> tags;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@OneToMany
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
@Convert(converter = StringToListConverter.class)
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
}

View File

@ -0,0 +1,27 @@
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
/**
* @author Vlad Mihalcea
*/
@Entity(name = "Person")
@TypeDef( name = "comma_delimited_strings", typeClass = CommaDelimitedStringsType.class)
public class Person {
@Id
private Long id;
@Type(type = "comma_delimited_strings")
private List<String> phones = new ArrayList<>();
public List<String> getPhones() {
return phones;
}
}

View File

@ -0,0 +1,28 @@
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.jpamodelgen.test.collectionbasictype.extras.Phone;
/**
* @author Vlad Mihalcea
*/
@Entity(name = "Person")
@TypeDef( name = "comma_delimited_strings", typeClass = CommaDelimitedStringsType.class)
public class PersonPhone {
@Id
private Long id;
@Type(type = "comma_delimited_strings")
private List<Phone> phones = new ArrayList<>();
public List<Phone> getPhones() {
return phones;
}
}

View File

@ -0,0 +1,27 @@
package org.hibernate.jpamodelgen.test.collectionbasictype;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
/**
* @author Vlad Mihalcea
*/
@Entity(name = "Person")
@TypeDef( name = "comma_delimited_string_map", typeClass = CommaDelimitedStringsMapType.class)
public class PhoneBook {
@Id
private Long id;
@Type(type = "comma_delimited_string_map")
private Map<String, String> phones = new HashMap<>();
public Map<String, String> getPhones() {
return phones;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author helloztt
*/
@Entity
public class Product {
private int proId;
private String proName;
@Id
public int getProId() {
return proId;
}
public void setProId(int proId) {
this.proId = proId;
}
public String getProName() {
return proName;
}
public void setProName(String proName) {
this.proName = proName;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.test.collectionbasictype;
import javax.persistence.AttributeConverter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author helloztt
*/
public class StringToListConverter implements AttributeConverter<List<String>, String> {
private static final String COMMA = ",";
@Override
public String convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.size() == 0) {
return null;
}
return attribute.stream().collect(Collectors.joining(COMMA));
}
@Override
public List<String> convertToEntityAttribute(String dbData) {
if (dbData == null || dbData.length() == 0) {
return null;
}
return Arrays.asList(dbData.split(COMMA));
}
}

View File

@ -0,0 +1,19 @@
package org.hibernate.jpamodelgen.test.collectionbasictype.extras;
import java.io.Serializable;
/**
* @author Vlad Mihalcea
*/
public class Phone implements Serializable {
private String phoneNumber;
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}

View File

@ -101,6 +101,18 @@ public class TestUtil {
);
}
public static void assertAttributeTypeInMetaModelFor(Class<?> clazz, String fieldName, Type expectedType, String errorString) {
Field field = getFieldFromMetamodelFor( clazz, fieldName );
assertNotNull( "Cannot find field '" + fieldName + "' in " + clazz.getName(), field );
ParameterizedType type = (ParameterizedType) field.getGenericType();
Type actualType = type.getActualTypeArguments()[1];
assertEquals(
"Types do not match: " + buildErrorString( errorString, clazz ),
expectedType,
actualType
);
}
public static void assertListAttributeTypeInMetaModelFor(Class<?> clazz, String fieldName, Class<?> expectedType, String errorString) {
Field field = getFieldFromMetamodelFor( clazz, fieldName );
assertNotNull( "Cannot find field '" + fieldName + "' in " + clazz.getName(), field );