HHH-10277 - AttributeConverter not applied to attributes of an embeddable used as collection element

(cherry picked from commit b7f17ce898)
This commit is contained in:
Steve Ebersole 2016-06-16 11:09:13 -05:00 committed by Gail Badner
parent f4bc38f92f
commit e8e8538228
5 changed files with 98 additions and 44 deletions

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg; package org.hibernate.cfg;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.persistence.Convert; import javax.persistence.Convert;
import javax.persistence.Converts; import javax.persistence.Converts;
@ -144,16 +145,31 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
} }
else { else {
// the @Convert named an attribute... // 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 ) { // we have different "resolution rules" based on whether element and key can be converted
final String keyPath;
final String elementPath;
if ( canElementBeConverted && canKeyBeConverted ) {
keyPath = removePrefix( info.getAttributeName(), "key" );
elementPath = removePrefix( info.getAttributeName(), "value" );
if ( keyPath == null && elementPath == null ) {
// specified attributeName needs to have 'key.' or 'value.' prefix // specified attributeName needs to have 'key.' or 'value.' prefix
throw new IllegalStateException( throw new IllegalStateException(
"@Convert placed on Map attribute [" + collection.getRole() "@Convert placed on Map attribute [" + collection.getRole()
+ "] must define attributeName of 'key' or 'value'" + "] must define attributeName of 'key' or 'value'"
); );
} }
}
else if ( canKeyBeConverted ) {
keyPath = removePrefix( info.getAttributeName(), "key", info.getAttributeName() );
elementPath = null;
}
else {
keyPath = null;
elementPath = removePrefix( info.getAttributeName(), "value", info.getAttributeName() );
}
if ( keyPath != null ) { if ( keyPath != null ) {
keyAttributeConversionInfoMap.put( keyPath, info ); keyAttributeConversionInfoMap.put( keyPath, info );
@ -161,6 +177,17 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
else if ( elementPath != null ) { else if ( elementPath != null ) {
elementAttributeConversionInfoMap.put( elementPath, info ); elementAttributeConversionInfoMap.put( elementPath, info );
} }
else {
// specified attributeName needs to have 'key.' or 'value.' prefix
throw new IllegalStateException(
String.format(
Locale.ROOT,
"Could not determine how to apply @Convert(attributeName='%s') to collection [%s]",
info.getAttributeName(),
collection.getRole()
)
);
}
} }
} }
@ -172,6 +199,10 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
* @return Path without prefix, or null, if path did not have the prefix. * @return Path without prefix, or null, if path did not have the prefix.
*/ */
private String removePrefix(String path, String prefix) { private String removePrefix(String path, String prefix) {
return removePrefix( path, prefix, null );
}
private String removePrefix(String path, String prefix, String defaultValue) {
if ( path.equals(prefix) ) { if ( path.equals(prefix) ) {
return ""; return "";
} }
@ -180,7 +211,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
return path.substring( prefix.length() + 1 ); return path.substring( prefix.length() + 1 );
} }
return null; return defaultValue;
} }
@Override @Override

View File

@ -64,7 +64,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
private Component component; private Component component;
private boolean isOrWithinEmbeddedId; private boolean isOrWithinEmbeddedId;
private boolean virtual; // private boolean virtual;
private String embeddedAttributeName; private String embeddedAttributeName;
private Map<String,AttributeConversionInfo> attributeConversionInfoMap; private Map<String,AttributeConversionInfo> attributeConversionInfoMap;
@ -84,14 +84,20 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
( embeddedXProperty.isAnnotationPresent( Id.class ) ( embeddedXProperty.isAnnotationPresent( Id.class )
|| embeddedXProperty.isAnnotationPresent( EmbeddedId.class ) ) ); || embeddedXProperty.isAnnotationPresent( EmbeddedId.class ) ) );
this.virtual = embeddedXProperty == null; if ( embeddedXProperty != null ) {
if ( !virtual ) { // this.virtual = false;
this.embeddedAttributeName = embeddedXProperty.getName(); this.embeddedAttributeName = embeddedXProperty.getName();
this.attributeConversionInfoMap = processAttributeConversions( embeddedXProperty ); this.attributeConversionInfoMap = processAttributeConversions( embeddedXProperty );
} }
else { else {
embeddedAttributeName = ""; // could be either:
this.attributeConversionInfoMap = Collections.emptyMap(); // 1) virtual/dynamic component
// 2) collection element/key
// temp
// this.virtual = true;
this.embeddedAttributeName = "";
this.attributeConversionInfoMap = processAttributeConversions( inferredData.getClassOrElement() );
} }
} }
@ -119,30 +125,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
// from the Embedded // from the Embedded
// first apply conversions from the Embeddable... // first apply conversions from the Embeddable...
{ processAttributeConversions( embeddableXClass, infoMap );
// @Convert annotation on the Embeddable class level
final Convert convertAnnotation = embeddableXClass.getAnnotation( Convert.class );
if ( convertAnnotation != null ) {
final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
throw new IllegalStateException( "@Convert placed on @Embeddable must define attributeName" );
}
infoMap.put( info.getAttributeName(), info );
}
}
{
// @Converts annotation on the Embeddable class level
final Converts convertsAnnotation = embeddableXClass.getAnnotation( Converts.class );
if ( convertsAnnotation != null ) {
for ( Convert convertAnnotation : convertsAnnotation.value() ) {
final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
throw new IllegalStateException( "@Converts placed on @Embeddable must define attributeName" );
}
infoMap.put( info.getAttributeName(), info );
}
}
}
// then we can overlay any conversions from the Embedded attribute // then we can overlay any conversions from the Embedded attribute
{ {
@ -173,6 +156,39 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
return infoMap; return infoMap;
} }
private void processAttributeConversions(XClass embeddableXClass, Map<String, AttributeConversionInfo> infoMap) {
{
// @Convert annotation on the Embeddable class level
final Convert convertAnnotation = embeddableXClass.getAnnotation( Convert.class );
if ( convertAnnotation != null ) {
final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
throw new IllegalStateException( "@Convert placed on @Embeddable must define attributeName" );
}
infoMap.put( info.getAttributeName(), info );
}
}
{
// @Converts annotation on the Embeddable class level
final Converts convertsAnnotation = embeddableXClass.getAnnotation( Converts.class );
if ( convertsAnnotation != null ) {
for ( Convert convertAnnotation : convertsAnnotation.value() ) {
final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, embeddableXClass );
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
throw new IllegalStateException( "@Converts placed on @Embeddable must define attributeName" );
}
infoMap.put( info.getAttributeName(), info );
}
}
}
}
private Map<String,AttributeConversionInfo> processAttributeConversions(XClass embeddableXClass) {
final Map<String,AttributeConversionInfo> infoMap = new HashMap<String, AttributeConversionInfo>();
processAttributeConversions( embeddableXClass, infoMap );
return infoMap;
}
@Override @Override
protected String normalizeCompositePath(String attributeName) { protected String normalizeCompositePath(String attributeName) {
return embeddedAttributeName + '.' + attributeName; return embeddedAttributeName + '.' + attributeName;
@ -189,9 +205,9 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
return; return;
} }
if ( virtual ) { // if ( virtual ) {
return; // return;
} // }
// again : the property coming in here *should* be the property on the embeddable (Address#city in the example), // again : the property coming in here *should* be the property on the embeddable (Address#city in the example),
// so we just ignore it if there is already an existing conversion info for that path since they would have // so we just ignore it if there is already an existing conversion info for that path since they would have
@ -239,7 +255,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
@Override @Override
protected AttributeConversionInfo locateAttributeConversionInfo(String path) { protected AttributeConversionInfo locateAttributeConversionInfo(String path) {
final String embeddedPath = embeddedAttributeName + '.' + path; final String embeddedPath = StringHelper.qualifyConditionally( embeddedAttributeName, path );
AttributeConversionInfo fromParent = parent.locateAttributeConversionInfo( embeddedPath ); AttributeConversionInfo fromParent = parent.locateAttributeConversionInfo( embeddedPath );
if ( fromParent != null ) { if ( fromParent != null ) {
return fromParent; return fromParent;

View File

@ -1402,6 +1402,8 @@ public abstract class CollectionBinder {
} }
if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) { if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) {
holder.prepare( property );
EntityBinder entityBinder = new EntityBinder(); EntityBinder entityBinder = new EntityBinder();
PersistentClass owner = collValue.getOwner(); PersistentClass owner = collValue.getOwner();
boolean isPropertyAnnotated; boolean isPropertyAnnotated;

View File

@ -497,6 +497,13 @@ public final class StringHelper {
return prefix + '.' + name; return prefix + '.' + name;
} }
public static String qualifyConditionally(String prefix, String name) {
if ( name == null ) {
throw new NullPointerException( "name was null attempting to build qualified name" );
}
return isEmpty( prefix ) ? name : prefix + '.' + name;
}
public static String[] qualify(String prefix, String[] names) { public static String[] qualify(String prefix, String[] names) {
if ( prefix == null ) { if ( prefix == null ) {
return names; return names;

View File

@ -32,7 +32,6 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SimpleValue;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.After; import org.junit.After;
@ -68,7 +67,6 @@ public class CollectionCompositeElementExplicitConversionTest extends BaseUnitTe
} }
@Test @Test
@FailureExpected( jiraKey = "HHH-10277" )
public void testCollectionOfEmbeddablesWithConvertedAttributes() throws Exception { public void testCollectionOfEmbeddablesWithConvertedAttributes() throws Exception {
final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) final MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr )
.addAnnotatedClass( Disguise.class ) .addAnnotatedClass( Disguise.class )