HHH-10277 - AttributeConverter not applied to attributes of an embeddable used as collection element
(cherry picked from commit b7f17ce898
)
This commit is contained in:
parent
d2ed801ea8
commit
10123cf46a
|
@ -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,15 +145,30 @@ 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
|
||||||
// specified attributeName needs to have 'key.' or 'value.' prefix
|
final String keyPath;
|
||||||
throw new IllegalStateException(
|
final String elementPath;
|
||||||
"@Convert placed on Map attribute [" + collection.getRole()
|
|
||||||
+ "] must define attributeName of 'key' or 'value'"
|
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
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"@Convert placed on Map attribute [" + collection.getRole()
|
||||||
|
+ "] 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 ) {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1415,6 +1415,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;
|
||||||
|
|
|
@ -498,6 +498,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;
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
Loading…
Reference in New Issue