Jump to: navigation, search

EclipseLink/Development/JPA 2.0/metamodel api

Contents

JPA 2.0: MetaModel API

Status

  • The JPA 2.0 Metamodel API as part of EclipseLink 2.0 is complete as of 20091123, all the major design issues below have been addressed via their associated patch and SVN rev# - the last being 5849.
  • 20090925: All API interface functions are implemented for EclipseLink 2.0 as of SVN rev# 5303
    • - the next step after the 2.0 release is to verify the implementation against the specification, do a full 2nd review, fine tune and possibly refactor the implementation for performance and extensibility (IE: Dyanamic Persistence).
  • The JPA 2.0 implementation of the Metamodel API in EclipseLink 1.2 is a preview snapshot of SVN rev# 5189
    • - the implementation of the IdentifiableType interface is incomplete in this older view - consequently any interaction with id or version attributes will throw a "Not Yet Implemented" PersistenceException.

Code Submission Summary

JPA 2.0 Root | bug 266912

Date Committer(s) Description, SVN Rev# and Design Issue #'s
Mar 3, 2009 gyorke Initial feature template
Mar 3, 2009 mobrien Start analysis
Mar 25, 2009 mobrien Review MappedSuperclass preliminary Design - off
Mar 31, 2009 mobrien Return to project
Apr 6, 2009 mobrien Rev# 3831, Update metamodel UML diagram, post prototype for @MappedSuperclass, expand on design issues
Apr 21, 2009 mobrien Rev# 4011, javax.persistence build changes - Return to API work
May 25, 2009 mobrien Rev# 4265, Return to API work and update to latest specification changes
May 28, 2009 mobrien Rev# 4353, Partial implementation snapshot 1
May 29, 2009 mobrien Rev# 4357, Partial test suite snapshot 1
Jun 4, 2009 mobrien Rev# 4415, EntityManagerImpl IllegalStateException fix
Jun 4, 2009 mobrien Rev# 4416, MetadataProject OO API fix
Jun 5, 2009 mobrien Rev# 4433, FullRegressionTestSuite launch fix
Jun 17, 2009 mobrien Rev# 4519, Use a Map keyed on MetadataClass instead of overriding equals/hashCode in RelationalDescriptor
Jun 30, 2009 mobrien Rev# 4587, Complete JPA Metadata modifications in support of parameterized generics in Map, Embeddable and ElementCollection types on MappedSuperclass Descriptors in support of the JPA Metamodel API
Jul, 6 2009 mobrien Rev# 4614, 282518 Metamodel superType requires javaClass set on custom descriptor on MappedSuperclassAccessor
Jul, 8 2009 mobrien Rev# 4630, See bug 266912 comment 66 - solved design issues 34, 35 ,38, 40
Jul, 9 2009 mobrien Rev# 4643, See bug 266912 comment 69
Jul, 10 2009 mobrien Rev# 4644, See bug 266912 comment 70 getBindableJavaType for SingularAttribute, adjust BasicType processing to handle non-Entity Java types.
Jul, 14 2009 mobrien Rev# 4661, See bug 266912 comment 74 Implement all ManagedType.getDeclaredX() functionality.
Jul, 17 2009 mobrien Rev# 4683, See bug 266912 Implement EntityTypeImpl.getIdType functionality without full CMP3Policy null testing yet.
Jul, 23 2009 mobrien Rev# 4710, See bug 266912 Design Issue 47 - Implement Map Support for IdClass.
Jul, 29 2009 mobrien Rev# 4754, See bug 266912 Design Issue 52 - Implement recursive ManagedType.getDeclared* algorithm to differentiate by IdentifiableType.
Aug, 3 2009 mobrien Rev# 4777, See bug 266912 Implement design issues 47 ( IdentifiableType.getIdType() for composite keys )

50 (Handle all mapping types in the SingularAttribute constructor - partial) 48 (Map support).

Aug, 4 2009 mobrien Rev# 4779, See bug 266912 Adjust Metamodel.type(Clazz) implementation to handle null(not present) and BasicImple type (IAE) type returns.
Aug, 9 2009 mobrien Rev# 4820 and Rev# 4827, See bug 266912 Design Issue #58: differentiate between Collection and List for lazy IndirectList during plural attribute Type generation for Managed Types.
Aug, 12 2009 mobrien Rev# 4845, See bug 266912

Design Issue #49: @OneToMany on @MappedSuperclass with @JoinTable should not add pseudo PK Field if an @Id already exists on the MappedSuperclass.

Aug, 17 2009 mobrien Rev# 4883 Rev# 4886, See bug 266912 Design Issue 52 - Refactor base case of recursive check for ManagedType.getDeclared* algorithm.
Aug, 20 2009 mobrien Rev# 4931, See bug 266912 Design Issue 61 - ManagedType.getDeclaredX() leaks members into entity-entity hierarchy.
Aug, 25 2009 mobrien Rev# 4966, See bug 266912 Design Issue 62, 63.
Sept, 4 2009 mobrien Rev# 5050, See bug 266912 Design Issue 67 and 63, 66, 67.
Sept, 10 2009 mobrien Rev# 5104, See bug 266912 Design Issue 79 MapAttribute keyType requires @MapKey handler in the template case when CMP3Policy.getPKClass() is null .
Sept, 14 2009 mobrien Rev# 5124, See bug 266912 Design Issue 80 MappedSuperclassTypeImpl.create() does not use ConversionManager to get the EE classLoader

.

Sept, 16 2009 mobrien Rev# 5148, See bug 266912 Design Issue 81 Enable @BasicMap DirectMapContainerPolicy support in MapAttributeImpl .
Sept, 17 2009 mobrien Rev# 5165, See bug 266912 Design Issue 80 Prepare advanced model metamodel tests for future EE server testing capability via !isJPA10()
Sept, 18 2009 mobrien Rev# 5189, See bug 266912 Design Issue 83 @BasicMap elementType (Map value parameter) support in DirectCollectionMapping.attributeClassification
Sept, 25 2009 mobrien Rev# 5303, See bug 266912 Design Issue 70 - 77 and 65 Implement remaining 7 IdentifiableType functions (Id, IdClass and version).
Sept, 29 2009 mobrien Rev# 5347, See bug 266912 Design Issue 85 SingularAttributeImpl: Relax IllegalArgumentException checking on autoboxed primitives
Oct, 02 2009 mobrien Rev# 5425, See bug 266912 JPA 2.0 Specification adjustments (Metamodel.type() to Metamodel.managedType())
Oct, 16 2009 mobrien Rev# 0000, See bug 266912 Design Issue 93 - We currently use the new isJPAId() flag in IdentifiableTypeImpl.getIdType() - we need to refactor this custom processing for MappedSuperclass descriptors up into metadata processing by implementing custom initialization in EntityManagerSetupImpl.assignCMPPolicy() which sets the CMPPolicy on our custom MS descriptors and sets the pkClass so we can remove the bulk of the code in getIdType() - this is in the absence of an ObjectBuilder for our MS descriptor. See bug 290567 in rev 5602
Nov, 4 2009 mobrien Rev# 5729, See bug 266912 DI 96: Add support to JPA to detect a m:1 as 1:1 and a 1:m as m:m mapping for use by Attribute.getPersistentAttributeType()
Nov, 8 2009 mobrien Rev# 5761, See bug 266912 DI 86: MapAttribute support for entity mapkey
Nov, 10 2009 mobrien Rev# 5776, See bug 266912 DI 98: Leverage MappedKeyContainerPolicy.keyMapping for determining keyType:K for Map<K,V>, add @BasicMap support and tune workarounds for attributeClassification (bug# 294765) and CMP3Policy (bug# 294811) workarounds for missing @MapKey name attribute support
Nov, 13 2009 mobrien Rev# 5793, See bug 294765 DI 98: MapKey keyType DirectToField processing should return attributeClassification class in getMapKeyTargetType when accessor.attributeField is null
Nov, 17 2009 mobrien Rev# 5805,Rev# 5808, See bug 266912 DI 80: MappedSuperclassTypeImpl.create() does not use ConversionManager to get the EE classLoader - make test suite reentrant
Nov, 22 2009 mobrien Rev# 5849, See bug 288972 DI 78: Composite @IdClass on inherited MappedSuperclass chain causes new ValidationException - the reproduction model was incorrect.

Summary

In JPA 2.0 (JSR-317), the specification has defined standard APIs for representing the structure of a persistence unit model. This is referred to as the Metamodel APIs. There are two main aspects to providing this functionality. The first is the runtime model accessed from EntityManagerFactory.getMetamodel() and the second is the APT generated metamodel classes. Our first goal is to provide functionality for runtime access.

For details see sections 5.1 and 6.6 of the Proposed Final Draft.



EclipseLink API code Changes

This list of API modifications are detailed as of SVN Rev# 4644 on 24 July 2009

Project.java - Native core

ExceptionLocalizationResource.java - Native core

MetadataProject.java - JPA core

MetadataConstants.java - JPA core

MetadataDescriptor.java - JPA core

MetadataHelper.java - JPA core

EmbeddableAccessor.java - JPA core

EntityAccessor.java - JPA core

MappedSuperclassAccessor.java - JPA core

ElementCollectionAccessor.java - JPA core

MappingAccessor.java - JPA core

MetadataAnnotatedElement.java - JPA core

SerializedMetadata.java - JPA core

EntityManagerImpl.java - JPA core

AttributeImpl.java - New

BasicTypeImpl.java - New

CollectionAttributeImpl.java - New

EmbeddableTypeImpl.java - New

EntityTypeImpl.java - New

IdentifiableTypeImpl.java - New

ListAttributeImpl.java - New

ManagedTypeImpl.java - New

MapAttributeImpl.java - New

MappedSuperclassTypeImpl.java - New

MetamodelImpl.java - New

PluralAttributeImpl.java - New

SetAttributeImpl.java - New

SingularAttributeImpl.java - New

TypeImpl.java - New

Analysis

  • My understanding is that the metamodel API provides 4 (four) major services to the developer.
    • 1) The Metamodel API exposes the entity annotation API and the XML mapping API as one unified "metadata" type-safe API.
    • 2) The Metamodel creation and validation occurs at predeploy() and is independent of the runtime state of the application.
    • 3) The Metamodel API provides for dynamic Criteria queries without using generated or manually written Metamodel classes (IE: the _underscore prefixed ones).
    • 4) The Metamodel API is not used internally to initialize the canonical metamodel classes that were generated by the [APT tool API in bug# 267391 - however the two API's must match at runtime.

API Usage

  • There are three ways to query using the Criteria API which can wrap the Metamodel API
    • 1) Static metamodel class model for type safe queries - these are the _Underscore design time classes
    • 2) Dynamic metamodel class model for type safe queries - we use generics and pass in both the return type and the type containing the return type
    • 3) String attribute references for non-type safe queries - see p.262 of the JPA 2.0 specification section 6.7 - there may be type or generic usage compiler warnings that the user will need to workaround when using this non-type-safe query in a type-safe environment.
  • This enhancement deals only with # 2) Dynamic metamodel query generation.

Metamodel

  • In the context of EclipseLink, a metamodel is an abstract view of the managed classes in the persistence unit. We already have an in memory model that we we construct to wrap the class MetadataProcessor - we must transition or refactor this.
  • We use the metamodel to construct a runtime query structure that is "object-based".
  • The metamodel enties and attributes represent static structure that is not changed by runtime behavior (these are not entity instances with object state)

Types Hierarchy

  • The following block diagram illustrates the hierarchical relationship between types in the metamodel. The positioning of the 4 concrete types Entity, MappedSuperclass, Embeddable and Basic can be seen in the 3-level type hierarchy tree Identifiable --> Managed --> Type.

Metamodel types hierarchy block.jpg

Relation to JPQL?

Functional Requirements

  • Todo: 20090715: verify that we cover off what is not being developed like variable OneToOne (unidirectional) mappings (See DI 39) and other mappings that are custom ORM mappings (native API).
  • Todo: 20090715: describe how we will handle extension of existing mappings
  • Requirements and constraints have traceability down to their associated use cases.

Requirements Table

Req# A# C# Use Cases# Description
R1 - - Support runtime Metamodel APIs - See Spec. section 5.2
R1.1 - - Develop, leverage or refactor current Metamodel processor
R1.1.1 - - Implement metamodel.PluralAttribute interface
R1.1.1.1 - - Implement metamodel.MapAttribute interface
R1.1.1.2 - - Implement metamodel.SetAttribute interface (empty)
R1.1.1.3 - - Implement metamodel.ListAttribute interface (empty)
R1.1.1.4 - - Implement metamodel.CollectionAttribute interface
R1.1.5 - - Implement metamodel.SingularAttribute interface
R1.1.6 - - Implement metamodel.BasicType interface (currently empty)
R1.1.7 - - Implement metamodel.MappedSuperclassType interface (currently empty)
R1.1.8 C3 C4 C5 - Implement metamodel.EmbeddableType interface (currently empty)
R1.1.9 - - Implement metamodel.EntityType interface
R1.1.10 - - Implement metamodel.IdentifiableTpe interface
R1.2 - - Dynamic access to the metamodel is provided by the javax.persistence.metamodel.Metamodel interface
R1.2.1 - - Implement public Metamodel EntityManagerFactory.getMetamodel()
R1.2.2 - - Implement public Metamodel EntityManager.getMetamodel()
R1.3 - - - "Metamodel classes are produced for every entity, mapped superclass, and embeddable class in the persistence unit."
R2 A1 - Support APT generation of Canonical Metamodel classes see JPA 2.0 Spec. "Sect. 5.2.1"
R2.1 - - "For each managed class X in package p, a metamodel class X_ in package p is created."

I2: Name collisions with entities that are already named Entity_ in the current package

I3: Package level splitting is incompatible with OSGI

R2.2 - - "The name of the metamodel class is derived from the name of the managed class by appending "_" to the name of the managed class."
R2.3 - - "The metamodel class X_ must be annotated with the javax.annotation.Generated annotation and with the javax.persistence.TypesafeMetamodel annotation."

The example Order_ metamodel class is missing these two annotations in the spec sect 5.2.1.1 p.161

R2.4 - - "If class X extends another class S, where S is the most derived managed class (i.e., entity or mapped superclass) extended by X, then class X_ must extend class S_, where S_ is the metamodel class created for S."
R2.5 - - "For every persistent non-collection-valued attribute y declared by class X, where the type of y is Y, the metamodel class must contain a declaration as follows:"
public static volatile Attribute<X, Y> y;
(and where javax.persistence.Attribute is imported by the class X_ )
R2.6 - - For every persistent collection-valued attribute z declared by class X, where the element type of z is Z, the metamodel class must contain a declaration as follows:
R2.6.1 - - Collection if the collection type of z is java.util.Collection, then
public static volatile Collection<X, Z> z;
(and where javax.persistence.Collection is imported by the class X_ )
R2.6.2 - - Set if the collection type of z is java.util.Set, then
public static volatile Set<X, Z> z;
(and where javax.persistence.Set is imported by the class X_ )
R2.6.3 - - List if the collection type of z is java.util.List, then
public static volatile List<X, Z> z;
(and where javax.persistence.List is imported by the class X_ )
R2.6.4 - - Map if the collection type of z is java.util.Map, then
public static volatile Map<X, K, Z> z;
where K is the type of the key of the map in class X (and and where javax.persistence.Map is imported by the class X_)
R2.7 - - Import statements must be included for all classes X, Y, Z, and K. specification down to the level of import statements vs fully qualified names - is a pending decision
R2.8 - - Sect 5.2.1.2 "When generated metamodel classes are used, they must be specified as part of the persistence unit."
R2.9 - - Sect 5.2.1.2 "When the entity manager factory for the persistence unit is created, it is the responsibility of the persistence provider to initialize the state of its metamodel classes."
R2.10 - - No extra runtime paramenters are required beyond existing -javaagent runtime flag

Assumptions Table

A# Req# Use Cases# Description
A1 - - Java 5 is the minimum compile target we support (unchanged)
A2 - - Runtime environment for metamodel construction is SE and compilation time only

Constraints Table

C# A# Use Cases# Description
C1 - - Dependency on the APT tool specific to the SUN JDK 1.5.0? Yes (tools.jar)

Determine if we are ok running on the IBM J9 JVM and WLS JRockit JVM in SE mode - Yes both have tools.jar.

C2 - - Wrap our existing metamodel (Descriptors on a Project hava a 1-1 correspondence to Mappings)
C3 - - A collection of embedded objects is not supported from entities in JPA 1.0
C4 - - An embedded object referencing other embedded objest is not supported in JPA 1.0
C5 - - An embedded object having a relationship to to an entity or entities is not supported in JPA 1.0
C6 - - Metamodel managedTypes should not be affected by the runtime instantiation or persistence of entities.

Metamodel Interfaces

The criteria API runs on top of the metamodel API and expects that classes of the form X_ exist. It is the responsibility of the metamodel to create and compile these enhanced classes. An ATP tooling library will either need to be developed, extended or imported.

For the primary requirement R1.3 "Metamodel classes are produced for every entity, mapped superclass, and embeddable class in the persistence unit.", we will use the existing metadata API surrounding the Descriptor to crate the Metamodel instance.

Implementors of the Metamodel API

  • The following projects or APIs require the Metamodel API in order to function.
  • See section 6.2.1 of the specification

267391: Canonical Metamodel Static Class Generation Intialization

: Criteria API Typesafe Queries

: Criteria API String based Queries

ATP Tooling Library

This section discusses requirement #2 in enhacement 267391, it is currently on hold as of 20090306.

Issue 4: Develop, Extend or use an ATP tooling library

We require an ATP tooling library that has implementations of AnnotationProcessorFactory and AnnotationProcessor classes for metamodel types such as List, Collection, Map, Set, Attribute and Basic.

Decision

As of 20090306 we will defer to the provider of the RI for JPA 2.0 to submit a binary ATP tooling implementation jar and/or the source so it can be extended. In the future we may extend or refactor this tool to provide extended support - see enhancement# 267391


Use Cases

  • Use cases have traceability back to their requirements and constraints via their id# and have a 1-1 correspondence with test cases.

Use Case Partitioning

  • The following are design points that we will partition the use cases around.
    • Attributes
      • Singular = Basic/Direct
      • Plural
        • List
        • Set
        • Collection (See design issue # 37) Collection in Java and CollectionAttribute in the Metamodel are different)
        • Map
    • Types
      • Basic
      • Entity
      • MappedSuperclass
        • Levels (1 to n)
        • Mapping (Basic,)
      • Embeddable
    • Mappings (assume all bidirectional where appropriate)
      • Singular
        • Basic
        • OneToOne
        • ManyToOne
      • Plural
        • OneToMany
        • ManyToMany
        • ElementCollection

Use Case Maps

  • The following use cases will involve a maximum coverage minimum path through the API.
    • For example using an EntityType that has a oneToMany relationship (ListAttribute) to another entity that inherits its' id from a MappedSuperclassType will exercise 10 interfaces of the 16 interface API.


Use Case Table

Use Case ID# Assumptions# Requirements# Description
UC1 - - Construct object-based query definition object
UC2 - - @Mappings
UC2.1 - - @OneToOne Mappings
UC2.2 - - @OneToMany Mappings
UC2.2.1 - - @OneToMany Unidirectional Mappings
UC2.2.2 - - @OneToMany Bidirectional Mappings
UC2.3 - - @ManyToOne Mappings
UC2.3.1 - - @ManyToOne Unidirectional Mappings
UC2.4 - - @ManyToMany Mappings
UC2.5 - - @Embedded Mappings
UC2.6 - - Basic/Direct Mappings
UC2.7 - - ElementCollection Mappings
UC2.8 - - Unknown/Invalid Mappings
UC3 - - Inheritance Hierarchy Mappings
UC3.1 - - Mapped superclass parent - 1 level
UC3.2 - - Mapped superclass parent - n (2) levels
UC4 - - template

Concrete Use Cases

Details on each use case.

UC5: Metamodel from fully populated entity tree

The tree will contain examples of 1:1, 1:n and n:1 (bidirectional), n:n, variable 1:1 and simulated variable 1:n

  • Preconditions
  • Postconditions
  • Path
  • Exceptions

UC2: Metamodel containing mapped superclass heiarchy

  • Preconditions
  • Postconditions
  • Path
  • Exceptions

UC3: Metamodel containing embeddable

  • Preconditions
  • Postconditions
  • Path
  • Exceptions


......

Variant Use Cases

Details on each variant (negative test) use case.

Design

API

Metamodel and Criteria packages interfaces API - Specification

  • Step 1) determine which interfaces will be extended by concrete classes and which are abstract - this will affect how we deal with multiple inheritance.
    • Basic, Embeddable, Entity, Map, Set, List, Collection are concrete.
    • AbstractCollection, Attribute, ManagedType are all abstract as they implement 2 interfaces.
  • Note that the internal API access path to the EclipseLink mappings is via the entityManager --> AbstractSession --> Project --> Mappings
  • (The criteria package is ommitted here)

Uml class diagram metamodel criteria packages.gif

Metamodel Implementation

The following UML class diagram illustrates the static relationships and hierarchy of the Metamodel implementation classes.

Eclipselink uml class metamodel impl.gif

  • During preprocess() the JPA API will save each MappedSuperclass as an empty RelationalDescriptor (without a table).
  • Later during accessor processing we save the mappings defined on MappedSuperclass objects on the appropriate RelationalDescriptor.
  • The Metamodel retrieves a collection of MappedSuperclassTypeImpl objects from the core Project on the Session on the MetadataProject.

Class Heirarchy Design

How much implementation will be in the abstract classes (TypeImpl and ManagedTypeImpl) and how much will be in the concrete classes (BasicImpl, EmbeddableTypeImpl, EntityTypeImpl and IdentifiableTypeImpl)?

ManagedTypeImpl

  • We will cache the ManagedType on it's core descriptor as a property so we do not have to recreate it.
  • All managedTypeImpl instances are static, so there will only be one object per class. Each static managedTypeImpl is stored on the Metamodel instance on the EM or EMF.

MetamodelImpl

  • Implement handlers and storage for all 4 types of the enumeration PersistenceType (ENTITY, EMBEDDABLE, MAPPED_SUPERCLASS, BASIC)
    • We have the folloing instance fields on MetamodelImpl
metamodel    MetamodelImpl  (id=113) 
    embeddables LinkedHashMap<K,V>  (id=251)    
        size    0   
    entities    LinkedHashMap<K,V>  (id=253)    
        size    10  
    managedTypes    LinkedHashMap<K,V>  (id=254)    
        size    14  
    mappedSuperclasses  HashSet<E>  (id=255)    
        map HashMap<K,V>  (id=278)  
            size    4   
    types   LinkedHashMap<K,V>  (id=259)    
        size    17  
  • In the above list we have the following type containment hierarchy
    • types is the superset of all types including basic types.
    • entities is the subset of entity types (a subset of identifiable types)
    • mappedSuperclasses is the subset of mapped superclasses (a subset of identifiable types)
    • managedTypes is the subset of embeddable, mappedSuperclass and entity types
    • embeddables is the subset of embeddable types (a subset of managed types)


Design Issues

  • Legend: Anything with an italics-green header is resolved.

DI 4: QueryBuilder Interface Implementation

  • See section 5 of the spec.
  • The new javax.persistence.criteria.QueryBuilder interface in section 5.4.1 has getter functions in EntityManager* in sect 3.1.1 and 6.4 that return javax.persistence.QueryBuilder.
    • This existing unimplemented Criteria API EntityManager.getQueryBuilder() function that returns an instance of javax.persistence.QueryBuilder for the 2.0 spec that in EntityManagerImpl needs to be either removed or extended.
 /**
 * @see javax.persistence.EntityManager#getQueryBuilder()
 * @since Java Persistence API 2.0
 */
 public QueryBuilder getQueryBuilder() {
 // TODO - May change as Query API is redefined
 throw new PersistenceException("Not Yet Implemented");
 }
EntityManagerImpl and EntityManagerFactoryImpl will require modification
Alternative #1: Remove javax.persistence.QueryBuilder
  • Can we change the signature of getQueryBuilder to return a javax.persistence.criteria.QueryBuilder instance instead?
  • I assume the existing functions in the old javax.persistence.QueryBuilder interface will be discarded.
package javax.persistence;
public interface QueryBuilder {
 QueryDefinition createQueryDefinition();
 DomainObject createQueryDefinition(Class root);
 DomainObject createSubqueryDefinition(PathExpression path);
}
Alternative #2: Extend javax.persistence.QueryBuilder
  • Or, if the 3 functions above are still required can we make javax.persistence.criteria.QueryBuilder inherit from the 1.99/2.0 javax.persistence.QueryBuilder?
public interface QueryBuilder extends javax.persistence.QueryBuilder
  • However, In this situation a client will still need to cast which is not advisable.
QueryBuilder qb = (QueryBuilder)entityManager.getQueryBuilder();
Decision
  • I expect that since the Criteria API is new to JPA 2.0 that we do not have to deprecate the existing javax.persistence.QueryBuilder interface.
  • Alt #2 : The existing javax.persistence.QueryBuilder interface will be removed and replaced by the updated javax.persistence.criteria.QueryBuilder interface.

DI 5: Metamodel package is public or internal API

Decision Criteria

What are the constraints on going public or internal with this new Metamodel API? Normally a public facing API like the Metamodel one would exist in the public package. We are however adding extensions beyond the JPA 2.0 specification that the developer can take advantage of. In order to provide an extended Metamodel API - an intermediate interface will be required. For an existing example of this design pattern see the class hierarchy EntityManagerImpl --> JpaEntityManager(I) --> EntityManager(I).

DI 6: MappedSuperclass does not currently have a Descriptor

  • Mapped Superclass descriptors do not represent entities and therefore do not have a database table behind them - they are usually abstract classes.
  • A mapped superclass can contain mappings such as @Id but these are accessible via child entities - therefore we do not currently store a descriptor for the mapped superclass.
  • In order to support the MappedSuperclass interface we will need to modify core code to break out our metadata as follows
    • Descriptor --> * List<MappedSuperclassMetadata<List<MappedSuperclassType>> --> * List<DatabaseMapping>
    • We need to support multiple levels of mapped superclasses, each with their own mappings
Adding additional MappedSuperclass support
  • Currently any mapped superclasses of entities are processed during predeploy in the 3rd step of PersistenceUnitProcessor.processORMetadata() below as part of entity processing.
	Thread [main] (Suspended)	
		ArrayList<E>.add(E) line: 351	
		EntityAccessor.discoverMappedSuperclassesAndInheritanceParents() line: 245	
		EntityAccessor.process() line: 509	
		MetadataProject.processStage1() line: 720	
		MetadataProcessor.processORMMetadata() line: 450	
		PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 297	
		EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 834	
		JavaSECMPInitializer(JPAInitializer).callPredeploy(PersistenceUnitInfo, Map, PersistenceInitializationHelper) line: 110	
		JavaSECMPInitializer(JPAInitializer).initPersistenceUnits(Archive, Map, PersistenceInitializationHelper) line: 159	
		JavaSECMPInitializer(JPAInitializer).initialize(Map, PersistenceInitializationHelper) line: 144	
		PersistenceProvider.createEntityManagerFactory(String, Map, ClassLoader) line: 107	
		PersistenceProvider.createEntityManagerFactory(String, Map) line: 67	
		Persistence.createEntityManagerFactory(String, Map) line: 158	
		Persistence.createEntityManagerFactory(String) line: 132	
  • This adds an element to the MetdataProject.
m_mappedSuperclasses	ArrayList<E>  (id=171)	
	elementData	Object[10]  (id=196)	
		[0]	MappedSuperclassAccessor  (id=199)	
  • We need to add processing that will persist the mappings on the mapped superclass tree here.
Proposal 1: Add MappedSuperclass (Object) to core Descriptor (Map based) - deprecated

We pass a RelationalDescriptor containing the mappings on the Mapped Superclass parent to the Project as part of a new HashMap (that will disallow duplicates).

Finding Mapped Superclasses during predeploy()
EntityAccessor.discoverMappedSuperclassesAndInheritanceParents()
 this.getProject().getSession().getProject().getMappedSuperclasses().put(parent.getClass(), mappedSuperclassDesc);
this	EntityAccessor  (id=165)	
	m_project	MetadataProject  (id=57)	
		m_session	ServerSession  (id=49)	
			project	Project  (id=182)	
				mappedSuperclasses	HashMap<K,V>  (id=236)	
					entrySet	null	
					keySet	null	
					loadFactor	0.75	
					modCount	1	
					size	1	
					table	HashMap$Entry<K,V>[2]  (id=248)	
						[0]	HashMap$Entry<K,V>  (id=251)	

Retrieving Mapped Superclasses during Metamodel construction
Later, when a request to build a MetamodelImpl instance is requested, we use the stored Map of mapped superclass descriptors on the Project (not the ClassDescriptors themselves).
MetamodelImpl.initialize()
 Map<Class, RelationalDescriptor> mappedSuperclassesSet = project.getMappedSuperclasses(); 
 for(RelationalDescriptor mappedSuperclass : mappedSuperclassesSet.values()) {
 .....
 }
  • How to get the mapped superclass fields into our new ClassDescriptor
  • If we add a RelationalDescriptor instance to MappedSuperclassTypeImpl - then how do we handle the fact that this class does not directly inherit from ManagedTypeImpl but does inherit from the ManagedType interface via the IdentifiableType interface?
Proposal 2: Add MappedSuperclass (Object) to core Descriptor (Set based)
  • This is a variation on proposal 1, where we use a Set<RelationalDescriptor> to avoid duplicates instead of a Map<Class, RelationalDescriptor>.
  • We have the following design changes.
    • Store the Class (Metamodel client class) as the javaClass field on the Descriptor.
Decision:

We introduce a RelationalDescriptor for MappedSuperclasses so that we can process and retain mappings defined on these classes (usually abstract) for Metamodel processing. This custom RelationalDescriptor is only used during metamodel processing and does not actually represent a table on the data store (database).

DI 7: When to bootstrap Metamodel creation

  • We will be creating our Metamodel as a thin wrapper around the existing Metadata model.
  • The MetadataProcessor is invoked during EntityManagerFactory creation on predeploy().

DI 8: Ordering of Metamodel collections

  • Do we need to preserve ordering in the Metamodel?
  • If yes then we need to use LinkedHashMap maps instead of unordered HashMap containers
Solution:

LinkedHashMaps are used on MetamodelImpl

DI 9: Custom Logger for Metamodel

  • Do we need a custom logging wrapper class for the Metamodel API or should we just use the existing AbstractSession.log() functions directly?.
    • Use the current AbstractSession.log()

DI 10: Embeddable Support

  • How are we supporting Embeddables?
    • Via the protected java.util.Map<Class, EmbeddableTypeImpl<?>> embeddables; field on MetamodelImpl.

DI 11: Handle No Duplicates in MappedSuperclass Collection on Metamodel

We will need a collection of MappedSuperclass objects on the metamodel. We need to ensure that this collection does not allow duplicates to be added. Duplicates may be attempted because we make several passes during Metadata construction.

Override hashCode and equals on the key/value objects
  • We must override hashCode() and equals() on the key/value objects in our Collection class used to store mappedSuperclasses.
    • RelationalDescriptor will need these two methods.

DI 12: 20090506: Do we need an implementation of IdentifiableType

The IdentifiableType interface for entities and mappedSuperclasses that subclasses ManagedType is implemented by MappedSuperclassType and EntityType - do we need it?

Solution:

No

DI 16: 20090508: Implement IdentifiableType?

  • IdentifiableType - I have no implementation yet like I do for BasicImpl, Map|List|Set|CollectionImpl, EntityTypeImpl and EmbeddableTypeImpl
    • Just implement the interface in managed types

DI 13: 20090506: EntityManager/EntityManagerFactory.clear() resets MetaModel?

  • Should we null the metaModel field on a close()?
  • I think we should because all the other fields such as the session have been nulled as well - therefore we are in an invalid state.
  • We also by specification throw an ISE on a closed EM or EMF.
  • However, when we reopen the EM/EMF we end up with the same metaModel - because the PU has not changed. So do we really need to clear the metaModel?

DI14: 20090507: EntityTypeImpl support for primitive Id and Version

  • The Entity interface only supports Object versions such as Integer for Id and Version, currently the user will need to wrap their primitive types
    • public <Y> Attribute<? super X, Y> getId(Class<Y> type)
    • public <Y> Attribute<? super X, Y> getVersion(Class<Y> type)

DI 15: 20090508: What is the scope of "Declared"?

  • "Declared" keyword - as in getDeclaredMap() - how is this a subset of getMap()?
    • "Declared" means only in scope of this particular Entity - not via inheritance or mapped superclasses.
  • How are we going to determine whether an attribute is on a particular entity or is inherited via a mapped superclass?
    • We will determine if there exists a mappedSuperclass parent that contains this mapping - then it is non-declared.
Solution: Fixed
  • Issue fixed by Design Issue # 52

DI 17: 20090508: Clear Metamodel on EMF.close()?

  • em|emf.close - do we clear the metamodel?
    • No, keep the instance around
    • Q) How will we know to invalidate the current metamodel if a new persistence unit is loaded?
      • It will be left to the user handle this use case
      • For this variant I will possibly add a clear() function outside of the spec. The user will need to call this and then regenerate the new metamodel via getMetamodel() call.

DI 18: 20090508: Type Checking at metamodel creation or runtime?

  • Type checking exceptions during metamodel creation or in runtime calls? IE: getList("homeAddress") actually returns a single Entity instead of a List.
    • Leave checking to runtime and throw an exception at that time.

DI 24: 20090609: Move accessor processing out of BasicAccessor into MetadataProject

Instead of adding processing code to each Accessor class, we will add a processing step in addition to our current 3 stage processing so that we can reuse the accessor processing code already existing.

Solution
  • We need a fake sequence table name set in MappedSuperclassAccessor so that the processMetamodelDescriptor() call in MetadataProject.processStage2() succeeds.

DI 25: 20090616: Inherited parameterized generics for Element Collections (Basic)

  • Update: see bug 322166 fix.
  • An issue arose where the following Collection<X> acclaims attribute on a MappedSuperclass RatedBeerConsumer<X, Y, Z> that inherits the parameterized generic type X from an entity superclass BeerConsumer<T> cannot reference its' type.
  • The solution is to default in this case to void since we are not able to determine the parameterized type.
@MappedSuperclass
@Access(FIELD)
public abstract class RatedBeerConsumer<X, Y, Z> extends BeerConsumer<String> {
 @BasicCollection(valueColumn=@Column(name="ACCLAIM"))
 private Collection<X> acclaims;
 
and...
 
@Entity
@Table(name="CMP3_CONSUMER")
@Inheritance(strategy=JOINED)
@DiscriminatorValue(value="BC")
public class BeerConsumer<T> implements ChangeTracker, Cloneable {
ElementCollectionAccessor Modifications
  • Previously we threw a ValidationException for ElementCollectionAccessor.getReference(), we now return a Void MetadataClass in the specific use case where the classAccessor and metadataDescriptor are both MappedSuperclasses.

DI 26: 20090616: TableGenerator on Id column Accessor is null

