Hi Matthew,
I know what you mean and went through a similar thought process to
yours. In the end, the typing of the converter interface was compelling
enough to warrant using it. Describing the method constraints would be a
lot more work that just seemed unnecessary and more error-prone during
development.
On the scanning end, providers have not thus far had to do any
class-scanning based on interface implementation, so the annotation made
some sense. It also gives us a nicer place to add the extra autoApply
option (although in the absence of the annotation I suppose we could
just add an autoApply() method to the interface).
-Mike
On 14/03/2012 10:10 AM, Matthew Adams wrote:
> Sorry, catching up on this thread. There is one point about which I
> have a question.
>
> Often, the use of annotations is to not require an implementation
> class of some behavior to implement an interface, like the lifecycle
> callback annotations (@PrePersist, etc). Then, the developer has an
> often mutually exclusive choice to either implement the interface or
> use the annotations.
>
> In this case, it looks like we're offering the interface,
> AttributeConverter<X,Y>, and the annotation, @Converter. It seems to
> me that these should be mutually exclusive or, if both used, only be
> allowed to used compatibly.
>
> To abide by the spirit of the entity lifecycle callback methods, I
> expected the proposal to look more like this:
>
> @Retention(RetentionPolicy.RUNTIME)
> @Target(ElementType.TYPE)
> public @interface AttributeConverter {
> boolean autoApply() default true;
> }
>
> @Retention(RetentionPolicy.RUNTIME)
> @Target(ElementType.METHOD)
> public @interface Converter {
> Direction value();
> }
>
> public enum Direction {
> TO_DATABASE,
> FROM_DATABASE
> }
>
>
> So that it could be used like this:
>
> @AttributeConverter // types inferred from methods below
> public class BooleanToIntegerConverter {
> @Converter(Direction.TO_DATABASE)
> public Integer booleanToInteger(Boolean b) {
> return b == null ? null : b ? : 1 : 0;
> }
> @Converter(Direction.FROM_DATABASE)
> public Boolean integerToBoolean(Integer i) {
> return i == null ? null : i == 1 ? true : false;
> }
> }
>
> It's behaviorally equivalent and in the same spirit as the entity
> callback annotations, except that multiple methods would be required
> within the class. I'd say users could use the interface or the
> annotations, but not both.
>
> I also feel that the persistence provider could scan the classpath for
> either annotated classes or classes that implement the interface.
>
> Thoughts?
>
> -matthew
>
> On Mon, Mar 5, 2012 at 7:26 PM, Linda DeMichiel
> <linda.demichiel_at_oracle.com> wrote:
>> Here's an update of my original writeup. This attempts to incorporate
>> the results of the discussion so far, and cleans up some of the
>> description. There are a couple of open issues at the end.
>>
>> -Linda
>>
>> ---------------------------
>>
>> Converters may be specified to provide conversion between the entity
>> attribute representation and the database representation for
>> attributes of basic types. Converters may be used to convert
>> attributes defined by entity classes, mapped superclasses, or
>> embeddable classes.
>>
>> The conversion of all basic types are supported except for the
>> following: Id attributes, version attributes, relationship attributes,
>> and attributes explicitly annotated (or designated via XML) as
>> Enumerated or Temporal. Auto-apply converters will not be applied to
>> such attributes, and applications that apply converters to such
>> attributes through use of the Convert annotation will not be portable.
>>
>> The persistence provider runtime is responsible for invoking the
>> corresponding conversion method when loading the entity attribute from
>> the database and before storing the entity attribute state to the
>> database. The persistence provider must apply any conversion mappings
>> to instances of attribute values used within JPQL or criteria queries
>> (such as in comparisons, bulk updates, etc.) before sending them to
>> the database for the query execution. If the result of a JPQL or
>> criteria query includes one or more entity attributes for which
>> conversion mappings have been specified, the persistence provider must
>> apply the specified conversions to the corresponding values in the
>> query result before returning them to the application.
>>
>> An attribute converter must implement the
>> javax.persistence.mapping.AttributeConverter interface.
>>
>> /**
>> * A class that implements this interface can be used to convert entity
>> * attribute state into database column representation and back again.
>> * Note that the X and Y types may be the same Java type.
>> *
>> * @param X the type of the entity attribute
>> * @param Y the type of the database column
>> */
>> public interface AttributeConverter<X,Y> {
>>
>> /**
>> * Converts the value stored in the entity attribute into the data
>> * representation to be stored in the database.
>> *
>> * @param attribute the entity attribute value to be converted
>> * @return the converted data to be stored in the database column
>> */
>> public Y convertToDatabaseColumn (X attribute);
>>
>> /**
>> * Converts the data stored in the database column into the value
>> * to be stored in the entity attribute.
>> * Note that it is the responsibility of the converter writer to
>> * specify the correct dbData type for the corresponding column for
>> * use by the JDBC driver, i.e., persistence providers are not expected
>> * to do such type conversion.
>> *
>> * @param dbData the data from the database column to be converted
>> * @return the converted value to be stored in the entity attribute
>> */
>> public X convertToEntityAttribute (Y dbData);
>> }
>>
>>
>> A converter class must be annotated with the Converter annotation or
>> defined in the object/relational mapping descriptor as a converter.
>>
>> /**
>> * Specifies that the annotated class is a converter and defines its scope
>> */
>> @Target({Type})
>> @Retention(RUNTIME)
>> public @interface Converter {
>>
>> /**
>> * If set to true, specifies that the converter will automatically
>> * be applied to all mapped attributes of the specified
>> * target type for all entities in the persistence unit
>> * unless overridden by means of the Convert annotation (or XML
>> equivalent).
>> * In determining whether a converter is applicable to an attribute,
>> * the provider must treat primitive types and wrapper types as
>> equivalent.
>> *
>> * Note that Id attributes, version attributes, relationship
>> * attributes, and attributes explicitly annotated as Enumerated
>> * or Temporal (or designated as such via XML) will not be converted.
>> *
>> * If autoApply is false, only those attributes of the target type
>> * for which the Convert annotation (or corresponding XML element) has
>> * been specified will be converted.
>> *
>> * If there is more than one converter defined for the same target
>> * type, the Convert annotation should be used to explicitly specify
>> * which converter to use.
>> *
>> * Note that if autoApply is true, the Convert annotation may be used to
>> * override or disable auto-apply conversion on a per-attribute basis.
>> */
>> boolean autoApply() default false;
>> }
>>
>>
>> Type conversion may be specified at the level of individual attributes
>> by means of the Convert annotation. The Convert annotation may also be
>> used to override or disable an auto-apply conversion.
>>
>> The Convert annotation may be applied directly to an attribute of an
>> entity, mapped superclass, or embeddable class to specify conversion of
>> the attribute or to override the use of a converter that has been
>> specified as autoApply=true. When persistent properties are used, the
>> Convert
>> annotation is applied to the getter method.
>>
>> The Convert annotation may be applied to an entity that extends a mapped
>> superclass to specify or override the conversion mapping for an inherited
>> basic or embedded attribute.
>>
>>
>> @Target({METHOD, FIELD, TYPE})
>> @Retention(RUNTIME)
>> public @interface Convert {
>>
>> /**
>> * Specifies the converter to be applied. A value for this
>> * element must be specified if multiple converters would
>> * otherwise apply.
>> */
>> Class converter() default void.class;
>>
>> /**
>> * The attributeName must be specified unless the Convert annotation
>> * is on an attribute of basic type or on an element collection of basic
>> * type. In these cases, attributeName must not be specified.
>> */
>> String attributeName() default "";
>>
>> /**
>> * Used to disable an auto-apply or inherited converter.
>> * If disableConversion is true, the converter element should
>> * not be specified.
>> */
>> boolean disableConversion() default false;
>> }
>>
>> /**
>> * Used to group Convert annotations
>> */
>> @Target({METHOD, FIELD, TYPE})
>> @Retention(RUNTIME)
>> public @interface Converts {
>> Convert[] value();
>> }
>>
>>
>>
>> The Convert annotation is used to specify the conversion of a Basic
>> (whether explicit or default) field or property. The Convert annotation
>> should not be used to specify conversion of the following:
>> Id attributes, version attributes, relationship attributes, and attributes
>> explicitly annotated (or designated via XML) as Enumerated or Temporal.
>> Applications that specify such conversions will not be portable.
>>
>>
>> The Convert annotation may be applied to a basic attribute or to
>> an element collection of basic type (in which case the converter
>> is applied to the elements of the collection). In these cases, the
>> attributeName element must not be specified.
>>
>> Examples:
>>
>> @Converter
>> public class BooleanToIntegerConverter implements
>> AttributeConverter<Boolean, Integer> { ... }
>>
>> @Converter(autoApply=true)
>> public class EmployeeDateConverter implements
>> AttributeConverter<com.acme.EmployeeDate, java.sql.Date> { ... }
>>
>> @Entity
>> public class Employee {
>> @Id long id;
>>
>> @Convert(BooleanToIntegerConverter.class)
>> boolean fullTime;
>> ...
>> // EmployeeDateConverter is applied automatically
>> EmployeeDate startDate;
>> }
>>
>>
>> // Apply a converter to an element collection of basic type
>> @ElementCollection
>> @Convert(NameConverter.class) // applies to each element in the collection
>> List<String> names;
>>
>>
>> // Apply a converter to an element collection that is a map of basic values
>> // The converter is applied to the map *value*
>> @ElementCollection
>> @Convert(EmployeeNameConverter.class)
>> Map<String, String> responsibilities;
>>
>> When the Convert annotation is applied to a map to specify conversion
>> of a map key of basic type, "key" must be used to specify that it
>> is the map key that is to be converted.
>>
>> // Apply a converter to a Map key of basic type (relationship)
>> @OneToMany
>> @Convert(converter=ResponsibilityCodeConverter.class, attributeName="key")
>> Map<String, Employee> responsibilities;
>>
>> // Apply a converter to a Map key of basic type (element collection)
>> @ElementCollection
>> @Convert(converter=ResponsibilityCodeConverter.class, attributeName="key")
>> Map<String, String> responsibilities;
>>
>>
>> // Disable conversion in the presence of an autoApply converter
>> @Convert(disableConversion=true)
>> String myString;
>>
>>
>> The Convert annotation may be applied to an embedded attribute or to a
>> map collection attribute whose key or value is of embeddable type (in
>> which case the converter is applied to the specified attribute of the
>> embeddable instances contained in the collection). In these cases the
>> attributeName element must be specified.
>>
>> To override conversion mappings at multiple levels of embedding, a dot
>> (".") notation form must be used in the attributeName element to
>> indicate an attribute within an embedded attribute. The value of each
>> identifier used with the dot notation is the name of the respective
>> embedded field or property.
>>
>> When the Convert annotation is applied to a map containing
>> embeddables, the attributeName element must be specified, and "key."
>> or "value." must be used to prefix the name of the attribute that is
>> to be converted in order to specify it as part of the map key or map
>> value.
>>
>> // Apply a converter to an embeddable attribute
>> @Embedded
>> @Convert(converter=CountryConverter.class, attributeName="country")
>> Address address;
>>
>>
>> // Apply a converter to a nested embeddable attribute:
>> @Embedded
>> @Convert(converter=CityConverter.class, attributeName="region.city")
>> Address address;
>>
>> @Entity public class PropertyRecord {
>> ...
>> // Apply a converter to a nested attribute of an embeddable that is
>> // a map key of an element collection
>> @Convert(name="key.region.city", converter=CityConverter.class)
>> @ElementCollection
>> Map<Address, PropertyInfo> parcels;
>> }
>>
>> @OneToMany
>> // Apply to an embeddable that is a map key for a relationship
>> @Convert(attributeName="key.type",
>> converter=ResponsibilityTypeConverter.class)
>> Map<Responsibility, Employee> responsibilities;
>>
>>
>> The Convert annotation may be applied to an entity class that extends
>> a mapped superclass to specify or override a conversion mapping
>> for an inherited basic or embedded attribute.
>>
>> // Override conversion mappings for attributes inherited from a mapped
>> superclass
>> @Entity
>> @Converts({
>> @Convert(attributeName="startDate", converter=DateConverter.class),
>> @Convert(attributeName="endDate", converter=DateConverter.class)})
>> public class FullTimeEmployee extends GenericEmployee { ... }
>>
>>
>> OPEN ISSUES:
>>
>> There are still a couple of open issues:
>>
>> Open Issue: Conversion of @Id and @Version.
>>
>> Open Issue: Explicit listing of converters in persistence.xml file.
>> I'm not sure I understand what is being proposed here.
>>
>> Open Issue: What to do about the "specialization" issue that was
>> raised. Would someone care to provide some use cases and/or flesh out
>> that part of the proposal further?
>>
>> Please let me know if I have missed anything or if there are any other
>> corrections.
>>
>>
>