Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "EclipseLink/Development/396542"

(Programmatic)
Line 275: Line 275:
 
     public <X> Class<X> getClassType();
 
     public <X> Class<X> getClassType();
 
}
 
}
 +
</source>
 +
</div>
 +
 +
 +
<div style="width:700px">
 +
<source lang="java">
 +
/**
 +
* Represents an AttributeNode of an entity graph.
 +
*/
 +
public interface AttributeNode<T> {
 +
 +
    /*
 +
    * returns the name of the referencing attribute.
 +
    */
 +
    public String getAttributeName();
 +
}
 +
</source>
 +
</div>
 +
 +
API will be added to JAXBContext to create a new instance of Object Graph.
 +
 +
Example:
 +
<div style="width:700px">
 +
<source lang="java">
 +
ObjectGraph graph = JAXBContext.createObjectGraph(Customer.class);
 +
graph.addAttributeNodes("lastName");
 +
graph.addAttributeNodes("age");
 +
SubGraph<Address> subGraph = graph.addSubGraph("address", Address.class);
 +
subGraph.addAttributeNodes("country");
 +
 +
jaxbMarshaller.setProperty(MarshallerProperties.MARSHAL_OBJECT_GRAPH, graph);
 
</source>
 
</source>
 
</div>
 
</div>

Revision as of 17:28, 13 February 2013

Design Specification: MOXy NamedObjectGraphs

ER 396542

Currently EclipseLink provides support for FetchGroups/AttributeGroups in ORM to control which attributes of a given object are to be written out and read in. Similar configuration should be allowed in OXM/JAXB to allow a subset of attributes to be marshaled and unmarshaled.

Requirements:

  1. Provide an API to configure which attributes should be marshaled and unmarshaled.
  2. Allow for nested subgraphs to be defined inline or globally on the target class.
  3. Allow different operations to use different subsets of attributes.
  4. Minimal impact on performance for the default case.
  5. Align with the JPA NamedEntityGraph API.

Configuration:

The configuration for this feature will use a set of annotations to set up attribute groups on the descriptor. The following annotations will be defined:

@Target({TYPE})
@Retention(RUNTIME)
public @interface NamedObjectGraphs{
     NamedObjectGraph[] value();
}
@Target({TYPE})
@Retention(RUNTIME)
public @interface NamedObjectGraph {
    /**
     * The name of this object graph. Defaults to the name of the class
     */
    String name();
 
    /**
     * The list of properties to be marshalled/unmarshalled for this graph.
     */
    NamedAttributeNode[] attributeNodes();
 
    /**
     * Optional: a list of named subgraphs that are referenced 
     * from the property entries.
     */
    NamedSubGraph[] subGraphs();
 
    /**
     * Optional: a list of named subgraphs for any subclasses
     * of this class.
     */
    NamedSubGraph[] subclassGraphs();
}
@Target({TYPE})
@Retention(RUNTIME)
public @interface NamedAttributeNode
    /**
     * required: the name of the property
     */
    String value();
 
    /** optional: if this property referenced another JAXB Object, 
     *  specify the name of the object graph to use for that nested object. 
     */ By default, the full object will be read.
    String subgraph();
}
@Target({TYPE})
@Retention(RUNTIME)
public @interface NamedSubGraph {
   /**
    * required: the name of the subgraph
    */
   String name();
 
   /**
    * optional: only required for inheritance or with ChoiceMappings 
    * to specify which of the possible targets this subgraph is to be
    * applied to. 
   Class type();
 
   /**
    * The list of properties to include in this graph
    */
   NamedAttributeNode[]  properties();
}

This allows for the user to specify a list of ObjectGraphs for each JAXB object, and to reference those from within each other. In the case that a targetObjectGraphName exists as both a subgraph of the current ObjectGraph AND as defined on the target class, the subgraph will win.

Example:

