Skip to main content

[jsr338-experts] Re: converters (updated)

  • From: michael keith <michael.keith@...>
  • To: jsr338-experts@...
  • Subject: [jsr338-experts] Re: converters (updated)
  • Date: Wed, 14 Mar 2012 16:57:11 -0400
  • Organization: Oracle Corporation

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@...>  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.





[jsr338-experts] converters (updated)

Linda DeMichiel 03/06/2012

[jsr338-experts] Re: converters (updated)

Matthew Adams 03/14/2012

[jsr338-experts] Re: converters (updated)

michael keith 03/14/2012
 
 
Close
loading
Please Confirm
Close