  • During testing of the InstanceVariableAttributeAccessor, the following annotations on an @Id field in a @MappedSuperclass fail to get their accessor set.
@MappedSuperclass
public abstract class Person {
 @Id
 @GeneratedValue(strategy=TABLE, generator="PERSON_MM_TABLE_GENERATOR")
 @TableGenerator(
 name="PERSON_MM_TABLE_GENERATOR", 
 table="CMP3_MM_PERSON_SEQ", 
 pkColumnName="SEQ_MM_NAME", 
 valueColumnName="SEQ_MM_COUNT",
 pkColumnValue="CUST_MM_SEQ"
 )
 @Column(name="PERSON_ID") 
 private Integer id;
  • The offending end-of-line source
MetadataProject
 protected void processSequencingAccessors() {
...
 for (MetadataClass entityClass : m_generatedValues.keySet()) {
 ClassAccessor accessor = m_allAccessors.get(entityClass.getName());
-->NPE MetadataDescriptor descriptor = accessor.getDescriptor();
  • The stacktrace
Thread [main] (Suspended (breakpoint at line 944 in MetadataProject))	
	MetadataProject.processSequencingAccessors() line: 944	
	MetadataProject.processStage3() line: 1110	
Solution
  • We need a fake sequence table name set in MappedSuperclassAccessor so that the processMetamodelDescriptor() call in MetadataProject.processStage2() succeeds.

DI 27: 20090616: Embeddable access type conflict exception

  • The stacktrace
Exception Description: The *metadata-less* embeddable class [class org.eclipse.persistence.testing.models.jpa.inherited.Accredidation] is used in entity classes with conflicting access-types.  Its property access flag is set to [true]. It is being embedded in a [class org.eclipse.persistence.testing.models.jpa.inherited.RatedBeerConsumer] with property access flag set to: [false]. This is not allowed as this may result in inconsistent mappings of the embeddable class in different points of use. This problem can be corrected in two ways: 1. Provide metadata on class [class org.eclipse.persistence.testing.models.jpa.inherited.Accredidation] that allows the access type to be determined. 2. Ensure all users of class [class org.eclipse.persistence.testing.models.jpa.inherited.Accredidation] have the same access type.
	at org.eclipse.persistence.exceptions.ValidationException.conflictingAccessTypeForEmbeddable(ValidationException.java:2358)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor.process(EmbeddableAccessor.java:173)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor.processAccessors(MetadataDescriptor.java:1158)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor.processAccessors(ClassAccessor.java:774)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.MappedSuperclassAccessor.processMetamodelDescriptor(MappedSuperclassAccessor.java:1087)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage2(MetadataProject.java:1074)

Solution

Add a MappedSuperclass check in process()

if(!owningDescriptor.getClassAccessor().hasMappedSuperclasses())

DI 28: 20090622: Composite PK support for MappedSuperclasses

  • The following exception is not occurring because of our change to add extra processing for MappedSuperclasses in processStage2, it happens in the use case where a composite key contains a mappedSuperclass and is inherited by a normal Entity.

RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.inherited.BeerConsumer --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(CMP3_XML_CONSUMER)])

We should not be initializing the new mappedSuperclass descriptor in this composite PK case.

entity	EntityAccessor  (id=44)	
  m_className	"org.eclipse.persistence.testing.models.jpa.xml.inherited.ExpertBeerConsumer" (id=93)	
  m_descriptor	MetadataDescriptor  (id=66)	
    m_inheritanceRootDescriptor	MetadataDescriptor  (id=68)	
      m_descriptor	RelationalDescriptor  (id=131)	
        javaClassName	"org.eclipse.persistence.testing.models.jpa.xml.inherited.BeerConsumer" (id=70)	
        primaryKeyFields	ArrayList<E>  (id=177)	
          elementData	Object[2]  (id=256)	
            [0]	DatabaseField  (id=258)	
              name	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=260)	
              qualifiedName	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=260)	
 public void processStage2() {
 // 266912: process mappedSuperclasses without going to the database
 for(MappedSuperclassAccessor msAccessor : m_mappedSuperclassAccessors.values()) {
 if(!msAccessor.isProcessed()) { 
 msAccessor.processMetamodelDescriptor(); 
 }
 }
 
 for (EntityAccessor entity : getEntityAccessors()) {
 // If the accessor hasn't been processed yet, then process it. An
 // EntityAccessor may get fast tracked if it is an inheritance
 // parent.
 if (! entity.isProcessed()) {
-->here entity.process();
Exception Description: The multiple table foreign key relationship refers to an unknown table [DatabaseTable(qualified.__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME)].
Descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.inherited.ExpertBeerConsumer --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(CMP3_XML_CONSUMER), DatabaseTable(XML_EXPERT_CONSUMER)])
	at org.eclipse.persistence.exceptions.DescriptorException.illegalTableNameInMultipleTableForeignKeyField(DescriptorException.java:698)
	at org.eclipse.persistence.descriptors.ClassDescriptor.verifyMultipleTablesForeignKeysTables(ClassDescriptor.java:977)
	at org.eclipse.persistence.descriptors.ClassDescriptor.createMultipleTableInsertOrder(ClassDescriptor.java:888)
	at org.eclipse.persistence.descriptors.ClassDescriptor.adjustMultipleTableInsertOrder(ClassDescriptor.java:498)
	at org.eclipse.persistence.descriptors.ClassDescriptor.preInitialize(ClassDescriptor.java:3303)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:429)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:406)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:669)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.login(DatabaseSessionImpl.java:633)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:233)
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:260)
Exception Description: Invalid composite primary key specification. The names of the primary key fields or properties in the primary key class [org.eclipse.persistence.testing.models.jpa.xml.merge.inherited.TelephoneNumberPK] and those of the entity bean class [class org.eclipse.persistence.testing.models.jpa.xml.merge.inherited.TelephoneNumber] must correspond and their types must be the same. Also, ensure that you have specified ID elements for the corresponding attributes in XML and/or an @Id on the corresponding fields or properties of the entity class.

at org.eclipse.persistence.exceptions.ValidationException.invalidCompositePKSpecification(ValidationException.java:1124) at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.validatePrimaryKey(EntityAccessor.java:1433) at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.processAccessors(EntityAccessor.java:799) at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.process(EntityAccessor.java:651) at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage2(MetadataProject.java:1129)

Solution:

DI 29: 20090622: Multiple Table PK support for MappedSuperclasses

  • Most of our issues at this point surround class heirarchies involving a MappedSuperclass between entities.
    • as in Entity --> MappedSuperclass --> Entity
  • Our addition of fake PK field names do not handle multiple table PK's.
Exception Description: Multiple table primary key field names must be fully qualified.
Descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.inherited.ExpertBeerConsumer --> [DatabaseTable(__RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(XML_EXPERT_CONSUMER)])
	at org.eclipse.persistence.exceptions.DescriptorException.multipleTablePrimaryKeyMustBeFullyQualified(DescriptorException.java:967)
	at org.eclipse.persistence.descriptors.ClassDescriptor.addForeignKeyFieldForMultipleTable(ClassDescriptor.java:405)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor.addForeignKeyFieldForMultipleTable(MetadataDescriptor.java:288)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.addMultipleTableKeyFields(EntityAccessor.java:170)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.processInheritancePrimaryKeyJoinColumns(EntityAccessor.java:981)
	at org.eclipse.persistence.internal.jpa.metadata.inheritance.InheritanceMetadata.process(InheritanceMetadata.java:144)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.processTableAndInheritance(EntityAccessor.java:1226)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor.process(EntityAccessor.java:633)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage2(MetadataProject.java:1129)
  • Cause: The table.name on targetPrimaryKeyField is not set.
    • sourceForeignKeyField DatabaseField (id=64)
      • table DatabaseTable (id=70)
      • name "XML_EXPERT_CONSUMER" (id=79)
      • qualifiedName "XML_EXPERT_CONSUMER" (id=79)
      • tableQualifier "" (id=69)
    • targetPrimaryKeyField DatabaseField (id=66)
      • table DatabaseTable (id=72)
      • name "" (id=69)
      • qualifiedName "" (id=69)
      • tableQualifier "" (id=69)
  • The primaryKeyTable (eventually sourceTable) on the descriptor (on the pkField) is not set.
addMultipleTableKeyFields(pkJoinColumns, getDescriptor().getPrimaryKeyTable(), getDescriptor().getPrimaryTable(), MetadataLogger.INHERITANCE_PK_COLUMN, MetadataLogger.INHERITANCE_FK_COLUMN);
this	EntityAccessor  (id=31)	
  m_descriptor	MetadataDescriptor  (id=2421)	
    m_descriptor	RelationalDescriptor  (id=2438)	
      javaClassName	"org.eclipse.persistence.testing.models.jpa.xml.inherited.ExpertBeerConsumer" (id=2407)	
	primaryKeyFields	ArrayList<E>  (id=2581)	
		elementData	Object[2]  (id=2594)	
			[0]	DatabaseField  (id=2598)	
				name	"__PK_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=78)	
				qualifiedName	"__PK_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=78)	
				table	DatabaseTable  (id=72)	
					name	"" (id=57)	
					qualifiedName	"" (id=57)	
					tableQualifier	"" (id=57)	

and

this	EntityAccessor  (id=30)	
  m_className	"org.eclipse.persistence.testing.models.jpa.xml.merge.inherited.Alpine" (id=120)	
    m_descriptor	MetadataDescriptor  (id=122)	
      m_descriptor	RelationalDescriptor  (id=52)	
	alias	"MergeAlpine" (id=125)	
		mappings	NonSynchronizedVector  (id=272)	
			elementCount	5	
			elementData	Object[10]  (id=281)	
				[0]	DirectToFieldMapping  (id=283)	
					attributeName	"id" (id=309)	
					descriptor	RelationalDescriptor  (id=52)	
						primaryKeyFields	ArrayList<E>  (id=359)
							elementData	Object[2]  (id=368)	
								[0]	DatabaseField  (id=374)	
									name	"__PK_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=379)	
									qualifiedName	"__PK_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=379)	
									table	DatabaseTable  (id=382)	
								[1]	DatabaseField  (id=311)	
									name	"ID" (id=380)	
									qualifiedName	"ID" (id=380)	
									table	null	

then

Exception Description: The map key [] on the entity class [class org.eclipse.persistence.testing.models.jpa.inherited.RatedBeerConsumer] could not be found for the mapping [org.eclipse.persistence.mappings.DirectMapMapping[awards]].
	at org.eclipse.persistence.exceptions.ValidationException.couldNotFindMapKey(ValidationException.java:2053)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processMapKey(MappingAccessor.java:1294)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processContainerPolicyAndIndirection(MappingAccessor.java:1116)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.DirectCollectionAccessor.processDirectMapMapping(DirectCollectionAccessor.java:328)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.BasicMapAccessor.process(BasicMapAccessor.java:178)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processDirectCollectionAccessors(MetadataProject.java:833)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1154)
  • Fixed by checking that we are processing a Metamodel reserved table name.
 if(referenceDescriptor.hasDescriptorReservedForMetamodel())
Solution:
  • We need to add fake names to both tables in ClassDescriptor
if ((!sourceForeignKeyField.hasTableName()) || (!targetPrimaryKeyField.hasTableName())) {


  • The following 2 checks on the table and PK avoid fully processing our custom descriptors.
 public void processDirectCollectionAccessors() {
 for (DirectCollectionAccessor accessor : m_directCollectionAccessors) {
--> if(!accessor.getDescriptor().isPkClassDefinedOnMappedSuperclass()) {
--> if(!accessor.getDescriptor().hasDescriptorReservedForMetamodel()) {
 accessor.process();
 }
 }
 }
 }
  • And where we validate the PKClass, we check first to see if it our fake ID field by cross-referencing the PK field name on the accessor.
  • We are missing annotation processing after skipping accessor.process() above
Exception Description: The @JoinColumns on the annotated element [method getBeerConsumer] from the entity class [class org.eclipse.persistence.testing.models.jpa.xml.inherited.Certification] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referenceColumnName elements must be specified in each such @JoinColumn.
	at org.eclipse.persistence.exceptions.ValidationException.incompleteJoinColumnsSpecified(ValidationException.java:1789)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumnsAndValidate(MappingAccessor.java:461)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumns(MappingAccessor.java:418)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:463)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:509)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:97)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.processRelationship(RelationshipAccessor.java:443)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processRelationshipAccessors(MetadataProject.java:879)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1167)


  • In this case the join column size on the parent (MappedSuperclass) descriptor does not match the inheriting entity
 if (joinColumns.size() != descriptor.getPrimaryKeyFields().size()) {
  • child
    • org.eclipse.persistence.testing.models.jpa.xml.inherited.NoviceBeerConsumer
  • parent
    • RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.inherited.BeerConsumer --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(CMP3_XML_CONSUMER)])
Thread [main] (Suspended (breakpoint at line 464 in MappingAccessor))	
	ManyToManyAccessor(MappingAccessor).getJoinColumnsAndValidate(List<JoinColumnMetadata>, MetadataDescriptor) line: 464	
	ManyToManyAccessor(CollectionAccessor).processJoinTable(ManyToManyMapping, JoinTableMetadata) line: 562	
	ManyToManyAccessor.process() line: 105	
	ManyToManyAccessor(RelationshipAccessor).processRelationship() line: 443	
	MetadataDescriptor.getMappingForAttributeName(String, MetadataAccessor) line: 728	
	ManyToManyAccessor(RelationshipAccessor).getOwningMapping(String) line: 273	
	ManyToManyAccessor.process() line: 113	
	ManyToManyAccessor(RelationshipAccessor).processRelationship() line: 443	
	MetadataProject.processRelationshipAccessors() line: 879	
	MetadataProject.processStage3() line: 1167	
  • Fixed by returning a default DatabaseTable when requested of our reserved descriptor.
 public DatabaseTable getPrimaryTable() {
 if (m_primaryTable == null && isInheritanceSubclass()) {
 return getInheritanceRootDescriptor().getPrimaryTable();
 } else {
 if (m_descriptor.isAggregateDescriptor()) {
 // Aggregate descriptors don't have tables, just return a 
 // a default empty table.
 return new DatabaseTable();
 }
 
 // 266912: return a reserved table name for Metamodel processing
--> if(this.hasDescriptorReservedForMetamodel()) {
--> return new DatabaseTable();
  • 20090623:1500 Full JPA test suite preprocessing completes, however we have errors in the regression suite.
    [junit] Exception [EclipseLink-46] (Eclipse Persistence Services - 2.0.0.qualifier): org.eclipse.persistence.exceptions.DescriptorException
    [junit] Exception Description: There should be one non-read-only mapping defined for the primary key field [__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME.__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME].
    [junit] Descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.cacheable.SubCacheableFalseEntity --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(XML_SUB_CACHEABLE_FALSE)])
    [junit]     at org.eclipse.persistence.exceptions.DescriptorException.noMappingForPrimaryKey(DescriptorException.java:1048)
    [junit]     at org.eclipse.persistence.internal.descriptors.ObjectBuilder.initializePrimaryKey(ObjectBuilder.java:2472)
    [junit]     at org.eclipse.persistence.internal.descriptors.ObjectBuilder.initialize(ObjectBuilder.java:2349)
    [junit]     at org.eclipse.persistence.descriptors.ClassDescriptor.initialize(ClassDescriptor.java:2691)
    [junit]     at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:449)
    [junit]     at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:406)
    [junit]     at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.postConnectDatasource(DatabaseSessionImpl.java:669)
    [junit]     at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.login(DatabaseSessionImpl.java:633)
    [junit]     at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:233)
    [junit]     at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:260)
    [junit]     at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.getServerSession(EntityManagerFactoryImpl.java:135)
    [junit]     at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManagerImpl(EntityManagerFactoryImpl.java:187)
    [junit]     at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:175)
    [junit]     at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getServerSession(JUnitTestCase.java:299)
    [junit]     at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getEntityManagerFactory(JUnitTestCase.java:316)
    [junit]     at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getEntityManagerFactory(JUnitTestCase.java:303)
    [junit]     at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getServerSession(JUnitTestCase.java:299)
    [junit]     at org.eclipse.persistence.testing.tests.jpa.cacheable.CacheableModelJunitTest.testCachingOnDISABLE_SELECTIVE(CacheableModelJunitTest.java:167)


    [junit] Exception Description: There should be one non-read-only mapping defined for the primary key field
 [__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME.__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME].
    [junit] Descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.xml.cacheable.SubCache
ableFalseEntity --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME), DatabaseTable(XML_SUB_CACHEAB
LE_FALSE)])
    [junit]
    [junit] Runtime Exceptions:
    [junit] ---------------------------------------------------------
    [junit] )
    [junit] Tests run: 1219, Failures: 5, Errors: 31, Time elapsed: 1,153.016 sec
    [junit] Test org.eclipse.persistence.testing.tests.jpa.FullRegressionTestSuite FAILED
  • see

TEST NAME: testSequencePreallocationUsingCallbackTest(org.eclipse.persistence.testing.tests.jpa.fieldaccess.advanced.EntityManagerJUnitTestSuite)

DI 30: 20090623: BasicMap Parameterized Generic return type cannot be the Void class

MapAccessors cannot use Void because it does not implement Serializable. We will not process this accessor when isMappedSuperclass() is true.

Exception Description: The type [class java.lang.Void] for the attribute [acclaims] on the entity class [class org.eclipse.persistence.testing.models.jpa.inherited.RatedBeerConsumer] is not a valid type for a serialized mapping. The attribute type must implement the Serializable interface.
	at org.eclipse.persistence.exceptions.ValidationException.invalidTypeForSerializedAttribute(ValidationException.java:1078)
	at org.eclipse.persistence.internal.jpa.metadata.converters.SerializedMetadata.process(SerializedMetadata.java:67)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processSerialized(MappingAccessor.java:1496)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processJPAConverters(MappingAccessor.java:1270)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processMappingConverter(MappingAccessor.java:1363)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.processMappingValueConverter(MappingAccessor.java:1381)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.DirectCollectionAccessor.processDirectCollectionMapping(DirectCollectionAccessor.java:330)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.BasicCollectionAccessor.process(BasicCollectionAccessor.java:157)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processDirectCollectionAccessors(MetadataProject.java:833)FATAL ERROR in native method: processing of -javaagent failed

	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1150)

DI 31: 20090703: EntityTypeImpl inherits from ManagedType but MappedSuperclassTypeImpl does not

We have the following heirarchy.

  • Interfaces
  • Type
    • ManagedType
      • IdentifiableType
        • EntityType
        • MappedSuperclassType
  • Concrete classes
    • EntityTypeImpl extends ManagedTypeImpl implements EntityType (which extends IdentifiableType, Bindable)
    • MappedSuperclassTypeImpl extends Object implements MappedSuperclassType (which extends IdentifiableType)


Solution:
  • IdentifiableType requires an implementation class.
  • Introduce a new IdentifiableTypeImpl concrete class that extends ManagedTypeImpl
    • MappedSuperclassTypeImpl will also inherit from ManagedTypeImpl via IdentifiableTypeImpl
    • EntityTypeImpl now inherits from the IdentifiableTypeImpl subclass instead of from ManagedTypeImpl


DI 32: 20090703: Metamodel superType requires javaClass set on custom descriptor on MappedSuperclassAccessor

See enhancement 282518

Solution:

We are setting the javaClass later during metamodel processing instead of during metadata processing - so we can avoid persistence operations on our custom Relational Descriptor.

See FishEye Rev# 4614

  • 20100217: see bug# 303063 for an issue where RelationalDescriptor.getJavaClass() is null (out of spec) causing a possible NPE for SingularAttributes.

DI 33: 20090707: Application Server Container - Verify that we get the correct classLoader from the right session

In MetamodelImpl.initialize() when we are iterating the MappedSuperclass descriptors, we must test and verify that the classLoader is valid in container mode and not just for SE clients.

  • Currently we are using getSession().getActiveSession() just in case we running on an external transaction controller, otherwise getSession() and getActiveSession() return the same session.
    • ClassLoader classLoader = this.getSession().getActiveSession().getClass().getClassLoader();
Solution:

Continue to use the above code until we test the eclipselink.jar on an app server.

DI 34: 20090707: Temporary CNFE when loading Metamodel Impl class

  • Just before resolving an internal metamodel API class we get a ClassNotFoundException on the classLoader when running in Eclipse.
  • I expect that this is normal behavoir and that the catch block in the JRE eventually finds the class on a different branch in the tree - odd since the referencing class is in the same package as the class causing the CNFE.
MappedSuperclassTypeImpl<?> mappedSuperclassType = new MappedSuperclassTypeImpl(this, descriptor);
Thread [Thread-3] (Suspended)	
	ClassNotFoundException(Throwable).<init>(String, Throwable) line: 217	
	ClassNotFoundException(Exception).<init>(String, Throwable) line: not available	
	ClassNotFoundException.<init>(String) line: not available	
	ClassLoader.findBootstrapClass(String) line: not available [native method]	
	Launcher$ExtClassLoader(ClassLoader).findBootstrapClass0(String) line: not available	
	Launcher$ExtClassLoader(ClassLoader).loadClass(String, boolean) line: not available	
	Launcher$AppClassLoader(ClassLoader).loadClass(String, boolean) line: not available	
	Launcher$AppClassLoader.loadClass(String, boolean) line: not available	
	Launcher$AppClassLoader(ClassLoader).loadClass(String) line: not available	
	Launcher$AppClassLoader(ClassLoader).loadClassInternal(String) line: not available	
	MetamodelImpl.initialize() line: 167	
	MetamodelImpl.<init>(DatabaseSession) line: 77	
	MetamodelImpl.<init>(EntityManagerFactory) line: 82	
	EntityManagerFactoryImpl.getMetamodel() line: 416	
	EntityManagerImpl.getMetamodel() line: 1987	
	MetamodelMetamodelTest.testImplementation() line: 449	
  • The class is found in another loader ok
    • org/eclipse/persistence/internal/jpa/metamodel/MappedSuperclassTypeImpl
Thread [Thread-3] (Suspended)	
	MappedSuperclassTypeImpl<X>.<init>(MetamodelImpl, RelationalDescriptor) line: 59	
	MetamodelImpl.initialize() line: 167	
	MetamodelImpl.<init>(DatabaseSession) line: 77	
	MetamodelImpl.<init>(EntityManagerFactory) line: 82	
	EntityManagerFactoryImpl.getMetamodel() line: 416	
	EntityManagerImpl.getMetamodel() line: 1987	
	MetamodelMetamodelTest.testImplementation() line: 449	


Solution:

No code change required

DI 35: 20090707: Where to initialize IdentifiableTypeImpl.supertype

  • The following field on IdentifiableTypeImpl currently has a delayed instantiation after all ManagedTypeImpl objects have been instantiated.
  • MetamodelImpl.initialize() handles setting the supertype field for all MappedSuperclassType and EntityType classes.
    • protected IdentifiableType<? super X> supertype;
Solution:
  • We will continue to initialize the supertype in the second part of initialize() - this will create an IdentifiableType that is in an inconsistent state only within the initialize() function. This is acceptable because the type will be fully instantiated before it is used by any public API.

DI 36: 20090708: The StaticMetamodel requires an implementation class

  • The StaticMetamodel interface does not have a concrete class yet (it used to be TypeSafeMetamodel) - see bug# 282868
  • No work may be required beyond providing this annotation via the javax.persistence interface class.

DI 37: 20090708: CollectionAttribute acts as a peer of Map, Set, List but should be a super interface

In Java the Set and List interfaces inherit from Collection, with Map acting like a Collection but not inheriting from Collection - but in the Metamodel, Collection is at the same level as these collection child interfaces.

We may need special handling for CollectionAttribute so that we can treat the following.

  • PluralAttribute (previously AbstractCollectionAttribute)
    • Collection
    • ListAttribute
    • MapAttribute
    • SetAttribute

Like

  • Collection
    • List
    • Set
  • Map
Analysis:
  • We have 2 scenarios here, 1) we treat CollectionAttribute distinct from List, and Set, or 2) we tread CollectionAttribute as the superclass of List and Set without actually being a super interface.
  • It is evident from the fact that we have only getAttributes(), getCollections() and getSingularAttributes() in ManagedType - that a Collection is a superset of all Set, List and even Map.
Solution: Fixed
  • Treat Collection as a super interface of Set, List and Map in Java but treat is a peer of Set, List and Map in the Metamodel.
  • Solved in SVN Rev# 4820 and 4827 as part of Issue #58

DI 38: 20090708: OneToOne Set defaults to IndirectList

  • The following OneToOne attribute defined as a Collection and instantiated as a HashSet will default to an IndirectList - which will create the wrong ListAttributeImpl member for ManagedType.initialize().
public class Manufacturer extends Corporation implements java.io.Serializable{
 @OneToMany(cascade=ALL, mappedBy="manufacturer")
 private Collection<Computer> computers = new HashSet<Computer>();
  • Causing a CCE later on in
java.lang.ClassCastException: org.eclipse.persistence.internal.jpa.metamodel.ListAttributeImpl cannot be cast to javax.persistence.metamodel.CollectionAttribute
	at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getCollectionHelper(IdentifiableTypeImpl.java:120)
	at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getCollection(IdentifiableTypeImpl.java:47)
  • However, by changing the declared type to Set - we will default to an IndirectSet - which will create a correct SetAttributeImpl member for ManagedType.initialize().
public class Manufacturer extends Corporation implements java.io.Serializable{
 @OneToMany(cascade=ALL, mappedBy="manufacturer")
 private Set<Computer> computers = new HashSet<Computer>();
Solution: Fixed
  • Add support for Indirect collections by also checking that this type is assignable on the ContainerPolicy.
 colMapping.getContainerPolicy().getContainerClass().isAssignableFrom(IndirectSet.class)

DI 39: 20090708: Handle MappedSuperclass in ManagedTypeImpl.create()

  • We currently create an EmbeddableTypeImpl or an EntityTypeImpl depending on whether the RelationalDescriptor is aggregate or not. We need to also handle MappedSuperclasses.
  • We also need to investigate whether we support descriptor types NORMAL or AGGREGATE_COLLECTION.
  • We need to describe why we do not support descriptor type INTERFACE - we do not support variable OneToOne mappings.
Solution:
  • 20090813: The MappedSuperclass creation code has been moved from Metamodel.initialize() to ManagedTypeImpl.create() and its' subclass
  • The MappedSuperclass part is in SVN rev#.

DI 40: 20090708: Type hierarchy numbers are missing MappedSuperclass types

  • In MetamodelImpl we have the following fields and example number of instances
    • entities = 8
    • mappedSuperclasses = 4
    • embeddable = 2
    • (implied basic types) = 1
  • The combinations of these types should be
    • Types = Basic + Embeddable + MappedSuperclass + Entity = (1 + 2 + 4 + 8)
    • managedTypes = Embeddable + MappedSuperclass + Entity = (2 + 4 + 8)
    • IdentifiableTypes = MappedSuperclass + Entity = (4 + 8)
  • However, I am not seeing MappedSuperclasses in types
Solution: Fixed
  • Move member processing of all Types to a separate iteration step after all types have already been created
  • Add another put to the MappedSuperclass processing loop in MetamodelImpl.initialize()
    • this.types.put(mappedSuperclassType.getJavaType(), mappedSuperclassType);
  • We now get the following valid Metamodel (It contains no EmbeddableTypes or BasicTypes)
metamodel	MetamodelImpl  (id=47)	
	embeddables	LinkedHashMap<K,V>  (id=62)	
		size	0	
		table	HashMap$Entry<K,V>[16]  (id=439)	
	entities	LinkedHashMap<K,V>  (id=70)	
		size	10	
		table	HashMap$Entry<K,V>[16]  (id=435)	
	managedTypes	LinkedHashMap<K,V>  (id=71)	
		size	14	
		table	HashMap$Entry<K,V>[32]  (id=431)	
	mappedSuperclasses	HashSet<E>  (id=72)	
		map	HashMap<K,V>  (id=88)	
			size	4	
			table	HashMap$Entry<K,V>[16]  (id=95)	
	types	LinkedHashMap<K,V>  (id=80)	
		modCount	14	
		size	14	
		table	HashMap$Entry<K,V>[32]  (id=117)	


DI 41: When to throw IAE for missing member or wrong type on get() call

In ManagedType for all the get() calls that also check the type of the member being returned - the javadoc comment for IllegalArgumentException handling may need some expansion to handle the case where the type exists but the type is incorrect.
 * @throws IllegalArgumentException if attribute of the given
 * name and type is not present in the managed type
    • There are 3 valid cases out of a possible 4 for these functions
      • 1) attribute exists, type is correct = normal
      • 2) attribute exists, type is incorrect = throw IAE (assumed only for type) - not presently in the spec.
      • 3) attribute missing, type is X=irrelevant = throw IEA (according to javadoc) - here we have no way of verifying whether the type is correct
    • Based on the 3 cases above I would expect that the javadoc should read
 * @throws IllegalArgumentException if attribute of the given
 * name and type is not present in the managed type
 * @throws IllegalArgumentException if attribute of the given
 * name is present but the type is incorrect in the managed type
  • So if we pass in the wrong type - here EntityType:Memory instead of EntityType:Computer, we should get an IAE even though the attribute "computers" was found.
java.lang.IllegalArgumentException: Expected attribute type 
  [class org.eclipse.persistence.testing.models.jpa.metamodel.Memory] 
  on the existing attribute [computers] 
  on the managed type [ManagedTypeImpl[RelationalDescriptor
  (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer -->
   [DatabaseTable(CMP3_MM_MANUF)])]] 
  but found attribute type 
  [org.eclipse.persistence.testing.models.jpa.metamodel.Computer].
Solution:

Pending review but, return two different IAE exceptions based on whether we are in case 2 or 3


DI 42: 20090709: IdentifiableType.supertype - what do top-level types set it to

  • If we have a metamodel type hierarchy like the example in this document where there are several entities or mappedSuperclasses that are at the root of their inheritance tree (Person, Computer, Board, Memory, Location). What do we set the supertype to? null or Object.
  • The API reserves null to mean no supertype, but I think we should differentiate between null==not set and null==no supertype - but we are constrained by the fact that a supertype must be an IdentifiableType - Object is not an IdentifiableType (Entity or MappedSuperclass because it has no @Entity or @MappedSuperclass annotation)
    • Java vs Metamodel object spaces discussion:
    • The superclass for top-level types is Object - however we set [null] as the supertype for root types.
    • The reasoning behind this is to separate the Java and Metamodel object spaces.
    • In Java all type inherit from Object, however in the JPA Metamodel all types DO NOT inherit from a common type.
    • Therefore in the metamodel top-level root types have a superType of null.
 /**
 * Return the identifiable type that corresponds to the most
 * specific mapped superclass or entity extended by the entity 
 * or mapped superclass. 
 * @return supertype of identifiable type or null if no such supertype
 */
 public IdentifiableType<? super X> getSupertype() {
Test model hiearchy:
entityManufacturer EntityTypeImpl<X> (id=5854) 
 javaClass Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=4201) 
 superType MappedSuperclassTypeImpl<X> (id=5858) 
 javaClass Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Corporation) (id=870) 
 superType MappedSuperclassTypeImpl<X> (id=5838) 
 javaClass Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Person) (id=1488) 
 superType null
  • Normally for example Manufacturer.getSupertype() would return Corporation since it inherits from it.
  • However, currently however Person.getSupertype() returns null (we don't know if a null means unset or a top-level identifiableType)
Solution:

Return null for any top level managed types and not Object.


DI 43: 20090710: Implement getDeclaredX() methods

  • The following 15 typesafe and non-typesafe functions need to be implemented on ManagedType (for BasicType and EmbeddableType) or in combination with IdentifiableType (for EntityType and MappedSuperclassType).
    • ManagedType
      • getDeclaredAttribute(String)
      • getDeclaredAttributes()
      • getDeclaredAttribute(String)
      • getDeclaredCollection(String)
      • getDeclaredCollection(String, Class)
      • getDeclaredCollections()
      • getDeclaredList(String)
      • getDeclaredList(String, Class)
      • getDeclaredMap(String)
      • getDeclaredMap(String, Class)
      • getDeclaredSet(String)
      • getDeclaredSet(String, Class)
      • getDeclaredSingularAttribute(String)
      • getDeclaredSingularAttribute(String, Class)
      • getDeclaredSingularAttributes()
    • IdentifiableType
      • getDeclaredId(Class)
      • getDeclaredVersion(Class)


Analysis:
  • Constraints:
    • C1: Return the valued attribute of the managed type that corresponds to the specified name and Java element type.
    • C2: throws IllegalArgumentException if attribute of the given name and type is not present in the managed type
  • Q1) We know Basic types can't but can Embeddable types declare attributes/mappings?
    • The answer will determine how high in the hierarcy we implement declared functionality (IdentifiableType, ManagedType or Type)
  • Declared Use Cases:
    • Use Case Partitioning:
      • - attribute positioning(none, current, 1st parent, Nth parent)
      • - attribute type (right, wrong type)
      • - attribute classification for current and parents (Entity, MappedSuperclass, embeddable?, Basic?)
    • UC1) Mapping is not declared on current attribute (or its' superclasses)
      • - Throw IAException
    • UC2) Mapping is declared on current attribute but is of the wrong type
      • - Throw IAException
    • UC3) Attribute is found on on current managedType Entity/MappedSuperclass - (but not found anywhere on the supertype hierarchy - declared above). In this case we do the reverse - keep checking only when attribute is null.
      • - Return attribute
    • UC4) Mapping is declared on immediate superclass
      • - Throw IAException
    • UC5) Mapping is declared on Nth superclass
      • - Throw IAException
Design:
  • We use two functions, one public, one a private recursive function.
    • If the attribute is not found at the current level or above, or is of the wrong type - throw an IAException
    • If the attribute is found then we still need to search to the top of the hierarchy tree to verify it is not declared above - if it is also not found above - return the attribute in this case only
Solution:

DI 44: 20090710: Criteria API FromImpl equality check requires enum instead of BasicTypeImpl

  • Now that i have SingularAttribute.getType() and Bindable.getBindableJavaType() working for SingularAttributeImpl.java - there are a couple adjustments in its' usage in FromImpl that will require changes that we were unable to test previously.
  • Note: this same change should be made to all the other instances of .equals(PeristenceType.
this	BasicTypeImpl<X>  (id=137)	
	javaClass	Class<T> (java.lang.String) (id=149)	
arg0	Type$PersistenceType  (id=184)	
	name	"BASIC" (id=187)	
	ordinal	3	
  • In the following code from FromImpl.java:300
 }else{
 Class clazz = ((SingularAttribute)attribute).getBindableJavaType();
--> if (((SingularAttribute)attribute).getType().equals(PersistenceType.BASIC)){
 return new PathImpl<Y>(this, this.metamodel, clazz, this.currentNode.get(attribute.getName()), (Bindable)attribute);
 }else{
 join = new JoinImpl(this, this.metamodel.type(clazz), this.metamodel, clazz, this.currentNode.get(attribute.getName()), (Bindable)attribute);
 }
Solution:
  • The fix is to to compare enums by changing
 if (((SingularAttribute)attribute).getType().equals(PersistenceType.BASIC)){
  • to
 if (((SingularAttribute)attribute).getType().getPersistenceType().equals(PersistenceType.BASIC)){
  • change
 }else{
 Class clazz = ((SingularAttribute)attribute).getBindableJavaType();
 //if (((SingularAttribute)attribute).getType().equals(PersistenceType.BASIC)){
 if (((SingularAttribute)attribute).getType().getPersistenceType().equals(PersistenceType.BASIC)){ 
-->modified path return new PathImpl<Y>(this, this.metamodel, clazz, this.currentNode.get(attribute.getName()), (Bindable)attribute);
 }else{
-->original path join = new JoinImpl(this, this.metamodel.type(clazz), this.metamodel, clazz, this.currentNode.get(attribute.getName()), (Bindable)attribute);
 }
  • Results with addition of .getPersistenceType() above
results	Vector<E>  (id=205)	
	capacityIncrement	0	
	elementCount	2	
	elementData	Object[2]  (id=212)	
		[0]	Computer  (id=77)	
		[1]	Computer  (id=86)	
	modCount	2	
  • Results with no code changes
results	Vector<E>  (id=205)	
	capacityIncrement	0	
	elementCount	2	
	elementData	Object[2]  (id=209)	
		[0]	Computer  (id=88)	
		[1]	Computer  (id=79)	
	modCount	2	
  • I get the same results for either code path but I think that the [return new PathImpl] positive part of the if will never execute --> .equals() ill always be false without the addition of .getPersistenceType().
  • For another use case - try to cause the following ISE.
 if (((SingularAttribute)attribute).getType().equals(PersistenceType.BASIC)){
 throw new IllegalStateException(ExceptionLocalization.buildMessage("CAN_NOT_JOIN_TO_BASIC"));
 }

DI 45: 20090713: getAttributes() returns a new HashSet - not the actual Metamodel.members.values Collection

  • In ManagedTypeImpl.getAttributes() and especially ManagedTypeImpl.getDeclaredAttributes() - we return a new HashSet instead of directly returning the Collection of values from the Metamodel.members HashMap.
  • See also getCollections()

DI 46: 20090715: Metamodel initialization on EntityManagerFactory creation

provider to initialize the state of the metamodel classes of the persistence unit. Any generated metamodel classes must be accessible on the classpath."

  • This design document involves the "dynamic" metamodel and not the static canonical metamodel - however in the introduction for section 6.2, the spec describes the two metamodel access methods dynamic or static to be interchangable.
  • So my question is - do we allow the dynamic metamodel to continue to to be lazy initialized on the EMF when a getMetamodel() call is requested - or do we create the metamodel automatically during EMF construction.
    • This question is best answered when we have the requirements of the canonical metamodel - which will use the dynamic metamodel to introspect the metadata.
  • The current EntityManagerFactoryImpl metamodel functions.
    /**
     * Return an instance of Metamodel interface for access to the
     * metamodel of the persistence unit.
     * @return Metamodel instance
     * @throws IllegalStateException if the entity manager factory has
     * been closed.
     * @see javax.persistence.EntityManagerFactory#getMetamodel()
     * @since Java Persistence 2.0
     */
    public Metamodel getMetamodel() {
        if(!this.isOpen()) {
            throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager_factory"));
        }
        // perform lazy initialization
        if(null == metaModel) {
            metaModel = new org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl(this);
            //If the canonical metamodel classes exist, initialize them
            initializeCanonicalMetamodel(metaModel);
 
        }
        return metaModel;
    }
 
    /**
     * INTERNAL:
     * Convenience function to allow us to reset the Metamodel 
     * in the possible case that we want to regenerate it.
     * This function is outside of the JPA 2.0 specification.
     * @param aMetamodel
     */
    public void setMetamodel(Metamodel aMetamodel) {
        if(!this.isOpen()) {
            throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager_factory"));
        }
        metaModel = aMetamodel;
    }
Solution 46: - Fixed
  • 20091006 status: This issue is reopened - we are moving the initialization higher up to EMF creation
    • We will need a baseline with 100's of persistence units holding xx entities/mappedSuperclasses/embeddables which we can use to measure any performance increase
  • After a discussion with Gordon it would be best to move the metamodel to EntityManagerSetupImpl from EntityManagerFactoryImpl where it will get initialized during the predeploy - after stage 3 is complete.
	EntityAccessor.preProcess(boolean) line: 636	
	EntityAccessor.preProcess() line: 589	
	MetadataProject.processStage1() line: 1281	
	MetadataProcessor.processORMMetadata() line: 461	
	PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 337	
	EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 857	
  • 20091214 status: Issue reopened again - we will not let any exception during metamodel processing to hold up the entityManager deploy - see bug# 297555 and enhancement# 297nnn

DI 47: 20090715: Implement IdentifiableType.getIdType() for composite keys

 this.metamodel.getType(((CMP3Policy) cmpPolicy).getPKClass());
  • However, a test model and more investigation is required for composite keys where the CMP3Policy is null.
  • We need scenarios where this policy is null.
 java.util.List<DatabaseMapping> pkMappings = getDescriptor().getObjectBuilder().getPrimaryKeyMappings(); 
 if (pkMappings.size() == 1) {
 Class aClass = pkMappings.get(0).getAttributeClassification(); // null for OneToOneMapping
 // lookup class in our types map
 Type<?> aType = this.metamodel.type(aClass);
 return aType;
 }

DI 48: 20090721: Implement Map support

  • 20090824: See continuation part 2 in DI 63 of this fix that adds support for the @MapKey annotation in addition to this default behavior of using the Primary Key of the target entity when the @MapKey annotation is not present.
  • We are getting a NPE during metamodel creation for the JPQL test suite that includes a Map where the Primary Key is an @IdClass.
    • policy.getKeyType() is null.
 protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping) {
 super(managedType, mapping);
 MapContainerPolicy policy = (MapContainerPolicy) mapping.getContainerPolicy();
---> Type<?> keyType = managedType.getMetamodel().getType(policy.getKeyType().getClass());
Analysis:
  • For the object model where there is a PK on the mappedSuperclass of the owning entity there is no NPE issue but I am getting the class twice in error.
  • The thing to note here is that "key" means PK not the k:key of the Map<K,v>
  • The key is
 private java.lang.Integer
org.eclipse.persistence.testing.models.jpa.metamodel.Person.id
@Entity(name="HardwareDesignerMetamodel")
@Table(name="CMP3_MM_HWDESIGNER")
public class HardwareDesigner extends Designer implements java.io.Serializable{
 // The M:1 side is the owning side
 @ManyToOne(fetch=EAGER)//LAZY)
 @JoinTable(name="CMP3_MM_MANUF_MM_HWDES_MAP", 
 joinColumns = @JoinColumn(name="DESIGNER_MAP_ID"), 
 inverseJoinColumns =@JoinColumn(name="MANUF_ID")) 
 private Manufacturer mappedEmployer;
 
@Entity(name="ManufacturerMetamodel")
@Table(name="CMP3_MM_MANUF")
public class Manufacturer extends Corporation implements java.io.Serializable{
 // If a JoinTable with a JoinColumn is used - then we need a mappedBy on
the inverse side here
 @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
 private Map<String, HardwareDesigner> hardwareDesignersMap = new
HashMap<String, HardwareDesigner>();
  • and associated relational model join table
 public static TableDefinition
buildMANUFACTURER_HARDWAREDESIGNER_MAP_JOINTable() {
 TableDefinition table = new TableDefinition();
 table.setName("CMP3_MM_MANUF_MM_HWDES_MAP");
 
 FieldDefinition field1 = new FieldDefinition();
 field1.setName("MANUF_ID");
 field1.setTypeName("NUMERIC");
 field1.setSize(15);
 field1.setShouldAllowNull(false);
 field1.setIsPrimaryKey(false);
 field1.setUnique(false);
 field1.setIsIdentity(false);
 field1.setForeignKeyFieldName("CMP3_MM_MANUF.PERSON_ID");
 table.addField(field1); 
 
 FieldDefinition field2 = new FieldDefinition();
 field2.setName("DESIGNER_MAP_ID");
 field2.setTypeName("NUMERIC");
 field2.setSize(15);
 field2.setShouldAllowNull(false);
 field2.setIsPrimaryKey(false);
 field2.setUnique(false);
 field2.setIsIdentity(false);
 field2.setForeignKeyFieldName("CMP3_MM_HWDESIGNER.PERSON_ID");
 table.addField(field2); 
 return table;
 }
  • We pass through metamodel type creation without a NPE but the Class for getType() is wrong
Thread [Thread-3] (Suspended) 
 MapContainerPolicy.getKeyType() line: 360 
 MapAttributeImpl<X,K,V>.<init>(ManagedTypeImpl<X>, CollectionMapping)
line: 53 
 EntityTypeImpl<X>(ManagedTypeImpl<X>).initialize() line: 903 
 MetamodelImpl.initialize() line: 229 
 MetamodelImpl.<init>(DatabaseSession) line: 85 
 MetamodelImpl.<init>(EntityManagerFactory) line: 90 
 EntityManagerFactoryImpl.getMetamodel() line: 416 
 EntityManagerImpl.getMetamodel() line: 1991 
 MetamodelMetamodelTest.testImplementation() line: 415 
 NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not
available [native method] 
 NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39 
 DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25 
 Method.invoke(Object, Object...) line: 597 
 MetamodelMetamodelTest(TestCase).runTest() line: 168 
 MetamodelMetamodelTest(TestCase).runBare() line: 134 
 MetamodelMetamodelTest(JUnitTestCase).runBare() line: 379 
 TestResult$1.protect() line: 110 
 TestResult.runProtected(Test, Protectable) line: 128 
 TestResult.run(TestCase) line: 113 
 MetamodelMetamodelTest(TestCase).run(TestResult) line: 124 
 TestExecutor.execute(Test) line: 248 
 TestExecutor.runTest(Test) line: 671 
 SynchronizedTestExecutor.run() line: 61 
 
policy MapContainerPolicy (id=104) 
 cloneMethod null 
 constructor Constructor<T> (id=109) 
 containerClass Class<T>
(org.eclipse.persistence.indirection.IndirectMap) (id=111) 
 containerClassName 
"org.eclipse.persistence.indirection.IndirectMap" (id=112) 
 elementClass Class<T>
(org.eclipse.persistence.testing.models.jpa.metamodel.HardwareDesigner)
(id=115) 
 elementClassName 
"org.eclipse.persistence.testing.models.jpa.metamodel.HardwareDesigner"
(id=117) 
 elementDescriptor RelationalDescriptor (id=118) 
 keyField Field (id=126) 
 annotations byte[60] (id=133) 
 clazz Class<T>
(org.eclipse.persistence.testing.models.jpa.metamodel.Person) (id=135) 
 type Class<T> (java.lang.Integer) (id=143) 
 keyMethod null 
 keyName "id" (id=121)
  • getType(Class) should not be creating a type for java.lang.Class
type BasicTypeImpl<X> (id=150) 
 javaClass Class<T> (java.lang.Class) (id=82)
  • I should be doing something like the following to handle when there is no PK and not gettting the class of a class
  • Note we get a Class in all instances of policy.getKeyType() except for AggregateObjectMappings.
    protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping) {
        super(managedType, mapping);
 
        MapContainerPolicy policy = (MapContainerPolicy) mapping.getContainerPolicy();
        Object policyKeyType = policy.getKeyType(); // returns a Class<?> or descriptor (via AggregateObjectMapping)        
        Type<?> aKeyType = null;
        // Default to Object class for any variant cases that are not handled
        Class<?> javaClass = Object.class;
        if(null == policyKeyType) {
            // No policy key type = IdClass (use CMP3Policy.pkClass)
            if(managedType.isIdentifiableType()) {
                // Use the CMPPolicy on the element not the one on the managedType
                if(policy.getElementDescriptor() != null && policy.getElementDescriptor().getCMPPolicy() != null) {
                    javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
                } else {
                    if(null == policy.getElementDescriptor()) {
                        // check for a keyMapping on the mapping
                        if(policy.isMappedKeyMapPolicy()) {
                            MapKeyMapping mapKeyMapping = ((MappedKeyMapContainerPolicy)policy).getKeyMapping();
                            RelationalDescriptor descriptor = (RelationalDescriptor)((DatabaseMapping)mapKeyMapping).getDescriptor();
                            // If the reference descriptor is null then we are on a direct mapping
                            if(null == descriptor) {
                                throw new IllegalArgumentException("Unsupported operation on " + managedType);
                            } else {
                                if(null == descriptor.getCMPPolicy()) { // for __PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME
                                    //throw new IllegalArgumentException("Unsupported operation on " + managedType);
                                    javaClass = Object.class;
                                } else {
                                    javaClass = descriptor.getCMPPolicy().getPKClass();        
                                }
                            }
                        }
                    } else {                        
                    }
                }
            } else {
                // Handle EmbeddableType
            }
        } else {            
            if(policyKeyType instanceof ClassDescriptor) { // from AggregateObjectMapping
                javaClass = (Class<?>)((ClassDescriptor)policyKeyType).getJavaClass();
            } else {
                javaClass = (Class<?>)policyKeyType;
            }            
        }
        aKeyType = getMetamodel().getType(javaClass);
        this.keyType = (Type<K>) aKeyType;
    }
  • which results in a BasicType<Integer>
keyType BasicTypeImpl<X> (id=88) 
 javaClass Class<T> (java.lang.Integer) (id=82)
Solution:
  • Add a Map attribute to the test model
    • a hardwareDesignersMap on Manufacturer with the mappedBy on HardwareDesigner and a join table across the two entity tables.
  • verify whether we should also use getElementClass()

DI 49: 20090723: OneToMany on MappedSuperclass with JoinTable causes Composite PK in error

  • Fixed in SVN Rev# 4845 using Option 3b (sacrifice space constraints in favor of a no-search performance improvement)
  • 20090724: see also bug# 284147
  • Here, because of the pseudo RelationalDescriptor PK name for MappedSuperclasses added a month ago, there is a small issue arising for @OneToMany mappings on a MappedSuperclass with a JoinTable where the pseudo PK is being added on top of the existing PK in the PK fields list.
  • The result of this is that some of the Metamodel processing changes PK is leaking into the real world Metamodel processing code.
Reproduction:
  • Adding the following unidirectional @OneToMany to a MappedSuperclass that defines an @Id like Person (inherited by the MappedSuperclass Corporation which is inherited by the Entity Manufacturer)
@MappedSuperclass
public abstract class Person {
    @Id
    @GeneratedValue(strategy=TABLE, generator="PERSON_MM_TABLE_GENERATOR")
    @TableGenerator(
        name="PERSON_MM_TABLE_GENERATOR", 
        table="CMP3_MM_PERSON_SEQ", 
        pkColumnName="SEQ_MM_NAME", 
        valueColumnName="SEQ_MM_COUNT",
        pkColumnValue="CUST_MM_SEQ"
    )    
    // InstanceVariableAttributeAccessor testing
    @Column(name="PERSON_ID")    
    private Integer id;
 
 
    // Verify special handling for PK for OneToMany (custom descriptor with fake PK name)
    // If a JoinTable with a JoinColumn is used - then we need a mappedBy on the inverse side here
    // However, bidirectional relationships are not allowed to MappedSuperclasses - as they have no identity
    // This @OneToMany implements internally as a @ManyToMany
    @OneToMany(fetch=EAGER, cascade=ALL)
    @JoinTable(name="CMP3_MM_HIST_EMPLOY", 
                joinColumns = @JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID"),
                inverseJoinColumns = @JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID"))   
    private Collection<Manufacturer> historicalEmployers = new HashSet<Manufacturer>();
  • You will see the following exception
Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.0.0.qualifier): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The @JoinColumns on the annotated element [field historicalEmployers] from the entity class [class org.eclipse.persistence.testing.models.jpa.metamodel.Person] is incomplete. When the source entity class uses a composite primary key, a @JoinColumn must be specified for each join column using the @JoinColumns. Both the name and the referencedColumnName elements must be specified in each such @JoinColumn.
	at org.eclipse.persistence.exceptions.ValidationException.incompleteJoinColumnsSpecified(ValidationException.java:1789)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumnsAndValidate(MappingAccessor.java:483)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.CollectionAccessor.processJoinTable(CollectionAccessor.java:578)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.OneToManyAccessor.processManyToManyMapping(OneToManyAccessor.java:153)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.OneToManyAccessor.process(OneToManyAccessor.java:108)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.RelationshipAccessor.processRelationship(RelationshipAccessor.java:459)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processRelationshipAccessors(MetadataProject.java:1046)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1325)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:460)
	at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:297)
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:849)
  • This validation exception is caused by 2 defined primary keys (one defined and the other added for MappedSuperclass processing). Because this particular MappedSuperclass already defines its' own @Id we should not add the fake proceessing Id here in this use case.
descriptor	MetadataDescriptor  (id=75)	
	m_descriptor	RelationalDescriptor  (id=74)	
		primaryKeyFields	ArrayList<E>  (id=182)	
			elementData	Object[2]  (id=195)	
				[0]	DatabaseField  (id=197)	
					name	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=199)	
					qualifiedName	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=199)	
					table	DatabaseTable  (id=200)	
					type	null	
					typeName	null	
				[1]	DatabaseField  (id=186)	
					name	"PERSON_ID" (id=202)	
					qualifiedName	"__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME.PERSON_ID" (id=204)	
					table	DatabaseTable  (id=151)	
					type	null	
					typeName	null	
			modCount	2	
			size	2	
	m_primaryTable	DatabaseTable  (id=151)	
		name	"__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME" (id=157)	
		qualifiedName	"__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME" (id=157)	
  • The 1st (pseudo) key is added in stage 1 of metadata processing
    • Note: at the time this is a valid operation becuase the PK List is empty
Thread [main] (Suspended (breakpoint at line 465 in MetadataProject))	
	MetadataProject.addMappedSuperclassAccessor(MetadataClass, MappedSuperclassAccessor) line: 465	
	EntityAccessor.addPotentialMappedSuperclass(MetadataClass) line: 199	
	EntityAccessor.discoverMappedSuperclassesAndInheritanceParents(boolean) line: 298	
	EntityAccessor.preProcess(boolean) line: 597	
	EntityAccessor.preProcess() line: 580	
	MetadataProject.processStage1() line: 1257	
	MetadataProcessor.processORMMetadata() line: 458	
	PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 297	
	EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 849	
  • The 2nd (Real) key is added in stage 2 of metadata processing
Thread [main] (Suspended (breakpoint at line 458 in ClassDescriptor))	
	RelationalDescriptor(ClassDescriptor).addPrimaryKeyField(DatabaseField) line: 458	
	MetadataDescriptor.addPrimaryKeyField(DatabaseField, MappingAccessor) line: 331	
	IdAccessor.process() line: 82	
	MetadataDescriptor.processAccessors(MetadataDescriptor) line: 1242	
	MappedSuperclassAccessor(ClassAccessor).processAccessors() line: 787	
	MappedSuperclassAccessor.processMetamodelDescriptor() line: 883	
	MetadataProject.processStage2() line: 1285	
	MetadataProcessor.processORMMetadata() line: 459	
	PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 297	
	EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 849	
Q1) Do we need the inverseJoinColumns annotation
  • It would seem not, because bidirectional (using mappedBy) is not allowed on a mappedSuperclass
Q2) How do we define the join table columns
  • All the concrete entities must then define the @OneToMany fully with both joinColumns and inverseJoinColums
Analysis:
  • The simple solution would be - when there is an existing key, do not add another key.
  • However, the real key is added after we have customized the descriptor by adding a pseudo key to allow MappedSuperclass descriptors to process.
  • The full solution is to check the PK List for our custom MappedSuperclass pseudo key and remove it before adding the real one.
  • An even better solution would be to not add the "pseudo" key by checking that we "will" be adding a PK in the future - by possibly parsing the mappings in the stage 1 call
    • Note: The following use cases are covered by this operation.
      • UC1: The 2nd key is another pseudo key - we end up with a single one which is ok
      • UC2: The 2nd key is a real key - we delete the pseudo one only
      • UC3: The nth key is part of a composite key - we do not affect any existing composite keys already in the PK List
    • Note: The following use cases may not be covered
      • UCA: Two threads attempt to modify the PK List in an un-Synchronized state
        • This UC may not be valid
Implementation Option 1: Remove pseudo PK field from relational descriptor later in stage 2
  • Here we allow the add of the pseudo pk field to the relational descriptor for our MappedSuperclass when no other PK fields exist in phase 1.
  • Later when we enter phase 2 - we delete the pseudo pk field outside of the PK list and only add it at the end of metadata processing.
    • Issues
      • It seems wastefull to add the Field and then search for and delete it - however during stage 1 we do not know yet whether we will be processing ID accessors yet.
MetadataDescriptor.java
    public void addPrimaryKeyField(DatabaseField field, MappingAccessor accessor) {
// New code start
        /**
         *  266912: For MappedSuperclass processing, if a fake PK already exists,
         *  delete the existing transient key uses solely by non-relational metamodel processing
         *  before adding the real one.
         *  In the case that this key is part of a real composite key, 
         *  the pk name check will still allow multiple keys to be added.
         */
        // Iterate the existing fields and remove the first fake PK used for metamodel processing.
        List<DatabaseField> pkList = m_descriptor.getPrimaryKeyFields();
        for(Iterator<DatabaseField> it = pkList.iterator(); it.hasNext(); ) {
            if(it.next().getName().equals(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME)) {
                it.remove();
                // exit loop
                break;
            }
        }
// New code end
        m_descriptor.addPrimaryKeyField(field);
 
        // Store the primary primary key mappings based on their field name.
        m_primaryKeyAccessors.put(field.getName(), accessor);
    }
Implementation Option 2: Cache pseudo PK field on metadata descriptor until stage 3
  • Here we cache the pseudo descriptor for our MappedSuperclass outside of the PK list and only add it at the end of metadata processing.
    • Issues
      • We will need to track when the descriptor has been fully processed and only add the PK field then
      • We are in a wierd state between stage 1 and 3 - anything that requires at least 1 PK field on the relational descriptor may misbehave.
Implementation Option 3: Determine if there will be 1 or more pk fields before adding pseudo PK field in stage 1
  • Here we before we add the pseudo descriptor for our MappedSuperclass relational descriptor - we first determine (by examining the mappings) whethere there will be one or more (composite PK) on the descriptor - where we do not ever add the pseudo PK field.
    • Issues
      • This would be the cleanest - however we would be duplicating PK processing functionality before it is required in stage 2 - just so that we can predict if there may be another PK field.
MetadataProject.addMapedSuperclassAccessor()
                metadataDescriptor.setPrimaryTable(new DatabaseTable(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_TABLE_NAME));
                // Add PK field to the relationalDescriptor only if there are none yet - or "will be none"
                if(relationalDescriptor.getPrimaryKeyFields().isEmpty()) {
                    // Also check that there are no PK Fields to be added later in stage 2
                    // Check accessor collection on the metadataDescriptor (note: getIdAttributeName() and getIdAttributeNames() are not populated yet - so are unavailable
                    // We will check for an IdAttribute instance directly
                    boolean idAccessorFound = false;
                    for(MappingAccessor anAccessor : metadataDescriptor.getAccessors()) {
                        if(anAccessor.isId()) {
                            idAccessorFound = true;
                            break;
                        }
                    }
                    if(!idAccessorFound) {
-->old code only        relationalDescriptor.addPrimaryKeyFieldName(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME);
                    }
                }
Decision:

I am going with Option 3 (preemptively check for an Id attribute) above until the design review is complete.

  • Fixed in SVN Rev# 4845 using Option 3b (sacrifice space constraints in favor of a no-search performance improvement)
MetadataDescriptor.addAccessor():222
    public void addAccessor(MappingAccessor accessor) {
        m_accessors.put(accessor.getAttributeName(), accessor);
        // Store IdAccessors in a separate map for use by hasIdAccessor()
new-->  if(accessor.isId()) {
new-->      m_idAccessors.put(accessor.getAttributeName(), (IdAccessor)accessor);
new-->  }
    }
MetadataProject.addMappedSuperclassAccessor():469
            metadataDescriptor.setPrimaryTable(new DatabaseTable(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_TABLE_NAME));
new-->      if(!metadataDescriptor.hasIdAccessor()) {
new-->          relationalDescriptor.addPrimaryKeyFieldName(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME);
new-->      }
            m_session.getProject().addMappedSuperclass(metadataClass, relationalDescriptor);

DI 50: 20090727: Handle all mapping types in the SingularAttribute constructor

  • The following code needs to be fully implemented so that the elementType can be set correctly.
  • Currently it is only set for Basic and OneToOne mappings, but requires support for MappedSuperclass variants of these plus the supported ManyToMany, Aggregate and OneToMany mappings.
    • elementType == managedType (not good)
Before
    protected SingularAttributeImpl(ManagedTypeImpl<X> managedType, DatabaseMapping mapping) {
        super(managedType, mapping);
        Class attributeClassification = mapping.getAttributeClassification();
 
        if (null == attributeClassification) {
            // EntityType
            // We support @OneToOne but not EIS, Reference or VariableOneToOne
            if(mapping.isOneToOneMapping()) {
                elementType = (Type<T>)getMetamodel().getType(
                        ((OneToOneMapping)mapping).getReferenceClass());
            } else {
                // TODO: default to containing class
not good--->    elementType = (Type<T>)getMetamodel().getType(managedType.getJavaType()); 
            }
        } else {
            // BasicType
            elementType = (Type<T>)getMetamodel().getType(attributeClassification);
        }
    }
After
    protected SingularAttributeImpl(ManagedTypeImpl<X> managedType, DatabaseMapping mapping, boolean validationEnabled) {
        super(managedType, mapping);
        Class attributeClass = mapping.getAttributeClassification();
 
        // The attribute classification is null for non-collection mappings
        if (null == attributeClass) { // BasicType will != null --> else clause
            // EntityType
            // We support @OneToOne but not EIS, Reference or VariableOneToOne
            if(mapping.isOneToOneMapping()) {
                attributeClass = ((OneToOneMapping)mapping).getReferenceClass();
                if(null == attributeClass && validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);                    
                }
            } else if (mapping.isAggregateObjectMapping()) { // IE: EmbeddedId
                attributeClass = ((AggregateMapping)mapping).getReferenceClass();
                if(null == attributeClass && validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);            
                }                
            } else if (mapping.isVariableOneToOneMapping()) { // interfaces are unsupported in the JPA 2.0 spec for the Metamodel API
                if(validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_mapping_type_is_unsupported", mapping, this);                    
                }
                // see JUnitCriteriaUnitTestSuite.testSelectPhoneNumberAreaCode() line: 246
                // VariableOneToOne mappings are unsupported - default to referenceClass  (Interface) anyway
                attributeClass = ((VariableOneToOneMapping)mapping).getReferenceClass();
                if(null == attributeClass && validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);            
                }                
 
            } else if (mapping.isEISMapping()) { // unsupported in the JPA 2.0 spec for the Metamodel API
                if(validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_mapping_type_is_unsupported", mapping, this);                    
                }
                // TODO: refactor
                // VariableOneToOne mappings are unsupported - default to Object:
                attributeClass = Object.class;
            } else if ( mapping.isReferenceMapping()) { // unsupported in the JPA 2.0 spec for the Metamodel API
                if(validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_mapping_type_is_unsupported", mapping, this);                    
                }
                // VariableOneToOne mappings are unsupported - default to referenceClass anyway
                attributeClass = ((ReferenceMapping)mapping).getReferenceClass();
                if(null == attributeClass && validationEnabled) {
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);            
                }                
            } else if (mapping.isDirectToFieldMapping()) { // Also handles the keys of an EmbeddedId
                attributeClass = mapping.getField().getType();
                if(null == attributeClass) {
                    // lookup the attribute on the containing class                    
                    Class containingClass = mapping.getDescriptor().getJavaClass();
                    Field aField = null;
                    try {
                        aField = containingClass.getDeclaredField(mapping.getAttributeName());
                        attributeClass = aField.getType();
                    } catch (NoSuchFieldException nsfe) {
                        // This exception will be warned about below
                        //nsfe.printStackTrace();
                    }                    
                }
                // all Direct mappings that don't have a type on their field
                if(null == attributeClass && validationEnabled) {
                    // TODO: refactor
                    attributeClass = Object.class;
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);                    
                }
            } else {
                // All unsupported mappings
                if(null == attributeClass && validationEnabled) {
                    // TODO: refactor
                    attributeClass = Object.class;
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);                    
                }
            }
        }
        elementType = (Type<T>)getMetamodel().getType(attributeClass);        
    }
  • PluralAttributeImpl also has issues with Direct Mappings where the type on the Field is null
  • 20100217: see bug# 303063 for an issue where RelationalDescriptor.getJavaClass() is null (out of spec) causing a possible NPE for SingularAttributes.
20090731: DirectToField and DirectCollection Mapping Field Type may be null
  • The following null type is preventing us from setting the elementType on this Basic type for this specific use case.
mapping	DirectToFieldMapping  (id=192)	
	attributeAccessor	MethodAttributeAccessor  (id=197)	
	attributeClassification	null	
	attributeClassificationName	null	
	attributeName	"formerCompany" (id=198)	
	attributeObjectClassification	null	
	converter	null	
	converterClassName	null	
	descriptor	RelationalDescriptor  (id=194)	
	field	DatabaseField  (id=199)	
		columnDefinition	"" (id=205)	
		index	-1	
		isInsertable	true	
		isNullable	true	
		isUnique	false	
		isUpdatable	true	
		length	255	
		name	"FORMERCOMPANY" (id=206)	
		precision	0	
		qualifiedName	"FORMERCOMPANY" (id=206)	
		scale	0	
		sqlType	-2147483648	
		table	DatabaseTable  (id=207)	
		type	null	
		typeName	null	
		useDelimiters	false	
Solution:

SingularAttributeImpl constructor changes in progress.

DI 51: 20090728: Implement ManagedTypeImpl.getDeclaredAttributes() for non-collections

  • Currently the function public Set<Attribute<X, ?>> getDeclaredAttributes() is implemented only for collections - it requires a fully expansion.
Solution:

This solution will depend directly on DI 52 - Declared Recursion Algorithm below.

DI 52: 20090728: JPA 2: Implement recursive ManagedType.getDeclared* algorithm to differentiate by IdentifiableType

  • See bug# 284877
  • The get*Declared* functions in ManagedType will require a recursive search on their superType inheritance tree to correctly determine whether an attribute is declared on the current IdentifiableType or higher up the inheritance tree to the root.
    • We need to take into account whether the superType is an Entity or MappedSuperclass - as this will affect whether we continue to search up when a current IdentifiableType is missing the attribute.
    • For example when searching for a declared mapping somewhere in a MappedSuperclass inheritance chain - the inervening MappedSuperclasses will not have their own copy of inherited mappings - only the first Entity in the chain will inherit everything above (without overrides).
      • IE: Entity(Integer id) --> MappedSuperclass --> MappedSuperclass(Integer id).
    • - If superType is entity then inheriting entities will not have copies of the inherited mappings
    • - however, if superType is mappedSuperclass then all inheriting mappedSuperclasses and the first entity will have copies of the inherited mappings
    • - Note: a sub-entity can override a mapping above it - we need to handle this.
Use Cases:
  • UC1 Superclass declares attribute
  • UC1.1: Entity (searched) --> Entity --> Entity (declares attribute)
  • UC1.2: Entity (searched) --> Entity (copy of attribute) --> MappedSuperclass (declares attribute)
  • UC1.3: Entity (searched) --> MappedSuperclass --> Entity (declares attribute)
  • UC1.4: Entity (copy of attribute) (searched) --> MappedSuperclass (NO copy of attribute) (searched) --> MappedSuperclass (declares attribute) (searched)
  • UC1.5: Entity (copy of attribute) (searched) --> MappedSuperclass (declares attribute) (searched) --> MappedSuperclass (searched)
  • UC2 Nobody declares attribute
  • UC2.1: Entity (searched) --> Entity --> MappedSuperclass (declares attribute)
  • UC2.2: Entity (searched) --> Entity --> Entity (declares attribute)
  • UC2.3: Entity (searched) --> MappedSuperclass (searched) --> MappedSuperclass (declares attribute)
  • UC2.4: Entity (searched) --> MappedSuperclass (searched) --> Entity (declares attribute)
  • UC3 Superclass declares attribute but child overrides it
  • UC3.1: Entity (searched) --> Entity --> MappedSuperclass (declares attribute)
  • UC3.2: Entity (searched) --> Entity --> Entity (declares attribute)
  • UC3.3: Entity (searched) --> MappedSuperclass (override attribute) (searched) --> MappedSuperclass (declares attribute)
  • UC3.4: Entity (searched) --> MappedSuperclass (override attribute) (searched) --> Entity (declares attribute) (searched)
  • UC3.5: Entity (override attribute) (searched) --> MappedSuperclass (searched) --> MappedSuperclass (declares attribute) (searched)
  • UC3.6: Entity (override attribute) (searched) --> MappedSuperclass (searched) --> Entity (declares attribute)
Solution:
  • Results Expected for hasDeclaredAttribute()
    • True = attribute declared only on current type
    • False = attribute not found in superType tree or attribute found in more than one(1) level of the superType tree
  • Base Case
    • attribute found && no superType exists = true
    • attribute not found && no superType exists = false
  • Recursive Case
    • Exit(false) as soon as attribute is found in a superType - without continuing to the root
    • continue as long as we find an attribute in the superType (essentially only MappedSuperclass parents)
    private boolean hasDeclaredAttribute(String attributeName) {
        return hasDeclaredAttribute(attributeName, this.getMembers().get(attributeName));
    }
    private boolean hasDeclaredAttribute(String attributeName, Attribute firstLevelAttribute) {
 
        Attribute anAttribute = this.getMembers().get(attributeName);
        ManagedTypeImpl<?> aSuperType = getManagedSuperType();        
 
        // Base Case: If we are at the root, check for the attribute and return results immediately
        if(null == aSuperType) {
            if(null == anAttribute && null != firstLevelAttribute) { 
                return true; 
            } else {
                // UC 1.3 (part of the else condition (anAttribute != null)) is handled by the return false in null != aSuperTypeAttribute
                return false;
            }
        } else {            
           // Recursive Case: check hierarchy only if the immediate superclass is a MappedSuperclassType
           if(aSuperType.isMappedSuperclass()) { 
               Attribute aSuperTypeAttribute = aSuperType.getMembers().get(attributeName);
               // UC1.3 The immediate mappedSuperclass may have the attribute - we check it in the base case of the next recursive call 
               if(null != aSuperTypeAttribute) {
                   // return false immediately if a superType exists above the first level
                   return false;
               } else {
                   // UC1.4 The immediate mappedSuperclass may not have the attribute if another one up the chain of rmappedSuperclasses declares it
                   if(null == aSuperTypeAttribute) {
                       // UC 1.5: keep searching a possible chain of mappedSuperclasses
                       return aSuperType.hasDeclaredAttribute(attributeName, firstLevelAttribute);
                   } else {
                       // superType does not contain the attribute - check that the current attribute and the first differ
                       if(anAttribute != firstLevelAttribute) {
                           return false;
                       } else {
                           return true;
                       }
                   }
               }
           } else {
               // superType (Entity) may declare the attribute higher up - we do not need to check this
               if(null == anAttribute) {
                   return false;
               } else {
                   return true;
               }
           }
        }
    }
DI:52 Refactor: 20090817
  • See line 935 of ManagedTypeImpl.java

ManagedTypeImpl 935 patch

  • The base case for the recursive function managedTypeImpl.hasDeclaredAttribute() does not handle use case 1.4 (root-level managedType) when the caller of the function does not do it's own inheritedType check.
  • This occurs only for managedType.getDeclaredAttributes() - here the returned Set is empty.
  • There are two sets of internal references to hasDeclaredAttribute() - the call getDeclaredAttributes() does not do optimization before calling this recursive function - all the other getDeclaredList()|getDeclaredMap etc - check the immediate hierarchy and only call the recursive hasDeclaredAttribute() if needed.
  • The function is renamed isAttributeDeclaredOnlyInLeafTypee from hasDeclaredAttribute to avoid any developer confusion - since I am really returning the opposite boolean of what my function is named - thank you Chris.
  • Why are we checking the superType tree recursively in the first place? Because a possible MappedSuperclass hierarchy will not declare their own copies of a parent superclass - only the first inheriting entity will get copies of attributes declared at the top of a MappedSuperclass hierarchy tree.
  • A possible time-space optimization would be to store the java members in a separate data structure from the metamodel members.
  • The fix is to change the following code
ManagedTypeImpl.java:897
private boolean isAttributeDeclaredOnlyInLeafType(String attributeName, Attribute firstLevelAttribute) {
        // Base Case: If we are at the root, check for the attribute and return results immediately
        if(null == aSuperType) {
            if(null == anAttribute && null != firstLevelAttribute) { 
                return true; 
            } else {
                // UC 1.3 (part of the else condition (anAttribute != null)) is handled by the return false in null != aSuperTypeAttribute
--->expand      return false;
            }
        } else {
  • To the expanded version below that handles the case where we are checking the root when the type is declared there.
ManagedTypeImpl.java:897
private boolean isAttributeDeclaredOnlyInLeafType(String attributeName, Attribute firstLevelAttribute) {
        // Base Case: If we are at the root, check for the attribute and return results immediately
        if(null == aSuperType) {
            if(null == anAttribute && null != firstLevelAttribute) { 
                return true; 
            } else {
                // UC 1.3 (part of the else condition (anAttribute != null)) is handled by the return false in null != aSuperTypeAttribute
                // UC 1.4 (when caller is firstLevel) superType does not contain the attribute - check that the current attribute and the first differ
                if(null != anAttribute && anAttribute == firstLevelAttribute) {
                    return true;
                } else {
                    return false;
                }
            }
        } else {
  • New testing that fails without this fix (no managedTypes were returned for a person.getDeclaredAttributes() call)
                /**
                 * Hierarchy:
                 *   Person : MappedSuperclass
                 *     +
                 *     +- id : Integer
                 *     +- name : String
                 *     +- historicalEmployers : Manufacturer
                 *     
                 *     Corporation : MappedSuperclass extends Person
                 *       +
                 *       +- corporateComputers : Collection 
                 *       
                 *       Manufacturer : Entity extends Corporation
                 *         +
                 *         +- computers : Set
                 *         +- hardwareDesigners : List
                 *         +- hardwareDesignersMap : Map
                 *         +- version : int
                 */
                Set<Attribute<Person, ?>> declaredAttributesSetForPerson = msPerson.getDeclaredAttributes();
                assertNotNull(declaredAttributesSetForPerson);
                // We should see 3 declared out of 3 attributes for Person 
                assertEquals(3, declaredAttributesSetForPerson.size());
                // Id is declared at this level
                assertTrue(declaredAttributesSetForPerson.contains(msPerson.getAttribute("id"))); //
                // name is declared at this level
                assertTrue(declaredAttributesSetForPerson.contains(msPerson.getAttribute("name"))); //
                // historicalEmployers is declared at this level
                assertTrue(declaredAttributesSetForPerson.contains(msPerson.getAttribute("historicalEmployers"))); //
Solution 52: - Fixed

DI 53: 20090729: Verify that inheritied non-JPA class mappings are handled by the Metamodel

  • Talking with Shaun, the following issue came up surrounding the support of declared functionality for ManagedTypes
  • There is a way to map fields that are inherited from non-Entity and non-MappedSuperclass java classes (No @Entity or @MappedSuperclass annotation) that have their fields mapped in XML.
  • These fields are not picked up by default and are warned about in Dali - however the inheriting entities will pick up the fields in the metadata.
  • We therefore need a test model and should decide on how we will handle this case.
  • This is related but is different that the issue of support for interfaces via the VariableOneToOneMapping.
Solution:
  • Support should be provided by default in the Metamodel because the inherited fields should show up in all inheriting entities.

DI 54: 20090803: Metamodel.type(Clazz) should differentiate between null and BasicType

  • The spec states that an IllegalArgumentException should be thrown in the Type is not managed (Embeddable, Entity or MappedSuperclass).
  • However the current code does not differentiate between a null type and a Basic Type
Solution 54: - Fixed after Reopen
  • 20090804 - Fix is part of the diff for enhancement 285512
  • 20091014 - reopened bug
    • During a pass through the code - I have decided that the specification is clear - all non-ManagedType clazz parameters should throw an IAE - even null.

The function needs to be reverted back to it's original state - not that it has been renamed .managedType() and its' usage in the Criteria tests should be modified to getType().

    • I discussed this with Chris
    • There is a secondary issue to the fix. In the Metamodel interface there is no specification function that will return a Type - the implementation classes will need to be used in this case.
CriteriaQueryImpl.java:118
    public CriteriaQuery<T> select(Selection<? extends T> selection) {
      // From:
      ManagedType type = this.metamodel.managedType(this.queryType);
      // To:
      TypeImpl type = ((MetamodelImpl)this.metamodel).getType(this.queryType);
  • Test results
    • With Metamodel change and no Criteria change
TEST MODEL NAME: (JUnit test): Criteria
Errors: (failures): 0
Fatal Errors: (errors): 15
Passed: 104
Total Tests: 119
    • With Metamodel change and one of the Criteria changes (fixes 12 of the 15 issues)
TEST MODEL NAME: (JUnit test): Criteria
Errors: (failures): 0
Fatal Errors: (errors): 3
Passed: 116
Total Tests: 119
  • Fixed in SVN rev# 5540 in the following diff - reviewed by Gordon

DI 55: 20090806: Basic Type processing currently lazy loaded

  • Currently after the metamodel is initialized we still have not processed Basic types - Entity, MappedSuperclass and Embeddable types have been processed.
  • When a runtime Metamodel.getType(Clazz) call is run and the Clazz is not recognized - a Basic Type is created and added to the metamodel.
  • This behaviour violates the metamodel contract - where the entire model must be fully initialized during EMF creation - and not affected by runtime behavior. Two different users will see different numbers of types - depending on whether the Basic types are accessed (lazy load) or not.
Solution:
  • Move delayed Basic type processing back into metamodel.initialize() from metamodel.getType().

DI 56: 20090807: IdentifiableTypeImpl.getId and getVersion should handle null and Object.class for non-strict typing

  • From Gordon:
    • Relax the IAE exception handling for these functions to handle null and Object.class
Solution: - Fixed

DI 57: 20090807: Refactor ManagedTypeImpl.create factory method to also build MappedSuperclass Types

  • Currently during metamodel.initialize() we create Entity/Embeddable types separate from MappedSuperclass types using the MappedSuperclassTypeImpl constructor
  • This is not currently an issue because we are only iterating "real" descriptors in this part of initialize() and not the "pseudo" ones on the Metadata Project.
  • The ManagedTypeImpl.create() factory method does not handle MappedSuperclasses
Solution 57: - Partially fixed
  • Refactor the create() method to call the MappedSuperclassTypeImpl constructor as well - by creating a version of the create() static method on MappedSuperclassTypeImpl and possibly EntityTypeImpl - bypassing the abstrace IdentifiableTypeImpl.

DI 58: 20090807: ManagedType Attribute Initialization must differentiate between Collection and List

  • The collection handling for Collection, Set, List and Map is picking up the wrong type based on the runtime instantiation of the attribute.
    • The issue is we need to handle false positives for the List type because both List and Collection use IndirectList when lazy.
    • This was a known issue on ManagedTypeImpl.initialize()
      • } else if (colMapping.getContainerPolicy().isListPolicy()) { // TODO: isListPolicy() will return true for IndirectList (a lazy Collection)
  • We also need to handle lazy loaded mappings (IE: IndirectList)
  • Field access types will require special handling in the absence of a getMethod on the accessor where we use reflection directly.
Model:
  • See Computer.circuitBoards
    • Both of these should be the same
Computer
    @OneToMany(cascade=ALL, mappedBy="computer")
    // A Collection where the Collection type (Map, Set, List) is not defined at design time
    private Collection<Board> circuitBoards;
Computer
    @OneToMany(cascade=ALL, mappedBy="computer")
    private Collection<Board> circuitBoards = new HashSet<Board>()
  • Also the following List should be treated differently than a Collection - even though both show up as IndirectList on the containerPolicy
Manufacturer
    @OneToMany(cascade=ALL, mappedBy="employer")
    private List<HardwareDesigner> hardwareDesigners = new ArrayList<HardwareDesigner>();
Analysis
Q1: Why do some ManyToMany MethodAttributeAccessors have the get method unpopulated
  • This seems to be due to the fact that the class uses field as opposed to method level access.
  • This fact requires a workaround in ManagedTypeImpl that goes directly to the class
    Method aMethod = this.getJavaType().getDeclaredMethod(getMethodName);
    Class aType = aMethod.getReturnType();
  • Example with no getMethod (Embeddable)
    • class org.eclipse.persistence.testing.models.jpa.inherited.Accredidation
attributeAccessor	MethodAttributeAccessor  (id=187)	
	attributeName	"witnesses" (id=188)	
	getMethod	null	
	getMethodName	"getWitnesses" (id=193)	
	isReadOnly	false	
	isWriteOnly	false	
	setMethod	null	
	setMethodName	"setWitnesses" (id=194)	
  • Example with a getMethod
    • class org.eclipse.persistence.testing.models.jpa.xml.merge.relationships.Item
attributeAccessor	MethodAttributeAccessor  (id=109)	
	attributeName	"partsLists" (id=112)	
	getMethod	Method  (id=139)	
	getMethodName	"getPartsLists" (id=140)	
	isReadOnly	false	
	isWriteOnly	false	
	setMethod	Method  (id=143)	
	setMethodName	"setPartsLists" (id=144)	
Q2: When plural attribute type is not found - default, fail or skip
  • In the corner cases where we cannot find the type after exercising all the options below - do we default to List, fail on metamodel processing, or skip the attribute - leave it at Object, log a warning and continue.
    • Check container policy
    • Check getMethod attribute class
    • Check getMethodName reflective call
    • Check type on declared field (in the absence of a get method)
Solution: Fixed
  • See reference in
    • MappingAccessor.setIndirectionPolicy(CollectionMapping mapping, String mapKey, boolean usesIndirection)
  • Check CollectionContainerPolicy.isCollection()
  • Get the type (either Collection or List) via the following call
    Class type = ((InstanceVariableAttributeAccessor)colMapping.getAttributeAccessor()).getAttributeField().getType();

DI 59: 20090818: PluralAttribute.elementType not set for non-lazy instantiated Collection Attribute

    @OneToMany(cascade=ALL)
    private Collection<Computer> corporateComputers;
  • Previously broken example in Corporation.java - a MappedSuperclass - therefore the mapping is unidirectional
    @OneToMany(cascade=ALL)
    private Collection<Computer> corporateComputers = new HashSet<Computer>();
Solution: Fixed
    protected PluralAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
..
        if (elementDesc != null) {
..
        } else {
            if(mapping.isDirectCollectionMapping() || mapping.isAbstractCompositeDirectCollectionMapping()) {// || mapping.isAbstractDirectMapping() ) {
..
            } else {
                // Example: Collection with an instantiated Set
                Class attributeClass = ((CollectionMapping)mapping).getReferenceClass();
                // TODO: refactor
                if(null == attributeClass && validationEnabled) {
                    attributeClass = Object.class;
                    AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this);
                } else {
                    this.elementType = (Type<V>)getMetamodel().getType(attributeClass);
                }

DI 60: 20090820: Refactor SingularAttribute and PluralAttribute constructor elementType discovery up into Attribute

Solution:

DI 61: 20090820: ManagedType.getDeclaredX() leaks members into entity-entity hierarchy

  • It turns out that members are being leaked into inheriting entities in a entity-entity hierarchy because the expected "gap" or no-copy behavior expected that occurs for MappedSuperclass hierarchy Descriptors is not happening.
  • Currently the code does not handle the fact that there are copies of inherited declared mappings in an entity subclass.
    private boolean isAttributeDeclaredOnlyInLeafType(String attributeName, Attribute firstLevelAttribute) {
        if(null == aSuperType) {
...        } else {            
           if(aSuperType.isMappedSuperclass()) {  
...           } else {
               // superType (Entity) may declare the attribute higher up - we do not need to check this
               // TODO: verify handling of XML mapped non-Entity (plain Java Class) inherited mappings
               // http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_53:_20090729:_Verify_that_inheritied_non-JPA_class_mappings_are_handled_by_the_Metamodel
               if(null == anAttribute) {
                   return false;
               } else {
--->in error       return true;
               }
           }
        }
    }
Example
@Entity(name="ProcessorMetamodel")
@Table(name="CMP3_MM_PROC")
@Inheritance(strategy=JOINED)
public class Processor {
    // The M:1 side is the owning side
    @ManyToOne(fetch=EAGER)//LAZY)
    @JoinTable(name="CMP3_MM_BOARD_MM_PROC", 
            joinColumns = @JoinColumn(name="PROC_ID"), 
            inverseJoinColumns =@JoinColumn(name="BOARD_ID"))   
    private Board board;
}
 
@Entity(name="VectorProcessorMetamodel")
@Table(name="CMP3_MM_PROC")
public class VectorProcessor extends Processor {}
Solution: 61 - Fixed
  • Check the Entity superclasses as well as the already checked MappedSuperclasses.
  • see SVN Rev# 4931

DI 62: 20090820: Add support for Embeddable Collections as part of AGGREGATE_COLLECTION support

  • The ManagedTypeImpl.create() function requires enhancement to support collections of Embeddables.
  • We also need a way to differentiate between Embeddables and Entities here.
    public static ManagedTypeImpl<?> create(MetamodelImpl metamodel, RelationalDescriptor descriptor) {
                // AGGREGATE:2 or AGGREGATE_COLLECTION:3
                if (descriptor.isAggregateDescriptor()) {                    
                    // AGGREGATE:2 == EMBEDDABLE
                    managedType = new EmbeddableTypeImpl(metamodel, descriptor);                
                } else if (descriptor.isAggregateCollectionDescriptor()) {
--->functionality required both for Embeddables and Entities
Analysis:
Solution:
  • In progress as of 20090820

DI 63: 20090824: Add Map support for @MapKey to MapAttribute

  • See work done 1 month ago to add Map support in DI 48 that adds support for the default primary key as map key (when the @MapKey is absent). It turns out that this is only half the implementation - I still need to add support for obtaining the map key class when set using the @MapKey annotation.
  • For the following MapAttribute
@Entity
public class Manufacturer extends Corporation implements java.io.Serializable{
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;// = new HashMap<String, HardwareDesigner>();
}
 
@Entity
public class HardwareDesigner extends Designer implements java.io.Serializable{
    // The M:1 side is the owning side
    @ManyToOne(fetch=EAGER)//LAZY)
    @JoinTable(name="CMP3_MM_MANUF_MM_HWDES_MAP", 
            joinColumns = @JoinColumn(name="DESIGNER_MAP_ID"), 
            inverseJoinColumns =@JoinColumn(name="MANUF_ID"))   
    private Manufacturer mappedEmployer;
}
  • We are getting the Integer PK field for the ManagedType instead of the String Map Key - because the @MapKey annotation is missing and we default to the target entity primary key.
anAttribute    MapAttributeImpl<X,K,V>  (id=190)    
    elementType    EntityTypeImpl<X>  (id=156)    
        descriptor    RelationalDescriptor  (id=202)    
        javaClass    Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.HardwareDesigner) (id=131)    
        members    HashMap<K,V>  (id=208)    
        metamodel    MetamodelImpl  (id=109)    
        superType    MappedSuperclassTypeImpl<X>  (id=209)    
    keyType    BasicTypeImpl<X>  (id=196)    
        javaClass    Class<T> (java.lang.Integer) (id=211)    
    managedType    EntityTypeImpl<X>  (id=154)    
        descriptor    RelationalDescriptor  (id=213)    
        javaClass    Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=125)    
        members    HashMap<K,V>  (id=214)    
        metamodel    MetamodelImpl  (id=109)    
        superType    MappedSuperclassTypeImpl<X>  (id=94)    
    mapping    OneToManyMapping  (id=199)    
Use Cases:
  • The use cases are split into 2 sections of 2 based on whether the attribute is using generics or not to define the K, V pair, and whether the @MapKey annotation is set.
  • Prerequisites
    • Map attribute is on an Entity (it may inherit from a MappedSuperclass and may also be the superclass of a subclass Entity).
UC 1a: Generics KV set, no @MapKey present, PK is singular field
    @Id
    private Integer id;
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
    • keyType is the primary key (PK) of the target entity - in this case HardwareDesigner which inherits its @Id from the Person @MappedSuperclass as Integer.
UC 1b: Generics KV set, no @MapKey present, PK is Multiple fields
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
    • keyType is the primary key (PK) of the target entity.
UC 1c: Generics KV set, no @MapKey present, PK is EmbeddedId
    @EmbeddedId
    protected EmbeddedPK primaryKey;
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
    • keyType is the target entity primary key class.
UC 1d: Generics KV set, no @MapKey present, PK is ClassId
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
    • keyType is the target entity primary key class.
UC 2: Generics KV set, @MapKey is present
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    @MapKey(name="name")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
UC 3a: No Generics KV set, no @MapKey present, PK is singular field
    @Id
    private Integer id;
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    private Map hardwareDesignersMap;
  • Expected results:
UC 3b: No Generics KV set, no @MapKey present, PK is Multiple fields
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    private Map hardwareDesignersMap;
  • Expected results:
UC 3c: No Generics KV set, no @MapKey present, PK is EmbeddedId
    @EmbeddedId
    protected EmbeddedPK primaryKey;
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    private Map hardwareDesignersMap;
  • Expected results:
UC 3d: No Generics KV set, no @MapKey present, PK is ClassId
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    private Map hardwareDesignersMap;
  • Expected results:
UC 4: No Generics KV set, @MapKey is present
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    @MapKey(name="name")
    private Map hardwareDesignersMap;
  • Expected results:
  • Variant Use cases
  • Expected results:
UC 5: Run UC 1-4 on a MappedSuperclass
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
UC 6: No Generics KV set, no targetEntity set, @MapKey is *(set/unset)
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map hardwareDesignersMap;
  • Expected results:
    • Invalid
    • Exception Description: The target entity of the relationship attribute [hardwareDesignersMapUC6] on the class [class org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer] cannot be determined. When not using generics, ensure the target entity is defined on the relationship mapping.
UC 7: Generics KV set, targetEntity is also set, @MapKey is *(set/unset)
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap;
  • Expected results:
UC 8: Generics KV set, Map is instantated to HashMap
    @OneToMany(cascade=ALL, mappedBy="mappedEmployer")
    private Map<String, HardwareDesigner> hardwareDesignersMap = new HashMap<String, HardwareDesigner>;
  • Expected results:
Test Model:
@Entity
public class Manufacturer extends Corporation implements java.io.Serializable{
    // The following MapAttribute use cases are referenced in Design Issue 63
    // http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_63:_20090824:_Add_Map_support_for_.40MapKey_to_MapAttribute
    // UC 1a: Generics KV set, no @MapKey present, PK is singular field
    @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC1a")
    private Map<String, HardwareDesigner> hardwareDesignersMapUC1a;
    // UC 2: Generics KV set, @MapKey is present
    @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC2")
    @MapKey(name="name")
    private Map<String, HardwareDesigner> hardwareDesignersMapUC2;
    // UC 4: No Generics KV set, @MapKey is present
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC4")
    @MapKey(name="name")
    private Map hardwareDesignersMapUC4;
    // UC 6: No Generics KV set, no targetEntity set, @MapKey is *(set/unset)
    @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC6")
    private Map hardwareDesignersMapUC6;
    // UC 7: Generics KV set, targetEntity is also set, @MapKey is *(set/unset)
    @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC7")
    private Map<String, HardwareDesigner> hardwareDesignersMapUC7;
}


Analysis:
  • We have 3 options for getting the generic type key on the Map mapping.
    • 1) We can get if directly from the Java class
    • 2) We can get it from the MetadataClass
    • 2a) Add a JPA metadata processing step that stores a property on the mapping for the Map key
    • 2b) Add a field or property on the mapping
      • For this approach we will need to add a map of non-MappedSuperclass descriptors to Project for use by the Metamodel
  • Debugging the creation of the MapAttribute during keyType creation.
Thread [Thread-3] (Suspended)	
	MapAttributeImpl<X,K,V>.<init>(ManagedTypeImpl<X>, CollectionMapping, boolean) line: 76	
	EntityTypeImpl<X>(ManagedTypeImpl<X>).initialize() line: 1064	
	MetamodelImpl.initialize() line: 349	
	MetamodelImpl.<init>(DatabaseSession) line: 86	
	MetamodelImpl.<init>(EntityManagerFactory) line: 96	
	EntityManagerFactoryImpl.getMetamodel() line: 433	
	EntityManagerImpl.getMetamodel() line: 2054
  • Using the Java class - we don't get the key that we need
managedType	EntityTypeImpl<X>  (id=96)	
 descriptor	RelationalDescriptor  (id=139)	
 javaClass	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=262)	
  declaredFields	SoftReference<T>  (id=270)	
   referent	Field[5]  (id=279)	
    [3]	Field  (id=6201)	
     clazz	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=262)	
     name	"hardwareDesignersMap" (id=132)	
     signature	"Ljava/util/Map<Ljava/lang/String;Lorg/eclipse/persistence/testing/models/jpa/metamodel/HardwareDesigner;>;" (id=6216)	
     type	Class<T> (java.util.Map) (id=70)	
      genericInfo	ClassRepository  (id=6260)	
       typeParams	TypeVariable<D>[2]  (id=6266)	
        [0]	TypeVariableImpl<D>  (id=6278)	
         boundASTs	FieldTypeSignature[1]  (id=6285)	
          [0]	ClassTypeSignature  (id=6304)	
           path	ArrayList<E>  (id=6306)	
           elementData	Object[5]  (id=6309)	
            [0]	SimpleClassTypeSignature  (id=6311)	
  • We will use the MetadataClass JPA wrapper and obtain the keyMapping from modifications done in MappingAccessor.
managedType	EntityTypeImpl<X>  (id=133)	
 javaClass	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=151)	
 metamodel	MetamodelImpl  (id=149)	
  session	ServerSession  (id=192)	
   project	Project  (id=31)	
    mappedSuperclassDescriptors	HashMap<K,V>  (id=256)	
     table	HashMap$Entry<K,V>[8]  (id=350)	
      [4]	HashMap$Entry<K,V>  (id=353)	
       key	MetadataClass  (id=356)	
        m_fields	HashMap<K,V>  (id=367)	
         table	HashMap$Entry<K,V>[16]  (id=381)	
          [14]	HashMap$Entry<K,V>  (id=385)	
           key	"corporateComputers" (id=387)	
           value	MetadataField  (id=388)	
            m_annotations	HashMap<K,V>  (id=391)	
            m_attributeName	"corporateComputers" (id=387)	
            m_factory	MetadataAsmFactory  (id=364)	
            m_genericType	ArrayList<E>  (id=392)	
             elementData	Object[10]  (id=400)	
              [0]	"java.util.Collection" (id=402)	
              [1]	"org.eclipse.persistence.testing.models.jpa.metamodel.Computer" (id=403)	
               m_name	"corporateComputers" (id=387)	
               m_type	"java.util.Collection" (id=394)	
Solution: Fixed

DI 64: 20090825: ManagedTypeImpl.isAttributeDeclaredOnlyInLeafType() fails to find attributes declared root of mappedSuperclass<--entity<--entity hierarchy

  • The last recursive return flag in the following line is reversed for entity-->entity hierarchies - it is working for MappedSuperclass chains.
  • It also has no problem with single table inheritance - only shared table inheritance has an issue.
    return aSuperType.isAttributeDeclaredOnlyInLeafType(attributeName, firstLevelAttribute);
  • We also need to simplify the recursion by extracting out the dual functionality into 2 separate functions
  • We can eliminate recursion by using reflective getDeclaredField() calls and maintaining a separate list
  • We can skip the null check on a null attributeName
  • We can pass in an Attribute directly instead of using the wrapper function to get the attributeName
  • More testing is required for Entity-->Entity inheritance chains where attributes are declared on the leaf.
Solution:
  • Turns out that for one issue was a SVN sync issue.
  • Add an attribute declared on the top of a mappedSuperclass<--entity<--entity hierarcy

DI 65: 20090827: Handle DirectCollection elementType retrieval in the absence of a generic type

High Priority
  • If the designer omits generics in the attribute definition - we need a way to determine the type of the collection.
Example:
  • Notice there is no Collection<String> that the method level MethodAttributeAccessor can key off of - there may be one on the field declaration.