@NamedObjectGraph(name="simple", 
   properties={@NamedAttributeNode(value="firstName"), 
               @NamedAttributeNode(value="address", subgraph="simple")
   }
)
public class Employee {
   public String firstName;
   public String lastName;
   public Address address;
}
@NamedObjectGraph(name="simple", 
   properties={
      @NamedAttributeNode(value="firstName"), 
      @NamedAttributeNode(value="address", subgraph="basic")
   }, 
   subgraphs={
      @NamedSubGraph(name="basic", 
         properties={
            @NamedAttributeNode("city")
          }
   }
)
public class Customer {
   public String firstName;
   public String lastName;
   public Address address;
}
 
@NamedObjectGraphs(name="simple", 
   properties = {
      @NamedAttributeNode("street"), 
      @NamedAttributeNode("city"), 
      @NamedAttributeNode("country")
    }
)
public class Address {
   public String street;
   public String city;
   public String country;
   public String zipCode;
}

In this example, both Customer and Employee have “simple” fetch groups which only include the first name and the address. However, Customer is interested in only the city attribute on the address in it's simple form, whereas Employee wants a little more information, so uses the fetch group on Address that includes street, city, and country. Customer defines it's nested fetch group for Address inline. At marshal or unmarshal time, the name of the fetch group on the root object being marshalled (or unmarshalled) would be specified as part of the operation, and the appropriate attributes would be included.

External Metadata The equivalent of these annotations will also be provided in the eclispelink oxm external meta-data.

Programmatic

A set of interfaces will be defined to allow for the creation of ObjectGraphs dynamically at runtime. These ObjectGraphs can be set on the Marshaller or Unmarshaller instead of the name of a pre-defined ObjectGraph.

The following interfaces will be provided:

public interface ObjectGraph<T> {
    /**
     * Returns the name of the static EntityGraph.  Will return null if the
     * EntityGraph is not a named EntityGraph.
     */
    public String getName();
 
    /*
     * Add an AttributeNode attribute to the entity graph.
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this entity.
     * @throws IllegalStateException if this EntityGraph has been statically
     * defined
     */
    public <X> void addAttributeNodes(String ... attributeName);
 
 
    /*
     * Used to add a node of the graph that corresponds to a managed type. This
     * allows for construction of multi-node Entity graphs that include related
     * managed types.
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this entity.
     * @throws IllegalArgumentException if the attribute's target type is not a
     * managed type
     *
     * @throws IllegalStateException if this EntityGraph has been statically
     * defined
     */
    public <X> SubGraph<X> addSubGraph(String attribute);
 
    /**
     * Used to add a node of the graph that corresponds to a managed type with
     * inheritance.  This allows for multiple subclass sub-graphs to be defined
     * for this node of the entity graph.  Subclass sub-graphs will include the
     * specified attributes of superclass sub-graphs
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this managed type.
     * @throws IllegalArgumentException
     *             if the attribute's target type is not a managed type
     * @throws IllegalStateException
     *             if this EntityGraph has been statically defined
     */
    public <X> SubGraph<X> addSubGraph(String attribute, Class<X> type);
 
    /*
     * returns the attributes of this entity that are included in the entity
     * graph
     */
    public List<AttributeNode<?>> getAttributeNodes();
 
}
public interface SubGraph<T> extends AttributeNode<T> {
 
    /**
     * Add an AttributeNode attribute to the entity graph.
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this managed type.
     * @throws IllegalStateException
     *             if this EntityGraph has been statically defined
     */
    public <X> void  addAttributeNodes(String ... attributeName);
 
    /**
     * Used to add a node of the graph that corresponds to a managed type. This
     * allows for construction of multi-node Entity graphs that include related
     * managed types.
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this managed type.
     * @throws IllegalArgumentException
     *             if the attribute's target type is not a managed type
     * @throws IllegalStateException
     *             if this EntityGraph has been statically defined
     */
    public <X> SubGraph<X> addSubGraph(String attribute);
 
    /**
     * Used to add a node of the graph that corresponds to a managed type with
     * inheritance.  This allows for multiple subclass sub-graphs to be defined
     * for this node of the entity graph. Subclass sub-graphs will include the
     * specified attributes of superclass sub-graphs
     *
     * @throws IllegalArgumentException if the attribute is not an attribute of
     * this managed type.
     * @throws IllegalArgumentException
     *             if the attribute's target type is not a managed type
     * @throws IllegalStateException
     *             if this EntityGraph has been statically defined
     */
    public <X> SubGraph<X> addSubGraph(String attribute, Class<X> type);
 
 
 
    /**
     * returns the attributes of this managed type that are included in the
     * sub-graph
     */
    public List<AttributeNode<?>> getAttributeNodes();
 
    /**
     * returns the attribute that references this sub-graph.
     /
    public <T> Attribute<T,X> getReferencingAttribute();
 
    /**
     * returns the type of this sub-graph if it was used to extend a superclass
     * sub-graph definition.
     */
    public <X> Class<X> getClassType();
}


/**
 * Represents an AttributeNode of an entity graph.
 */
public interface AttributeNode<T> {
 
    /*
     * returns the name of the referencing attribute.
     */
    public String getAttributeName();
}

API will be added to JAXBContext to create a new instance of Object Graph.

Example:

ObjectGraph graph = JAXBContext.createObjectGraph(Customer.class);
graph.addAttributeNodes("lastName");
graph.addAttributeNodes("age");
SubGraph<Address> subGraph = graph.addSubGraph("address", Address.class);
subGraph.addAttributeNodes("country");
 
jaxbMarshaller.setProperty(MarshallerProperties.MARSHAL_OBJECT_GRAPH, graph);

Inheritance:

For the purpose of inheritance, by specifying an ObjectGraph on the subclass with the same name as the one specified on the superclass, the fetch group will extend the one from it's parent. Additionally, for subgraphs that are defined inline, multiple graphs with the same name can be used by specifying the type. For example, if we add CanadianAddress and AmericanAddress to the above example:

@NamedObjectGraph(name="simple", properties={@NamedAttributeNode("province")})
public class CanadianAddress extends Address {
  public String province;
}
@NamedObjectGraph(name="simple", properties={@NamedAttributeNode("state")})
public class AmericanAddress extends Address {
  public String state;
}

Now when marshalling using the "simple" fetch group for address, if the Address is a CanadianAddress then street, city, country and also province will be included.

ChoiceMappings:

One thing that differentiates OXM and JAXB from ORM is the concept of choice mappings. In this case an attribute can map to one or more different, unrelated objects. This could be handled in the same way as inheritance, where a subgraph is specified with the same name for each of the potential targets of this attribute. If the actual target at runtime doesn't include a graph/group by that name, then the default group is used.

Runtime API:

At runtime, when marshalling or unmarshalling, a property can be specified on the Marshaller or Unmarshaller to state which ObjectGraph should be used during marshalling:

JAXBContext ctx = JAXBContext.newInstance("foo.bar");
Marshaller m = ctx.createMarshaller();
m.setProperty(MarshalProperties.OBJECT_GRAPH, "simple");
m.marshal(myCustomer, System.out);

Additionally the OBJECT_GRAPH can be specified on the context, and will then apply to any Marshallers or Unmarshallers created from that context after the property is set.

JAXBContext ctx = JAXBContext.newInstance("foo.bar");
ctx.setProperty(MarshalProperties.OBJECT_GRAPH, "simple");
Marshaller m = ctx.createMarshaller();
m.marshal(myCustomer, System.out);


Error Cases:

If an AttributeNode is specified that doesn't correspond to an actual attribute on the domain class, an exception will be thrown

If a subgraph is specified then a subgraph with that name must exist or this is an exception case. In the case of a choice mapping, then at least one of the potential targets of that mapping must have a subGraph defined with that name.

If the OBJECT_GRAPH property is set to a name on the Marshaller or Unmarshaller, and there is no such ObjectGraph defined for the Object being marshalled/unmarshalled then an exception will be thrown.

Open Issues

  1. The underlying implementation in the runtime will make use of the existing AttributeGroup/AttributeItem classes. The dependency on these classes being added into the MOXy runtime will need to be considered as part of the ongoing footprint reduction work in MOXy.

Back to the top