Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
EclipseLink/Development/JPA 2.0/metamodel api
Contents
- 1 JPA 2.0: MetaModel API
- 1.1 Summary
- 1.1.1 Work Estimate
- 1.1.2 Work Items
- 1.1.3 EclipseLink API code Changes
- 1.1.3.1 Project.java - Native core
- 1.1.3.2 ExceptionLocalizationResource.java - Native core
- 1.1.3.3 MetadataProject.java - JPA core
- 1.1.3.4 MetadataConstants.java - JPA core
- 1.1.3.5 MetadataDescriptor.java - JPA core
- 1.1.3.6 MetadataHelper.java - JPA core
- 1.1.3.7 MetadataProject.java - JPA core
- 1.1.3.8 EmbeddableAccessor.java - JPA core
- 1.1.3.9 EntityAccessor.java - JPA core
- 1.1.3.10 MappedSuperclassAccessor.java - JPA core
- 1.1.3.11 ElementCollectionAccessor.java - JPA core
- 1.1.3.12 MappingAccessor.java - JPA core
- 1.1.3.13 MetadataAnnotatedElement.java - JPA core
- 1.1.3.14 SerializedMetadata.java - JPA core
- 1.1.3.15 EntityManagerImpl.java - JPA core
- 1.1.3.16 AttributeImpl.java - New
- 1.1.3.17 BasicTypeImpl.java - New
- 1.1.3.18 CollectionAttributeImpl.java - New
- 1.1.3.19 EmbeddableTypeImpl.java - New
- 1.1.3.20 EntityTypeImpl.java - New
- 1.1.3.21 IdentifiableTypeImpl.java - New
- 1.1.3.22 ListAttributeImpl.java - New
- 1.1.3.23 ManagedTypeImpl.java - New
- 1.1.3.24 MapAttributeImpl.java - New
- 1.1.3.25 MappedSuperclassTypeImpl.java - New
- 1.1.3.26 MetamodelImpl.java - New
- 1.1.3.27 PluralAttributeImpl.java - New
- 1.1.3.28 SetAttributeImpl.java - New
- 1.1.3.29 SingularAttributeImpl.java - New
- 1.1.3.30 TypeImpl.java - New
- 1.1.4 Unimplemented Functions
- 1.1.4.1 IdentifiableTypeImpl.java
- 1.1.4.1.1 public <Y> SingularAttribute<X, Y> getDeclaredId(Class<Y> type)
- 1.1.4.1.2 public <Y> SingularAttribute<X, Y> getDeclaredVersion(Class<Y> type)
- 1.1.4.1.3 public Set<SingularAttribute<? super X, ?>> getIdClassAttributes()
- 1.1.4.1.4 public boolean hasSingleIdAttribute()
- 1.1.4.1.5 public boolean hasVersionAttribute()
- 1.1.4.2 MappedSuperclassTypeImpl.java
- 1.1.4.3 EntityTypeImpl.java
- 1.1.4.1 IdentifiableTypeImpl.java
- 1.2 Analysis
- 1.3 Functional Requirements
- 1.4 Use Cases
- 1.5 Design
- 1.5.1 API
- 1.5.2 Design Issues
- 1.5.2.1 DI 4: QueryBuilder Interface Implementation
- 1.5.2.2 DI 5: Metamodel package is public or internal API
- 1.5.2.3 DI 6: MappedSuperclass does not currently have a Descriptor
- 1.5.2.4 DI 7: When to bootstrap Metamodel creation
- 1.5.2.5 DI 8: Ordering of Metamodel collections
- 1.5.2.6 DI 9: Custom Logger for Metamodel
- 1.5.2.7 DI 10: Embeddable Support
- 1.5.2.8 DI 11: Handle No Duplicates in MappedSuperclass Collection on Metamodel
- 1.5.2.9 DI 12: 20090506: Do we need an implementation of IdentifiableType
- 1.5.2.10 DI 16: 20090508: Implement IdentifiableType?
- 1.5.2.11 DI 13: 20090506: EntityManager/EntityManagerFactory.clear() resets MetaModel?
- 1.5.2.12 DI14: 20090507: EntityTypeImpl support for primitive Id and Version
- 1.5.2.13 DI 15: 20090508: What is the scope of "Declared"?
- 1.5.2.14 DI 17: 20090508: Clear Metamodel on EMF.close()?
- 1.5.2.15 DI 18: 20090508: Type Checking at metamodel creation or runtime?
- 1.5.2.16 DI 24: 20090609: Move accessor processing out of BasicAccessor into MetadataProject
- 1.5.2.17 DI 25: 20090616: Inherited parameterized generics for Element Collections (Basic)
- 1.5.2.18 DI 26: 20090616: TableGenerator on Id column Accessor is null
- 1.5.2.19 DI 27: 20090616: Embeddable access type conflict exception
- 1.5.2.20 DI 28: 20090622: Composite PK support for MappedSuperclasses
- 1.5.2.21 DI 29: 20090622: Multiple Table PK support for MappedSuperclasses
- 1.5.2.22 DI 30: 20090623: BasicMap Parameterized Generic return type cannot be the Void class
- 1.5.2.23 DI 31: 20090703: EntityTypeImpl inherits from ManagedType but MappedSuperclassTypeImpl does not
- 1.5.2.24 DI 32: 20090703: Metamodel superType requires javaClass set on custom descriptor on MappedSuperclassAccessor
- 1.5.2.25 DI 33: 20090707: Application Server Container - Verify that we get the correct classLoader from the right session
- 1.5.2.26 DI 34: 20090707: Temporary CNFE when loading Metamodel Impl class
- 1.5.2.27 DI 35: 20090707: Where to initialize IdentifiableTypeImpl.supertype
- 1.5.2.28 DI 36: 20090708: The StaticMetamodel requires an implementation class
- 1.5.2.29 DI 37: 20090708: CollectionAttribute acts as a peer of Map, Set, List but should be a super interface
- 1.5.2.30 DI 38: 20090708: OneToOne Set defaults to IndirectList
- 1.5.2.31 DI 39: 20090708: Handle MappedSuperclass in ManagedTypeImpl.create()
- 1.5.2.32 DI 40: 20090708: Type hierarchy numbers are missing MappedSuperclass types
- 1.5.2.33 DI 41: When to throw IAE for missing member or wrong type on get() call
- 1.5.2.34 DI 42: 20090709: IdentifiableType.supertype - what do top-level types set it to
- 1.5.2.35 DI 43: 20090710: Implement getDeclaredX() methods
- 1.5.2.36 DI 44: 20090710: Criteria API FromImpl equality check requires enum instead of BasicTypeImpl
- 1.5.2.37 DI 45: 20090713: getAttributes() returns a new HashSet - not the actual Metamodel.members.values Collection
- 1.5.2.38 DI 46: 20090715: Metamodel initialization on EntityManagerFactory creation
- 1.5.2.39 DI 47: 20090715: Implement IdentifiableType.getIdType() for composite keys
- 1.5.2.40 DI 48: 20090721: Implement Map support
- 1.5.2.41 DI 49: 20090723: OneToMany on MappedSuperclass with JoinTable causes Composite PK in error
- 1.5.2.41.1 Reproduction:
- 1.5.2.41.2 Q1) Do we need the inverseJoinColumns annotation
- 1.5.2.41.3 Q2) How do we define the join table columns
- 1.5.2.41.4 Analysis:
- 1.5.2.41.5 Implementation Option 1: Remove pseudo PK field from relational descriptor later in stage 2
- 1.5.2.41.6 Implementation Option 2: Cache pseudo PK field on metadata descriptor until stage 3
- 1.5.2.41.7 Implementation Option 3: Determine if there will be 1 or more pk fields before adding pseudo PK field in stage 1
- 1.5.2.41.8 Decision:
- 1.5.2.42 DI 50: 20090727: Handle all mapping types in the SingularAttribute constructor
- 1.5.2.43 DI 51: 20090728: Implement ManagedTypeImpl.getDeclaredAttributes() for non-collections
- 1.5.2.44 DI 52: 20090728: JPA 2: Implement recursive ManagedType.getDeclared* algorithm to differentiate by IdentifiableType
- 1.5.2.45 DI 53: 20090729: Verify that inheritied non-JPA class mappings are handled by the Metamodel
- 1.5.2.46 DI 54: 20090803: Metamodel.type(Clazz) should differentiate between null and BasicType
- 1.5.2.47 DI 55: 20090806: Basic Type processing currently lazy loaded
- 1.5.2.48 DI 56: 20090807: IdentifiableTypeImpl.getId and getVersion should handle null and Object.class for non-strict typing
- 1.5.2.49 DI 57: 20090807: Refactor ManagedTypeImpl.create factory method to also build MappedSuperclass Types
- 1.5.2.50 DI 58: 20090807: ManagedType Attribute Initialization must differentiate between Collection and List
- 1.5.3 Annotation Processors
- 1.6 Testing Models
- 1.7 Implementation
- 1.8 Building
- 1.9 Testing
- 1.10 Open Issues
- 1.11 Timeline
- 1.11.1 20090305 Meeting
- 1.11.2 20090306 Architecture Meeting
- 1.11.3 20090310 Dev Meeting
- 1.11.4 20090318 Dev Meeting
- 1.11.5 20090406 Review by Guy
- 1.11.6 20090406 Review by Gordon
- 1.11.7 20090508 Specification Questions for Gordon
- 1.11.8 20090513 Mapped Superclass Discussion with Gordon/Peter
- 1.11.9 20090525 Implement/Synchronize with lastest Specification API changes in rev 4265
- 1.11.10 20090529: Review Guy
- 1.11.11 20090609: Review by Guy & Gordon
- 1.11.12 20090612: Review by Guy & Gordon
- 1.11.13 20090617: RelationalDescriptor Review by James
- 1.11.14 20090618: Synchronize with 20090617 Criteria API specification changes
- 1.11.15 20090624: Code Review with Guy, Gordon
- 1.12 Decisions
- 1.13 Appendix A : Specification Details
- 1.14 Appendix B : Deprecated or Resolved Design Notes
- 1.15 Documentation
- 1.16 Future Considerations
- 1.1 Summary
JPA 2.0: MetaModel API
Date | Committer(s) | Description |
---|---|---|
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, 0n 2009 | mobrien | Remaining functions in IdentifiableType get*Id*() and get*Version() in progress. |
Note: this document is in progress as of 20090709, therefore the content is in flux.
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.
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
- Investigate EclipseLink Metamodel
- 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
- Create test model
- D Metamodel Build modifications
- P Metamodel XML/XSD modifications
- Metamodel integration with EclipseLink Workbench
- P Metamodel Design Documentation
- Metamodel User Documentation
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
MetadataProject.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
Unimplemented Functions
IdentifiableTypeImpl.java
public <Y> SingularAttribute<X, Y> getDeclaredId(Class<Y> type)
public <Y> SingularAttribute<X, Y> getDeclaredVersion(Class<Y> type)
public Set<SingularAttribute<? super X, ?>> getIdClassAttributes()
public boolean hasSingleIdAttribute()
public boolean hasVersionAttribute()
MappedSuperclassTypeImpl.java
public Type<?> getIdType() - In progress
EntityTypeImpl.java
public Type<?> getIdType() - In progress
Analysis
- My understanding is that the metamodel API provides 3 (three) 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).
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.
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; |
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; |
R2.6.2 | - | - | Set if the collection type of z is java.util.Set, then
public static volatile Set<X, Z> z; |
R2.6.3 | - | - | List if the collection type of z is java.util.List, then
public static volatile List<X, Z> z; |
R2.6.4 | - | - | Map if the collection type of z is java.util.Map, then
public static volatile Map<X, K, Z> z; |
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 objests 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.
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
Running APT to create AnnotationProcessorFactory classes
APT Static Usage
- Start with the following APT tutorial by SUN that shows how to list class names
- Create an SE JPA project - remember to include the provider resource \jpa\org.eclipse.persistence.jpa\resource\META-INF\services\javax.persistence.spi.PersistenceProvider
- Add the EclipseLink classpath variable TOOLS_LIB = tools.jar so we can import com.sun.mirror
- Create an AnnotationProcessorFactory class
- Create a pointer to this class
- Create a UTF8 no extension text file com.sun.mirror.apt.AnnotationProcessorFactory with the content org.eclipse.persistence.apt.MetamodelAnnotationProcessorFactory
- Delete the 3 chars upside down ?.. before the same - so we do not get Illegal provider-class name: ?org
- Place this class into META-INF/services
- Compile and verify the following inner classes were created
- MetamodelAnnotationProcessorFactory$ListClassAp$ListClassVisitor.class
- MetamodelAnnotationProcessorFactory$ListClassAp.class
- Run APT in this project
APT Dynamic Usage
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
- Singular
- Attributes
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)
Metamodel Implementation
The following UML class diagram illustrates the static relationships and hierarchy of the Metamodel implementation classes.
- 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
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(Iterator<RelationalDescriptor> anIterator = mappedSuperclassesSet.values().iterator(); anIterator.hasNext();) { RelationalDescriptor mappedSuperclass = anIterator.next(); ..... }
- 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)
- 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)
- sourceForeignKeyField DatabaseField (id=64)
- 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
- IdentifiableType
- ManagedType
- 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
Solution:
We are setting the javaClass later during metamodel processing instead of during metadata processing - so we can avoid persistence operations on our custom discriptor.
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)
- See Fix for Design Issue # 58
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
- There are 3 valid cases out of a possible 4 for these functions
* @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
/** * 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() {
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)
- ManagedType
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
- Use Case Partitioning:
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
- Fixed 20090731 in rev# 4765
- 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")); }
- Fixed 20090731 in rev# 4765
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
- See bug# 283607
- In section 6.2.3 "Bootstrapping" of the JPA 2.0 specification - it states that the metamodel must be initialized on EMF creation.
- "When the entity manager factory for a persistence unit is created, it is the responsibility of the persistence
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.
DI 47: 20090715: Implement IdentifiableType.getIdType() for composite keys
- The following code works fine for single keys such as Integer and @EmbeddedId classes
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
- 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
- UCA: Two threads attempt to modify the PK List in an un-Synchronized state
- Note: The following use cases are covered by this operation.
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.
- Issues
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(); // This iteration is self modifying so keep it out of an Iterator for(int i=0; i<pkList.size(); i++) { if(pkList.get(i).getName().equals(MetadataConstants.MAPPED_SUPERCLASS_RESERVED_PK_NAME)) { pkList.remove(i); // exit loop break; //i=pkList.size() + 1; } } // 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.
- Issues
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.
- Issues
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().size() == 0) { // 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(Iterator<MappingAccessor> anIterator = metadataDescriptor.getAccessors().iterator(); anIterator.hasNext();) { MappingAccessor anAccessor = anIterator.next(); 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.isOneToManyMapping()) { // TODO: verify attributeClass = ((OneToManyMapping)mapping).getReferenceClass(); if(null == attributeClass && validationEnabled) { AbstractSessionLog.getLog().log(SessionLog.FINEST, "metamodel_attribute_class_type_is_null", this); } } else if (mapping.isManyToManyMapping()) { // TODO: verify attributeClass = ((ManyToManyMapping)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
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 not.
- 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.
- - 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
- 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.
- 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 hasDeclaredAttribute(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 hasDeclaredAttribute(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"))); //
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:
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
- The implementation of these 2 functions is currently still in flux and incomplete.
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:
- Refactor the create() method to call the MappedSuperclassTypeImpl constructor as well.
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();
Annotation Processors
We require a design time annotation processing tool to create metamodel class files X_. The APT tool that has shipped since JDK 1.5 will be used to process and create our annotation factories.
APT
APT is the annotation processing tool that has been shipped since Java 5.
Design Criteria
Security
Concurrency
Performance
- The APT tools needs to perform as fast as normal javac compilation.
Configuration
Dependencies
- We have a compile time dependency on tools.jar for the com.sun.mirror packages.
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
Missing Test Model Attributes
- 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
- Entity-->MappedSuperclass(Abstract)-->Entity hierarchy
- 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
- Entity-->Entity hierarchy
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.
- org\eclipse\persistence\sessions
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="${release.version}",${line.separator}" file="META-INF/MANIFEST.MF" append="true"/> ... <echo message=" org.eclipse.persistence.internal.jpa.metamodel;version="${release.version}",${line.separator}" file="META-INF/MANIFEST.MF" append="true"/> ... <echo message=" javax.persistence.criteria;version="[1.99.0,2.1.0)",${line.separator}" file="META-INF/MANIFEST.MF" append="true"/> <echo message=" javax.persistence.metamodel;version="[1.99.0,2.1.0)",${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="${jpaproto.version}"${line.separator}" file="${jpaproto.resource.dir}/MANIFEST.MF" append="true"/> <echo message=" javax.persistence.metamodel;version="${jpaproto.version}"${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
- The EclipseLink descriptor off the session contains all the mappings we require to build implementations of the new Metamodel API interfaces
- 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
- The following issues may have occurred
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)
- See HashMap.put(K,V) "Associates the specified value with the specified key in this map. If the map previously contained a mapping for the key, the old value is replaced" in http://java.sun.com/javase/6/docs/api/java/util/HashMap.html
- 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
- Added 2 new exports for (metamodel and criteria) to the manifest - correction, it is generated - we do this in
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)
- /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/AbstractCollection.java 4265 deleted
- 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
- /trunk/jpa/plugins/javax.persistence/src/javax/persistence/metamodel/PluralAttribute.java 4265 (+56) 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
- http://fisheye2.atlassian.com/browse/eclipselink/trunk/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAccessibleObject.java?r1=4170&r2=4254
- the getLocation() function has changed to a more specific getClass() instead of one of URL, Class, Method or Field or we could use the new ORMetadata.getLocation().
- From: Object key = this.getClassAccessor().getAccessibleObject().getLocation();
- To: Object key = this.getClassAccessor().getAccessibleObject().getClass();
- Attribute.java
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
- 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()
- verify no validation for
- 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
- 456: verify we can skip compositePrimaryKey JoinColumn Metadata processing when
- 9) MetadataAnnotatedElement
- 240: verify Void parameterized generics override
- 10) InheritanceMetadata
- 81: no change to accessor.processInheritancePrimaryKeyJoinColumns();
- 11) MetadataDescriptor
- 285: TODO: workaround for empty table names may need to be higher than in addForeignKeyFieldForMultipleTable
- 872: verify early return with empty DatabaseTable is ok for getPrimaryTable() - same as for aggregateDescriptor
- if(hasDescriptorReservedForMetamodelAsParent()) {
- return new DatabaseTable();
- if(hasDescriptorReservedForMetamodelAsParent()) {
- 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
- By picking up our MS descriptors here - we end up causing them to be processed by initialize() during login() which we do not want
- 972: verify secondary check of our custom map of MappedSuperclass descriptors for entities
- 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)
Appendix B : Deprecated or Resolved Design Notes
Documentation
Wiki and formal documentation links here.
Future Considerations
During the research for this project the following items were identified as out of scope but are captured here as potential future enhancements. If agreed upon during the review process these should be logged in the bug system.