package org.eclipse.persistence.testing.models.jpa.advanced;
  public class Employee implements Serializable, Cloneable {
    private Collection<String> responsibilities;
...
    @BasicCollection(valueColumn=@Column(name="DESCRIPTION"))
    @CollectionTable(name="CMP3_RESPONS")
    // generics left off the Collection on purpose ...
    @Property(name="attributeName", value="responsibilities")
    public Collection getResponsibilities() {
        return responsibilities;
    }
Analysis:
  • We will need to key off the declared field's generic declaration on the clazz - if it exists.
  • However, we should not have to parse the Ljava/util/Collection<Ljava/lang/String;>; signature field - it should be handed to us by the metadata processing layer.
mapping	DirectCollectionMapping  (id=145)	
  attributeAccessor	MethodAttributeAccessor  (id=150)	
    attributeName	"responsibilities" (id=151)	
    getMethod	Method  (id=170)	
      clazz	Class<T> (org.eclipse.persistence.testing.models.jpa.advanced.Employee) (id=99)	
        declaredFields	SoftReference<T>  (id=6724)	
           referent	Field[31]  (id=6728)	
            [18]	Field  (id=6763)	
              annotations	null	
              clazz	Class<T> (org.eclipse.persistence.testing.models.jpa.advanced.Employee) (id=99)	
              declaredAnnotations	null	
              fieldAccessor	null	
              genericInfo	null	
              modifiers	2	
              name	"responsibilities" (id=6777)	
              override	false	
              overrideFieldAccessor	null	
              root	null	
              securityCheckCache	null	
              securityCheckTargetClassCache	null	
              signature	"Ljava/util/Collection<Ljava/lang/String;>;" (id=6779)	
              slot	126	
              type	Class<T> (java.util.Collection) (id=120)	
Solution:
  • We are currently defaulting to Object.class - which is not good in
    protected PluralAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {

DI 66:20090827: EnumSet support - expected/no-fix

  • 20090828: This issue is deprecated - the EnumSet should resolve to a SingularAttribute
  • We currently define an EnumSet as a SingularAttribute in error - it should be a SetAttribute of BasicType elements.
  • Here is the example from the advanced jpa model - part of the SRG.
package org.eclipse.persistence.testing.models.jpa.advanced;
@Entity
@Table(
    name="CMP3_BUYER",
    uniqueConstraints = { 
        @UniqueConstraint(columnNames={"BUYER_ID", "BUYER_NAME"}),
        @UniqueConstraint(columnNames={"BUYER_ID", "DESCRIP"})
    }
)
@Inheritance(strategy=JOINED)
public class Buyer implements Serializable {
    public enum Weekdays { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
    private EnumSet<Weekdays> buyingDays;
 
    @Column(name="BUY_DAYS")
    public EnumSet<Weekdays> getBuyingDays() {
        return buyingDays;
    }
  • In the following stacktrace
Thread [Thread-4] (Suspended (breakpoint at line 98 in SingularAttributeImpl))	
	SingularAttributeImpl<X,T>.<init>(ManagedTypeImpl<X>, DatabaseMapping, boolean) line: 98	
	EntityTypeImpl<X>(ManagedTypeImpl<X>).initialize() line: 1215	
	MetamodelImpl.initialize() line: 351	
	MetamodelImpl.<init>(DatabaseSession) line: 88	
	MetamodelImpl.<init>(EntityManagerFactory) line: 98	
	EntityManagerFactoryImpl.getMetamodel() line: 433	
	EntityManagerImpl.getMetamodel() line: 2054	
	AdvancedJPAJunitTest.testMetamodel() line: 184	
  • Here is what we instantiate the buyingDays member to.
entityBuyer	EntityTypeImpl<X>  (id=224)	
	descriptor	RelationalDescriptor  (id=230)	
	javaClass	Class<T> (org.eclipse.persistence.testing.models.jpa.advanced.Buyer) (id=223)	
	members	HashMap<K,V>  (id=231)	
		size	8	
		table	HashMap$Entry<K,V>[16]  (id=233)	
			[10]	HashMap$Entry<K,V>  (id=243)	
				key	"buyingDays" (id=264)	
				value	SingularAttributeImpl<X,T>  (id=265)	
					elementType	BasicTypeImpl<X>  (id=267)	
					managedType	EntityTypeImpl<X>  (id=224)	
					mapping	DirectToFieldMapping  (id=270)	
Analysis:
  • The EnumSet mapping is not recognized as a collectionMapping (Set) below because it is actually a DirectToFieldMapping (Basic) - so the existing code is correct in general and will not be modified.
public abstract class ManagedTypeImpl<X> extends TypeImpl<X> implements ManagedType<X> {
    protected void initialize() { // TODO: Check all is*Policy() calls
        for (DatabaseMapping mapping : getDescriptor().getMappings()) {
            if (mapping.isCollectionMapping()) {
            } else {
                // Handle 1:1 single object and direct mappings
-->             member = new SingularAttributeImpl(this, mapping, true);
Solution: Fixed
  • no-fix - code is working as expected

DI 67:20090827: AggregateCollectionMapping support in PluralAttribute

  • We are not currently handling AggregateCollections in PluralAttribute.
  • See org.eclipse.persistence.mappings.AggregateCollectionMapping[records]
Solution:
    protected PluralAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
            } else if(mapping.isAggregateCollectionMapping()) {
                // get reference class and check if managedType is a MappedSuperclass
                attributeClass = ((AggregateCollectionMapping)mapping).getReferenceClass();
            }

DI 68: 20090828: Double (auto-boxed) or direct double Attribute.javaType

  • This is an issue for all primitive Basic types if we expect the Object version of primitives.
  • Do we return an auto-boxed java.lang.Double for an attribute declared as the primitive double?
  • Currently I just return the class defined by the metadata - without transaltion (which can be double)
package org.eclipse.persistence.testing.models.jpa.advanced;
@Entity
public class LargeProject extends Project {
    private double m_budget;
    public double getBudget() {
Example:
  • See the advanced model
        EntityType<LargeProject> entityLargeProject = metamodel.entity(LargeProject.class);
        Set<Attribute<LargeProject, ?>> declaredAttributes = entityLargeProject.getDeclaredAttributes();
        assertTrue(declaredAttributes.size() > 0); // instead of a assertEquals(1, size) for future compatibility with changes to Buyer
 
        // check that getDeclaredAttribute and getDeclaredAttributes return the same attribute        
        Attribute<LargeProject, ?> budgetAttribute = entityLargeProject.getDeclaredAttribute("budget");
        assertNotNull(budgetAttribute);
        Attribute<LargeProject, ?> budgetSingularAttribute = entityLargeProject.getDeclaredSingularAttribute("budget");
        assertNotNull(budgetSingularAttribute);
        assertEquals(budgetSingularAttribute, budgetAttribute);
        assertTrue(declaredAttributes.contains(budgetSingularAttribute));        
        // check the type
        Class budgetClass = budgetSingularAttribute.getJavaType();
        // Verify whether we expect a boxed class or not 
        assertEquals(double.class, budgetClass);
        //assertEquals(Double.class, budgetClass);
Solution 68: - Fixed
  • It was decided in the 20090831 scrum that we do not translate primitive types to their boxed Object versions.
  • We set the TypeImpl.javaType directly from the type class returned from metadata processing.

DI 69: 20090831: Object.class as a default javaType is not compatible with JPA 1.0

  • Use of Object.class as the default javaType is not acceptable as this is not a compatible type for a JPA mapping.
  • See all
            attributeClass = MetamodelImpl.DEFAULT_ELEMENT_TYPE_FOR_UNSUPPORTED_MAPPINGS;
 
   protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
   public Type<?> getIdType() {
   protected PluralAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
   protected Class getTypeClassFromAttributeOrMethodLevelAccessor(DatabaseMapping mapping) {
   public Class<T> getJavaType() {
   protected SingularAttributeImpl(ManagedTypeImpl<X> managedType, DatabaseMapping mapping, boolean validationEnabled) {
Solution:
  • Use String.class as we do in other parts of the JPA API.
  • or, fail fast and halt metamodel processing in these undefined or unsupported cases.

DI 70: 20090901: Implement JPA 2.0 specification changes in 090828 draft

  • The following changes will affect the signature of the Metamodel API.
    • getCollections is now getPluralAttributes
    • getDeclaredCollections is now getDeclaredPluralAttributes
    • See enhancement# 288267.

DI 71: 20090909: Implement IdentifiableType.getId()

  • We need to answer the open questions in DI 47
  • We need to model the MappedById annotation - see section 2.4.1.1 of the JPA 2.0 Specification.
Solution: 71 - Fixed
References: 71
  • For all of the IdentifiableType implementation changes here and below refer the the main bug# 266912 as well as the following secondary enhancement bugs 289817

DI 72: 20090909: Implement IdentifiableType.getDeclaredId()

Solution: 72 - Fixed

DI 73: 20090909: Implement IdentifiableType.getIdClassAttributes()

Solution: 73 - Fixed

DI 74: 20090909: Implement IdentifiableType.hasSingleIdAttribute()

  • See SVN Rev# 5303
  • See related secondary bug 289817
  • see preliminary 266912 patch 20090925
  • For all of the IdentifiableType issues 70 to 77 we can implement this functionality in 2 ways
    • 1) Store flags on each attribute whether it is a version or id attribute - since the schema will not change at runtime until we implement dynamic persistence.
    • 2) Dynamically figure out which attribute is a version or id attribute by consulting the project metadata
  • In all cases we can either use the existing functionality in the CMP3Policy or figure out whether an attribute is an Id by consulting the Mapping and Descriptor
    • For example if the mapping on the attribute is also in the attribute.mapping.descriptor().getObjectBuilder().getPrimaryKeyMappings List.
    • Or we can call DatabaseMapping.isPrimaryKeyMapping.
  • The following text is the JPA2 specification of this function - it essentially states that the function returns false when the IdentifiableType has an IdClass.
     /**
     *  Whether the identifiable type has a single id attribute.
     *  Returns true for a simple id or embedded id; returns false
     *  for an idclass.
     *  @return boolean indicating whether the identifiable
     *          type has a single id attribute
     */
    boolean hasSingleIdAttribute();
  • We have the following requirements based on the specification of getSingleIdAttribute.
    • R1: A way to differentiate between IdClass and EmbeddedId id attributes.
Analysis: 74
  • There are a couple ways that we can get the Id for this IdentifiableType. It may involve extending the current core API to preserve some of the metadata processing in the JPA project preprocessing.
R1:I1: Query the descriptor directly
  • Code like the following can only go so far in returning our Id class - it will not differentiate between IdClass and EmbeddedId instances.
List<DatabaseField> pkFields = this.getDescriptor().getPrimaryKeyFields();
Class pkClass = ((CMP3Policy)this.getDescriptor().getCMPPolicy()).getPKClass();
R1:I2: Consult stored Id information on the session
  • We can store the IdClass and EmbeddedId names in separate Maps of Lists on the core Project during predeploy metamodel processing and consult this list later during runtime
  • The MetadataProject.addIdClass() function handles both EmbeddedId and IdClass attributes - we will key in on the code that only adds IdClass ones.
    • We have the following hierarchy
      • ClassAccessor (EmbeddedId processed but ignored in this case)
        • EmbeddableAccessor (nested EmbeddedId processed but ignored in this case)
        • MappedSuperclassAccessor (IdClass processed here)
    • 1) EmbeddableId attributes added in ClassAccessor.addAccessor() - not used
public abstract class ClassAccessor extends MetadataAccessor {
    protected void addAccessor(MappingAccessor accessor) {
        if (accessor != null) {
...            
            // Add any embeddedid references to the list of id 'used' classes.
            // That is @IdClass and @EmbeddedId reference classes.
            if (accessor.isEmbeddedId()) {
                getProject().addIdClass(accessor.getReferenceClassName());
                // 266912: Save Ids on the core project for Metamodel processing
not required -->  getCoreProject().addMetamodelEmbeddedId(this.getAccessibleObject().getName(), accessor.getAccessibleObject().getName());
            }
    • 2) Nested Embeddeable of an EmbeddableId - not used
public class EmbeddableAccessor extends ClassAccessor {
    protected void addPotentialEmbeddableAccessor(MetadataClass potentialEmbeddableClass) {
        if (potentialEmbeddableClass != null) {
            // Get embeddable accessor will add the embeddable to the 
            // project if it is a valid embeddable. That is, if one the class
            // has an Embeddable annotation of the class is used as an IdClass
            // for another entity within the persistence unit.
            EmbeddableAccessor embeddableAccessor = getProject().getEmbeddableAccessor(potentialEmbeddableClass, true);
            if (embeddableAccessor != null && ! embeddableAccessor.isPreProcessed()) {
                // 266912: Save Ids on the core project for Metamodel processing
not required -->  getCoreProject().addMetamodelEmbeddedId(this.getAccessibleObject().getName(), 
                        embeddableAccessor.getAccessibleObject().getName());
    • 3) IdClass attributes added in EntityAccessor(MappedSuperclassAccessor).initIdClass() - we will use this one.
public class MappedSuperclassAccessor extends ClassAccessor {
    protected void initIdClass() {
...        
        // Add the id class to the known list of id classes for this project.
        if (m_idClass != null && ! m_idClass.equals(void.class)) {
            getProject().addIdClass(m_idClass.getName());
-->         getCoreProject().addMetamodelIdClass(this.getAccessibleObject().getName(), m_idClass.getName());
     /**
     * Store the IdClass Id attributes for exclusive use by the Metamodel API
     * Keyed on the fully qualified accessible object owner class name.
     * Value is a List of ithe fully qualified id class name or id attribute name.
     * @since EclipseLink 1.2 for the JPA 2.0 Reference Implementation 
     */
    protected Map<String, List<String>> metamodelIdClassMap;
  • For example the following EmbeddedId is present in our model.
this	EntityTypeImpl<X>  (id=304)	
 javaClass	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Manufacturer) (id=287)	
 metamodel	MetamodelImpl  (id=317)	
  session	ServerSession  (id=328)	
   project	Project  (id=361)	
    metamodelEmbeddedIdMap	HashMap<K,V>  (id=381)	
     table	HashMap$Entry<K,V>[16]  (id=406)	
      [10]	HashMap$Entry<K,V>  (id=408)	
       key	"org.eclipse.persistence.testing.models.jpa.metamodel.GalacticPosition" (id=414)	
       value	ArrayList<E>  (id=415)	
        elementData	Object[10]  (id=417)	
         [0]	"primaryKey" (id=420)
  • The following IdClass has been added to the model
@Entity(name="EnclosureMetamodel")
@Table(name="CMP3_MM_ENCLOSURE")
@IdClass(org.eclipse.persistence.testing.models.jpa.metamodel.EnclosureIdClassPK.class)
public class Enclosure implements java.io.Serializable{
    @Id
    @Column(name="TYPE")
    public String type;
    @Id
    @Column(name="LENGTH")
    protected String length;
    @Id
    @Column(name="WIDTH")
    private String width;
}
 
public class EnclosureIdClassPK {
    public String type;
    protected String length;
    private String width;
}
  • Duplicates or composite keys are handles by adding to a current List
  • From
{
org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.CountryDweller=[name], 
org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.Vitals=[
	org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.PersonalVitals]
}
  • To
{
org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.CountryDweller=[name], 
org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.Vitals=[
	org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.PersonalVitals, 
	org.eclipse.persistence.testing.models.jpa.xml.complexaggregate.TeamVitals]
}
R1:I3: Check mapping.isPrimaryKeyMapping()
  • Here during AttributeImpl construction we lazy load this boolean directly onto the attribute and bypass checking the mapping or descriptor in the future (until the metamodel is optionally refreshed).
R1:I4: Check for more than one CMP3Policy.keyClassField on the descriptor
this	SingularAttributeImpl<X,T>  (id=185)	
descriptor	RelationalDescriptor  (id=139)	
	alias	"EnclosureMetamodel" (id=207)	
	cmpPolicy	CMP3Policy  (id=216)	
		keyClassFields	CMPPolicy$KeyElementAccessor[3]  (id=225)	
			[0]	CMP3Policy$FieldAccessor  (id=6049)	
			[1]	CMP3Policy$FieldAccessor  (id=6050)	
			[2]	CMP3Policy$FieldAccessor  (id=6051)	
		pessimisticLockingPolicy	null	
		pkClass	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.EnclosureIdClassPK)  id=229)
Issues: 74
  • 1) The specification of the hasSingleIdAttribute() is vague on what to do if there is no @Id.
    • This should not be an issue because we are only dealing with IdentifiableTypes which are Entities and MappedSuperclasses - not Basic, Embeddable and transient types - therefore we will always see an @Id of some type.
  • 2) An IdentifiableType with an @IdClass can be have a single @Id - discussed this with Chris.
    • The issue is that in my example I use an @IdClass that defines 3 @Id attributes - but we can have one - therefore this function may "return an ambiguous false-positive result" - in the words of Chris
Solution 74: - Fixed

DI 75: 20090909: Implement IdentifiableType.getVersion()

Solution 75: - Fixed

DI 76: 20090909: Implement IdentifiableType.getDeclaredVersion()

Solution 76: - Fixed

DI 77: 20090909: Implement IdentifiableType.hasVersionAttribute()

Solution 77: - Fixed

DI 78: 20090909: Composite @IdClass on inherited MappedSuperclass chain causes new ValidationException

Model:
@MappedSuperclass
public abstract class Person {
    @Id
    @GeneratedValue(strategy=TABLE, generator="PERSON_MM_TABLE_GENERATOR")
    @TableGenerator(
        name="PERSON_MM_TABLE_GENERATOR", 
        table="CMP3_MM_PERSON_SEQ", 
        pkColumnName="SEQ_MM_NAME", 
        valueColumnName="SEQ_MM_COUNT",
        pkColumnValue="CUST_MM_SEQ"
    )    
    // InstanceVariableAttributeAccessor testing
    @Column(name="PERSON_ID")    
    private Integer id;
 
    // Verify special handling for PK for OneToMany (custom descriptor with fake PK name)
    // If a JoinTable with a JoinColumn is used - then we need a mappedBy on the inverse side here
    // However, bidirectional relationships are not allowed to MappedSuperclasses - as they have no identity
    // This @OneToMany implements internally as a @ManyToMany
    @OneToMany(fetch=EAGER, cascade=ALL)
    // Note: DI We do not check the values of the join column names - they can be anything
    @JoinTable(name="CMP3_MM_HIST_EMPLOY", 
                joinColumns = {@JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID"), 
                    @JoinColumn(name="DESIGNER_ID", referencedColumnName="DESIGNER_ID")}, 
                inverseJoinColumns = {@JoinColumn(name="PERSON_ID", referencedColumnName="PERSON_ID"),
                    @JoinColumn(name="DESIGNER_ID", referencedColumnName="DESIGNER_ID")})   
    private Collection<Manufacturer> historicalEmployers = new ArrayList<Manufacturer>();
}
 
@MappedSuperclass
public abstract class Designer extends Person {
    @Id
        @Column(name="DESIGNER_ID")    
        private Integer ident;
}
 
@Entity(name="HardwareDesignerMetamodel")
@Table(name="CMP3_MM_HWDESIGNER")
public class HardwareDesigner extends Designer implements java.io.Serializable {}
Analysis:

General Model: MappedSuperclassRoot (3 @Id)

   - MappedSuperclassCenter (1 @Id)
        -  EntityRoot (0 @Id - by spec)

Q1) Where do we place the @IdClass annotation when an @IdClass has its @Id mappings distributed among multiple @MappedSuperclass abstract classes? - on both mappedSuperclasses

Q2) If in Q1) we the MSCenter class has 1 @Id defined as part of an @IdClass - is it safe to say that it has a single Id mapping?

   This is more of an internal API question since we don't have 2+ id fields we currently return false for metamodel.IdentifiableType.hasSingleIdAttribute.  - we may need to also look at all inherited parents in the hierarchy chain.
Solution 78: - fixed
  • Originally we discussed this and determined that this extra validation may not be an issue - we may allow this behavior change to proceed.
  • However at the end of Oct 2009 we decided to remove the validation for the case where an @Id is declared above and Entity on an inherited @MappedSuperclass


20091120:2200 the model is wrong
  • We must ensure that we have access to the IdClass attribute where we define @IdClass.

In the case of the model above - the 4th @Id on the middle subclass mappedSuperclass is not accessible from the top root.

  • Model
@MappedSuperclass
@IdClass(org.eclipse.persistence.testing.models.jpa.metamodel.MSIdClassPK.class) // invalid - should be one level down
public abstract class MS_MS_Entity_Root implements java.io.Serializable {
    @Id    protected String type;
    @Id    protected String length;
    @Id    protected String width;
}
 
@MappedSuperclass
public abstract class MS_MS_Entity_Center extends MS_MS_Entity_Root {
>   @Id    protected Integer ident;
}
 
@Entity(name="MS_MS_EntityLeafMetamodel")
public class MS_MS_Entity_Leaf extends MS_MS_Entity_Center {}
  • should be
@MappedSuperclass
public abstract class MS_MS_Entity_Root implements java.io.Serializable {
    @Id    protected String type;
    @Id    protected String length;
    @Id    protected String width;
}
 
@MappedSuperclass
@IdClass(org.eclipse.persistence.testing.models.jpa.metamodel.MSIdClassPK.class) // or defined on the entity
public abstract class MS_MS_Entity_Center extends MS_MS_Entity_Root {
>   @Id    protected Integer ident;
}
 
@Entity(name="MS_MS_EntityLeafMetamodel")
public class MS_MS_Entity_Leaf extends MS_MS_Entity_Center {}

DI 79: 20090910: MapAttribute keyType requires @MapKey handler in the template case when CMP3Policy.getPKClass() is null

  • Ran into this on the advanced model when I ran into bug# 289108.
  • The solution is to get the @MapKey also in this case via the attributeClassification and default to the owning Type's PK in the absence of a @MapKey.
                // Use the CMPPolicy on the element not the one on the managedType
                if(policy.getElementDescriptor() != null && policy.getElementDescriptor().getCMPPolicy() != null) {
                    javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
member	MapAttributeImpl<X,K,V>  (id=6560)	
	elementType	EmbeddableTypeImpl<X>  (id=6575)	
	keyType	BasicTypeImpl<X>  (id=6569)	
		javaClass	null	
	managedType	EntityTypeImpl<X>  (id=6535)	
	mapping	AggregateCollectionMapping  (id=6519)	
  • on the Attribute and Type
MapAttributeImpl[org.eclipse.persistence.mappings.AggregateCollectionMapping[redStripes]]
EntityTypeImpl@30704835:BeerConsumer [ javaType: class org.eclipse.persistence.testing.models.jpa.inherited.BeerConsumer descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.inherited.BeerConsumer --> [DatabaseTable(CMP3_CONSUMER)]), mappings: [org.eclipse.persistence.mappings.DirectToFieldMapping[id-->CMP3_CONSUMER.ID], org.eclipse.persistence.mappings.DirectToFieldMapping[version-->CMP3_CONSUMER.VERSION], org.eclipse.persistence.mappings.DirectToFieldMapping[name-->CMP3_CONSUMER.NAME], org.eclipse.persistence.mappings.OneToManyMapping[coronaBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[blueBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[telephoneNumbers], org.eclipse.persistence.mappings.OneToManyMapping[certifications], org.eclipse.persistence.mappings.OneToManyMapping[heinekenBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[blueLightBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[alpineBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[becksBeersToConsume], org.eclipse.persistence.mappings.OneToManyMapping[canadianBeersToConsume], org.eclipse.persistence.mappings.AggregateCollectionMapping[redStripes]]]
Reproduction:
  • A Map where the mapKey is templated is declared on a Entity root and is being processed 2 levels down on another inheriting Entity where a MappedSuperclass is between them in the following E-->MS-->E inheritance chain.
  • See the following model
BeerConsumer(id) : Entity
-->private Map<T, RedStripe> redStripeBeersToConsume;
    @ElementCollection
    // TODO: Correct resolving the T type without specifying the map key class
    // Map key class will get figured out through generic types.
    @MapKeyClass(String.class)
    @MapKeyColumn(name="RS_KEY")
    @CollectionTable(name="CONSUMER_REDSTRIPES", joinColumns=@JoinColumn(name="C_ID", referencedColumnName="ID"))
    public Map<T, RedStripe> getRedStripes() {
        return redStripeBeersToConsume;
    }
 
    RatedBeerConsumer : MappedSuperclass
        NoviceBeerConsumer : Entity
Solution:
  • When the pkClass is null on the elementDescriptor CMPPolicy - use the attributeClassification to get the type from the @MapKey (and default to the owning entity PK when there is no @MapKey).
    protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
            if(managedType.isIdentifiableType()) {
                // Use the CMPPolicy on the element not the one on the managedType
                if(policy.getElementDescriptor() != null && policy.getElementDescriptor().getCMPPolicy() != null) {
                    javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
 
--> new block       if(null == javaClass) {
                        // check for a @MapKeyClass annotation
                        if(policy.isMappedKeyMapPolicy()) {                            
                            MapKeyMapping mapKeyMapping = ((MappedKeyMapContainerPolicy)policy).getKeyMapping();
                            RelationalDescriptor descriptor = (RelationalDescriptor)((DatabaseMapping)mapKeyMapping).getDescriptor();
                            // If the reference descriptor is null then we are on a direct mapping
                            if(null != descriptor) {
                                if(null != descriptor.getCMPPolicy()) { 
                                    javaClass = ((DatabaseMapping)mapKeyMapping).getAttributeClassification();
                                    if(null == javaClass) {
                                        // Default to the PK of the owning descriptor when no MapKey is specified
                                        javaClass = descriptor.getCMPPolicy().getPKClass();

DI 80: 20090914: MappedSuperclassTypeImpl.create() does not use ConversionManager to get the EE classLoader

  • See Bug 266912 patch in comment 114
  • See Bug 266912 patch in comment 116 for EE testing capability
  • In MappedSuperclassTypeImpl.create() we are not using a classLoader that is shared library friendly.
relationalDescriptor.convertClassNamesToClasses(metamodel.getSession().getActiveSession().getClass().getClassLoader());
  • Should be
relationalDescriptor.convertClassNamesToClasses(metamodel.getSession().getDatasourcePlatform().getConversionManager().getLoader());
Reproduction:
  • Reproduction with old code - not EE friendly
  • I added a @MappedSuperclass and a em.getMetamodel() call to the @Local @Stateless session bean in the WebLogic tutorial and we fail using the existing classLoader call.
  • Before using
relationalDescriptor.convertClassNamesToClasses(metamodel.getSession().getActiveSession().getClass().getClassLoader());
Exception [EclipseLink-7198] (Eclipse Persistence Services - 2.0.0.qualifier): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Class: [org.eclipse.persistence.example.jpa.server.business.CellMS] was not found while converting from class names to classes.
Internal Exception: java.lang.ClassNotFoundException: org.eclipse.persistence.example.jpa.server.business.CellMS

Daemon Thread [[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended)	
	RelationalDescriptor(ClassDescriptor).convertClassNamesToClasses(ClassLoader) line: 1209	
	MappedSuperclassTypeImpl<X>.create(MetamodelImpl, RelationalDescriptor) line: 88	
	ManagedTypeImpl<X>.create(MetamodelImpl, RelationalDescriptor) line: 414	
	MetamodelImpl.initialize() line: 294	
	MetamodelImpl.<init>(DatabaseSession) line: 88	
	MetamodelImpl.<init>(EntityManagerFactory) line: 98	
	EntityManagerFactoryImpl.getMetamodel() line: 496	
	EntityManagerImpl.getMetamodel() line: 2054	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25	
	Method.invoke(Object, Object...) line: 597	
	TransactionalEntityManagerProxyImpl(BasePersistenceContextProxyImpl).invoke(Object, Method, Object[], Transaction) line: 90	
	TransactionalEntityManagerProxyImpl.invoke(Object, Method, Object[], Transaction) line: 89	
	TransactionalEntityManagerProxyImpl(BasePersistenceContextProxyImpl).invoke(Object, Method, Object[]) line: 80	
	TransactionalEntityManagerProxyImpl.invoke(Object, Method, Object[]) line: 24	
	$Proxy71.getMetamodel() line: not available	
	ApplicationService_5ptwty_Impl(ApplicationService).verifyMetamodel() line: 51	
	ApplicationService_5ptwty_Impl(ApplicationService).insertObjects(List<Cell>) line: 65	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25	
	Method.invoke(Object, Object...) line: 597	
	AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 310	
	ReflectiveMethodInvocation.invokeJoinpoint() line: 182	
	ReflectiveMethodInvocation.proceed() line: 149	
	DelegatingIntroductionInterceptor.doProceed(MethodInvocation) line: 131	
	DelegatingIntroductionInterceptor.invoke(MethodInvocation) line: 119	
	ReflectiveMethodInvocation.proceed() line: 171	
	MethodInvocationVisitorImpl.visit() line: 37	
	EnvironmentInterceptorCallbackImpl.callback(MethodInvocationVisitor) line: 54	
	EnvironmentInterceptor.invoke(MethodInvocation) line: 50	
	ReflectiveMethodInvocation.proceed() line: 171	
	ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89	
	ReflectiveMethodInvocation.proceed() line: 171	
	DelegatingIntroductionInterceptor.doProceed(MethodInvocation) line: 131	
	DelegatingIntroductionInterceptor.invoke(MethodInvocation) line: 119	
	ReflectiveMethodInvocation.proceed() line: 171	
	JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204	
	$Proxy72.insertObjects(List) line: not available	
	ApplicationService_5ptwty_ApplicationServiceLocalImpl.insertObjects(List<Cell>) line: 306	
	FrontController.generateGlider(PrintWriter) line: 237	
	FrontController.processGliderCommand(HttpServletRequest, HttpServletResponse, PrintWriter) line: 290	
	FrontController.processDemoCommand(HttpServletRequest, HttpServletResponse, PrintWriter) line: 359	
	FrontController.processAction(HttpServletRequest, HttpServletResponse) line: 388	
	FrontController.doGet(HttpServletRequest, HttpServletResponse) line: 459	
	FrontController(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 707	
	FrontController(HttpServlet).service(ServletRequest, ServletResponse) line: 820	
	StubSecurityHelper$ServletServiceAction.run() line: 227	
	StubSecurityHelper.invokeServlet(ServletRequest, HttpServletRequest, ServletRequestImpl, ServletResponse, HttpServletResponse, Servlet) line: 125	
	ServletStubImpl.execute(ServletRequest, ServletResponse, FilterChainImpl) line: 292	
	ServletStubImpl.execute(ServletRequest, ServletResponse) line: 175	
	WebAppServletContext$ServletInvocationAction.run() line: 3586	
	AuthenticatedSubject.doAs(AbstractSubject, PrivilegedAction) line: 321	
	SecurityManager.runAs(AuthenticatedSubject, AuthenticatedSubject, PrivilegedAction) line: 121	
	WebAppServletContext.securedExecute(HttpServletRequest, HttpServletResponse, boolean) line: 2196	
	WebAppServletContext.execute(ServletRequestImpl, ServletResponseImpl) line: 2102	
	ServletRequestImpl.run() line: 1428	
	ExecuteThread.execute(Runnable) line: 201	
	ExecuteThread.run() line: 173	
Solution:
  • With the ConversionManager fix - we create the MappedSuperclass ok now
  • After using
relationalDescriptor.convertClassNamesToClasses(metamodel.getSession().getDatasourcePlatform().getConversionManager().getLoader());
  • Note: I used to use getActiveSession() instead of getSession() as this will handle any JTS exteranal transaction if it exists - however a simple getSession() is all that is required.
   /**
     * PUBLIC:
     * Return the active session for the current active external (JTS) transaction.
     * This should only be used with JTS and will return the session if no external transaction exists.
     */
    public org.eclipse.persistence.sessions.Session getActiveSession() {
####<Sep 14, 2009 11:24:24 AM EDT> <Notice> <Stdout> <mfobrien-q4> <AdminServer> <[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'> <<WLS Kernel>> <> <> <1252941864996> <BEA-000000> <[EL Example]: enterprise: Metamodel: MetamodelImpl@29414608 [ 4 Types: , 2 ManagedTypes: , 1 EntityTypes: , 1 MappedSuperclassTypes: , 0 EmbeddableTypes: ]> 
this	MetamodelImpl  (id=10853)	
  mappedSuperclasses	LinkedHashSet<E>  (id=10871)	
    map	LinkedHashMap<K,V>  (id=10874)	
      size	1	
      table	HashMap$Entry<K,V>[16]  (id=10876)	
        [8]	LinkedHashMap$Entry<K,V>  (id=10883)	
          key	MappedSuperclassTypeImpl<X>  (id=10877)	
            descriptor	RelationalDescriptor  (id=10864)	
            inheritingIdentifiableTypes	HashMap<K,V>  (id=10878)	
              size	1	
              table	HashMap$Entry<K,V>[16]  (id=10887)	
                key	Class<T> (org.eclipse.persistence.example.jpa.server.business.Cell) (id=10882)	
                value	EntityTypeImpl<X>  (id=10858)	
            javaClass	Class<T> (org.eclipse.persistence.example.jpa.server.business.CellMS) (id=10879)	

DI 81: 20090914: Implement @BasicMap DirectMapContainerPolicy support in MapAttributeImpl

  • Refer to ManagedTypeImpl.initialize()
  • I currently support all Map policies where isMapPolicy() returns true.
  • There currently is no support for @BasicMap in because I was assuming that a call to CollectionContainerPolicy.isMapPolicy() returned true for DirectMapContainerPolicy.
Analysis:
  • Change
public abstract class ManagedTypeImpl<X> extends TypeImpl<X> implements ManagedType<X> {
    protected void initialize() {
        for (DatabaseMapping mapping : getDescriptor().getMappings()) {
            if (mapping.isCollectionMapping()) {
                ContainerPolicy collectionContainerPolicy = colMapping.getContainerPolicy();
--->            if (collectionContainerPolicy.isMapPolicy()) {
  • To
public abstract class ManagedTypeImpl<X> extends TypeImpl<X> implements ManagedType<X> {
    protected void initialize() {
        for (DatabaseMapping mapping : getDescriptor().getMappings()) {
            if (mapping.isCollectionMapping()) {
                ContainerPolicy collectionContainerPolicy = colMapping.getContainerPolicy();
--->            if (collectionContainerPolicy.isMapPolicy() 
                    || collectionContainerPolicy.isDirectMapPolicy()) {
  • As well as
    protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
        //MapContainerPolicy policy = (MapContainerPolicy) mapping.getContainerPolicy();
        ContainerPolicy policy = mapping.getContainerPolicy();
Solution 81 - Fixed:

DI 82: 20090914: Verify all is*Policy() calls for MapAttributeImpl instances

In ManagedTypeImpl.initialize() we need to verify handlers for all possible is*Policy() calls.

Analysis
Solution:

DI 83: 20090914: MapAttributeImpl.elementType incorrectly set when @ObjectTypeConverter is present

  • see bug# 289487
  • In the case where we have a @BasicMap with an @ObjectTypeConverter we end up with a valueField.type set to the database field (in this case String) and the valueField.typeName set to the object type of Long.
Analysis:
  • I currently key off the valueField.type when setting the elementType - I should be using the attributeClassification but none exists for DirectCollectionMapping.
Solution: 83 - Fixed
  • After discussing this with Guy, Gordon and Peter - We will add attributeClassification to DirectCollectionMapping
  • Note: For XML processing and the Mapping Workbench - this introduction of a new field on DirectCollectionMapping may need to be picked up.
  • See SVN rev# 5189

DI 84: 200909015: MapAttribute missing support for UC8: @MapKey with default name attribute

Analysis:
    // UC8: no targetEntity, MapKey uses name default
    @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC8")
    @MapKey // name attribute will default to "id"
    private Map<Integer, HardwareDesigner> hardwareDesignersMapUC8;
Solution:

DI 85: 20090916: Relax IllegalArgumentException checking on autoboxed primitives

  • This one found by Chris. Our current IAE check on functions like getSingularAttribute will throw an IAE on primitive attributes that have their type passed in as the boxed object wrapper class.
  • For example, if i have int id and I execute the function aManagedType.getSingularAttribute("name", Integer.class) - then an IAE will be thrown.
Analysis 85:
    /**
     *  Return the single-valued attribute of the managed 
     *  type that corresponds to the specified name and Java type 
     *  in the represented type.
     *  @param name  the name of the represented attribute
     *  @param type  the type of the represented attribute
     *  @return single-valued attribute with given name and type
     *  @throws IllegalArgumentException if attribute of the given
     *          name and type is not present in the managed type
     */
    public <Y> SingularAttribute<? super X, Y> getSingularAttribute(String name, Class<Y> type) {
        SingularAttribute<? super X, Y> anAttribute = (SingularAttribute<? super X, Y>)getSingularAttribute(name);
        Class<Y> aClass = anAttribute.getType().getJavaType();
-->relax  if(type != aClass) {
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
                "metamodel_managed_type_attribute_type_incorrect", 
                new Object[] { name, this, type, aClass }));
        }
        return anAttribute;
    }
Solution 85: - Fixed
  • We may want to relax primitive checking to not throw an IAE if the type is either the primitive class or its boxed type - int.class or Integer.class
  • This work is slated for review and completion next week after the remaining IdentifiableTypeImpl functions are finished.
  • 20090918: We have decided that we will relax the IAE rules and treat boxed classes the same as their primitive analogues - and not throw and IAE.
  • 20090929: see SVN Rev# 5347

DI 86: 20090921: Handle Embeddable Type keyType in MapAttributeImpl constructor

  • See SVN rev# 5761
  • See changes to keyType handling in DI 98
  • In the MapAttributeImpl we determine the keyType for the Map - support for embeddables may be missing here.
  • We also need to verity that elementType processing for maps is sufficient in the super PluralAttributeImpl constructor.
Analysis 86:
  • 20091105: starting investigation after discussion with Mitesh.
  • It looks like support for an Entity keyType needs to be modified.
Model Extensions: 86
  • We need to add the following extensions to our model in order to verify functionality
    • Enclosure (existing) already uses EnclusureIdClassPK as its IdClass
@Entity(name="ManufacturerMetamodel")
public class Manufacturer extends Corporation implements java.io.Serializable{
...
    // UC9: no targetEntity, no MapKey, but generics are set (MapKey has an IdClass with an Embeddable)
    @OneToMany(cascade=CascadeType.ALL, mappedBy="mappedManufacturerUC9")
---> new    private Map<Board, Enclosure> enclosureByBoardMapUC9;
}
 
@Entity(name="EnclosureMetamodel")
@IdClass(org.eclipse.persistence.testing.models.jpa.metamodel.EnclosureIdClassPK.class)
public class Enclosure implements java.io.Serializable {
...    
    @Id    @Column(name="TYPE")
    public String type;
    @Id    @Column(name="LENGTH")
    protected String length;
    @Id    @Column(name="WIDTH")
    private String width;
 
    // The M:1 side is the owning side
    @Id    @ManyToOne
    @JoinColumn(name="PERSON_ID")
---> new    private Manufacturer mappedManufacturerUC9;
}
 
public class EnclosureIdClassPK {
...
    public String type;
    protected String length;
    private String width;
---> new    private Integer mappedManufacturerUC9;
}
Testing before change

The following test passes with the change submitted in the DI86 patch below (without this fix we get the following test failure with the new model change above)

TEST SUITE NAME: org.eclipse.persistence.testing.tests.jpa.metamodel.MetamodelMetamodelTest
TEST NAME: testMapAttribute_getKeyJavaType_UC9_DI86_Embeddable_IdClass_keyType_Method(org.eclipse.persistence.testing.tests.jpa.metamodel.MetamodelMetamodelTest)
##FAILURE##
RESULT:      Error (failure)
junit.framework.AssertionFailedError: expected:<class org.eclipse.persistence.testing.models.jpa.metamodel.Board> but was:<class java.lang.Integer>
	at junit.framework.Assert.fail(Assert.java:47)
	at junit.framework.Assert.failNotEquals(Assert.java:277)
	at junit.framework.Assert.assertEquals(Assert.java:64)
	at junit.framework.Assert.assertEquals(Assert.java:71)
	at org.eclipse.persistence.testing.tests.jpa.metamodel.MetamodelMetamodelTest.testMapAttribute_getKeyJavaType_UC9_DI86_Embeddable_IdClass_keyType_Method(MetamodelMetamodelTest.java:4502)
Solution 86: - fixed
  • See DI 86 patch in SVN rev# 5761
  • The changes essentially involves adding support for an Entity @MapKey by remove the 2nd call to getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted when policyKeyType is not null - previously I defaulted in this specific case to the PK.
    • Remove the 2nd call to getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted when policyKeyType is not null.
  • Checking code coverage I see 18 hits on 20091023 on line 110 that is slated for removal - but running FullRegressionTestSuite gets no hits on my debugger with the 20091105 view.
  • However, later in 2 separate bugs this code was further enhanced to not use the policy.getElementDescriptor() directly and instead use policy is*Policy() calls like isMapPolicy/isDirectMapPolicy/isMappedKeyMapPolicy - reviewed by Guy.
  • See later fix in DI 98 in SVN rev# 5776 and 5793
  • Regression test case
    • MetamodelMetamodelTest.testMapAttribute_getKeyJavaType_UC9_DI86_Embeddable_IdClass_keyType_Method()

DI 87: 20090925: @JoinTable on JPA models is redundant if FK JoinColumn exists

  • Currently the join tables I am defining are empty because I am defining both a FK on each table as well as join column to model a bidirectional 1:m relationship.
Solution 87: - Fixed
  • Comment out all @JoinColumn annotations on my model.
  • In the future after Guy's next feature is complete - we should transition one of the FK join columns back to using a Join table.

DI 88: 20091006: Improve Performance via Caching and Minimal Iteration

  • On some legacy machines the metamodel initialization is taking up to 90 seconds. We will identify areas of the Metamodel that can be cached and iterative loops that can be minimized to start
Analysis 88:
  • After a talk with Gordon it would be better to move any requirements up back into metadata processing instead of keeping caching it only for the metamodel.
Solution 88:
  • In progress 20091005

DI 89: 20091007: Metamodel Implementation must implement Serializable - required by Criteria API

  • The Criteria API requires that all Metamodel Impl classes implement Serializable.
Analysis 89:
  • See Item 54 of Effective Java by Joshua Bloch p.223.
    • "declare an explicit serial version UID in every serializable class you write...If no serial version UID is provided, an expensive computation is required to generate one at run time."
  • See the section about Serialization in the SUN JDK Documentation
    • "It is strongly recommended that all serializable classes explicitly declare serialVersionUID values, since the default serialVersionUID computation is highly sensitive to class details that may vary depending on compiler implementations, and can thus result in unexpected serialVersionUID conflicts during deserialization, causing deserialization to fail."
  • Generate a unique serialVersionUID using the Eclipse 3.5 IDE using Source | Clean Up | Custom profile | Configure | Missing Code | Potential programming problems | Add serial version ID | Generated
  • No custom serialization handling with overrides of readObject and writeObject is required at this time.
  • How to handle deserialization
    • Do we rely on the serialization mechanism or do we implement an ExternalizableDelegator to regenerate the metamodel after readObject completes.
Solution 89: - Fixed
  • It is not entirely clear whether inherited abstract classes in the chain must have serialVersionUID - however a change is in that has all root classes in the hierachy implement Serializable and all concrete classes provide their non-default distinct serialVersionUID.
  • Fixed in SVN rev# 5506

DI 90: 20091007: Validate and Remove all remaining 12 TODO comments

  • Description Resource Path Location Type
  • TODO : Check all is*Policy() calls ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 1096 Java Task
  • TODO : Embeddable Collection handling is in progress ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 410 Java Task - finished remove.
  • TODO : expand on variant use case IdentifiableTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 231 Java Task - 0% code coverage - remove case handling
  • TODO : handle AggregateCollectionMapping PluralAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 77 Java Task
  • TODO : Handle EmbeddableType MapAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 93 Java Task - 0% code coverage - remove
  • TODO : refactor SingularAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 119 Java Task - 12 runs in code coverage - change required.
  • TODO : REFACTOR PluralAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 87 Java Task - 6 runs in code coverage - same change as above required.
  • TODO : refactor exception handling PluralAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 122 Java Task - 6 runs - same fix as above
  • TODO : refactor: default to the managedType PluralAttributeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 111 Java Task - 0% code coverage - invalid - remove case code line.
  • TODO : System.out.println("_Warning: defaulting to non-Set specific Collection type on " + colMapping); ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 1272 Java Task - implement a finer log.
  • TODO : System.out.println("_Warning: type is null on " + colMapping); ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 1084 Java Task - same as above - implement a real log message
  • TODO : verify handling of XML mapped non-Entity (plain Java Class) inherited mappings ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 1069 Java Task - remove - this is a future design enhancement in DI 53
  • TODO : verify that all entities or'd with embeddables matches the number of types MetamodelImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 311 Java Task - simple to implemement - or just remove
  • TODO : verify use case ManagedTypeImpl.java org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metamodel line 1045 Java Task - has 57 hits in code-coverage - identify test case - and remove todo

DI 91: 20091008: ManagedType.getDeclaredAttribute() does not throw expected IAE for Entity(target)-MappedSuperclass-MappedSuperclass(attribute) Hierarchy

  • The logic of the recursive call in getDeclaredAttribute works fine but it must be extended to be able to be used by getDeclaredId and getDeclaredVersion - in this case we already know that there is an attribute on the current target leaf element that we start the recursive search up to the root.
  • I found this when I did not return an expected IAE when testing the variant use case for IdentifiableType.getDeclaredId() where the id is declared 2 levels above on the Entity-MappedSuperclass-MappedSuperclass inheritance chain.
Analysis 91:
Solution 91: - Fixed
  • The fix is simple and is along the lines that Chris discussed for a similar issue with the recursive handler in ManagedTypeImpl during a review a couple months ago.
    • Reverse the if check against the recursive call return in getDeclaredAttribute()
ManagedTypeImpl
    public Attribute<X, ?> getDeclaredAttribute(String name){
        // get the attribute parameterized by <Owning type, return Type> - throw an IAE if not found (no need to check hierarchy)
        // Handles UC1 and UC2
        Attribute<X, ?> anAttribute = getAttribute(name);
        // If an Attribute is found then check the hierarchy for a declaration in the superclass(s)
        // Keep moving up only when the attribute is not found
        ManagedTypeImpl aManagedSuperType = getManagedSuperType();        
        if(null == aManagedSuperType) {
            return anAttribute;
        } else {
            // keep checking the hierarchy but skip this level
--add !     if(!aManagedSuperType.isAttributeDeclaredOnlyInLeafType(name)) {
                // Handles UC4 and UC5 - throw an IAE if the class is declared above
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
                        "metamodel_managed_type_declared_attribute_not_present_but_is_on_superclass",
                        new Object[] { name, this }));
            } else {
                // Handles UC3 (normal case - attribute is not declared on a superclass)
                return anAttribute;
            }
        }
    }
  • However there is more to this - we also need to pass in the original attribute found on the leaf - if found - for comparison.
  • Fixed in SVN rev# 5506 and 5509

DI 92: 20091008: Move metamodel instance field from EntityManagerFactory to EntityManagerSetupImpl

  • This will solve any GC issues that arise
  • Currently the metamodel field is part of the EntityManagerFactoryImpl - it would be better to place this at the end of the deploy in EntityManagerSetupImpl - Gordon.
Analysis 92:
  • Validation or not - metamodel is initialized
  • The EntityManagerSetupImpl does not have an accessor on EntityManagerFactoryImpl - therefore I will leave the non-spec. setMetamodel() function used to reset the metamodel for testing as an internal public function on EntityManagerFactoryImpl and not move it to EntityManagerSetupImpl as well.
  • We have the following new chain of execution that will initialize the metamodel just after deploy()
Thread [Thread-3] (Suspended (breakpoint at line 1930 in EntityManagerSetupImpl))	
	EntityManagerSetupImpl.getMetamodel() line: 1930	
	EntityManagerSetupImpl.deploy(ClassLoader, Map) line: 301	
	EntityManagerFactoryImpl.getServerSession() line: 160	
	EntityManagerFactoryImpl.createEntityManagerImpl(Map) line: 216	
	EntityManagerFactoryImpl.createEntityManager() line: 204	
	JUnitTestCase.getServerSession(String) line: 340	
	JUnitTestCase.getEntityManagerFactory(String, Map) line: 365	
	JUnitTestCase.getEntityManagerFactory(String) line: 344	
	JUnitTestCase.getServerSession(String) line: 340	
	MetamodelMetamodelTest(MetamodelTest).setUp() line: 46	
	MetamodelMetamodelTest.setUp() line: 134
Solution 92: - Fixed
  • Reviewed and discussed with Chris - See SVN rev# 5541.
  • 20091214 - part of the solution around initialization has been modified as part of bug# 297555 which will be further refined by enhancement# 297748 - a try/catch wrapper around any CCE or spec defined IAE that will allow all deployment scenarios to proceed to completion - regardless of whether the metamodel is correct or not.

DI 93: 20091014: IdentifiableTypeImpl.getIdType() assumes all Id mappings are DerivedId mappings

  • The IdentifiableType.getIdType() function assumes that all Entity types have a CMPPolicy set on their descriptor. This occurs because currently even for BasicType Id's (which are not DerivedId's) have the following line.
BasicType.java
    public void process() {
	...        
        // Derived ID: set if this mapping has been marked as an ID.
        mapping.setIsDerivedIdMapping(isId());
  • The getIdType() function has special handling for our pseudo descriptor MappedSuperclasses - where the CMPPolicy is null - otherwise they just check the CMPPolicy for entities.
  • The problem is that I am getting a NPE if there is no CMPPolicy on the mapping - as a possible refactor of the metadata code uncovered.
  • The second part of the code for single key support is outside of a null check on the CMPPolicy - this will cause a NPE in some cases - and has for upcoming changes to Derived Id work.
    public Type<?> getIdType() {
        CMPPolicy cmpPolicy = getDescriptor().getCMPPolicy();
        if (null == cmpPolicy) {
            // Composite key support (IE: @EmbeddedId)            
...
        }
 
        // Single Key support using any Java class - built in or user defined
        // There already is an instance of the PKclass on the policy
NPE-->  if (cmpPolicy.isCMP3Policy()) {
            // BasicType, EntityType or IdentifiableType are handled here, lookup the class in the types map and create a wrapper if it does not exist yet
            return this.metamodel.getType(((CMP3Policy) cmpPolicy).getPKClass());
        }
    }
Solution 93: - Fixed
  • For now we will not force all Id mappings to have a CMPPolicy - this is a future todo.
  • We will however introduce a new isJPAId() flag for basic type id's to replace using isDerivedId() in all cases - Guy is checking this in as of 20091015.
  • For the future we need to implement custom initialization code for our MappedSuperclass Decriptors that sets the CMPPolicy on the descriptor and initializes the pkClass - see the comment in EntityManagerSetupImpl.assignCMPPolicy().

DI 94: 20091015: Split and Granularize Test Suite

  • Historically, the test model runs as a second priority to getting the implementation stable - I don't agree with this, and now that the implementation is complete and relatively stable - we can fix up the test suite.
  • The test suite currently consists of 8 small tests and 1 massive 3000 line test case that excercises a majority of all the interface functions.
  • The biggest issue is if one part of the massive test case fails - the remaining part does not run. Also it is difficult to track the failure down - the stack trace must be used instead of a more traditional report format.
  • This suite needs to be split up into 100-200 test cases so we have more
Solution 94:
  • In progress in parallel with other issues

DI 95: 20091017: Attribute.getJavaMember() returns null for a BasicType on a MappedSuperclass because of an uninitialized accessor

    /**
     *  Return the <code>java.lang.reflect.Member</code> for the represented 
     *  attribute.
     *  @return corresponding <code>java.lang.reflect.Member</code>
     */
    java.lang.reflect.Member getJavaMember();
  • The AttributeAccessor on the DirectToField" Mapping is missing the attributeField required by getJavaMember().
this	SingularAttributeImpl<X,T>  (id=70)	
  elementType	BasicTypeImpl<X>  (id=81)	
  managedType	MappedSuperclassTypeImpl<X>  (id=54)	
  mapping	DirectToFieldMapping  (id=84)	
    attributeAccessor	InstanceVariableAttributeAccessor  (id=92)	
      attributeField	null	
      attributeName	"id" (id=95)	
      isReadOnly	false	
      isWriteOnly	false	
    attributeClassification	null	
    attributeClassificationName	null	
    attributeName	"id" (id=95)	
    attributeObjectClassification	null	
  • The workaround is to use our own java reflection code on the class or get the member from an inheriting subclass - in the case of a MappedSuperclass managingType.
Solution 95:
Good 95:
  • Get the attribute Field from the inheriting subclass.
    public Member getJavaMember() {
        AttributeAccessor accessor = getMapping().getAttributeAccessor();
        if (accessor.isMethodAttributeAccessor()) {
            return ((MethodAttributeAccessor) accessor).getGetMethod();
        }
        Member aMember = ((InstanceVariableAttributeAccessor) accessor).getAttributeField();
        // For primitive and basic types - we should not return null - the attributeAccessor on the MappedSuperclass is not initialized
        // MappedSuperclasses need special handling to get their type from an inheriting subclass
        if(null == aMember) {
            if(this.getManagedTypeImpl().isMappedSuperclass()) {
                // get inheriting subtype member (without handling @override annotations)                
                AttributeImpl inheritingTypeMember = ((MappedSuperclassTypeImpl)this.getManagedTypeImpl())
                    .getMemberFromInheritingType(mapping.getAttributeName());
                // Verify we have an attributeAccessor
                aMember = ((InstanceVariableAttributeAccessor)inheritingTypeMember.getMapping()
                        .getAttributeAccessor()).getAttributeField();
            }
        }
 
        return aMember;
    }
aMember	Field  (id=145)	
	annotations	 (id=151)	
	clazz	Class<T> (org.eclipse.persistence.testing.models.jpa.metamodel.Person) (id=91)	
	declaredAnnotations	null	
	fieldAccessor	null	
	genericInfo	null	
	modifiers	2	
	name	"id" (id=136)	
	override	true	
	overrideFieldAccessor	null	
	root	Field  (id=155)	
	securityCheckCache	null	
	securityCheckTargetClassCache	null	
	signature	null	
	slot	0	
        type	Class<T> (java.lang.Integer) (id=108)
  • The problem with this code is it does not account for Attribute Overrides on the inheriting Entity class or class subtree.
Better 95:
  • Initialize the MappedSuperclass descriptor.

DI 96: 20091019: Attribute.getPersistentAttributeType() treats ManyToOne the same as OneToOne

  • This one occurred because we treat ManyToOne the same as OneToOne internally by design
    • We solve this by either checking for Join columns or by storing a map of JPA accessors for later use by AttributeImpl.getPersistentAttributeType().
    • However we need to work on the MANY_TO_MANY and ELEMENT_COLLECTION case - as they do not have coverage yet.
  • This issue actually has 2 parts
    • We need a way to differentiate when a OneToOne was defined as a ManyToOne and also when a ManyToMany was defined as a OneToMany.
Analysis 96:
  • Search for the join table on the ManyToOne mapping.
    • For example we have the following relationTable:DatabaseTable on the OneToOneMapping that flags this mapping as an actual ManyToOneMapping
mapping	OneToOneMapping  (id=84)	
  mechanism	RelationTableMechanism  (id=116)	
    deleteQuery	DataModifyQuery  (id=184)	
    hasCustomDeleteQuery	false	
    hasCustomInsertQuery	false	
    insertQuery	DataModifyQuery  (id=187)	
    lockRelationTableQuery	null	
    relationTable	DatabaseTable  (id=188)	
      name	"CMP3_MM_COMPUTER_MM_BOARD" (id=195)	
      qualifiedName	"CMP3_MM_COMPUTER_MM_BOARD" (id=195)	
      tableQualifier	"" (id=196)	
      uniqueConstraints	Vector<E>  (id=197)	
      useDelimiters	false
@Entity(name="BoardMetamodel")
@Table(name="CMP3_MM_BOARD")
public class Board implements java.io.Serializable{
    // The M:1 side is the owning side for "circuitBoards"
    @ManyToOne(fetch=EAGER)
    @JoinTable(name="CMP3_MM_COMPUTER_MM_BOARD", 
            joinColumns = @JoinColumn(name="BOARD_ID"), 
            inverseJoinColumns = @JoinColumn(name="COMPUTER_ID"))   
    private Computer computer;
}
 
@Entity(name="ComputerMetamodel")
@Table(name="CMP3_MM_COMPUTER")
public class Computer implements java.io.Serializable {
    // Inverse side 
    @OneToMany(cascade=ALL, mappedBy="computer")
    private Collection<Board> circuitBoards;
}
Solution 96:
  • I will expand and post a patch shortly but here is the main point
    • Store a Map on the (core) Project of Key:DatabaseMapping and target:MappingAccessor
    • Use the map to lookup the Accessor based on the mapping on the attribute
    • Use this accessor to ask isManyToOne() or isOneToOne()
  • 20100419: There is a new ManyToOneMapping in ..

DI 97: 20091102: Add JPA 2.0 @MapKeyClass use cases to test model

  • In the same way that we have @MapKey testing on Manufacturer we need to verify MapAttribute processing for @MapKeyClass annotated elements.
Solution 97:
  • We extend the existing model where HardwareDesigner and Enclosure are mapped from Manufacturer and Computer.

DI 98: 20091109: MapAttribute keyType processing should offload more to MappedKeyContainerPolicy.keyMapping

  • After a discussion about the fix for DI 86 in SVN Rev# 5761 it looks like my fix is doing more than it requires when the key column is not specified and should not be using the CMP3Policy in the case that the keyType is null on the MappedKeyMapContainerPolicy.
  • Use cases 1a and 7 have been fixed by deferring to the MappedKeyMapContainerPolicy - for these 2 cases where the mapKey was actually on the attributeClassification
  • However, after debugging in detail though the current use cases which cover all scenarios except for one. It looks like we still need special processing for 2 cases in bug # 294765 and bug# 294811 that involve the superclass MapContainerPolicy - therefore some of the existing special handling code for determining the PK class when required will remain.
    • Case 1: No keyField set on MapContainerPolicy
      • Case 1a: Element class uses an @Id field - we require the mapPolicy.elementDescriptor.cmppolicy.pkClass
      • Case 1b: Element class uses an @Id function - we require reflection code
    • The following code should be done for me in the MKMCPolicy - but at this time still must exist lower in the Metamodel layer.
javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
  • Normally, I should just be using the key mapping on the MKMCPolicy and let this policy handle corner use cases in the specification for me. But, the way the MapContainerPolicy currently works requires some special handling in MapAttribute.
Analysis 98:
  • See section 10.1.26 (@MapKey) and 10.1.27 (@MapKeyClass) of the JSR-317 spec. for details on the two JPA annotations for specifying the Map key in addition to using generics.
    • A summary is that either the name of the map key attribute is specified or we use the K in the Map<K,V> declaration to determine the Map key - otherwise we default to the PK.
  • Use Case segmentation for keyType
    • MapContainerPolicy
      • keyField set (lazy loaded)
        • UC2 - name attribute defines mapKey, generics are not required and are secondary
          • @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC2")
          • @MapKey(name="name")
          • private Map<String, HardwareDesigner> hardwareDesignersMapUC2;
        • UC4 - name attribute defines mapKey, generics are not required
          • @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC4")
          • @MapKey(name="name")
          • private Map hardwareDesignersMapUC4;
        • UC8 - mapKey defined via generics
          • @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC8")
          • @MapKey // name attribute will default to "id"
          • private Map<Integer, HardwareDesigner> hardwareDesignersMapUC8;
      • Use mapPolicy.elementDescriptor.cmppolicy.pkClass (since KeyField == null)
        • UC10 - mapKey defined via generics and is a java class defined as an IdClass on the element(value) class - here Enclosure
          • @OneToMany(mappedBy="computer", cascade=ALL, fetch=EAGER)
          • @MapKey // key defaults to an instance of the composite pk class
          • private Map<EnclosureIdClassPK, Enclosure> enclosures;
      • or (get keyClass from mapping if the Id is a get() function)
      • TBD
    • MappedKeyMapContainerPolicy
      • mapKeyTargetType set on the keyMapping - normal processing
        • UC9 - mapKey defined by generics in the absence of a MapKey annotation
          • @OneToMany(cascade=CascadeType.ALL, mappedBy="mappedManufacturerUC9")
          • private Map<Board, Enclosure> enclosureByBoardMapUC9;
      • Secondary processing for Basic (DirectToField) mappings - use AttributeClassification (since keyMapping.attributeAccessor.attributeClass == null)
        • UC1a - used to return the Integer PK
          • @OneToMany(cascade=ALL, mappedBy="mappedEmployerUC1a")
          • private Map<String, HardwareDesigner> hardwareDesignersMapUC1a;
        • UC7 - used to return the Integer PK
          • @OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC7")
          • private Map<String, HardwareDesigner> hardwareDesignersMapUC7;
  • Note: we will not qualify validation for the following invalid scenarios.
    • The name attribute of the MapKey annotation defines a key class different from the one defined in the Map<K,V> generics field.
    • When both the name attribute and the Map<K,V> generics fields are specified - internal order of validation is not enforced.
Solution 98:
    protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
        // Set the managedType (X or owning Type)
        super(managedType, mapping, validationEnabled);
        // Override the elementType (V or Map value)
        // We need to set the keyType Type that represents the type of the Map key for this mapping
        ContainerPolicy policy = mapping.getContainerPolicy();
        ClassDescriptor policyElementDescriptor = policy.getElementDescriptor();
        Object policyKeyType = policy.getKeyType(); // returns a Class<?> or descriptor (via AggregateObjectMapping)        
        Type<?> aKeyType = null;
        // Default to Object class for any variant cases that are not handled
        Class<?> javaClass = null;
        if(null == policyKeyType) {
            // No policy key type = IdClass (use CMP3Policy.pkClass)
            if(managedType.isIdentifiableType()) {
                // Use the CMPPolicy on the element not the one on the managedType
                if(policyElementDescriptor != null && 
                        policyElementDescriptor.getCMPPolicy() != null) {
                    javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
                }
                if(null == javaClass) {
                    // check for a @MapKeyClass annotation
                    if(policy.isMappedKeyMapPolicy()) {                            
                        javaClass = getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted(
                                (MappedKeyMapContainerPolicy)policy);
                    }
                }
            }
        } else {            
            if(policyKeyType instanceof ClassDescriptor) { // from AggregateObjectMapping
                javaClass = ((ClassDescriptor)policyKeyType).getJavaClass();
            } else {
                javaClass = (Class<?>)policyKeyType;
            }            
        }
 
        aKeyType = getMetamodel().getType(javaClass);
        this.keyType = (Type<K>) aKeyType;
    }
  • after
    protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) {
        // Set the managedType (X or owning Type)
        super(managedType, mapping, validationEnabled);
        // We need to set the keyType Type that represents the type of the Map key for this mapping
        ContainerPolicy policy = mapping.getContainerPolicy();
        Type<?> aKeyType = null;
        Class<?> javaClass = null;
        MapKeyMapping keyMapping = null; 
        ClassDescriptor policyElementDescriptor = policy.getElementDescriptor();
        Object policyKeyType = null;
 
        // Step 1: We check the Map policy for the keyMapping
        if(policy.isMapPolicy()) { // There is always a MapPolicy
            // check for Either a generic Map (MapContainerPolicy) or specific MappedKeyMapContainerPolicy subclass
            if(policy.isMappedKeyMapPolicy()) {
                keyMapping = ((MappedKeyMapContainerPolicy)policy).getKeyMapping();
                policyKeyType = keyMapping.getMapKeyTargetType();
                // DirectToFieldMapping implements MapKeyMapping - here we get the key from the attributeClassification
                if(null == policyKeyType) {
                    policyKeyType = ((DatabaseMapping)keyMapping).getAttributeClassification();
                }
            } else {
                // Assume we have a MapContainerPolicy general superclass with a lazy-loaded keyType
                policyKeyType = policy.getKeyType(); // returns a Class<?> or descriptor (via AggregateObjectMapping)
                // The keyType will be null on the MapContainerPolicy when the keyField is null
            }
        }
 
        // Step 2: We determine the java class from the policyKeyType (class or ClassDecriptor) - and perform alternate keyType lookup
        if(null == policyKeyType) {
            // The keyType will be null on a MapContainerPolicy when the keyField is null - usually a composite key
            if(managedType.isIdentifiableType()) {
                // Use the PK of the element not the one on the managedType
                // Case: @MapKey private Map<K,V> // no name column specified
                if(policyElementDescriptor != null && policyElementDescriptor.getCMPPolicy() != null) {
                    javaClass = policy.getElementDescriptor().getCMPPolicy().getPKClass();
                }
                // Case: @MapKey private Map<K,V> // no name column specified and the PK is defined by a method
                if(null == javaClass) {
                    if(policy.isMappedKeyMapPolicy()) {                            
                        javaClass = getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted(
                                (MappedKeyMapContainerPolicy)policy);
                    }
                }
            }
        } else {
            // Process the policyKeyType
            if(policyKeyType instanceof ClassDescriptor) { // from AggregateObjectMapping
                javaClass = ((ClassDescriptor)policyKeyType).getJavaClass();
            } else {
                javaClass = (Class<?>)policyKeyType;
            }            
        }
 
        // Step 3: We wrap the java type in a Metamodel Type instance or retrieve an existing Type
        aKeyType = getMetamodel().getType(javaClass);
        this.keyType = (Type<K>) aKeyType;
    }

DI 99: 20091110: Metamodel.types LinkedHashMap appears to have null=null K,V pairs - expected Java SE behavior

  • During forensic analysis of the workarounds in DI 98 surrounding bug# 294765 I ran across what looked to be null KV pairs int the Metamodel.types LinkedHashMap - these turned out to be normal after adding debugging code.
MetamodelImpl@7674523 [ 40 Types: , 20 ManagedTypes: , 13 EntityTypes: , 4 MappedSuperclassTypes: , 3 EmbeddableTypes: ]
null=null
on
metamodel	MetamodelImpl  (id=192)	
	embeddables	LinkedHashMap<K,V>  (id=315)	
	entities	LinkedHashMap<K,V>  (id=316)	
	managedTypes	LinkedHashMap<K,V>  (id=317)	
	mappedSuperclasses	LinkedHashSet<E>  (id=318)	
	session	ServerSession  (id=319)	
	types	LinkedHashMap<K,V>  (id=263)	
		accessOrder	false	
		entrySet	HashMap$EntrySet  (id=352)	
		header	LinkedHashMap$Entry<K,V>  (id=270)	
		keySet	null	
		loadFactor	0.75	
		modCount	40	
		size	40	
		table	HashMap$Entry<K,V>[64]  (id=274)	
			[0]	null	
			[1]	LinkedHashMap$Entry<K,V>  (id=282)	
				after	LinkedHashMap$Entry<K,V>  (id=287)	
					after	LinkedHashMap$Entry<K,V>  (id=295)	
						after	LinkedHashMap$Entry<K,V>  (id=300)	
							after	LinkedHashMap$Entry<K,V>  (id=270)	
								after	LinkedHashMap$Entry<K,V>  (id=347)	
								before	LinkedHashMap$Entry<K,V>  (id=300)	
								hash	-1	
>								key	null	
								next	null	
>								value	null	
							before	LinkedHashMap$Entry<K,V>  (id=295)	
							hash	9542041	
							key	Class<T> (java.lang.Short) (id=304)	
							next	null	
							value	BasicTypeImpl<X>  (id=305)	
						before	LinkedHashMap$Entry<K,V>  (id=287)	
						hash	20782847	
						key	Class<T> (double) (id=301)	
						next	LinkedHashMap$Entry<K,V>  (id=302)	
						value	BasicTypeImpl<X>  (id=303)	
					before	LinkedHashMap$Entry<K,V>  (id=282)	
					hash	23367130	
					key	Class<T> (long) (id=298)	
					next	null	
					value	BasicTypeImpl<X>  (id=299)	
				before	LinkedHashMap$Entry<K,V>  (id=294)	
				hash	9273729	
				key	Class<T> (java.math.BigDecimal) (id=296)	
				next	null	
				value	BasicTypeImpl<X>  (id=297)	
			[2]	LinkedHashMap$Entry<K,V>  (id=283)	
 
			[62]	LinkedHashMap$Entry<K,V>  (id=370)	
			[63]	LinkedHashMap$Entry<K,V>  (id=295)	
		threshold	48	
		values	HashMap$Values  (id=242)
  • A non-issue solved
  • 20091216: reopening - we are running into an issue where an emf.getCriteriaBuilder() call before we create an EntityManager runs into the same invalid NPE on a null ClassDescriptor.javaClass - check secondarily in bug# 297928
MetamodelImpl.java
    private boolean putType(Class javaClassKey, TypeImpl typeValue) {
        boolean isValid = true;
        // DI99: Check for an invalid key without reporting it (a non-Fail-Fast
pattern)
        if(null == javaClassKey) {
--->        isValid = false;
        }
        this.types.put(javaClassKey, typeValue);
        return isValid;
    }

DI 100: 20100120: EmbeddedId on MappedSuperclass fails metadata validation on reserved temp metamodel PK

  • See EmbeddedIdAccessor:154 - The fake table name - inserted so that we can process metamodel types is leaking into metadata processing.
  • Either skip validation for this reserved temporary PK or delay/truncate insertion in metadata processing until after this step if not required for metamodel pre-processing.
  • 20100308: See related bug# 300051 fixed in SVN rev # 6784 for EclipseLink 2.1
  • 20100924: See related GlassFish bug# 13557 in EclipseLink 2.0.1 - fixed by 300051
  • See also enhancement # 326317 to name the attribute of this field to aide in triage of validation exceptions involving the Metadata or Metamodel API when using temporary internal processing fields.
DI 100: UML Model Extension
  • CPU (Abstract @MappedSuperclass) fields: @EmbeddedId CPUEmbeddableId

Eclipselink uml diagrams 300051 embeddedid on mappedsuperclass 20100310.gif

Analysis 100:
  • In the following code
public class EmbeddedIdAccessor extends EmbeddedAccessor {
    public void process() {
                // Check if we already processed an Id or IdClass.
                if (owningDescriptor.hasPrimaryKeyFields()) {
                    throw ValidationException.embeddedIdAndIdAnnotationFound(getJavaClass(), getAttributeName(), owningDescriptor.getIdAttributeName());
                }
  • The following exception will occur if we move @EmbeddedId down from E:GalacticPosition to MS:CoordinateMS (It does not need to be composite)
Exception Description: Entity class [class org.eclipse.persistence.testing.models.jpa.metamodel.CoordinateMS] has both an @EmbdeddedId (on attribute [primaryKey]) and an @Id (on attribute []. Both ID types cannot be specified on the same entity.
	at org.eclipse.persistence.exceptions.ValidationException.embeddedIdAndIdAnnotationFound(ValidationException.java:845)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.EmbeddedIdAccessor.process(EmbeddedIdAccessor.java:154)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor.processAccessors(MetadataDescriptor.java:1271)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor.processAccessors(ClassAccessor.java:825)
	at org.eclipse.persistence.internal.jpa.metadata.accessors.classes.MappedSuperclassAccessor.processMetamodelDescriptor(MappedSuperclassAccessor.java:1088)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage2(MetadataProject.java:1331)
	at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:461)
	at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:390)
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:947)
  • This validation exception is occurring because metadata processing does not expect the fake table names

(previously we did not have descriptors for MappedSuperclasses - before we introduced the Metadata JPA 2.0 API)

  • The fix will be to "not count" the fake ID's or either not add them in the presence of an IdClass or EmbeddedID
    • RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.metamodel.CoordinateMS --> [DatabaseTable(__METAMODEL_RESERVED_IN_MEM_ONLY_TABLE_NAME)])
primaryKeyFields	ArrayList<E>  (id=107)	
	elementData	Object[2]  (id=115)	
		[0]	DatabaseField  (id=117)	
			name	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=118)	
			qualifiedName	"__PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME" (id=118)
Option 100-1: Ignore fake MappedSuperclass IDs
  • If we check the id for our fake MappedSuperclass id as follows
                if (owningDescriptor.hasPrimaryKeyFields() && 
                    !owningDescriptor.getPrimaryKeyField().getQualifiedName().equalsIgnoreCase(
                                MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME)) {
                    throw ValidationException.embeddedIdAndIdAnnotationFound(getJavaClass(), getAttributeName(), owningDescriptor.getIdAttributeName());
                }
  • We get the following instead of the validation exception
cmpPolicy	CMP3Policy  (id=154)	
	descriptor	RelationalDescriptor  (id=124)	
	fieldToAccessorMap	null	
	forceUpdate	null	
	keyClassFields	null	
	mappedClass	null	
	modificationDeferralLevel	2	
	nonDeferredCreateTime	0	
	pessimisticLockingPolicy	null	
	pkClass	null	
	pkClassName	"org.eclipse.persistence.testing.models.jpa.metamodel.EmbeddedPK" (id=160)	
	updateAllFields	null
Option 100-2: Do not add fake MappedSuperclass IDs when IdClass or EmbeddedId will exist
  • Previously, I thought we do not have the Id accessors populated yet - to be able to check.
  • It turns out that the call accessor.addAccessors(); actually does present me with the IdClass or EmbeddedId accessors
  • The issue is that the call if (!metadataDescriptor.hasIdAccessor()) { is not doing enough - it should be also calling hasEmbeddedId() - however this field is currently not set yet and is null.
this	MappedSuperclassAccessor  (id=115)	
	m_descriptor	MetadataDescriptor  (id=117)	
		m_accessors	HashMap<K,V>  (id=119)	
			size	2	
			table	HashMap$Entry<K,V>[16]  (id=212)	
				[7]	HashMap$Entry<K,V>  (id=219)	
					key	"primaryKey" (id=223)	
					value	EmbeddedIdAccessor  (id=141)	
		m_defaultAccess	"FIELD" (id=202)	
		m_embeddedIdAccessor	null	
		m_pkClass	null	
		m_pkClassIDs	HashMap<K,V>  (id=242)	
	m_idClass	null
            if (!metadataDescriptor.hasIdAccessor()) {
                relationalDescriptor.addPrimaryKeyFieldName(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME);
            }
 
m_idAccessors	HashMap<K,V>  (id=91)	
	entrySet	HashMap$EntrySet  (id=128)	
	size	0	
 
Thread [main] (Suspended (breakpoint at line 467 in MetadataProject))	
	MetadataProject.addMetamodelMappedSuperclass(MetadataClass, MappedSuperclassAccessor) line: 467	
	EntityAccessor.addPotentialMappedSuperclass(MetadataClass, boolean) line: 188	
	EntityAccessor.discoverMappedSuperclassesAndInheritanceParents(boolean) line: 300	
	EntityAccessor.preProcess(boolean) line: 539	
	EntityAccessor.preProcess() line: 522	
	MetadataProject.processStage1() line: 1303	
	MetadataProcessor.processORMMetadata() line: 460	
	PersistenceUnitProcessor.processORMetadata(MetadataProcessor, boolean) line: 390	
	EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 947	
	JavaSECMPInitializer(JPAInitializer).callPredeploy(SEPersistenceUnitInfo, Map, PersistenceInitializationHelper, String, String) line: 88	
	JavaSECMPInitializer.initPersistenceUnits(Archive, Map, PersistenceInitializationHelper) line: 256	
	JavaSECMPInitializer.initialize(Map) line: 216
  • We must partially process the accessor by calling setEmbeddedIdAccessor()' and moving the validation check higher from EmbeddedIdAccessor.process() to MetadataDescriptor.addAccessor() - with this change, the addition of a hasIdAccessor() to the existing hasIdAccessor in MetadataProject.addMetamodelMappedSuperclass() will stop the addition of the temporary PK MAPPED_SUPERCLASS_RESERVED_PK_NAME to the MAPPED_SUPERCLASS_RESERVED_TABLE_NAME temporary table.
Option 100-3: Remove __PK_METAMODEL_RESERVED_IN_MEM_ONLY_FIELD_NAME temp PK when IdClass or EmbeddedId exists
  • In this option we remove the fake id when it turns out it is no longer needed.


Solution 100:
  • We will need to use option 2 - move validation earlier and set the accessor field on the descriptor so that we can check for it (partially pre-process it).
  • Fixed in SVN rev # 6784

DI 101: 20100218: Descriptor.javaClass is null on a container EM for a specific case

  • 20110303: We have reproduced this issue in bug# 338837 in a Java SE environment. We have two workarounds and will be modifying the code to warn about the actual cause - which is a not fully specified data model in persistence.xml.
    • See SVN rev # 9086
  • See the following distributed Java EE application tutorial on how to define persistence units with WebLogic.
  • This issue concerns the case where the metadata is not fully initialized - as in one or more of the descriptors has not passed through a Project.convertClassNamesToClasses() call after predeploy() but before using the persistence unit.
  • The Metamodel API must assume that all RelationalDescriptors are initialized - with both their class and className set. If we try to create a Metamodel Type from this un-initialized RelationalDescriptor - the user may see the following exception.
  java.lang.IllegalArgumentException: The type [null] is not the expected [EntityType] for the key class [class org.yourdomain.Enity(not-MappedSuperclass)].
Analysis 101
  • Some development history: It is assumed by the Metamodel API that metadata processing of RelationalDescriptors have their javaClass set (non-NULL)
  • There has been several scenarios where the javaClass is null - usually on @MappedSuperclass objects documented above.
  • I think we cannot in the future "assume" that all metadata processing is complete (aka convertClassNamesToClasses()
Reproduction 101
  • 20110303: We have reproduced this issue in bug# 338837 in a Java SE environment. We have two workarounds and will be modifying the code to warn about the actual cause - which is a not fully specified data model in persistence.xml.


  • The following specification mandated JPA 2.0 IllegalArgumentException occurs because entity classes were not processed because they are missing from the search path or not specified.
  • This usually occurs in Java SE environments only.
  • This is the result of an incomplete specification of the entities in persistence.xml for Java SE RESOURCE_LOCAL environments and possibly Java EE 6 Web Profile JTA environments.
  • Occurs when running JPA 2.0 Criteria or Metamodel code where we require an EntityType like the following
        EntityType unitOfWork_ = getEntityManager().getMetamodel().entity(UnitOfWork.class);
        Root<UnitOfWork> root =  getEntityManager().getCriteriaBuilder().createQuery().from(UnitOfWork.class);
DDLGenerationClient [Java Application]    
    org.eclipse.persistence.example.jpa.server.common.DDLGenerationClient at
localhost:4353    
        Thread [main] (Suspended)    
            MetamodelImpl.entity(Class<X>) line: 160    
            CriteriaQueryImpl<T>(AbstractQueryImpl<T>).from(Class<X>) line: 97  
            DDLGenerationClient.checkMetamodel() line: 436    
            DDLGenerationClient.doQuery() line: 460    
            DDLGenerationClient.main(String[]) line: 445    
  • in
    public <X> EntityType<X> entity(Class<X> clazz) {
        Object aType = this.entities.get(clazz);
        if(aType instanceof EntityType) {
            return (EntityType<X>) aType;
        } else {
-->         throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
  • We see the following exception - which is not very descriptive of the actual issue of missing metadata because of missing entity classes found during EntityManager predeploy
[EL Finest]: 2011-03-03
12:14:36.141--ServerSession(11582167)--Thread(Thread[main,5,main])--End deploying Persistence Unit dao.create.tables.derby.client; session file:/C:/view_w36a/examples/org.eclipse.persistence.example.jpa.server.common.ddlgen/bin/_dao.create.tables.derby.client;state Deployed; factoryCount 1
Exception in thread "main" java.lang.IllegalArgumentException: 
The type [null] is not the expected [EntityType] for the key class [class org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork].
    at
org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl.entity(MetamodelImpl.java:160)
    at
org.eclipse.persistence.example.jpa.server.common.DDLGenerationClient.checkMetamodel(DDLGenerationClient.java:435)
    at
org.eclipse.persistence.example.jpa.server.common.DDLGenerationClient.doQuery(DDLGenerationClient.java:459)
    at
org.eclipse.persistence.example.jpa.server.common.DDLGenerationClient.main(DDLGenerationClient.java:444)
  • using
    <persistence-unit name="dao.create.tables.derby.client"
transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <!-- exclude-unlisted-classes>false</exclude-unlisted-classes-->
       <!-- class>org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork</class-->
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
            <property name="eclipselink.target-database" value="Derby"/>        
            <property name="javax.persistence.jdbc.url" value="jdbc:derby://127.0.0.1:1527/dataparallel;create=true"/>
            <property name="javax.persistence.jdbc.user" value="APP"/>
            <property name="javax.persistence.jdbc.password" value="APP"/>
            <property name="eclipselink.logging.level" value="ALL"/>            
            <!-- property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
            <property name="eclipselink.ddl-generation.output-mode" value="both"/-->
        </properties>
    </persistence-unit>
  • workaround is
<exclude-unlisted-classes>false</exclude-unlisted-classes>
  • or
<class>org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork</class>
  • Result is OK with either fix
unitOfWork_ EntityType: EntityTypeImpl@672904:UnitOfWork [ javaType: class org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork descriptor: RelationalDescriptor(org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork--> [DatabaseTable(EL_UNITOFWORK)]), mappings: 7]
  • Solution:
    • There are two ways into this problem.
    • 1) We specify no entities at all and also do not specify
      • <exclude-unlisted-classes>false</exclude-unlisted-classes>
      • - the result of this is a completely empty metamodel
    • 2) We specify only part of the entities via <class> properties
      • - the result of this is selective IllegalArgumentException errors thrown when we attempt to use the missing EntityType classes - likely in a Criteria API query
  • We need to warn both for no (0) entities found and later that a specific type was not found - possibly as a result of a missing class reference
    • 1) warn the user that no entities were found during the predeploy so the deploy iterates nothing during metamodel.initialize - actually nothing is done during initialize
      • MetamodelImpl@21350241 [ 0 Types: , 0 ManagedTypes: , 0 EntityTypes: , 0 MappedSuperclassTypes: , 0 EmbeddableTypes: ]
      • or in
        • protected void initializeCanonicalMetamodel(Metamodel model)
DDLGenerationClient [Java Application]    
    org.eclipse.persistence.example.jpa.server.common.DDLGenerationClient at
localhost:3057    
        Thread [main] (Suspended)    
            MetamodelImpl.initialize() line: 363    
            MetamodelImpl.<init>(DatabaseSession) line: 109    
            MetamodelImpl.<init>(EntityManagerSetupImpl) line: 128    
            EntityManagerSetupImpl.getMetamodel() line: 2209    
            EntityManagerSetupImpl.deploy(ClassLoader, Map) line: 413    
            EntityManagerFactoryImpl.getServerSession() line: 185    
            EntityManagerFactoryImpl.getMetamodel() line: 531    
            DDLGenerationClient.initialize(String) line: 137    
            DDLGenerationClient.doQuery() line: 453    
            DDLGenerationClient.main(String[]) line: 444    
  • Descriptors map is empty - we need to warn the user about this
this    MetamodelImpl  (id=37)    
    session    ServerSession  (id=40)    
        descriptors    HashMap<K,V>  (id=61)    
            size    0    
  • 2) Change the IAE for a single missing entity to suggest that the entity class may not have been found or specified (it may be on purpose)
    • Security note: A search for a non-existent entity class may come through a user-generated query and may be a way of testing the system for hidden classes
      • - we may want to not print the name
    • The error message needs to change from
The type [null] is not the expected [EntityType] for the key class [class org.eclipse.persistence.example.jpa.server.business.Cell].
  • to something like
No [EntityType] was found for the key class [class org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork] in the Metamodel 
- please verify that the Entity class was referenced in persistence.xml using a specific
<class>org.eclipse.persistence.example.distributed.collatz.model.UnitOfWork</class>
property or a global <exclude-unlisted-classes>false</exclude-unlisted-classes> element.

DI 102: 20100421: Fully initialize MappedSuperclass Descriptors and Refactor existing workarounds

  • Because of bug# 309856 where we require a fully initialized MappedSuperclass Descriptor when merging orm.xml metadata (object model only) - we may need a 2 stage refactor.
  • Guy basically implemented equals() in all metadata subclasses
    • Note: Effective Java Item #8 does not apply yet - we don't have to override hashCode() as well because the metadata classes are not in hash based collections HashMap, HashSet and we are not serializing the classes yet.
  • This will allow us to remove a lot of the handling and usage of the specialized map on the core API Project class.
package org.eclipse.persistence.sessions;
public class Project implements Serializable, Cloneable {
    protected Map<Object, RelationalDescriptor> mappedSuperclassDescriptors;

DI 103: 20100601: 315287: Handle BasicType as inheritance root for ManagedTypes

DI 104: 20100614: 314906: PluralAttribute.getJavaType returns elementType instead of Collection Type

  • Fixed in SVN rev# 7618
  • Essentially the issue is what "we" mean by element type and what the "specification" describes as element type differs for the functions getJavaType() and getBindableType().
  • Q1: in the case of BindableType' in Bindable - is SINGULAR_ATTRIBUTE the same as ENTITY for non-Basic attributes (IE: a OneToOne to an Entity)?
Analysis 104:
  • This may be a bug, the return class is of type C which is not the element type of the collection V
  • in
    • public abstract class PluralAttributeImpl<X, C, V> extends AttributeImpl<X, C> implements PluralAttribute<X, C, V> {
we should be using 
 * @param <C> The type of the represented collection
not
 * @param <V> The element type of the represented collection
  • The specification does not go into details of the implementation beyond the javadoc for the function above
    • = "represented attribute"
  • However, check the javadoc for getBindableJavaType()
    • here we return the elementType
    • we will need to discuss what the spec means by elementType and our own internal view of what the elementType instance variable is - and how they differ
     * If the bindable type of the object is <code>PLURAL_ATTRIBUTE</code>,
     * the Java element type is returned. If the bindable type is
    public Class<V> getBindableJavaType() {
  • Regression testing will be affected by...

http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_83:_20090914:_MapAttributeImpl.elementType_incorrectly_set_when_.40ObjectTypeConverter_is_present

Decision 104:
Specification Discussion 104:
  • I have a spec question about the function in the JPA Metamodel API for PluralAttribute.getJavaType(). The question is currently directed at the JPA 2.0 specification leads so we can have a majority decision on 1) or 2). The modified function would return the middle declaration class of C for each subclass of PluralAttribute.
  • From the discussion below - I recommend that we do 2) - and modify the code to return the collection type - and fill the hole in the Impl of the API
  • Q) For PluralAttributeImpl<X, C, V> should we...
    • 1) continue to return Class<V> = element type of the represented collection - 1 of 4 of us
    • or
    • 2) (NEW) switch to Class<C> = type of the represented collection - 2 of 4 of us (as of end of day 20100614 - 3 of 4 of us)
    JPA:
    /**
     *  Return the Java type of the represented attribute.
     *  @return Java type
     */
     PluralAttributeImpl.getJavaType() --> Class
  • To aide in interpreting the specification we have 3 points.
    • 1) To me "represented attribute" for plural attributes means the whole collection - not the elements in the collection - which would never be Metamodel Attributes.
    • 2) Missing a get*Type() that returns the collection type
      • We have 3 functions that return the elementType (including a Type and Class version) - but we only have a Type enum version for collection Type
    • 3) Already have an existing get*Type() that returns the elementType
      • We can refer to the spec function getElementType() - which at this point returns the same thing (wrapped by a Type) as getJavaType(). The function getBindableJavaType() also currently returns the same thing as getJavaType()
    /**
     * Return the type representing the element type of the
     * collection.
     * @return element type
     */
    Type<E> getElementType();
  • Therefore, I suggest that we move the API from providing the same elementType return for all 3 getJavaType(), getBindableJavaType() and getElementType() and match this pattern by changing getJavaType() to complement the existing getCollectionType() which returns the collection type enum but not the Class itself.
    • So we will have the following structure if we modify the code
      • Collection Element Type <V>
        • Type
          • getElementType()
        • Class
          • getBindableJavaType()
      • Collection Type <C>
        • enum (as there are only 4 collection types)
          • getCollectionType()
        • Class
          • getJavaType()
  • Currently, the code is the same as SingularAttribute where the elementType class is returned.
  • The issue is that the spec is not fully specific on what to return if we interpret elementType the same as in SingularAttribute - see getBindableType for example
 // If the bindable type of the object is <code>PLURAL_ATTRIBUTE</code>,
 // the Java element type is returned. If the bindable type is
public Class<V> getBindableJavaType() {

DI 105: 20100616: 316991: Attribute.getJavaMember() returns null for all Embeddable Attributes

Analysis 105:
  • Also verify
    • Does this occur for all attributes including those from Entities and MappedSuperclasses (also check Basic)
    • The fix is for Method level access, verify Field level access for Embeddables - however we should be OK as reflective code already existed for Field level in getJavaMember()
Fixed 105:

DI 106: 20100810: 322585:Metamodel initialization via EMF.getMetamodel() before EM deploy results in an invalid Metamodel because of UNINITIALIZED descriptor.initializationStage

  • CRITICAL: The metamodel via EMF is not fully initialized until at least 1 em deploy - this affects both an emf.getMetamodel() and an emf.getCriteriaBuilder() - which internally calls emf.getMetamodel()
  • Metamodel initialization via EMF.getMetamodel() before EM deploy will result in an invalid Metamodel because of UNINITIALIZED descriptor.initializationStage. The consequences may be any of the following issues.
Reproduction
  • In SE the following sequence of operation will result in either an invalid metamodel and/or a NullPointerException accessing a null javaType on a ManagedType depending on the type of model. In all cases the underlying issue is that the metamodel does not have the javaClass for entities that are not finished processing - (their javaClassName is set but the javaClass is null on the ClassDescriptor). This will occur before a session login or entityManager deploy is done on at least on EM.
  • Invalid operation
emf  = Persistence.createEntityManagerFactory(puName);
// get the Metamodel before it is ready
metamodel = emf.getMetamodel(); // no em.deploy() or session.login() has occurred yet
// or
criteriaBuilder = emf.getCriteriaBuilder();
entityManager = emf.createEntityManager();
  • Valid operation
emf  = Persistence.createEntityManagerFactory(puName);
entityManager = emf.createEntityManager();
// get the Metamodel after it is ready
metamodel = emf.getMetamodel();            
// or
criteriaBuilder = emf.getCriteriaBuilder();
  • In order to support emf.getMetamodel() in this case we need to initialize before deploy.
  • History: in DI 92 (svn 5541 for 266912 in oct 2009) and later in bugs 297748(try/catch) and 297555 we moved from the EntityManagerFactoryImpl to the EntityMangerSetupImpl.
    • NPE
    • descriptor.javaClass == null
  • The following bugs are related to or dependent on this fix
  • The following Metamodel design issues are related to or deprecated by this fix
References:
    • 297928
      • 282518
    • 303063
      • 297748
Analysis 106:
  • What we need from the deploy() (after a predeploy()) is the following
    • 1) The javaClass set on all Entity and Embeddable descriptors - via a session.getProject().convertClassNamesToClasses(realClassLoader); call in EntityManagerSetupImpl.deploy()
    • 2) The CMP3Policy set on all descriptors (with PK fields set) - via later EntityManagerSetupImpl.initServerSession()
    • 3) Optionally customizers (these are processed later in deploy() below.
  • We do not require
    • Everything after state = STATE_DEPLOYED in EntityManagerSetupImpl.deploy()
    • DDL generation
    • Actual login to the database
      • Thread [main] (Suspended)
      • ConnectionPool.startUp() line: 441
      • ServerSession.connect() line: 495
      • ServerSession(DatabaseSessionImpl).login() line: 627
      • EntityManagerFactoryProvider.login(ServerSession, Map) line: 230
      • EntityManagerSetupImpl.deploy(ClassLoader, Map) line: 381
      • EntityManagerFactoryImpl.getServerSession() line: 157
      • EntityManagerFactoryImpl.createEntityManagerImpl(Map) line: 214
      • EntityManagerFactoryImpl.createEntityManager() line: 202


  • Todo:
    • EntityManagerSetupImpl
      • - issue with getMetamodel init variable assignment - if canonical init fails - the variable is still set
      • -I remove sessionsxml comment from non-sessions block in deploy
    • EntityManagerImpl
      • - comment should mention setup not factory
    • EntityManagerSetupImpl
  • the following property is named as though the persistence unit is "only" used for validation
    • In fact it validates the persistence unit by doing a deploy just after the predeploy - without waiting for client code to login
    • after that it behaves normally.
           <property name="eclipselink.validation-only" value="True"/>
Alt 106-1: Fail-fast with ISE: emf.getMetamodel() fails with non-spec ISE if in State_predeployed (entity descriptors will be in state 0 not 3)
Alt 106-2: Add State warning and continue to do nothing
  • No fail fast
  • Add warning to use EM not EMF when descriptor in state 0 (not initialized)
Alt 106-3: Force full real deploy
  • See patch for bug# 322585
  • In this scenario we force a login() to the serverSession during EntityManagerFactory creation.
  • We will also introduce a new persistence.xml property like LOGIN_AT_EMF where the default would be true - but clients running on EE servers for example could disable this and return to pre 2.2 behavior.
Issues 106:3
  • What will we do with the connections created
  • Performance degredation previously in the first EntityManager creation is now shifted earlier to EntityManagerFactory creation
  • Names for the persistence.xml property LOGIN_AT_EMF_PROPERTY that essentially turns of validation by default.
    • eclipselink.login-at-emf
    • eclipselink.force-deploy-at-predeploy
    • eclipselink.force-login-at-predeploy
    • eclipselink.fully-process-emf-before-login
    • eclipselink.enable-login-at-emf-predeploy
    • eclipselink.enable-full-login-at-emf
    • eclipselink.enable-deploy-during-predeploy
    • eclipselink.enable-deploy-during-emf-predeploy
    • eclipselink.enable-login-during-emf-predeploy
    • eclipselink.enable-validation-by-default
    • eclipselink.enable-default-login-at-predeploy - recommended
  • Issue: this new property will essentially deprecate the validation flag unless we make validation=false also disable the new forced login flag
Results 106
  • The following code including changes is run just after emf.predeploy()
    • emf.verifyOpen();
    • emf.getServerSession(); // calls ems.deploy()
  • The result is that the entity descriptors have their javaClass set but their state is still set as lt POST_INITIALIZED
  • There actually already is a mechanism to login or do a getServerSession() call this is in for validation.
PersistenceProvider:166 (SE) and 234 (EE)
            // 322586: This code has been added to allow validation to occur without actually calling createEntityManager
            if (emSetupImpl.shouldGetSessionOnCreateFactory(nonNullProperties)) {
                factory.getServerSession();
            }
  • or
PersistenceProvider:166 (SE) and 234 (EE)
            // 322586: This code has been added to allow validation to occur without actually calling createEntityManager
            if (emSetupImpl.shouldGetSessionOnCreateFactory(nonNullProperties)) {
                //factory.getServerSession();
                  factory.getEntityManagerSetupImpl().getSession().initializeDescriptors();
            }
EntityManagerFactoryImpl:685
    public EntityManagerSetupImpl getEntityManagerSetupImpl() { // 322586 NEW
        return setupImpl;
    }
EntityManagerSetupImpl.deploy():376
                        if (isValidationOnly(deployProperties, false)) {
                            // 324213: Add loginAndDetectDatasource() call before calling initializeDescriptors when validation-only is True
                            // we avoid a native sequence exception on a generic DatabasePlatform by auto-detecting the correct DB platform
                            session.loginAndDetectDatasource(); // NEW
                            session.initializeDescriptors();
  • We add the following change
PersistenceUnitProperties
    /** Validate deployment, but do not connect.
     * Note: this <code>eclipselink.validation-only</code> property will perform login early
     * during the initial predeploy of the persistence unit.  After the persistence unit can be used normally.<p>
     * The default is "False",  set the value to "True" to enable validation.<p> 
     * Note: setting this property to False will not disable the "LOGIN_AT_EMF_PROPERTY" - the two are independent.
     */
    public static final String VALIDATION_ONLY_PROPERTY = "eclipselink.validation-only";
 
    // See bug# 322585
    /** This property will in effect turn the above "validation-only" flag to "True"<p>
     * Note: this <code>eclipselink.enable-default-login-at-predeploy</code> property will perform login early
     * during the initial predeploy of the persistence unit.  After the persistence unit can be used normally.<p>
     * The default is "True",  set the value to "False" to disable logging in the persistence unit at predeploy.<p>
     * Note: setting this property to False will not disable the "VALIDATION_ONLY_PROPERTY" - the two are independent.
     */    
    public static final String LOGIN_AT_EMF_PROPERTY = "eclipselink.enable-default-login-at-predeploy";
 
EntityManagerSetupImpl
    // 322585: added isLoginEnabledAtEMFCreate() check that defaults to true
    public boolean shouldGetSessionOnCreateFactory(Map m) {
        m = mergeWithExistingMap(m);
        return isValidationOnly(m, false) || isLoginEnabledAtEMFCreate();
    }
 
    protected boolean isLoginEnabledAtEMFCreate(Map m, boolean shouldMergeMap) {
        if (shouldMergeMap) {
            m = mergeWithExistingMap(m);
        }
        String loginAtEMFString = getConfigPropertyAsStringLogDebug(PersistenceUnitProperties.LOGIN_AT_EMF_PROPERTY, m, session);
        if (loginAtEMFString != null) {
            return Boolean.parseBoolean(loginAtEMFString);
        } else {
            // default to true - also do a partial deploy at emf predeploy
            return true;
        }
    }
Analysis 106-3
  • After trying 2 locations of a getServerSession() call in order to do a login before an official deploy() on createEntityManager - I came across code that already exists for JPA validation that will do a login for us and effect a deploy just after the predeploy finishes on the EMF.
Performance Results 106-3
  • See enhancement request 324974 for general early EMF predeploy login
  • The following show that that the time taken to update the session, do DDL generation and intialize the metamodel and canonical metamodel has shifted to the EMF creation from the EM creation. In essence an emf.createEntityManager() now takes just a couple microseconds instead of seconds.
  • Doing all the following in the emf.predeploy
    • updateServerSession
    • session.login
    • generateDDL
    • metamodel initalization
    • canonical metamodel initialization
  • takes in SE
    • with change
      • predeploy() = 2.6 sec
        • [EL Finest]: 2010-08-17 18:01:12.002--ServerSession(27196165)--Thread(Thread[main,5,main])--Begin predeploying Persistence Unit dao.create.tables.derby; session default-session; state Initial; factoryCount 0
        • [EL Finest]: 2010-08-17 18:01:14.67--ServerSession(27196165)--Thread(Thread[main,5,main])--End deploying Persistence Unit dao.create.tables.derby; session default-session; state Deployed; factoryCount 1
      • deploy() = 0.09 sec
        • [EL Finest]: 2010-08-17 18:01:14.67--ServerSession(27196165)--Thread(Thread[main,5,main])--End deploying Persistence Unit dao.create.tables.derby; session default-session; state Deployed; factoryCount 1
        • [EL Finer]: 2010-08-17 18:01:14.763--ServerSession(27196165)--Thread(Thread[main,5,main])--client acquired
    • without change
      • predeploy() = 0.75 sec
        • [EL Finest]: 2010-08-17 17:58:29.169--ServerSession(2279771)--Thread(Thread[main,5,main])--Begin predeploying Persistence Unit dao.create.tables.derby; session default-session; state Initial; factoryCount 0
        • [EL Finest]: 2010-08-17 17:58:29.934--ServerSession(2279771)--Thread(Thread[main,5,main])--End predeploying Persistence Unit dao.create.tables.derby; session default-session; state Predeployed; factoryCount 1
      • deploy() = 1.87 sec
        • [EL Finest]: 2010-08-17 17:58:29.934--ServerSession(2279771)--Thread(Thread[main,5,main])--Begin deploying Persistence Unit dao.create.tables.derby; session default-session; state Predeployed; factoryCount 1
        • [EL Finer]: 2010-08-17 17:58:31.806--ServerSession(2279771)--Thread(Thread[main,5,main])--client acquired
  • On emf.createEntityManager() we just skip deploy/login in getServerSession(), login in createEntityManagerImpl as well as deploy/login in getServerSession() in the EntityManagerImpl constructor
  • because the emf.createEntityManager() does virtually nothing after this change
    • EE Server testing on WebLogic 10.3.3.0 via standard @PersitenceContext injection on a SSB @EJB injected on a servlet client is OK
    • Notice that a predeploy also does a deploy now - previously the deploy was only done when the client persisted or read from the persistence unit (via injection)
Daemon Thread [[ACTIVE] ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended (breakpoint at line 314 in EntityManagerSetupImpl))	
	EntityManagerSetupImpl.deploy(ClassLoader, Map) line: 314	
	EntityManagerFactoryImpl.getServerSession() line: 157	
	PersistenceProvider.createContainerEntityManagerFactory(PersistenceUnitInfo, Map) line: 243	
	PersistenceUnitInfoImpl.createEntityManagerFactory(boolean) line: 352	
	PersistenceUnitInfoImpl.createEntityManagerFactory() line: 332	
	PersistenceUnitInfoImpl.<init>(PersistenceUnitBean, PersistenceUnitConfigurationBean, GenericClassLoader, String, URL, URL) line: 134	
	ModulePersistenceUnitRegistry(AbstractPersistenceUnitRegistry).storeDescriptors(Map, Map) line: 336	
	ModulePersistenceUnitRegistry(AbstractPersistenceUnitRegistry).loadPersistenceDescriptor(VirtualJarFile, boolean, File) line: 250	
	ModulePersistenceUnitRegistry.<init>(GenericClassLoader, ApplicationContextInternal, Module, boolean) line: 69	
	EJBModule.setupPersistenceUnitRegistry() line: 221	
	EJBModule$1.execute() line: 322	
	PersistenceUnitRegistryInitializer.setupPersistenceUnitRegistries() line: 62	
	WebAppModule.prepare() line: 398	
	ScopedModuleDriver.prepare() line: 176	
	ModuleListenerInvoker.prepare() line: 199	
	DeploymentCallbackFlow$1.next(Object) line: 507	
	StateMachineDriver.nextState(StateChange, Object[]) line: 41	
	DeploymentCallbackFlow.prepare(Module[]) line: 149	
	DeploymentCallbackFlow.prepare() line: 45	
	BaseDeployment$1.next(Object) line: 1221	
	StateMachineDriver.nextState(StateChange, Object[]) line: 41	
	EarDeployment(BaseDeployment).prepare(DeploymentContext) line: 367	
	EarDeployment.prepare(DeploymentContext) line: 58	
	DeploymentStateChecker.prepare(DeploymentContext) line: 154	
	AppContainerInvoker.prepare(DeploymentContext) line: 60	
	RedeployOperation.createAndPrepareContainer() line: 98	
	RedeployOperation.doPrepare() line: 122	
	RedeployOperation(AbstractOperation).prepare() line: 217	
	DeploymentManager.handleDeploymentPrepare(Deployment, DeploymentManager$DeploymentRequestInfo) line: 747	
	DeploymentManager.prepareDeploymentList(ArrayList, DeploymentContext) line: 1216	
	DeploymentManager.handlePrepare(DeploymentContext) line: 250	
	DeploymentServiceDispatcher.prepare(DeploymentContext) line: 159	
	DeploymentReceiverCallbackDeliverer.doPrepareCallback(DeploymentContext) line: 171	
	DeploymentReceiverCallbackDeliverer.access$000(DeploymentReceiverCallbackDeliverer, DeploymentContext) line: 13	
	DeploymentReceiverCallbackDeliverer$1.run() line: 46	
	SelfTuningWorkManagerImpl$WorkAdapterImpl.run() line: 528	
	ExecuteThread.execute(Runnable) line: 201	
	ExecuteThread.run() line: 173	
Alt 106-4: Force full internal discardable deploy only for metamodel
  • Is this different than 5 below?
Alt 106-5: Partial deploy based on what metamodel needs (entity deployment for embeddables and MappedSuperclasses
  • Something like at least
ClassLoader realLoader = setupImpl.getPersistenceUnitInfo().getClassLoader();
session.getProject().convertClassNamesToClasses(realClassLoader);
Alt 106-6: Full deploy during predeploy only for SE platform
  • This option was suggested by Gordon 20100831 and is a valid alternative. Here we login on predeploy (new behavior) only on SE (Standard Edition platforms) and continue to have a separate predeploy and deploy for EE (Enterprise Edition platforms).
Alt 106-7: Do a login only for early emf.getMetamodel() users
  • This alternative moves the emf.getServerSession() lazy initialization call that does a deploy/login and our required initializeDescriptors() call down from emf.predeploy() to emf.getMetamodel() which is also called from emf.getCriteriaBuilder().
  • This changes behavior only for early calls to emf.getMetamodel before at least 1 login has occurred on an EntityManager.
Issues 106:
Issue 106-1: Does session.login() occur in predeploy() when validation=True on EE preDeploy()
  • Here we need to verify that we currently do not login in on emf.predeploy() and we also do not introduce a database login() with the change to add validation by default. False - we do not login during validation
Issue 106-2: EE preDeploy() and deploy() are currently separate
  • The xisting behavior for WebLogic 10 is a the following sequence of operations...
    • On EAR deployment - predeploy()
    • On user EM login (via a SSB) deploy() and associated login
  • Here is the debug point for the predeploy on EAR deployment
Daemon Thread [[STANDBY] ExecuteThread: '4' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended (breakpoint at line 837 in EntityManagerSetupImpl))	
	EntityManagerSetupImpl.predeploy(PersistenceUnitInfo, Map) line: 837	
	PersistenceProvider.createContainerEntityManagerFactory(PersistenceUnitInfo, Map) line: 205	
	PersistenceUnitInfoImpl.createEntityManagerFactory(boolean) line: 352	
	PersistenceUnitInfoImpl.createEntityManagerFactory() line: 332	
	PersistenceUnitInfoImpl.<init>(PersistenceUnitBean, PersistenceUnitConfigurationBean, GenericClassLoader, String, URL, URL) line: 134	
	ModulePersistenceUnitRegistry(AbstractPersistenceUnitRegistry).storeDescriptors(Map, Map) line: 336	
	ModulePersistenceUnitRegistry(AbstractPersistenceUnitRegistry).loadPersistenceDescriptor(VirtualJarFile, boolean, File) line: 250	
	ModulePersistenceUnitRegistry.<init>(GenericClassLoader, ApplicationContextInternal, Module, boolean) line: 69	
	EJBModule.setupPersistenceUnitRegistry() line: 221	
	EJBModule$1.execute() line: 322	
	PersistenceUnitRegistryInitializer.setupPersistenceUnitRegistries() line: 62	
	WebAppModule.prepare() line: 398	
	ScopedModuleDriver.prepare() line: 176	
	ModuleListenerInvoker.prepare() line: 199	
	DeploymentCallbackFlow$1.next(Object) line: 507	
	StateMachineDriver.nextState(StateChange, Object[]) line: 41	
	DeploymentCallbackFlow.prepare(Module[]) line: 149	
	DeploymentCallbackFlow.prepare() line: 45	
	BaseDeployment$1.next(Object) line: 1221	
	StateMachineDriver.nextState(StateChange, Object[]) line: 41	
	EarDeployment(BaseDeployment).prepare(DeploymentContext) line: 367	
	EarDeployment.prepare(DeploymentContext) line: 58	
	DeploymentStateChecker.prepare(DeploymentContext) line: 154	
	AppContainerInvoker.prepare(DeploymentContext) line: 60	
	ActivateOperation.createAndPrepareContainer() line: 207	
	ActivateOperation.doPrepare() line: 98	
	ActivateOperation(AbstractOperation).prepare() line: 217	
	DeploymentManager.handleDeploymentPrepare(Deployment, DeploymentManager$DeploymentRequestInfo) line: 747	
	DeploymentManager.prepareDeploymentList(ArrayList, DeploymentContext) line: 1216	
	DeploymentManager.handlePrepare(DeploymentContext) line: 250	
	DeploymentServiceDispatcher.prepare(DeploymentContext) line: 159	
	DeploymentReceiverCallbackDeliverer.doPrepareCallback(DeploymentContext) line: 171	
	DeploymentReceiverCallbackDeliverer.access$000(DeploymentReceiverCallbackDeliverer, DeploymentContext) line: 13	
	DeploymentReceiverCallbackDeliverer$1.run() line: 46	
	SelfTuningWorkManagerImpl$WorkAdapterImpl.run() line: 528	
	ExecuteThread.execute(Runnable) line: 201	
	ExecuteThread.run() line: 173	
Daemon Thread [[ACTIVE] ExecuteThread: '2' for queue: 'weblogic.kernel.Default (self-tuning)'] (Suspended (breakpoint at line 312 in EntityManagerSetupImpl))	
	EntityManagerSetupImpl.deploy(ClassLoader, Map) line: 312	
	EntityManagerFactoryImpl.getServerSession() line: 157	
	EntityManagerFactoryImpl.createEntityManagerImpl(Map) line: 214	
	EntityManagerFactoryImpl.createEntityManager() line: 202	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25	
	Method.invoke(Object, Object...) line: 597	
	EntityManagerFactoryProxyImpl.invoke(Object, Method, Object[]) line: 87	
	$Proxy94.createEntityManager() line: not available	
	TransactionalEntityManagerProxyImpl.newPersistenceContext(PersistenceUnitInfoImpl) line: 75	
	TransactionalEntityManagerProxyImpl(BasePersistenceContextProxyImpl).getPersistenceContext(Transaction) line: 160	
	TransactionalEntityManagerProxyImpl(BasePersistenceContextProxyImpl).invoke(Object, Method, Object[], Transaction) line: 88	
	TransactionalEntityManagerProxyImpl.invoke(Object, Method, Object[], Transaction) line: 91	
	TransactionalEntityManagerProxyImpl(BasePersistenceContextProxyImpl).invoke(Object, Method, Object[]) line: 80	
	TransactionalEntityManagerProxyImpl.invoke(Object, Method, Object[]) line: 26	
	$Proxy95.toString() line: not available	
	String.valueOf(Object) line: 2826	
	StringBuilder.append(Object) line: 115	
	ApplicationService_5ptwty_Impl(ApplicationService).insertObjects(List<Cell>) line: 154	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25	
	Method.invoke(Object, Object...) line: 597	
	AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 310	
	ReflectiveMethodInvocation.invokeJoinpoint() line: 182	
	ReflectiveMethodInvocation.proceed() line: 149	
	DelegatingIntroductionInterceptor.doProceed(MethodInvocation) line: 131	
	DelegatingIntroductionInterceptor.invoke(MethodInvocation) line: 119	
	ReflectiveMethodInvocation.proceed() line: 171	
	MethodInvocationVisitorImpl.visit() line: 37	
	EnvironmentInterceptorCallbackImpl.callback(MethodInvocationVisitor) line: 54	
	EnvironmentInterceptor.invoke(MethodInvocation) line: 50	
	ReflectiveMethodInvocation.proceed() line: 171	
	ExposeInvocationInterceptor.invoke(MethodInvocation) line: 89	
	ReflectiveMethodInvocation.proceed() line: 171	
	DelegatingIntroductionInterceptor.doProceed(MethodInvocation) line: 131	
	DelegatingIntroductionInterceptor.invoke(MethodInvocation) line: 119	
	ReflectiveMethodInvocation.proceed() line: 171	
	JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204	
	$Proxy97.insertObjects(List) line: not available	
	ApplicationService_5ptwty_ApplicationServiceLocalImpl.insertObjects(List<Cell>) line: 308	
	FrontController.generateGlider(PrintWriter) line: 237	
	FrontController.processGliderCommand(HttpServletRequest, HttpServletResponse, PrintWriter) line: 291	
	FrontController.processDemoCommand(HttpServletRequest, HttpServletResponse, PrintWriter) line: 360	
	FrontController.processAction(HttpServletRequest, HttpServletResponse) line: 389	
	FrontController.doGet(HttpServletRequest, HttpServletResponse) line: 460	
	FrontController(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 707	
	FrontController(HttpServlet).service(ServletRequest, ServletResponse) line: 820	
	StubSecurityHelper$ServletServiceAction.run() line: 227	
	StubSecurityHelper.invokeServlet(ServletRequest, HttpServletRequest, ServletRequestImpl, ServletResponse, HttpServletResponse, Servlet) line: 125	
	ServletStubImpl.execute(ServletRequest, ServletResponse, FilterChainImpl) line: 300	
	ServletStubImpl.execute(ServletRequest, ServletResponse) line: 183	
	WebAppServletContext$ServletInvocationAction.doIt(ServletStubImpl, HttpServletRequest, HttpServletResponse) line: 3686	
	WebAppServletContext$ServletInvocationAction.run() line: 3650	
	AuthenticatedSubject.doAs(AbstractSubject, PrivilegedAction) line: 321	
	SecurityManager.runAs(AuthenticatedSubject, AuthenticatedSubject, PrivilegedAction) line: 121	
	WebAppServletContext.securedExecute(HttpServletRequest, HttpServletResponse, boolean) line: 2268	
	WebAppServletContext.execute(ServletRequestImpl, ServletResponseImpl) line: 2174	
	ServletRequestImpl.run() line: 1446	
	ExecuteThread.execute(Runnable) line: 201	
	ExecuteThread.run() line: 173	
Regression Testing 106
  • We have issues with native sequencing as discovered by Doug. When I run the Core and JPA LRG with validation-only set to "True" for all persistence units - I get 2 failures and 12 errors as follows.
    • <testsuite errors="12" failures="2" hostname="xps435" name="org.eclipse.persistence.testing.tests.jpa.AllCMP3TestRunModel" tests="2139" time="1284.741" timestamp="2010-08-30T17:16:51">
  <testcase classname="org.eclipse.persistence.testing.tests.jpa.advanced.JPAAdvancedTestModel" name="JPAAdvancedTestModel" time="0.0">
Exception Description: SCIENTIST_SEQ: platform DatabasePlatform does not support NativeSequence.
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:411)
	at org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl.getServerSession(EntityManagerFactoryImpl.java:156)
	at org.eclipse.persistence.jpa.PersistenceProvider.createEntityManagerFactory(PersistenceProvider.java:170)
	at org.eclipse.persistence.jpa.PersistenceProvider.createEntityManagerFactory(PersistenceProvider.java:67)
	at javax.persistence.Persistence.createEntityManagerFactory(Unknown Source)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getEntityManagerFactory(JUnitTestCase.java:402)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getEntityManagerFactory(JUnitTestCase.java:385)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.getEntityManagerFactory(JUnitTestCase.java:412)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.createEntityManager(JUnitTestCase.java:328)
	at org.eclipse.persistence.testing.tests.jpa.CMP3TestModel.createEntityManager(CMP3TestModel.java:95)
	at org.eclipse.persistence.testing.tests.jpa.CMP3TestModel.getEntityManager(CMP3TestModel.java:80)
	at org.eclipse.persistence.testing.tests.jpa.CMP3TestModel.getServerSession(CMP3TestModel.java:100)
	at org.eclipse.persistence.testing.tests.jpa.advanced.JPAAdvancedTestModel.setup(JPAAdvancedTestModel.java:32)
Caused by: Exception [EclipseLink-7144] (Eclipse Persistence Services - 2.2.0.qualifier): org.eclipse.persistence.exceptions.ValidationException
Exception Description: SCIENTIST_SEQ: platform DatabasePlatform does not support NativeSequence.
	at org.eclipse.persistence.exceptions.ValidationException.platformDoesNotSupportSequence(ValidationException.java:1959)
	at org.eclipse.persistence.sequencing.NativeSequence.onConnect(NativeSequence.java:143)
	at org.eclipse.persistence.sequencing.Sequence.onConnect(Sequence.java:270)
	at org.eclipse.persistence.internal.sequencing.SequencingManager.onConnectAllSequences(SequencingManager.java:843)
	at org.eclipse.persistence.internal.sequencing.SequencingManager.onConnect(SequencingManager.java:706)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeSequencing(DatabaseSessionImpl.java:148)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:420)
	at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.initializeDescriptors(DatabaseSessionImpl.java:406)
	at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:376)
  </testcase>
  <testcase classname="org.eclipse.persistence.testing.tests.jpa.advanced.EntityManagerJUnitTestSuite" name="testEMFWrapValidationException" time="4.368">
    <error type="java.lang.NullPointerException">java.lang.NullPointerException
	at org.eclipse.persistence.testing.tests.jpa.advanced.EntityManagerJUnitTestSuite.testEMFWrapValidationException(EntityManagerJUnitTestSuite.java:4314)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.runBare(JUnitTestCase.java:466)
	at org.eclipse.persistence.testing.framework.TestExecutor.execute(TestExecutor.java:248)
	at org.eclipse.persistence.testing.framework.TestModel.execute(TestModel.java:208)
	at org.eclipse.persistence.testing.framework.TestCollection.run(TestCollection.java:313)
</error>
  </testcase>
  <testcase classname="org.eclipse.persistence.testing.tests.jpa.xml.merge.inherited.EntityMappingsMergeInheritedJUnitTestCase" name="testVerifyOneToManyRelationships" time="0.0">
    <error type="java.lang.NullPointerException">java.lang.NullPointerException
	at org.eclipse.persistence.testing.tests.jpa.xml.merge.inherited.EntityMappingsMergeInheritedJUnitTestCase.testVerifyOneToManyRelationships(EntityMappingsMergeInheritedJUnitTestCase.java:240)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.runBare(JUnitTestCase.java:466)
	at org.eclipse.persistence.testing.framework.TestExecutor.execute(TestExecutor.java:248)
	at org.eclipse.persistence.testing.framework.TestModel.execute(TestModel.java:208)
	at org.eclipse.persistence.testing.framework.TestCollection.run(TestCollection.java:313)
</error>
  </testcase>
  <testcase classname="org.eclipse.persistence.testing.tests.jpa.delimited.DelimitedPUTestSuite" name="testReadEmployee" time="0.0">
    <error message="getSingleResult() did not retrieve any entities." type="javax.persistence.NoResultException">javax.persistence.NoResultException: getSingleResult() did not retrieve any entities.
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.throwNoResultException(EJBQueryImpl.java:1246)
	at org.eclipse.persistence.internal.jpa.EJBQueryImpl.getSingleResult(EJBQueryImpl.java:750)
	at org.eclipse.persistence.testing.tests.jpa.delimited.DelimitedPUTestSuite.testReadEmployee(DelimitedPUTestSuite.java:105)
	at org.eclipse.persistence.testing.framework.junit.JUnitTestCase.runBare(JUnitTestCase.java:466)
	at org.eclipse.persistence.testing.framework.TestExecutor.execute(TestExecutor.java:248)
	at org.eclipse.persistence.testing.framework.TestModel.execute(TestModel.java:208)
	at org.eclipse.persistence.testing.framework.TestCollection.run(TestCollection.java:313)
</error>
  </testcase>
Fixed 106:

DI 107: 20110228: Metamodel missing Dynamic JPA metadata requires Regeneration

  • After an email request from product management surrounding the rethrow validation exception during metamodel initialization - see line 2245 of EntityManagerSetupImpl.initializeCanonicalMetamodel().
throw v;
  • If entities are added at runtime via dynamic persistence - these entities are missing from the runtime and design time (_ underscore classes) Metamodel.
  • One alternative may be to null and regenerate the metamodel.
  • We regenerate the metamodel when it becomes inconsistent with dynamic entities. We ran into this issue when we moved the metamodel generation from "on-demand" during em creation to emf.predeploy.
  • In this case we could call the existing internal API (out of spec) set/getMetamodel - by nulling the metamodel and regenerating - we should pick up the merged metadata from (xml, annotations and dynamic api - all three). However this will not solve the design time _ class generation - only the internal metamodel runtime instance
    // clear the metamodel (INTERNAL) - clears the metamodel on emsetupimpl
    emf.setMetamodel(null);
    // re-get the metamodel - and invoke regeneration
    emf.getMetamodel()
  • See EntityManagerFactory.......
    /**
     * INTERNAL:
     * Convenience function to allow us to reset the Metamodel
     * in the possible case that we want to regenerate it.
     * This function is outside of the JPA 2.0 specification.
     * @param aMetamodel
     * @since Java Persistence 2.0
     */
    public void setMetamodel(Metamodel aMetamodel) {
        this.metaModel = aMetamodel;
    }

DI 108: 20110321: Expose JPA Metamodel with Extensible API relationships via JMX Management API

  • After a quick discussion with Peter on exposing the Extensibility API via JMX - it may be a better solution to expose the entire Metamodel which would include any Extensible relationships added dynamically to the schema.
  • This enhancement for EclipseLink will be documented as part of the change in the following design wiki.

Testing Models

Mapped Superclass Test Model

  • In the entity model below, the Person class is abstract representing the root of a @MappedSuperclass hierarchy.
  • The inheriting subclasses of Person is 2 levels wide.
  • We have instances of @OneToOne, @OneToMany, @ManyToOne and @ManyToMany mappings

Eclipselink metamodel model.gif

Missing Test Model Attributes

    • 315287:BasicType as inheritance root
    • DirectCollection
    • Entity-->Entity hierarchy
      • Done - 20090806 - see Processor.class superclass
      • Via DTYPE discriminator type column field
      • Here we override the default @Inheritance strategy of SINGLE_TABLE and use JOINED instead of TABLE_PER_CLASS - where the two entities have separate tables.
    • Entity-->MappedSuperclass(Abstract)-->Entity hierarchy
      • Location(E)-->CoordinateMS(MS)-->GalacticPosition(E)
      • Subclassing a MappedSuperclass from a root Entity is currently supported in JPA 2.0, although this is not explicitly stated...
        • See p.53 section 2.11.2 and p.398 section 11.1.34 of the JPA 2.0 specification - "A class designated with the MappedSuperclass annotation can be mapped in the same way as an entity except the the mappings will apply only to its subclasses since no table exists for the mapped superclass itself". Since Entities can extend MappedSuperclasses or other Entities.
        • For references see p.302 listing 10-27 of the JPA 2.0 book Pro JPA2: Mastering the Java Persistence API - where a MappedSuperclass extends an Entity.
    • MappedSuperclass containing a 1:m @OneToMany mapping
      • Done - see Corporation.corporateComputers Collection
    • MappedSuperclass containing an Integer @Version field
      • Done - see Person.class
    • MappedSuperclass containing an @ClassId field
      • Pending
    • MappedSuperclass containing a composite primary key
      • Pending
    • MappedSuperclass containing an @EmbeddedId composite primary key
      • Done - see EmbeddedPK in Location.class
    • Collection attribute without Set/List/Map instantiation (to verify element type)
      • Done - see Collection<Board> circuitBoards in Computer
    • Maps
      • Embeddable as Map key
      • Embeddable as Map value
      • Map field in Embedded - see 297748
      • A unidirectional @ManyToMany in Observation back to Person (as observer)
      • Embeddable as ManagedType
    • 20101130 - 300626: Nested Embeddable ObservationDetail to Observation on GalacticPosition added - diagram needs to be updated

Implementation

Module Reuse

How much of the existing JPA annotation processor will be used or transitioned? We will wrap most of the JPA metadata processing - however significant changes are required in order to support MappedSuperclass descriptors - since they currently have no RelationalDescriptor because all their state information is persisted by implementing concrete entities.

Modified Modules

The following module changes are implemented as of Rev# 4587.

Foundation

  • org.eclipse.persistence.core
    • org\eclipse\persistence\sessions
      • Project.java - Holds our Map of MappedSuperclass RelationalDescriptors (metadata processing only - these do not goto the database) keyed on MetadataClass. This map is on the core project because Metadata processing is transient.

javax.persistence 2.0

  • MANIFEST.MF

From

Export-Package: javax.persistence;version="1.99.3",
 javax.persistence.spi;version="1.99.3"

To

Export-Package: javax.persistence;version="1.99.3",
 javax.persistence.criteria;version="1.99.3,
 javax.persistence.metamodel;version="1.99.3",
 javax.persistence.spi;version="1.99.3"

org.eclipse.persistence.jpa

  • org\eclipse\persistence\internal\jpa\metadata
    • accessors
    • objects
      • MetadataAnnotatedElement.java - Return Void.class for all parameterized generics in MappedSuperclass descriptors
    • classes
      • EntityAccessor.java - In addPotentialMappedSuperclass() - get MappedSuperclass descriptor stored previously on the core Project and add the MappedSuperclassAccessor based on the mapping
      • EmbeddableAccessor.java - Add workaround for ValidationException when the accessor is on a MappedSuperclass
      • ClassAccessor.java - add @Override function isMappedSuperclass()
      • MappedSuperclassAccessor.java - override processAccessType and add processMetamodelDescriptor() so we can process our custom MappedSuperclassAccessor Map from the core Project
      • accessors
    • mappings
      • MappingAccessor.java - Added support for parameterized generics in MappedSuperclass descriptors
      • ElementCollectionAccessor.java -
    • MetadataProject.java - Add extra processing step for our custom Map of MappedSuperclassAccessor objects where they are processed early as part of processStage2. In addMappedSuperclassAccessor() add to our custom map of accessors and create a new RelationalDescriptor based on this accessor to the core Project.
    • converters
      • SerializedMetadata.java - Relax the ValidationException on serialization if the accessor represents a MappedSuperclass.
    • MetadataConstants.java - Our custom RelationalDescriptor requires a fake PK and Table name to pass metadata processing - even though we don't make it to the database with this descriptor.
    • MetadataDescriptor.java - Add helper function isMappedSuperclass()
    • MetadataHelper.java - Make getClassForName() public so we can use it to test for parameterized generics in MappingAccessor.
EntityManagerImpl
package org.eclipse.persistence.internal.jpa;
public class EntityManagerImpl implements org.eclipse.persistence.jpa.JpaEntityManager {
 
 // 266912: Criteria API and Metamodel API (See Ch 5 of the JPA 2.0 Specification)
 private javax.persistence.metamodel.Metamodel metaModel; 
 
 public javax.persistence.metamodel.Metamodel getMetaModel() {
 return metaModel;
 }

New Modules

Building

Eclipse IDE Required Changes

Ant Build Required Changes

For all these manifest changes we will require the change to be done in build.xml - as the these are generated files at the moment (pre-Orbit).

JPA Project

build.xml
 <echo message=" org.eclipse.persistence.internal.jpa.criteria;version=&quot;${release.version}&quot;,${line.separator}" 
 file="META-INF/MANIFEST.MF" append="true"/>
...
 <echo message=" org.eclipse.persistence.internal.jpa.metamodel;version=&quot;${release.version}&quot;,${line.separator}" 
 file="META-INF/MANIFEST.MF" append="true"/>
...
 <echo message=" javax.persistence.criteria;version=&quot;[1.99.0,2.1.0)&quot;,${line.separator}" 
 file="META-INF/MANIFEST.MF" append="true"/>
 <echo message=" javax.persistence.metamodel;version=&quot;[1.99.0,2.1.0)&quot;,${line.separator}" 
 file="META-INF/MANIFEST.MF" append="true"/>
.MANIFEST.MF
Require-Bundle: org.eclipse.persistence.core;bundle-version="2.0.0";visibility:=reexport,
--> javax.persistence
...
Import-Package: javax.persistence;version="[1.99.0,2.1.0)",
--> javax.persistence.criteria,

Core Project

javax.persistence Project

build.xml
 <echo message=" javax.persistence.criteria;version=&quot;${jpaproto.version}&quot;${line.separator}" 
 file="${jpaproto.resource.dir}/MANIFEST.MF" append="true"/>
 <echo message=" javax.persistence.metamodel;version=&quot;${jpaproto.version}&quot;${line.separator}"
 file="${jpaproto.resource.dir}/MANIFEST.MF" append="true"/>
.MANIFEST.MF
Require-Bundle: org.eclipse.persistence.core;bundle-version="2.0.0";visibility:=reexport,
--> javax.persistence
 org.eclipse.persistence.internal.jpa.criteria;version="2.0.0", 
 org.eclipse.persistence.internal.jpa.metamodel;version="2.0.0", 
 
Import-Package: javax.persistence;version="[1.99.0,2.1.0)",
 javax.persistence.criteria;version="[1.99.0,2.1.0)",
 javax.persistence.metamodel;version="[1.99.0,2.1.0)",

Testing

Client Test Cases

 EntityManagerImpl em = (EntityManagerImpl)getEntityManager();
 Metamodel metamodel = em.getMetamodel();

Example Code

Order Entity

package org.eclipse.persistence.example.jpa.server.business;
 
import java.math.BigDecimal;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
 
@Entity 
 public class Order {
 @Id 
 Integer orderId;
 @ManyToOne 
 Customer customer;
 @OneToMany 
 Set<Item> lineitems;
 Address shippingAddress;
 BigDecimal totalCost;
}

Order_ TypesafeMetamodel

  • The following class has been generated from the Order entity above
    • Note the @Generated and @TypesafeMetamodel annotations
    • Note the static volitile elements of type (Attribute, Set, Collection or List)
package org.eclipse.persistence.example.jpa.server.business;
 
import java.math.BigDecimal;
 
import javax.annotation.Generated;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Set;
import javax.persistence.metamodel.TypesafeMetamodel;
 
// added annotations from 5.2.1.3
@Generated(value="EclipseLink")
@TypesafeMetamodel(Order.class)
 
// from JPA 2.0 spec section 5 p.162
public class Order_ {
 public static volatile Attribute<Order, Integer> orderId;
 public static volatile Attribute<Order, Customer> customer;
 public static volatile Set<Order, Item> lineitems;
 public static volatile Attribute<Order, Address> shippingAddress;
 public static volatile Attribute<Order, BigDecimal> totalCost;
}

Test Results

Client Test Case

StackTrace
Console
_Metamodel: org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl@2a122a12
_Metamodel entities: [ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Customer --> [DatabaseTable(CUSTOMER)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Item --> [DatabaseTable(ITEM)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Address --> [DatabaseTable(ADDRESS)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Cell --> [DatabaseTable(EL_CELL)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Order --> [DatabaseTable(ORDER)])]]
_Metamodel embeddables: []
_Metamodel managedTypes: [ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Customer --> [DatabaseTable(CUSTOMER)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Item --> [DatabaseTable(ITEM)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Address --> [DatabaseTable(ADDRESS)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Cell --> [DatabaseTable(EL_CELL)])], ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Order --> [DatabaseTable(ORDER)])]]
_Metamodel EntityType for [ManagedType[RelationalDescriptor(org.eclipse.persistence.example.jpa.server.business.Customer --> [DatabaseTable(CUSTOMER)])]]: 

Open Issues

This section lists the open issues that are still pending that must be decided prior to fully implementing this project's requirements.

Issue # Owner Description / Notes
I1 20090304 mobrien compile time dependency on tools.jar for com.sun.mirror packages
I2 20090305 mobrien Name collisions with entities that are already named Entity_ in the current package - see the spec 5.2.1 #1
I3 20090305 mobrien Package level splitting is incompatible with OSGI - see the spec 5.2.1 #1
DI4 20090309 mobrien Alt#1: The existing javax.persistence.QueryBuilder interface will be removed and replaced by the updated javax.persistence.criteria.QueryBuilder interface
DI5 20090316 mobrien Direct public access or indirect intermediate interface access to Metamodel implementation
DI6 20090316 mobrien MappedSuperclass does not currently have a descriptor
DI7 20090327 mobrien When to bootstrap Metamodel creation
DI8 20090327 mobrien Ordering of Metamodel collections
DI9 20090327 mobrien Custom Logger for Metamodel
DI11 20090402 mobrien Handle no duplicates in MappedSuperclass collection on Metamodel

Timeline

20090305 Meeting

  • How to handle broken annotations? skip?
  • How to handle dependent annotations that are broken
  • performance (javac/apt) should be very fast
  • dynamic regeneration on all modifications - should be automatic with -javaagent type ide hook
  • For non-typesafe string based metadata accessors - if we verify type internally we have incremental type safety
  • verify all examples in the spec work with get and not just the static metamodel
  • Is there state for the metadata entity - 5.2.1.2 states it must be initialized
  • Name collisions with entities that are already named Entity_ in the current package see 5.2.1.1
    • package level splitting is incompatible with OSGI
  • multi-level fetch joins(there are 3) (with multiple . operators) must be supported
    • The query api must support paths

20090306 Architecture Meeting

  • We will concentrate on R1 and the Metamodel exposure work
  • The metamodel will be built separately in the JPA codebase
    • The EclipseLink descriptor off the session contains all the mappings we require to build implementations of the new Metamodel API interfaces
      • The MappedSuperclass interface is currently empty in the spec - so it will require no mapping work
      • The mapping of join metadata may be an issue
  • We are not reusing the code from the existing runtime in memory metadata package code in JPA
    • The following issues may have occurred
      • Lazy loading will have a partial metamodel load
      • The entityManager may not be available yet

20090310 Dev Meeting

  • Received initial implementation from Doug
  • slightly modified it and it runs fine with a standard entity model (no embeddables or mappedSuperclass)
  • See updated UML class diagram from 20090311

20090318 Dev Meeting

  • With Guy, Peter
  • How are we going to architect support for mapped superclass?
    • The spec example 5.7.1 only gets entities and embeddables from the Metamodel object - not MappedSuperclass objects - therefore our implementation is open
    • We will provide the following metadata model that is an extension of the current core API
      • Descriptor --> * List<MappedSuperclassMetadata<List<MappedSuperclassType>> --> * List<DatabaseMapping>
      • The type of mappings will include the minimum annotation data that we require in order to complete the model
        • For bidirectional many mappings we need attributes such as the referenceType in the absence of a generic class type reference.

20090406 Review by Guy

  • Presently I clone the mapping for each MappedSuperclass - this clone needs to have some fields not set as they are not relevant for our RelationalDescriptor (that does not actually have a database table associated with it)
  • use reload to get clones for free by using the XML metadata processing that has already happened.
  • Otherwise we will need custom clone() code for each mapping - this is potentially problemmatic because a future mapping may miss its' clone implementation.

20090406 Review by Gordon

  • Make all Entity*Impl fields protected not private - done
  • We need to resolve why MappedSuperclassTypeImpl does not inherit from ManagedTypeImpl but does implement the ManagedType interface
  • Instead of storing just mappedSuperClasses in our Map - store all ManagedTypes to aide lookup later
  • Get the spec interfaces from the pdf and don't wait for an update of the circulating spec jar
  • Change the key in our Map so that duplicates are handled for us - however a Map.put() will overwrite the existing value in our case because our key is the same object but the value is different in pass 2 (we end up clearing our custom RelationalDescriptor)
  • Remove core build changes that reference JPA 2.0 - done - there should be none but we presently require 360 references to JPA 1.0 in core - one of these I added in the past for a JBoss workaround
  • Move metamodel api interfaces to the main jpa 1.99 jar - I was keeping them separate until the spec finalized - in progress
    • Added 2 new exports for (metamodel and criteria) to the manifest - correction, it is generated - we do this in
      • javax.persistence.metamodel;version="1.99.3,
      • javax.persistence.criteria;version="1.99.3,
      • Fixed access restriction "Access rules" for the new new packages in the JPA project
    • Version 2/1 should not be an issue but do we bump the version from 1.99.3 to 1.99.4?
    • We are targetting the 2.0 version that is off of trunk/jpa/plugins not trunk/plugins
    • Add new JPA 2.0 classes to "package-eclipselink-jar" target in trunk/build.xml

20090508 Specification Questions for Gordon

  • 1) "Declared" keyword - as in getDeclaredMap() - how is this a subset of getMap()?
    • "Declared" means only in scope of this particular Entity - not via inheritance or mapped superclasses
  • 2) getVersion and getId do not handle primitives
    • Java SE autoboxing will handle this for us
  • 3) IdentifiableType - I have no implementation yet like I do for BasicImpl, Map|List|Set|CollectionImpl, EntityTypeImpl and EmbeddableTypeImpl
    • Just implement the interface in managed types
  • 4) em|emf.close - do we clear the metamodel?
    • No, keep the instance around
    • Q) How will we know to invalidate the current metamodel if a new persistence unit is loaded?
      • It will be left to the user handle this use case
      • For this variant I will possibly add a clear() function outside of the spec. The user will need to call this and then regenerate the new metamodel via getMetamodel() call.
  • 5) Type checking exceptions during metamodel creation or in runtime calls? IE: getList("homeAddress") actually returns a single Entity instead of a List.
    • Leave checking to runtime and throw exception at that time

20090513 Mapped Superclass Discussion with Gordon/Peter

  • 1) I have switched from using a HashMap<Class, RelationalDescriptor> mappedSuperclasses on the Project to a HashSet<RelationalDescriptor> where the Class key is stored as existing javaClass and javaClassName fields on Project. However while overriding equals() and hashCode() on ClassDescriptor I see different hashCodes for Class objects with the same name?
    • Turns out that I am seeing the Class loaded from two different classLoaders
    • Continue to use javaClassName and not javaClass in the equals() method, but change your code as follows
      • When adding a RelationalDescriptor to the Set, do not store the javaClass - wait until later and call convertClassNamesToClasses() to generate the javaClass at that time - to avoid adding two different Class objects that represent the same metamodel class.
  • 2) When we call get*() do we return separate exceptions for the cases where either an attribute name was not found or it is of the wrong type? Currently the spec. defines a single IAE when both are wrong.
    • Go with the spec for now, but possibly add extra detail and/or a ISE or CCE exception for either runtime exception.

20090525 Implement/Synchronize with lastest Specification API changes in rev 4265

  • Gordon and I started to discuss this late 20090526
  • deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/AbstractCollection.java 4265 deleted
      • (replaced by PluralAttribute)
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Member.java 4265 deleted
      • (replaced by Attribute)
  • renamed
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Basic.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/BasicType.java 4265 (+34) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Collection.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/CollectionAttribute.java 4265 (+36) new
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Embeddable.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/EmbeddableType.java 4265 (+33) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Entity.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/EntityType.java 4265 (+41) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/List.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/ListAttribute.java 4265 (+36) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Map.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/MapAttribute.java 4265 (+50) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/MappedSuperclass.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/MappedSuperclassType.java 4265 (+34) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/Set.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/SetAttribute.java 4265 (+36) new
      • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/TypesafeMetamodel.java 4265 deleted
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/StaticMetamodel.java 4265 (+44) new
  • new
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/PluralAttribute.java 4265 (+56) new
      • (replaces AbstractCollection)
    • /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/SingularAttribute.java 4265 (+63) new
  • changes
    • Attribute.java
      • Essentially a renamed Member.java with addition of the PersistentAttributeType
      • isId, isVersion, isOptional moved to SingularAttribute
      • added getName, getDeclaringType, getJavaType, getJavaMember, isAssociation, isCollection
      • Attribute.getMultiplicity replaced by getPersistentAttributeType
        • AbstractCollection.Multiplicity moved to Attribute.Multiplicity
        • added MANY_TO_MANY, ONE_TO_MANY, MANY_TO_ONE ELEMENT_COLLECTION mappings
        • Attribute no longer extends Member (deleted) or Bindable
    • New SingularAttribute replaces much of previous Attribute with addition of getType()
    • Bindable.BindableType
      • ATTRIBUTE, COLLECTION, MANAGED_TYPE
      • changed to
      • SINGULAR_ATTRIBUTE, PLURAL_ATTRIBUTE, ENTITY_TYPE
    • IdentifiableType adds hasVersionAttribute, getIdClassAttributes
    • ManagedType no longer extends Bindable - it only continues to extend Type now
      • Added getAttributes, getDeclaredAttributes, the rest are renamed get*Singular* for non-plural methods
    • EntityType now extends Bindable directly as well as IdentifiableType
    • In BasicAccessor.process() the function

Implementation Changes

  • New SingularAttributeImpl takes over part of previous Attribute
  • Attribute becomes abstract
  • Implementation of Member moves to Attribute

Snapshot Checkin for Rev 4350

20090528:1700

This checkin for rev 4353 matches the Metamodel interface model diagram above and is 50% complete.

  • What is working
    • Metamodel interface
    • MappedSuperclassType support in BasicAccessor (for DirectMapping) for @MappedSuperclass annotation (not XML yet)
    • Part of EntityType
  • What is not working
    • Any function that still contains a stubbed TODO and returns null, 0 or false
    • MappedSuperclassType support for all non-Basic types (non-DirectMapping)
      • XML support for MappedSuperclass
    • Fully tested hashcode/equals implementation to disallow duplicates of RelationalDescriptor for MappedSuperclassType support - it needs a more detailed equals()
    • EmbeddableType
    • BasicType
    • XML support for Metamodel annotations for use by WorkBench
    • Completed test suite and Model

The Test configuration patch off of 4350 has been checked in under rev# 4357

20090529: Review Guy

  • 1 - I'd like to see the code you added to EntityAccessor moved to the MetadataProject. That is, instead of seeing getProject().getProject() ... call getProject().addMappedSuperclass(MetadataClass). In their you'll deal with the TopLink project and add the mappedsuperclass descriptor.
  • 2 - classAccessor instanceof check should be changed to classAccessor.isMappedSuperclass()
  • You should have a getMappedSuperclass method on MetadataProject and call it. In there you deal with the actual EclipseLink Project again like comment #1 above.
  • See rev 4415 and 4416

20090609: Review by Guy & Gordon

  • The following changes were done to the way we process mappings on MappedSuperclasses. Instead of creating our own pseudo RelationalDescriptor, we force processing of accessors which creates RelationalDescriptors for all our accessors for us. We no longer need to modify the process() function on every accessor type.
    • The model has been extended with 1:1 and M:1 unidirectional mappings out of a MappedSuperclass - Person.
    • We do not support relationship mappings into mappedSuperclasses - as they have no identity.

20090612: Review by Guy & Gordon

  • See double table exceptions with our new forced accessor processing
    • The exceptions we see where there are 2 tables is because we are adding to the set more than once

(the standard hashCode/equals is missing or we should use a HashMap keyed on className) - using className key for identity

    • Retest on regression and ORM tests
    • Moving on to test Embeddables

Current Results on MappedSuperclasses

_Metamodel mappedSuperclassType:MappedSuperclassTypeImpl@18227730 [descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.metamodel.Processor --> [DatabaseTable(__RESERVED_IN_MEM_ONLY_TABLE_NAME)]), mappings: []]
_Metamodel mappedSuperclassType:MappedSuperclassTypeImpl@27916412 [descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.metamodel.Corporation --> [DatabaseTable(__RESERVED_IN_MEM_ONLY_TABLE_NAME)]), mappings: []]
_Metamodel mappedSuperclassType:MappedSuperclassTypeImpl@11832081 [descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.metamodel.Designer --> [DatabaseTable(__RESERVED_IN_MEM_ONLY_TABLE_NAME)]), mappings: [org.eclipse.persistence.mappings.OneToOneMapping[secondaryEmployer], org.eclipse.persistence.mappings.OneToOneMapping[primaryEmployer]]]
__Mapping: org.eclipse.persistence.mappings.OneToOneMapping[secondaryEmployer]
__Mapping: org.eclipse.persistence.mappings.OneToOneMapping[primaryEmployer]
_Metamodel mappedSuperclassType:MappedSuperclassTypeImpl@1497769 [descriptor: RelationalDescriptor(org.eclipse.persistence.testing.models.jpa.metamodel.Person --> [DatabaseTable(__RESERVED_IN_MEM_ONLY_TABLE_NAME)]), mappings: [org.eclipse.persistence.mappings.DirectToFieldMapping[id-->PERSON_ID], org.eclipse.persistence.mappings.DirectToFieldMapping[name-->NAME]]]
__Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[id-->PERSON_ID]
__Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[name-->NAME]

20090617: RelationalDescriptor Review by James

  • See the following rev done in parallel with the accessor/metadataProject refactor that is in progress

4519

  • 266912: Use a Map keyed on MetadataClass instead of overriding equals/hashCode

for MappedSuperclass RelationalDescriptors

  • Suggested by and discussed with James
    • 4519 diff
    • - we may merge Project.mappedSuperclassDescriptors with Project.descriptors in the future to gain query and SessionCustomizer functionality on mappedSuperclasses
    • - The key is Object instead of MetadataClass so that we do not intruduce a Core-->JPA dependency
    • - The value may be ClassDescriptor in the future, for now we use a concrete RelationalDescriptor that matches what the accessors use
    • - Use of a HashMap will enforce identity based on the MetadataClass but we still require a contains(key) check because we do not want a put(key,value) to overwrite a descriptor that already contains mappings as we do 3 passes during metadata processing
    • - Our custom metamodel processing for mappedSuperclasses in the next transaction should be done in the normal initialize() and not treated specially
    • This raises the issue of whether MappedSuperclasses should be treated as real descriptors (minus actually going to the database)

20090618: Synchronize with 20090617 Criteria API specification changes

  • See rev 4525
  • Relevant changes
    • All "Declared" comments are now "Present" - function definition retain "Declared"
    • ManagedType
      • Added Attribute<? super X, ?> getAttribute(String name);
      • Added Attribute<X, ?> getDeclaredAttribute(String name);
    • MappedSuperclassTypeImpl
      • Removed public <Y> Attribute<X, Y> getDeclaredAttribute(String name, Class<Y> type)
    • EntityTypeImpl
      • Removed public boolean hasIdAttribute()
      • Removed public Attribute<X, ?> getDeclaredAttribute(String name)
    • EmbeddableTypeImpl
      • Removed public Attribute<X, ?> getDeclaredAttribute(String name)

20090624: Code Review with Guy, Gordon

  • 20090624:1400
  • This review is for design issues 27-30
  • Code Review prep notes for
  • Prep Before:
    • 1) move constants from MetadataConstants (JPA) to AbstractSessionLog (Core)
    • 2) Javadoc on superclass ClassAccessor.hasMappedSuperclasses()
    • 3) remove/merge three if statements in EmbeddableAccessor:170
    • 4) EntityAccessor:1417
      • verify no validation for
        • if(!getDescriptor().isPkClassDefinedOnMappedSuperclass()) {
      • Javadoc on hasMappedSuperclasses()
    • 5) MappedSuperclassAccessor
      • remove getIdClass() as it is no longer used by MetadataProject.addMappedSuperclassAccessor
    • 6) DirectCollectionAccessor
      • 151: verify workaround for no referenceClass in mappedSuperclass descriptor
      • 311: verify we do not need to processMappingValueConverter() for mappedSuperclass descriptors
        • because currently I do not process in 311 but I do in 344
      • Note: either one must be modified
    • 7) ElementCollectionAccessor
      • 415: verify we are good to override a generic Type definition (IE: T) to Void in getReferenceClass()
    • 8) MappingAccessor
      • 456: verify we can skip compositePrimaryKey JoinColumn Metadata processing when
        • if(!descriptor.hasDescriptorReservedForMetamodelAsParent()) {
      • 519: verify Void parameterized generics override
      • 568: verify getReferenceName override for MappedSuperclass where we return "" empty string - need to refactor this
      • 1271: verify early return with null - avoiding a NPE when getting the name for a mapkey
    • 9) MetadataAnnotatedElement
      • 240: verify Void parameterized generics override
    • 10) InheritanceMetadata
      • 81: no change to accessor.processInheritancePrimaryKeyJoinColumns();
    • 11) MetadataDescriptor
      • 872: verify early return with empty DatabaseTable is ok for getPrimaryTable() - same as for aggregateDescriptor
        • if(hasDescriptorReservedForMetamodelAsParent()) {
          • return new DatabaseTable();
    • 12) MetadataProject
      • 972: verify secondary check of our custom map of MappedSuperclass descriptors for entities
        • TODO: this is where we may have a problem that causes most of the issue with multiple PK's above
      1. By picking up our MS descriptors here - we end up causing them to be processed by initialize() during login() which we do not want
  • Performance:
    • 1) Verify that any of the if() checks do not affect performance - as in they are in a tight loop
  • During review:

