You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Converting between types fails when an AttributeConverter class has a generic type parameter, but implements AttributeConverter<..., ...> is specified on a superclass.
For example, a converter static class PowerControlSystemJpaConverter extends JsonSerializableStringConverter<PowerControlSystem>, where base is abstract class JsonSerializableStringConverter<T> implements AttributeConverter<T, String>.
Some debugging seems to suggest that the problem is in org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ConverterAccessor#initClassificationClasses where in such case genericTypes are resolved as:
Because of this fieldClassification is determined incorrectly (as it is assumed to be the last generic type).
Context and partial example
We have many different types of objects that know how to serialize themselves to and from Jackson's JsonNode, and we wanted to create a generic attribute converter that would handle the generic JsonNode <-> String conversion, yet would allow pluggable Object <--> JsonNode conversion.
The generic converter looked like this:
package com.company.infrastructure.persistence.jpa;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.AttributeConverter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.function.Function;
public abstract class JsonSerializableStringConverter<T> implements AttributeConverter<T, String> {
private static final JsonMapper mapper = JsonMapper.builder().build();
private final Function<T, JsonNode> serializationFunction;
private final Function<JsonNode, T> deserializationFunction;
protected JsonSerializableStringConverter(Function<T, JsonNode> serializationFunction, Function<JsonNode, T> deserializationFunction) {
this.serializationFunction = serializationFunction;
this.deserializationFunction = deserializationFunction;
}
@Override
public final String convertToDatabaseColumn(T attribute) {
JsonNode json = serializationFunction.apply(attribute);
try {
return mapper.writeValueAsString(json);
} catch (JsonProcessingException e) {
throw new UncheckedIOException("Can't convert JsonNode to String. Reason: " + e.getMessage(), e);
}
}
@Override
public final T convertToEntityAttribute(String dbData) {
try {
JsonNode json = mapper.readTree(dbData);
return deserializationFunction.apply(json);
} catch (IOException e) {
throw new UncheckedIOException("Failed to convert String to JsonNode. Reason: " + e.getMessage(), e);
}
}
}
and then the converter for specific type of objects can be as simple as:
@Converter
public static class PowerControlSystemJpaConverter extends JsonSerializableStringConverter<PowerControlSystem> {
public JpaConverter() {
super(
object -> object.asJsonNode(), // serialization function
json -> new PowerControlSystem(json) // deserialization function
);
}
}
org.eclipse.persistence.exceptions.ConversionException:
Exception Description: The object [{"definitionId":"b1cc196a-e01f-4063-8110-396fbe5d1202"}], of class [class java.lang.String], from mapping [org.eclipse.persistence.mappings.DirectToFieldMapping[powerControlSystem-->pvsystem.POWERCONTROLSYSTEM]] with descriptor [RelationalDescriptor(com.company.core.domain.AbstractPhotovoltaicGridTieSystem --> [DatabaseTable(pvsystem)])], could not be converted to [class com.company.core.domain.PowerControlSystem].
at org.eclipse.persistence.exceptions.ConversionException.couldNotBeConverted(ConversionException.java:81) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.internal.helper.ConversionManager.convertObject(ConversionManager.java:268) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform.convertObject(DatasourcePlatform.java:251) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.getFieldValue(AbstractDirectMapping.java:801) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.buildCloneValue(AbstractDirectMapping.java:261) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.mergeIntoObject(AbstractDirectMapping.java:1061) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.mergeIntoObject(ObjectBuilder.java:4281) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfCloneIntoWorkingCopy(MergeManager.java:612) ~[org.eclipse.persistence.core-4.0.2.jar:na]
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:324) ~[org.eclipse.persistence.core-4.0.2.jar:na]
...
EclipseLink version: 4.0.2
Java/JDK version: 21
Expected behavior
I'd hope the AttributeConverter would work regardless of whether implements AttributeConverter<T1, T2> is specified on the class itself, or on a superclass.
Workaround
Specify implements AttributeConverter again on the child class:
@Converter
public static class PowerControlSystemJpaConverter extends JsonSerializableStringConverter<PowerControlSystem>
implements AttributeConverter<PowerControlSystem, String> // <-- add this
{
public JpaConverter() {
super(
object -> object.asJsonNode(), // serialization function
json -> new PowerControlSystem(json) // deserialization function
);
}
}
Problem
Converting between types fails when an AttributeConverter class has a generic type parameter, but
implements AttributeConverter<..., ...>
is specified on a superclass.For example, a converter
static class PowerControlSystemJpaConverter extends JsonSerializableStringConverter<PowerControlSystem>
, where base isabstract class JsonSerializableStringConverter<T> implements AttributeConverter<T, String>
.Some debugging seems to suggest that the problem is in
org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ConverterAccessor#initClassificationClasses
where in such casegenericTypes
are resolved as:while if we do not use a base class (i.e., flatten the hierarchy), it looks like this:
Because of this
fieldClassification
is determined incorrectly (as it is assumed to be the last generic type).Context and partial example
We have many different types of objects that know how to serialize themselves to and from Jackson's
JsonNode
, and we wanted to create a generic attribute converter that would handle the genericJsonNode
<->String
conversion, yet would allow pluggableObject
<-->JsonNode
conversion.The generic converter looked like this:
and then the converter for specific type of objects can be as simple as:
Converter definition then looks like this then:
The exception we get is e.g.:
Expected behavior
I'd hope the
AttributeConverter
would work regardless of whetherimplements AttributeConverter<T1, T2>
is specified on the class itself, or on a superclass.Workaround
Specify
implements AttributeConverter
again on the child class:then
genericTypes
are resolved correctly:The text was updated successfully, but these errors were encountered: