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/DesignDocs/340192"

(Metadata Repository)
 
(56 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
= Flex Columns Extension =
 
= Flex Columns Extension =
  
This feature allows mappings to be added to preexisting (unmapped) columns in a table.
+
== Why use this feature? ==
  
The Schema is designed to include preallocated columns that can be used to map additional data. In this example, Customer table might look like this:
+
# You are building an application where some mappings are common to all users and some mappings are user-specific
 +
# You want to add mappings to your application after it is made available to a customer (even post-deployment)
 +
# You want to use the same EntityManagerFactory to work with your data even after the mappings have changed
 +
# You can provide an additional source of metadata to be used by the application ( an xml-based source is provided, or you can build your own)
 +
 
 +
An example of the type of user this is designed for is a Software-as-a-Service provider who designs a generic application that can be provided to users and allow them to customize the application to make use of data that is particular to their domain.
 +
 
 +
== What basic steps do I have to take to use this feature ==
 +
 
 +
1. Design a standard JPA Application
 +
 
 +
2. Decide which Entities will allow flexible mappings, annotate them as such and provide facilities to store the addional data.
 +
 
 +
<source lang="java">
 +
 
 +
  @Entity
 +
  @VirtualAccessMethods
 +
  public class Customer{
 +
 
 +
...
 +
 
 +
    @Transient
 +
    private Map<String, Object> extensions;
 +
 
 +
    public <T> T get(String name) {
 +
        return (T) extentions.get(name);
 +
    }
 +
 
 +
    public Object set(String name, Object value) {
 +
        return extensions.put(name, value);
 +
    }
 +
</source>
 +
 
 +
 
 +
3. When you design your schema, provide enough extra columns in your tables to accomodate the number of flexible mappings you will allow.  e.g. The following table has 3 predefined columns and 3 columns designed to accomodate mappings added after design (FLEX_COL1, FLEX_COL2, FLEX_COL3)
  
 
* CUSTOMER
 
* CUSTOMER
Line 12: Line 46:
 
** VARCHAR FLEX_CO31
 
** VARCHAR FLEX_CO31
  
The columns starting with the String "FLEX_COL" are flexible and may have mappings that point to after initial application design. For instance at after the application has been designed, a user may add a mapping called "company" that maps to FLEX_COL1.
+
4. Deploy your application
  
The use case for a scenario like this is:
+
5. To provide additional mappings, provide an eclipselink-orm.xml file that contains the additional mappings.
  
* One company develops an application with the goal of making it available to a customer. The application contains all the functionality necessary for basic use of the application
+
<source lang="xml">
* The application is deployed and made available to the customer.  The customer can then tweak the application by adding mappings to data that is specific to their business
+
   
 +
  <basic name="idNumber" access="VIRTUAL" attribute-type="String">
 +
      <column name="FLEX_1"/>
 +
      <access-methods get-method="get" set-method="set"/>
 +
    </basic>
  
This kind of application works well in concert with a multi-tenant application where the initial application is developed and then each tenant can add mappings that are particular to their business.
+
</source>
 +
 
 +
6. Use persistence unit properties to get your application to use the file:
 +
 
 +
<source lang='xml'>
 +
  <property name="eclipselink.metadata-source" value="XML"/>
 +
  <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>
 +
</source>
  
 
= Requirements =
 
= Requirements =
  
* Users MUST be able to add after design time
+
* Users MUST be able to add mappings after initial deployment
 
* Extensions MUST be persistent.  (i.e. Extensions must continue to exist if an application goes down and comes back up)
 
* Extensions MUST be persistent.  (i.e. Extensions must continue to exist if an application goes down and comes back up)
 
* Extensions MUST be shared amoung all EntityManagers configured to use them
 
* Extensions MUST be shared amoung all EntityManagers configured to use them
 
* Extensions MUST be compatible with our multi-tenant features.  (i.e. Extensions must be able to make use of our Multi-tenant features to be definable on a tenant by tenant basis)
 
* Extensions MUST be compatible with our multi-tenant features.  (i.e. Extensions must be able to make use of our Multi-tenant features to be definable on a tenant by tenant basis)
* Extensions MUST have DDL Generation support
 
 
* BasicMappings MUST be supported
 
* BasicMappings MUST be supported
* Extensibility MUST be configurable using tradition JPA means (annotations, eclispelink-orm.xml)
+
* Extensibility MUST be configurable using traditional JPA means (annotations, eclipselink-orm.xml)
 
* It MUST be possible to use JPA Queries to query based on the extensions
 
* It MUST be possible to use JPA Queries to query based on the extensions
 
* Extensions SHOULD be supported through the JPA metamodel
 
* Extensions SHOULD be supported through the JPA metamodel
Line 41: Line 85:
 
Extensions will be supported using our VIRTUAL access type.  Virtual Access type allows properties to be set through a getter that takes an attribute name as an argument and a setter that takes an argument name and a value as an argument.
 
Extensions will be supported using our VIRTUAL access type.  Virtual Access type allows properties to be set through a getter that takes an attribute name as an argument and a setter that takes an argument name and a value as an argument.
  
Annotations and xml will be used to configure which mehtods will be used.  Annotating a field as @Extensible will indicate to EclipseLink that the class is extensible.  The value "FLEX" will be provided to the @Extensible annotation to indicate Flex Columns Extensibility.  Equivalent XML will be provided.
+
VIRTUAL access does not currently allow annotation-based configurationAs part of this feature, annotations will be added to configure the methods used by Virtual accessThese annotations will have the same effect as using the eclipselink-orm.xml construct <access-methods> at a class levelThey will default which methods are used by virtual mappingsEclipseLink will weave these methods if weaving is enabled.  This weaving will provide equivalent functionality to weaving for PROPERTY and FIELD access. (e.g. change tracking, fetch groups, etc)  
 
+
Along with @Extensible, and @FlexExtensions annotation will be provided that allows overrides to defaultsThe main purpose of this annotation will be to provide information about additional columns needed to our DDL generation featureThat annotation will allow the following data.
+
 
+
* numberOfColunms (default 10) - the number of flexible columns to generate into the DDL
+
* columnNamePrefix (default "FLEX_") - the prefix for the column name. When EclipseLink autogenerates the columns a number will be provided as a suffix to complete the column name(i.e. the first column will be called FLEX_1 and the second column will be called FLEX_2)
+
* defaultColumnType (default VARCHAR(255) - the default type of the columns
+
* createNonExistingColumns - Hook for ALTER TABLE Functionality''Link to feature doc will be provided later''
+
* columns (default is an emptyList) - Note: This functionality will be implemented in our 2nd iteration of development.  This is an override column that allows you to override the names and types of certain columns and contains a list of @FlexColumn annotations.
+
 
+
@FlexColumn contains: (Note: as above FlexColumn will be implemented in our 2nd iteration of development)
+
  
* index - required - the index of the column to update
+
DDL generation for tables with flexible columns will be addressed in a separate feature that addresses flexible DDL generation as a complete feature.
* name - an override for the default column name provided
+
* type - an override for the default type provided
+
  
 
=== Examples ===
 
=== Examples ===
Line 62: Line 94:
 
* Field Access for non extension fields
 
* Field Access for non extension fields
 
* Virtual Access for extension fields uses defaults (get(String), set(String, Object))
 
* Virtual Access for extension fields uses defaults (get(String), set(String, Object))
 +
* get(String) and set(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods
 
* extensions mapped in a portable way - @Transient
 
* extensions mapped in a portable way - @Transient
* @FlexExtensions optional annotation for configuring overrides for defaults - used mainly by DDL Generation
 
  
 
<source lang="java">
 
<source lang="java">
  
 
   @Entity
 
   @Entity
   @Extensible(FLEX)
+
   @VirtualAccessMethods
  @FlexExtensions(columnNamePrefix="FLEX",
+
    numberOfColumns=10,
+
    defaultColumnType="VARCHAR(30)"   
+
    createNonExistingColumns=false)
+
 
   public class Address {
 
   public class Address {
  
Line 89: Line 117:
 
     }
 
     }
  
     public Object set(String name, String value) {
+
     public Object set(String name, Object value) {
 
         return extensions.put(name, value);
 
         return extensions.put(name, value);
 
     }
 
     }
Line 101: Line 129:
 
* Field Access for non extension fields
 
* Field Access for non extension fields
 
* extensions mapped in a portable way - @Transient
 
* extensions mapped in a portable way - @Transient
* Preexisting table assumed.  @FlexExtensions annotation omitted
+
* @VirtualAccessMethods annotation overrides method to be used for get and for set.
* Virtual Access for extension fields uses XML to override default methods.
+
* getExtension(String) and setExtension(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods
 +
* XML for extended mapping indicates which get and set method to use
  
  
 
<source lang="java">
 
<source lang="java">
 
   @Entity
 
   @Entity
   @Extensible(FLEX)
+
   @VirtualAccessMethods(get="getExtension", set="setExtension")
 
   public class Address {
 
   public class Address {
  
Line 124: Line 153:
 
     }
 
     }
  
     public Object setExtension(String name, String value) {
+
     public Object setExtension(String name, Object value) {
 
         return extensions.put(name, value);
 
         return extensions.put(name, value);
 
     }
 
     }
Line 134: Line 163:
 
<source lang="xml">
 
<source lang="xml">
 
   
 
   
   <basic name="name" attribute-type="String">
+
   <basic name="name" access="VIRTUAL" attribute-type="String">
 
       <column name="FLEX_1"/>
 
       <column name="FLEX_1"/>
 
       <access-methods get-method="getExtension" set-method="setExtension"/>
 
       <access-methods get-method="getExtension" set-method="setExtension"/>
Line 146: Line 175:
 
* Virtual Access for extension fields uses defaults (get(String), set(String, Object))
 
* Virtual Access for extension fields uses defaults (get(String), set(String, Object))
 
* extensions mapped in a portable way - no @Transient required because of Property access
 
* extensions mapped in a portable way - no @Transient required because of Property access
* Preexisting table assumed.  @FlexExtensions annotation omitted
+
* get(String) and set(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods
 +
 
  
 
<source lang="java">
 
<source lang="java">
 
   @Entity
 
   @Entity
   @Extensible(FLEX)
+
   @VirtualAccessMethods
 
   public class Address {
 
   public class Address {
  
Line 166: Line 196:
 
     }
 
     }
  
     public Object set(String name, String value) {
+
     public Object set(String name, Object value) {
 
         return extensions.put(name, value);
 
         return extensions.put(name, value);
 
     }
 
     }
Line 174: Line 204:
 
</source>
 
</source>
  
 +
== EntityManagerFactory and Metadata Repository ==
  
== Metadata Repository ==
+
Extensions will be added at bootstrap time through access to a metadata repository. A Metadata Repository will accessed through a class that provides methods to retrieve the metadata it holds.
  
Extensions will be stored and retrieved from a metadata repository. The metadata repository will be a pluggable part of the EntityManagerFactory.  Implementations for several types of metadata repostory will be provided.
+
The user will specifiy the class to use and any configuration information for the metadata repository through persistence unit properties. As an EntityManagerFactory bootstraps, if metadata repository information is provided, the EMF will check the metadata repository for additional mapping information and integrate it into the metadata it uses to bootstrap.
  
* Database Table - information about extensions is stored in a database table
+
EclipseLink will initially ship with the capability of connecting to two types of metadata repository.
* XML - information about extensions is stored in XML
+
* User-specified - The user will be able to provide an implementation of the class that access the metadata repository
+
  
MetadataRepository will be configured through persistence unit properties
+
# XML (high priority) - information about extensions is stored in XML
 +
# Database Table (medium priority) - information about extensions is stored in a database table
  
'''Example - EclipseLink provided repository stored in database'''
+
Additionally, the user will be able to provide an implementation of the class that access the metadata repository.
 +
 
 +
Each metadata repository access class will specify an individual set of properties to use to connect to the repository
 +
 
 +
=== Examples ===
 +
 
 +
==== XML File ====
  
 
<source lang="xml">
 
<source lang="xml">
   <property name="eclipselink.metadata-repository" value="Database"/>
+
   <property name="eclipselink.metadata-source" value="XML"/>
   <property name="eclipselink.metadata-repository-location" value="jdbc:mysql://localhost/metadata"/>
+
   <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>
 
</source>
 
</source>
  
'''Example - User provided class'''
+
==== User-Specified ====
  
 
<source lang="xml">
 
<source lang="xml">
   <property name="eclipselink.metadata-repository" value="com.foo.Bar"/>
+
   <property name="eclipselink.metadata-source" value="com.foo.MetadataRepository"/>
   <property name="eclipselink.metadata-repository-location" value="foo://bar"/>
+
   <property name="com.foo.MetadataRepository.location" value="foo://bar"/>
 +
  <property name="com.foo.MetadataRepository.extra-data" value="foo-bar"/>
 
</source>
 
</source>
  
At startup, an EntityManagerFactory retrieves additional mappings from the metadata factory and applies them to its metadata.
+
''Note: The implementer of com.foo.MetadataRepository will be free to choose the properties that their implementation requires.''
  
== EntityManagerFactory ==
+
= Design =
  
It will be possible to notify an EntityManagerFactory that it should refresh its mappings.  On notificatin, it will rebootstrap a new session that includes the new mappings.  The existing session will be kept as along as existing entity managers reference it.  New EntityManagers will use the new session.
+
== Configuration ==
  
Notification will be availabe through direct API or through our RemoteCommandManager.
+
Please use the examples above as a guideline configuration XML and annotations.  Note that the configuration options to allow a mapping to use VIRTUAL access have been available in EclipseLink for several releases.  We will be using those configuration options as they exist and any changes to those will be handled as bugs rather than through this design document.''
  
= Design =
+
== Weaving ==
  
== Metadata Repository ==
+
The intial VIRTUAL access feature did not include weaving of the get and set methods.  As part of the extensions feature will will add weaving of get and set methods that use virtual access.  The initial implementation will not support OneToOne mappings and throw an exception at Transformer construction time if weaving is requested for a VIRTUAL mapping that is OneToOne.
  
''MetadataRepository will require discussion to build correct.  The following is provided for discussion''
+
=== Get method ===
  
Abstract class MetadataRepository will contain 3 types of abstract API
+
==== Original ====
  
* API to retreive extensions
+
<source lang="java">
** URL getExtensionsAsXML() - returns a URL to an eclipselink-orm.xml document that maps the extensions.  This document will be included in the xml documents processed at bootstrap time when creating an entityManager
+
** refresh() - refreshes the underlying session by rebootstrapping.
+
* API to store extensions and cause them to be used
+
** addMapping(DatabaseMapping, Descriptor)
+
** notify() - notify send a message to all known MetadataRepositories to indicate they should refresh.  (RemoteCommandManager message)
+
* Configuration API
+
** setLocation(String location) - the location of the store, a URL.  This could represent the a file system location where the repository is, a database connection URL, or any other location
+
  
DatabaseMetadataRepository will be a subclass of MetadataRepository containing additional configuration API
+
    public <T> T get(String name) {
 +
        return (T) getExtensions().get(name);
 +
    }
 +
</source>
  
* setUser(String)
+
==== Weaved ====
* setPassword(String)
+
* setDriver(String)
+
* API to configure which table to access and how the table is configured
+
  
XMLMetadataRepository will also be a subclass of MetadataRepository providing file-based XML lookup
+
<source lang="java">
  
= Old Design =
+
    public Object get(String name)
 +
    {
 +
        _persistence_checkFetched(name);
 +
        return getExtensions().get(name);
 +
    }
 +
</source>
  
Please ignore the design below.  It has been kept around until any useful parts of it can be incorporated in the update design.
+
=== Set method ===
  
== FlexExtensionManager ==
+
==== Original ====
  
Each ClassDescriptor may have an ExtensionManager.  For extensible types, the ExtensionManager will be non-null and will be set either by annotation, xml, or explicitly on the descriptor.  Different types of ExtensionManager are possible.  The flex column behavior is managed by a FlexExtensionManager. 
+
<source lang="java">
  
FlexExtensionManager holds the following state:
+
    public Object set(String name, Object value)
 +
    {
 +
        return getExtensions().put(name, value);
 +
    }
 +
</source>
  
* extensionsAccessor - an Accessor that gives it access to the Map on the domain class that holds the extensions.  e.g. the Map annotated as @FlexExtensions
+
==== Weaved ====
* availableFields - the list of DatabaseField that it believes can be used for new extensions
+
* usedFields - the list of DatabaseField that it believes is used for existing extensions
+
* shouldCreateNonExistingColumns - a boolean used to enable the additional feature that will allow ALTER schema language to create new columns (more to come)
+
  
 +
<source lang="java">
  
== Extension Data ==
+
    public Object set(String name, Object value)
 +
    {
 +
        Object obj = null;
 +
        if(_persistence_listener != null)
 +
        {
 +
            obj = get(name);
 +
        } else
 +
        {
 +
            _persistence_checkFetchedForSet(name);
 +
        }
 +
        _persistence_propertyChange(name, obj, value);
 +
        return getExtensions().put(name, value);
 +
    }
  
Extension data will be persisted to the database.  Each extension will be represented by an ExtensionProperty holding the following data:
+
</source>
  
* String - entityType - the name of the Entity it represents
+
To allow weaving, RelationalDescriptor will have a list virtual methods added.  This list will be used at transformer-construction time to allow EclipseLink to know which methods it should weave.
* DatabaseField - field - a DatabaseField representing the column used by this extension
+
* Class - type - the target class of the mapping
+
* String - name - the name of the extension
+
* mapping - a reference to the mapping for this extension
+
  
An project containing an Extensible type will have a descriptor added to it for ExtensionProperty.  Any time an Extension is added, its ExtensionProperty will be persisted.  The table will contain the following information:
+
<source lang="java">
  
* ENTITY_TYPE (PK) - Directly mapped from entityType
+
    /** The methods that are used by virtual attributes as getter methods and setter methods. 
* COLUMN_NAME (PK) - Name of the field stored in field
+
    * These will be used by our weaver to properly weave those methods
* TARGET_TYPE - Class name of the class represented by type
+
    **/
* NAME - Directly mapped from name
+
    protected List<VirtualAccessMethods> virtualMethods = null;
 +
</source>
  
At descriptor initialization time, a ReadAllQuery will be issued for all ExtensionProperties with an ENTITY_TYPE.  These will be stored by the ExtensionManager and used to build the appropritate mappings.
+
== EntityManagerFactory ==
  
== Adding Extensions ==
+
=== Bootstrap ===
  
Extensions are added through a call to extensionManager.addExtension(name, type)When addExtension is called, the following will occur:
+
EntityManagerFactory bootstrapping occurs withing EntityManagerSetupImpl. In the predeploy method, there is code that obtains the orm.xml files that contain metadataAt that point, the metadata repository will be consulted.  It will provide additional metadata information in the same format as is obtained from the orm.xml file.
  
# a check will be made to ensure the extension does not already exist in the descriptor and an exception will be thrown if it does
+
=== Refresh ===
# the list of available fields will be checked for a field that can represent the type that is passed in
+
# If no field is available either the Column Creation feature will be used, or an exception will be thrown.
+
# the availableFields and usedFields lists will be updated
+
# An instance of ExtensionProperty will be created and persisted, if persisting fails for any reason, an exception will be thrown and the availableFields and usedFields lists will be reset
+
# A mapping will be created to represent the ExtensionProperty and it will be added to the descriptor
+
  
=== Mapping Types ===
+
A mechanism will be provided that allows a user to tell a Metadata repository to refresh.  That mechanism will take two forms.
  
Extensions may only be the following types of mappings
+
# A direct refresh API call
 +
# A RemoteCommandManager command that causes all subscribed EntityManagerFactories to refresh themselves as described above.
  
* Basic
+
Refresh will be supported by adding an additional proxy to our EntityManagerFactory archtecture.
* OneToOne (single foreign key)
+
  
==== MapValueAttributeAccessor ====
+
'''Current:''' EntityManagerFactoryImpl -> ServerSession
  
Since all extensions will be stored in the same map, mappings cooresponding with those extensions will use a MapValueAttributeAccessor. 
+
'''New:''' EntityManagerFactoryWrapper implements EntityManagerFactory -> EntityManagerFactoryImpl -> ServerSession
  
MapValueAttributeAccessor will have knowledge of the map holding the extensions and a call to getAttributeValueFromObject will retreive the value from the map based on the extension's property name and a call to setAttributeValueInObject will put a value in the map based on the property name.
+
EntityManagerFactoryWrapper will implement:
  
==== Basic Mappings ====
+
<source lang='java'>
 +
  public void refreshMetadata()
 +
</source>
  
The initial iteration will allow Basic mappings.  Basic mappings are fairly easy to implement as they are a simple coorespondance between a field and value in the map.  A basic mapping will be constructed with the appropriate data and added to the descriptor
+
In both cases, a live EntityManager holds a reference to EntityManagerFactoryImpl
  
==== OneToOne Mappings ====
+
When a call is made to refreshMetadata(), EntityManagerFactoryWrapper will bootstrap a new EntityManagerFactoryImpl and use it as the basis for any new EntityManagers.  The old EntityManagerFactoryImpl will continue to be available until the last EntityManager is no longer used, at which point we will rely on garbage collection to clean it up.
  
OneToOne mappings to Objects with a single primary key will be supported.  The flex column will be used as the foreign key. 
+
== Metadata Source ==
  
== Extension Propogation ==
+
An implementation of MetadataSoruce will access metadata for extensions.  Metadata is accessed in the form of an eclipselink-orm.xml file.
  
Extensions will be refreshed from the database each time an EntityManagerFactory or EntityManager is created.
+
<source lang='java'>
  
No Push style notification via our RemoteCommandManager or other mechanism is currently planned.
+
package org.eclipse.persistence.jpa.metadata;
  
== DDL Generation ==
+
public interface MetadataSource{
  
DDL generation will allow the creation of the extension table and also include any known extension columns to domain tables
+
    /**
 +
    * ADVANCED:
 +
    * In most cases, this method should not be overridden.  The implementation of
 +
    * this method uses getEntityMappingsReader() to obtain a reader that will
 +
    * that reads a stream in the eclipselink-orm.xml format
 +
    * Advanced implementations of MetadataRepository have to option of overriding
 +
    * this method.
 +
    * @return XMLEntityMappings which are then merged in using existing metadata
 +
    * processing at bootstrap time when creating an entityManager
 +
    */
 +
    public XMLEntityMappings getMetadata(Properties properties, ClassLoader loader)
  
== Column Creation ==
+
</source>
  
See <URL for Column Creation doc here>
+
Additionally, an adapter class will be provided that implements MetadataSource containing stubbed out methods.  Customers will be encouraged to implement a MetadataSource by subclasing the adapter class rather than directly implementing the interface.  This strategy will allow them to tranaparently absorb any new versions of the interface in new EclipseLink versions.
  
= Future Enhancemts =
+
=== XMLMetadataSource ===
  
== Virtual Access access the extensions field directly ==
+
The first implementation of MetadataSource provided by EclipseLink will access a simple XML File.
  
* Field Access for non extension fields
+
It will provide an implementation of getMetadata(properties, classlaoder) that uses the property "eclipselink.metadata-repository.xml-file.url", specified like this:
* Virtual Access for extension fields uses @Extensions annotation on attribute that will hold the extensions. This assumes an enhancement to allow Virtual access to operate on a field
+
* extensions mapped in a non portable way - @Extensions
+
* Preexisting table assumed. @FlexExtensions annotation omitted
+
  
<source lang="java">
+
<source lang='xml'>
  @Entity
+
  <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>
  @Extensible(FLEX)
+
</source>
  public class Address {
+
  
    @Id
+
To create an input stream on the eclipselink-orm.xml file at URL: "foo://bar"/" and build an XMLEntityMappings using our existing EclipseLink ORM parsing code.
    private int id;
+
  
    @Extensions
+
=== Writing to a metadata repository ===
    private Map<String, Object> extensions;
+
  
    public int getId(){
+
In the initial implementation writing to the metadata repository will be left up to the user.
        return id;
+
    }
+
  
...
+
== Remote Command Manager ==
  
</source>
+
A Command for RemoteCommandManager will be implemented that triggers a refreshMetadata() call on all subscribed EntityManagerFactories.
  
= Appendices =
+
'''Design is in progress and will be added as it becomes available'''
  
== Apendix 1: Annotations ==
+
= Future Enhancements =
  
<source lang="java">
+
== Weaving of OneToOne mappings ==
@Target({TYPE})
+
@Retention(RUNTIME)
+
public @interface FlexExtensions {
+
    /**
+
    * (Optional) Specify number of columns.  This is used to automatically generate a default set of columns.
+
    * Unnecessary if columns is specified
+
    */
+
    int numberOfColumns default 10;
+
  
    /**
+
Implement support for weaving of non-basic VIRTUAL mappings
    * The string to prefix to the name of automatically generated columns.
+
    * e.g. the first flex column could be "FLEX_COL1" and the second, "FLEX_COL2" etc
+
    */
+
    String columnNamePrefix default "FLEX_COL";
+
  
    /**
+
* Handle OneToOneMappings
    * The string to prefix to the name of automatically generated columns.
+
* Handle indirection
    * e.g. the first flex column could be "FLEX_COL1" and the second, "FLEX_COL2" etc
+
    */
+
    String defaultColumnType default VARCHAR(255);
+
  
    /**
+
== Allow metadata to be updated with an in-memory structure ==
    * When set to true this will activate a feature that executes ALTER table statements to
+
    * make the required columns available if they do not exist
+
    */
+
    boolean createNonExistingColumns default false;
+
  
    /**
+
From GlassFish team:
    *  (Optional) The table that holds the extensions to use.  This table can be shared.
+
    */
+
    Table table;
+
  
    /**
+
# Programmatic  API to call into EclipseLink to "push" the extension definitions.
    * (Optional) Define column overrides
+
# The data exchanged between the caller and EclipseLink via API should be in a format that just refers to extension information and not a generic data structure.
    */
+
# The API call should be on an EclipseLink artifact that does not trigger deploy. That is it should be on an artifact at EMF level.
    FlexColumn[] columns
+
}
+
  
@Target({TYPE})
+
== Database Metadata Source ==
@Retention(RUNTIME)
+
public @interface FlexColumn {
+
  
    /**
+
Provide an implementation of Metadata Source that reads from a database.
    * Mandatory - the index of the column to override. Between 1 and numberOfColumns.
+
    * Exception will be thrown for numbers < 1 or > numberOfColumns
+
    */
+
    int index;
+
  
    /**
+
== Writing to a metadata Source ==
    * Column name - used to override default column names
+
    */
+
    String name;
+
 
+
    /**
+
    * Column type - used to override default column type
+
    */
+
    String type default VARCHAR(255)
+
}
+
</source>
+
  
== Appendix 2: eclipselink-orm ==
+
Provide API to write to the XMLFile and DatabaseMetadata Sources.
  
<source lang="java">
+
== Configuration of muliple getter and setter methods ==
  
  <xsd:complexType name="attributes">
+
* Allow annotations and xml to specify a list of methods for VIRTUAL mappings to weave
    <xsd:annotation>
+
      <xsd:documentation>
+
  
        This element contains the entity field or property mappings.
+
= Appendices =
        It may be sparsely populated to include only a subset of the
+
        fields or properties. If metadata-complete for the entity is true
+
        then the remainder of the attributes will be defaulted according
+
        to the default rules.
+
  
      </xsd:documentation>
+
== Appendix 1: Alternatives Considered ==
    </xsd:annotation>
+
    <xsd:sequence>
+
    ...
+
    <xsd:element name="flex-extensions" type="orm:flex-extensions" minOccurs="0"/>
+
  
<!-- **************************************************** -->
+
http://wiki.eclipse.org/EclipseLink/DesignDocs/335601
  
<xsd:complexType name="flex-extensions">
+
== Appendix 2: Annotations ==
  <xsd:annotation>
+
    <xsd:documentation>
+
      ...
+
    </xsd:documentation>
+
  </xsd:annotation>
+
  <xsd:sequence>
+
    <xsd:attribute name="number-of-columns" type="xsd:integer"/>
+
    <xsd:element name="column-name-prefix" type="xsd:string"/>
+
    <xsd:attribute name="default-column-type" type="xsd:string"/>
+
    <xsd:attribute name="create-non-existing-columns" type="xsd:boolean"/>
+
    <xsd:attribute name="extension-table" type="eclipselink-orm:table"/>
+
    <xsd:element name="flex-columns" type="eclipselink-orm:flex-column" minOccurs="0" maxOccurs="unbounded"/>
+
  </xsd:sequence>
+
</xsd:complexType>
+
  
<xsd:complexType name="flex-column">
+
<source lang="java">
  <xsd:annotation>
+
    <xsd:documentation>
+
      ...
+
    </xsd:documentation>
+
  </xsd:annotation>
+
  <xsd:sequence>
+
    <xsd:attribute name="index" type="xsd:integer"/>
+
    <xsd:attribute name="name" type="xsd:string"/>
+
    <xsd:attribute name="type" type="xsd:string"/>
+
  </xsd:sequence>
+
</xsd:complexType>
+
  
 +
/**
 +
* Specifies that this class contains virtual attributes.
 +
* This annotation is used in an EclipseLink-specific way to define
 +
* access methods used by mappings with accessType=VIRTUAL.
 +
* The xml-equivalent is the <access-methods> tag
 +
*/
 +
@Documented
 +
@Target(TYPE)
 +
@Retention(RUNTIME)
 +
public @interface VirtualAccessMethods {
  
 +
    /**
 +
    * (Optional) The name of the getter method to use for the virtual property
 +
    * This method must take a single java.lang.String parameter and return a java.lang.Object.
 +
    * If setMethod is specified, getMethod must be specified
 +
    */
 +
    String get() default "get";
 +
   
 +
    /**
 +
    * (Optional) The name of the setter method to use for the virtual property
 +
    * This method must take a java.lang.String parameter and a java.lang.Object parameter.
 +
    * If getMethod is specified, setMethod must be specified
 +
    */
 +
    String set() default "set";
 +
}
 
</source>
 
</source>
 +
 +
== Appendix 3: eclipselink-orm ==
 +
 +
No changes were made to eclipseLink-orm.xml

Latest revision as of 12:24, 11 January 2012

Flex Columns Extension

Why use this feature?

  1. You are building an application where some mappings are common to all users and some mappings are user-specific
  2. You want to add mappings to your application after it is made available to a customer (even post-deployment)
  3. You want to use the same EntityManagerFactory to work with your data even after the mappings have changed
  4. You can provide an additional source of metadata to be used by the application ( an xml-based source is provided, or you can build your own)

An example of the type of user this is designed for is a Software-as-a-Service provider who designs a generic application that can be provided to users and allow them to customize the application to make use of data that is particular to their domain.

What basic steps do I have to take to use this feature

1. Design a standard JPA Application

2. Decide which Entities will allow flexible mappings, annotate them as such and provide facilities to store the addional data.

  @Entity
  @VirtualAccessMethods
  public class Customer{
 
...
 
    @Transient
    private Map<String, Object> extensions;
 
    public <T> T get(String name) {
        return (T) extentions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }


3. When you design your schema, provide enough extra columns in your tables to accomodate the number of flexible mappings you will allow. e.g. The following table has 3 predefined columns and 3 columns designed to accomodate mappings added after design (FLEX_COL1, FLEX_COL2, FLEX_COL3)

  • CUSTOMER
    • INTEGER ID
    • VARCHAR NAME
    • VARCHAR FLEX_COL1
    • VARCHAR FLEX_COL2
    • VARCHAR FLEX_CO31

4. Deploy your application

5. To provide additional mappings, provide an eclipselink-orm.xml file that contains the additional mappings.

 
   <basic name="idNumber" access="VIRTUAL" attribute-type="String">
      <column name="FLEX_1"/>
      <access-methods get-method="get" set-method="set"/>
    </basic>

6. Use persistence unit properties to get your application to use the file:

  <property name="eclipselink.metadata-source" value="XML"/>
  <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>

Requirements

  • Users MUST be able to add mappings after initial deployment
  • Extensions MUST be persistent. (i.e. Extensions must continue to exist if an application goes down and comes back up)
  • Extensions MUST be shared amoung all EntityManagers configured to use them
  • Extensions MUST be compatible with our multi-tenant features. (i.e. Extensions must be able to make use of our Multi-tenant features to be definable on a tenant by tenant basis)
  • BasicMappings MUST be supported
  • Extensibility MUST be configurable using traditional JPA means (annotations, eclipselink-orm.xml)
  • It MUST be possible to use JPA Queries to query based on the extensions
  • Extensions SHOULD be supported through the JPA metamodel
  • OneToOneMappings SHOULD be supported
  • It MAY be possible to add extensions without logging in the session. (See ER 341429.)

Configuration

Metadata

Extensions will be supported using our VIRTUAL access type. Virtual Access type allows properties to be set through a getter that takes an attribute name as an argument and a setter that takes an argument name and a value as an argument.

VIRTUAL access does not currently allow annotation-based configuration. As part of this feature, annotations will be added to configure the methods used by Virtual access. These annotations will have the same effect as using the eclipselink-orm.xml construct <access-methods> at a class level. They will default which methods are used by virtual mappings. EclipseLink will weave these methods if weaving is enabled. This weaving will provide equivalent functionality to weaving for PROPERTY and FIELD access. (e.g. change tracking, fetch groups, etc)

DDL generation for tables with flexible columns will be addressed in a separate feature that addresses flexible DDL generation as a complete feature.

Examples

Example 1

  • Field Access for non extension fields
  • Virtual Access for extension fields uses defaults (get(String), set(String, Object))
  • get(String) and set(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods
  • extensions mapped in a portable way - @Transient
  @Entity
  @VirtualAccessMethods
  public class Address {
 
    @Id
    private int id;
 
    @Transient
    private Map<String, Object> extensions;
 
    public int getId(){
        return id;
    }
 
    public <T> T get(String name) {
        return (T) extentions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }
 
...

Example 2

  • Field Access for non extension fields
  • extensions mapped in a portable way - @Transient
  • @VirtualAccessMethods annotation overrides method to be used for get and for set.
  • getExtension(String) and setExtension(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods
  • XML for extended mapping indicates which get and set method to use


  @Entity
  @VirtualAccessMethods(get="getExtension", set="setExtension")
  public class Address {
 
    @Id
    private int id;
 
    @Transient
    private Map<String, Object> extensions;
 
    public int getId(){
        return id;
    }
 
    public <T> T getExtension(String name) {
        return (T) extensions.get(name);
    }
 
    public Object setExtension(String name, Object value) {
        return extensions.put(name, value);
    }
 
...
 
   <basic name="name" access="VIRTUAL" attribute-type="String">
      <column name="FLEX_1"/>
      <access-methods get-method="getExtension" set-method="setExtension"/>
    </basic>

Example 3

  • Property Access for non extension fields
  • Virtual Access for extension fields uses defaults (get(String), set(String, Object))
  • extensions mapped in a portable way - no @Transient required because of Property access
  • get(String) and set(String, Object) method will be woven even if no mappings use them because of the presence of @VirtualAccessMethods


  @Entity
  @VirtualAccessMethods
  public class Address {
 
    private int id;
 
    private Map<String, Object> extensions;
 
    @Id
    public int getId(){
        return id;
    }
 
    public <T> T get(String name) {
        return (T) extensions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }
 
...

EntityManagerFactory and Metadata Repository

Extensions will be added at bootstrap time through access to a metadata repository. A Metadata Repository will accessed through a class that provides methods to retrieve the metadata it holds.

The user will specifiy the class to use and any configuration information for the metadata repository through persistence unit properties. As an EntityManagerFactory bootstraps, if metadata repository information is provided, the EMF will check the metadata repository for additional mapping information and integrate it into the metadata it uses to bootstrap.

EclipseLink will initially ship with the capability of connecting to two types of metadata repository.

  1. XML (high priority) - information about extensions is stored in XML
  2. Database Table (medium priority) - information about extensions is stored in a database table

Additionally, the user will be able to provide an implementation of the class that access the metadata repository.

Each metadata repository access class will specify an individual set of properties to use to connect to the repository

Examples

XML File

  <property name="eclipselink.metadata-source" value="XML"/>
  <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>

User-Specified

  <property name="eclipselink.metadata-source" value="com.foo.MetadataRepository"/>
  <property name="com.foo.MetadataRepository.location" value="foo://bar"/>
  <property name="com.foo.MetadataRepository.extra-data" value="foo-bar"/>

Note: The implementer of com.foo.MetadataRepository will be free to choose the properties that their implementation requires.

Design

Configuration

Please use the examples above as a guideline configuration XML and annotations. Note that the configuration options to allow a mapping to use VIRTUAL access have been available in EclipseLink for several releases. We will be using those configuration options as they exist and any changes to those will be handled as bugs rather than through this design document.

Weaving

The intial VIRTUAL access feature did not include weaving of the get and set methods. As part of the extensions feature will will add weaving of get and set methods that use virtual access. The initial implementation will not support OneToOne mappings and throw an exception at Transformer construction time if weaving is requested for a VIRTUAL mapping that is OneToOne.

Get method

Original

    public <T> T get(String name) {
        return (T) getExtensions().get(name);
    }

Weaved

    public Object get(String name)
    {
        _persistence_checkFetched(name);
        return getExtensions().get(name);
    }

Set method

Original

    public Object set(String name, Object value)
    {
        return getExtensions().put(name, value);
    }

Weaved

    public Object set(String name, Object value)
    {
        Object obj = null;
        if(_persistence_listener != null)
        {
            obj = get(name);
        } else
        {
            _persistence_checkFetchedForSet(name);
        }
        _persistence_propertyChange(name, obj, value);
        return getExtensions().put(name, value);
    }

To allow weaving, RelationalDescriptor will have a list virtual methods added. This list will be used at transformer-construction time to allow EclipseLink to know which methods it should weave.

    /** The methods that are used by virtual attributes as getter methods and setter methods.  
     * These will be used by our weaver to properly weave those methods 
     **/
    protected List<VirtualAccessMethods> virtualMethods = null;

EntityManagerFactory

Bootstrap

EntityManagerFactory bootstrapping occurs withing EntityManagerSetupImpl. In the predeploy method, there is code that obtains the orm.xml files that contain metadata. At that point, the metadata repository will be consulted. It will provide additional metadata information in the same format as is obtained from the orm.xml file.

Refresh

A mechanism will be provided that allows a user to tell a Metadata repository to refresh. That mechanism will take two forms.

  1. A direct refresh API call
  2. A RemoteCommandManager command that causes all subscribed EntityManagerFactories to refresh themselves as described above.

Refresh will be supported by adding an additional proxy to our EntityManagerFactory archtecture.

Current: EntityManagerFactoryImpl -> ServerSession

New: EntityManagerFactoryWrapper implements EntityManagerFactory -> EntityManagerFactoryImpl -> ServerSession

EntityManagerFactoryWrapper will implement:

  public void refreshMetadata()

In both cases, a live EntityManager holds a reference to EntityManagerFactoryImpl

When a call is made to refreshMetadata(), EntityManagerFactoryWrapper will bootstrap a new EntityManagerFactoryImpl and use it as the basis for any new EntityManagers. The old EntityManagerFactoryImpl will continue to be available until the last EntityManager is no longer used, at which point we will rely on garbage collection to clean it up.

Metadata Source

An implementation of MetadataSoruce will access metadata for extensions. Metadata is accessed in the form of an eclipselink-orm.xml file.

package org.eclipse.persistence.jpa.metadata;
 
public interface MetadataSource{
 
    /**
     * ADVANCED:
     * In most cases, this method should not be overridden.  The implementation of
     * this method uses getEntityMappingsReader() to obtain a reader that will
     * that reads a stream in the eclipselink-orm.xml format
     * Advanced implementations of MetadataRepository have to option of overriding
     * this method.
     * @return XMLEntityMappings which are then merged in using existing metadata 
     * processing at bootstrap time when creating an entityManager
     */
    public XMLEntityMappings getMetadata(Properties properties, ClassLoader loader)

Additionally, an adapter class will be provided that implements MetadataSource containing stubbed out methods. Customers will be encouraged to implement a MetadataSource by subclasing the adapter class rather than directly implementing the interface. This strategy will allow them to tranaparently absorb any new versions of the interface in new EclipseLink versions.

XMLMetadataSource

The first implementation of MetadataSource provided by EclipseLink will access a simple XML File.

It will provide an implementation of getMetadata(properties, classlaoder) that uses the property "eclipselink.metadata-repository.xml-file.url", specified like this:

  <property name="eclipselink.metadata-source.xml.url" value="foo://bar"/>

To create an input stream on the eclipselink-orm.xml file at URL: "foo://bar"/" and build an XMLEntityMappings using our existing EclipseLink ORM parsing code.

Writing to a metadata repository

In the initial implementation writing to the metadata repository will be left up to the user.

Remote Command Manager

A Command for RemoteCommandManager will be implemented that triggers a refreshMetadata() call on all subscribed EntityManagerFactories.

Design is in progress and will be added as it becomes available

Future Enhancements

Weaving of OneToOne mappings

Implement support for weaving of non-basic VIRTUAL mappings

  • Handle OneToOneMappings
  • Handle indirection

Allow metadata to be updated with an in-memory structure

From GlassFish team:

  1. Programmatic API to call into EclipseLink to "push" the extension definitions.
  2. The data exchanged between the caller and EclipseLink via API should be in a format that just refers to extension information and not a generic data structure.
  3. The API call should be on an EclipseLink artifact that does not trigger deploy. That is it should be on an artifact at EMF level.

Database Metadata Source

Provide an implementation of Metadata Source that reads from a database.

Writing to a metadata Source

Provide API to write to the XMLFile and DatabaseMetadata Sources.

Configuration of muliple getter and setter methods

  • Allow annotations and xml to specify a list of methods for VIRTUAL mappings to weave

Appendices

Appendix 1: Alternatives Considered

http://wiki.eclipse.org/EclipseLink/DesignDocs/335601

Appendix 2: Annotations

/**
 * Specifies that this class contains virtual attributes.
 * This annotation is used in an EclipseLink-specific way to define
 * access methods used by mappings with accessType=VIRTUAL.
 * The xml-equivalent is the <access-methods> tag
 */
@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface VirtualAccessMethods {
 
    /**
     * (Optional) The name of the getter method to use for the virtual property
     * This method must take a single java.lang.String parameter and return a java.lang.Object.
     * If setMethod is specified, getMethod must be specified
     */
    String get() default "get";
 
    /**
     * (Optional) The name of the setter method to use for the virtual property
     * This method must take a java.lang.String parameter and a java.lang.Object parameter.
     * If getMethod is specified, setMethod must be specified
     */
    String set() default "set";
}

Appendix 3: eclipselink-orm

No changes were made to eclipseLink-orm.xml

Back to the top