Decisions

This section lists decisions made. These are intended to document the resolution of open issues or constraints added to the project that are important.

Issue # Description / Notes Decision

Appendix A : Specification Details

Version 20090307 and 20090317 PFD Differences

  • Entity interface
    • removed Class<?> getIdJavaType()
  • IdentifiableType interface (super interface of Entity)
    • added boolean hasIdAttribute()
    • added Type<?> getIdType()
  • ManagedType interface (super interface of IdentifiableType)
    • added Attribute<? super X, ?> getDeclaredAttribute(String name)
    • added Attribute<? super X, ?> getCollection(String name)
    • added Attribute<? super X, ?> getSet(String name)
    • added Attribute<? super X, ?> getList(String name)
    • added Attribute<? super X, ?> getMap(String name)
    • added Attribute<? super X, ?> getDeclaredCollection(String name)
    • added Attribute<? super X, ?> getDeclaredSet(String name)
    • added Attribute<? super X, ?> getDeclaredList(String name)
    • added Attribute<? super X, ?> getDeclaredMap(String name)

Version 20090725 PFD1 and 20090831 PFD2 Differences

  • The following changes need to be made to the following location for enhancement# 288267.
  • See parallel corresponding Criteria API changes in 5088.
    • <trunk>\jpa\plugins\javax.persistence\src\javax\persistence\metamodel
  • Metamodel changes :
    • The only code changes are ManagedType.get*Collections to get*PluralAttributes
  • IdentifiableType
    • - getDeclaredId and getVersion - switched places - no update required
  • ManagedType
    • - added javadoc
     *  Returns empty set if the managed type has no declared
     *  attributes.
- reordered
    <E> CollectionAttribute<X, E> getDeclaredCollection(String name, Class<E>
elementType);
    <E> SetAttribute<X, E> getDeclaredSet(String name, Class<E> elementType);
    <E> ListAttribute<X, E> getDeclaredList(String name,

Appendix B: Deprecated Design Documentation Sections

Work Estimate

  • Original March 2009:
    • Investigate EclipseLink Metamodel
      approx 3 days
    • Develop implementations of MetaModel or refactor current metamodel
      approx 10 days
    • APT investigation and prototype - future work
      approx 5 days
    • APT tooling/testing - future work
      approx 10 days
  • Supplemental
    • Learn JPA test model architecture and generation framework
    • Learn 1:1, n:n, 1:n and n:1 mapping functionality (IE: JoinTable and JoinColumn)
    • Add new Metamodel test model with table generation
    • Modify test build scripts for new Metamodel test model
    • Investigate parameterized generics functionality
    • Debug and investigate metadata project generation (what we will wrap)
    • Write up design/issues both before and during development

Work Items

The following work items have been identified. The prefix D signifies done, P means in progress.

  • P - Metamodel Implementation
    • DWrap existing metadata implementation for non-mappedSuperclass code
    • DImplement mappedSuperclass changes during predeploy()
      • DImplement mappedSuperclass accessor changes
      • Implement EmbeddedAccessor mappedSuperclass changes
      • Implement EmbeddedIdAccessor mappedSuperclass changes
      • Implement ManyToManyAccessor mappedSuperclass changes
      • Implement ManyToOneAccessor mappedSuperclass changes
      • DImplement OneToManyAccessor mappedSuperclass changes
      • DImplement OneToOneAccessor mappedSuperclass changes
      • Implement TransientAccessor mappedSuperclass changes - out of spec.
      • Implement VariableOneToOneAccessor mappedSuperclass changes - out of spec.
    • DImplement ManagedTypeImpl
    • DImplement EntityTypeImpl
    • P Prepare for 2-3 code review and code adjustment cycles
  • P Metamodel Test Implementation
    • Create test model
      • add @ManyToMany
      • add @Embeddable
  • D Metamodel Build modifications
  • P Metamodel XML/XSD modifications
  • Metamodel integration with EclipseLink Workbench
  • P Metamodel Design Documentation
  • Metamodel User Documentation

User Use Cases of the Metamodel API

  • Now that JPA 2.0 has a metadata introspection API for the persistence unit in the form of the Metamodel - what can we use the Metamodel API for - outside of its use in the Criteria API?
    • Examining the model
    • Creating a query on the fly - thank you Laird Nelson

References