/*
* Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// 03/19/2009-2.0 dclarke - initial API start
// 06/30/2009-2.0 mobrien - finish JPA Metadata API modifications in support
// of the Metamodel implementation for EclipseLink 2.0 release involving
// Map, ElementCollection and Embeddable types on MappedSuperclass descriptors
// - 266912: JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API)
// 07/06/2009-2.0 mobrien - Metamodel implementation expansion
// - 282518: Metamodel superType requires javaClass set on custom
// descriptor on MappedSuperclassAccessor.
// 07/10/2009-2.0 mobrien - Adjust BasicType processing to handle non-Entity Java types
// - 266912: As part of Attribute.getType() and specifically SingularAttribute.getBindableJavaType
// set the appropriate elementType based on the mapping type.
// 09/23/2009-2.0 mobrien - 266912: Implement hasSingleIdAttribute() and
// all other 6 remaining methods for Id and Version support.
// DI 70 - 77 and 56
// http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_74:_20090909:_Implement_IdentifiableType.hasSingleIdAttribute.28.29
// 10/14/2009-2.0 mobrien - 285512: managedType(clazz) now throws IAE again for
// any clazz that resolves to a BasicType - use getType(clazz) in implementations instead
// when you are expecting a BasicType
// 08/06/2010-2.2 mobrien 322018 - reduce protected instance variables to private to enforce encapsulation
// 08/06/2010-2.2 mobrien 303063 - handle null descriptor.javaClass passed from the metadata API
// 03/06/2011-2.3 mobrien 338837 - Metamodel entity processing requires specified entities in persistence.xml
// to avoid IllegalArgumentException when accessing an EntityType|EmbeddableType|ManagedType
// "The type [null] is not the expected [EntityType] for the key class" will result in certain managed persistence contexts
package org.eclipse.persistence.internal.jpa.metamodel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.EmbeddableType;
import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.IdentifiableType;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.Metamodel;
import jakarta.persistence.metamodel.Type;
import jakarta.persistence.metamodel.Type.PersistenceType;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jpa.JpaHelper;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sessions.Project;
/**
*
* Purpose : Provides the implementation for the Metamodel interface
* of the JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API)
*
* Description :
* Provides access to the metamodel of persistent
* Entities, MappedSuperclasses, Embeddables, ManagedTypes and Types in the persistence unit.
* Note: Since the types Map is lazy-loaded with key:value pairs - the designer and especially the user
* must realized that a particular BasicType may not be in the Map until it is referenced.
*
* @see jakarta.persistence.metamodel.Metamodel
*
* @since EclipseLink 1.2 - JPA 2.0
*
*/
public class MetamodelImpl implements Metamodel, Serializable {
/** Item 54: DI 89: explicit UID will avoid performance hit runtime generation of one */
private static final long serialVersionUID = -7352420189248464690L;
/** The EclipseLink Session associated with this Metamodel implementation that contains all our descriptors with mappings **/
private AbstractSession session;
/** The Map of entities in this metamodel keyed on Class **/
private Map> entities;
/** The Map of embeddables in this metamodel keyed on Class **/
private Map> embeddables;
/** The Map of managed types (Entity, Embeddable and MappedSuperclass) in this metamodel keyed on Class **/
private Map> managedTypes;
/** The Map of types (Entity, Embeddable, MappedSuperclass and Basic - essentially Basic + managedTypes) in this metamodel keyed on Class **/
private Map> types;
/** The Set of MappedSuperclassTypes in this metamodel**/
private Set> mappedSuperclasses;
/** maintains initialization state to avoid extra work in calls to initialize **/
private boolean isInitialized = false;
/** Default elementType Class when we the type cannot be determined for unsupported mappings such as Transformation and VariableOneToOne */
public static final Class DEFAULT_ELEMENT_TYPE_FOR_UNSUPPORTED_MAPPINGS = Object.class;
public MetamodelImpl(AbstractSession session) {
this.session = session;
preInitialize();
}
public MetamodelImpl(EntityManager em) {
// Create a new Metamodel using the EclipseLink session on the EM
this(JpaHelper.getEntityManager(em).getAbstractSession());
}
public MetamodelImpl(EntityManagerFactory emf) {
// Create a new Metamodel using the EclipseLink session on the EMF
this(((AbstractSession)JpaHelper.getDatabaseSession(emf)));
}
/**
* INTERNAL:
*/
public MetamodelImpl(EntityManagerSetupImpl emSetupImpl) {
// Create a new Metamodel using the EclipseLink session on the EM
this(emSetupImpl.getSession());
}
/**
* Return the metamodel embeddable type representing the
* embeddable class.
* @param clazz the type of the represented embeddable class
* @return the metamodel embeddable type
* @throws IllegalArgumentException if not an embeddable class
*/
@Override
public EmbeddableType embeddable(Class clazz) {
String key = clazz == null ? null : clazz.getName();
EmbeddableTypeImpl> aType = this.embeddables.get(key);
if(aType == null) {
entityEmbeddableManagedTypeNotFound(embeddables, null, clazz, "Embeddable", "EmbeddableType");
}
return (EmbeddableType) aType;
}
/**
* INTERNAL:
* This function will warn the user and/or throw an IllegalArgumentException if the type is not found in the metamodel.
* There is a chance that the entire metamodel is empty because the model was not found during entity search. This is
* usually due to the case where an expected managed persistence unit in a Java EE environment is actually processed as a
* Java SE (unmanaged) persistence unit.
* This may occur on certain configurations of Spring or on Java EE 6 Web Profile implementations that are not in compliance
* with the specification.
* See http://bugs.eclipse.org/338837
*/
private void entityEmbeddableManagedTypeNotFound(Map typeMap, Object aType, Class> clazz, String metamodelType, String metamodelTypeName) {
// 338837: verify that the collection is not empty - this would mean entities did not make it into the search path
if(typeMap.isEmpty()) {
AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.METAMODEL, "metamodel_type_collection_empty_during_lookup", clazz, metamodelTypeName);
}
// A null type will mean either the metamodel type does not exist because it was not found/processed, or the clazz is not part of the model
if(null == clazz) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
"metamodel_class_null_type_instance_for_null_key",
new Object[] { metamodelTypeName, metamodelType}));
} else {
if(null == aType) {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
"metamodel_class_null_type_instance",
new Object[] { clazz.getCanonicalName(), metamodelTypeName, metamodelType}));
} else {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage(
"metamodel_class_incorrect_type_instance",
new Object[] { clazz, metamodelTypeName, aType}));
}
}
}
/**
* Return the metamodel entity type representing the entity.
* @param clazz the type of the represented entity
* @return the metamodel entity type
* @throws IllegalArgumentException if not an entity
*/
@Override
public EntityType entity(Class clazz) {
String key = clazz == null ? null : clazz.getName();
EntityTypeImpl> aType = this.entities.get(key);
if(aType == null) {
entityEmbeddableManagedTypeNotFound(entities, null, clazz, "Entity", "EntityType");
}
return (EntityType) aType;
}
/**
* INTERNAL:
* Return a List of all attributes for all ManagedTypes.
*/
public List getAllManagedTypeAttributes() {
List attributeList = new ArrayList();
for(ManagedType managedType : this.managedTypes.values()) {
attributeList.addAll(managedType.getAttributes());
}
return attributeList;
}
/**
* Return the metamodel embeddable types.
* @return the metamodel embeddable types
*/
@Override
public Set> getEmbeddables() {
return new LinkedHashSet>(this.embeddables.values());
}
/**
* Return the metamodel entity types.
* @return the metamodel entity types
*/
@Override
public Set> getEntities() {
return new LinkedHashSet>(this.entities.values());
}
/**
* Return the metamodel managed types map.
*/
public Map> getManagedTypesMap() {
return this.managedTypes;
}
/**
* Return the metamodel managed types.
* @return the metamodel managed types
*/
@Override
public Set> getManagedTypes() {
return new LinkedHashSet>(this.managedTypes.values());
}
/**
* INTERNAL:
* Return the Set of MappedSuperclassType objects
*/
public Set> getMappedSuperclasses() {
return new LinkedHashSet>(this.mappedSuperclasses);
}
/**
* INTERNAL:
* Return the core API Project associated with the DatabaseSession
* that is associated with this Metamodel
*/
public Project getProject() {
return this.getSession().getProject();
}
/**
* INTERNAL:
* Return the DatabaseSession associated with this Metamodel
*/
protected AbstractSession getSession() {
return this.session;
}
/**
* INTERNAL:
* This function is a wrapper around a Map.put(K,V)
* We return a boolean that is unused but provides a way to add a
* breakpoint for the false condition.
*/
private boolean putType(String javaClassKey, TypeImpl typeValue) {
boolean isValid = true;
// DI99: Check for an invalid key without reporting it (a non-Fail-Fast pattern)
if(null == javaClassKey) {
// Use Case: doing an emf.getCriteriaBuilder() before an EM has been created
isValid = false;
}
this.types.put(javaClassKey, typeValue);
return isValid;
}
/**
* INTERNAL:
* Return a Type representation of a java Class for use by the Metamodel Attributes.
* If a type does not yet exist - one will be created and added to the Metamodel - this usually only for Basic types.
* This function will handle all Metamodel defined and core java classes.
*
*/
public TypeImpl getType(Class javaClass) {
// Return an existing matching type on the metamodel keyed on class name
String key = javaClass == null ? null : javaClass.getName();
TypeImpl type = this.types.get(key);
// the type was not cached yet on the metamodel - lets add it - usually a non Metamodel class like Integer
if (null == type) {
// make types field modification thread-safe
synchronized (this.types) {
// check for a cached type right after we synchronize
type = this.types.get(key);
// If a type is not found (not created during metamodel.initialize() - it is usually a Basic type
if(null == type) {
type = new BasicTypeImpl(javaClass);
// add the type to the types map keyed on Java class
putType(key, type);
}
} // synchronized end
}
return type;
}
/**
* INTERNAL:
* Return the Map of types on this metamodel.
* This includes all Entity, MappedSuperclass, Embeddable and Basic types
*/
public Map> getTypes() {
return types;
}
/**
* INTERNAL:
* Return whether there is a descriptor that is keyed by the supplied class name.
* Referenced by ManagedTypeImpl.create()
*/
protected boolean hasMappedSuperclass(String qualifiedClassNameKeyString) {
/**
* This function is used before the metamodel has populated its Set of mappedSuperclasses -
* therefore we go directly to the descriptor source.
* Normally this functionality would be placed on the (core) Project class, however
* this would create a JPA dependency in Core when we try to use MetadataClass functionality there.
*/
return this.getSession().getProject().hasMappedSuperclass(qualifiedClassNameKeyString);
}
public boolean isInitialized() {
return isInitialized;
}
/**
* INTERNAL:
* First phase of metamodel intialization. Builds a list of the classes in the metamodel and
* stores them in appropriate lists indexed by the classname. We index by classname to avoid having
* to load classes at this phase of initialization
*/
private void preInitialize(){
// Design Note: Use LinkedHashMap and LinkedHashSet to preserve ordering
this.types = new LinkedHashMap>();
this.entities = new LinkedHashMap>();
this.embeddables = new LinkedHashMap>();
this.managedTypes = new LinkedHashMap>();
this.mappedSuperclasses = new LinkedHashSet>();
// Process all Entity and Embeddable types (MappedSuperclasses are handled later)
for (ClassDescriptor descriptor : this.getSession().getProject().getOrderedDescriptors()) {
// The ClassDescriptor is always of type RelationalDescriptor - the cast is safe
ManagedTypeImpl> managedType = ManagedTypeImpl.create(this, descriptor);
String descriptorJavaType = managedType.getJavaTypeName();
if(null == descriptorJavaType) {
AbstractSessionLog.getLog().log(SessionLog.FINEST, SessionLog.METAMODEL, "metamodel_relationaldescriptor_javaclass_null_on_managedType", descriptor, managedType);
}
putType(descriptorJavaType, managedType);
this.managedTypes.put(descriptorJavaType, managedType);
if (managedType.getPersistenceType().equals(PersistenceType.ENTITY)) {
this.entities.put(descriptorJavaType, (EntityTypeImpl>) managedType);
}
if (managedType.getPersistenceType().equals(PersistenceType.EMBEDDABLE)) {
this.embeddables.put(descriptorJavaType, (EmbeddableTypeImpl>) managedType);
}
// Process all Basic Types
// Iterate by typeName
// see
// http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_54:_20090803:_Metamodel.type.28Clazz.29_should_differentiate_between_null_and_BasicType
}
// Future: verify that all entities or'd with embeddables matches the number of types
// Handle all MAPPED_SUPERCLASS types
// Get mapped superclass types (separate from descriptors on the session from the native project (not a regular descriptor)
for (ClassDescriptor descriptor : this.getSession().getProject().getMappedSuperclassDescriptors().values()) {
MappedSuperclassTypeImpl> mappedSuperclassType = (MappedSuperclassTypeImpl)ManagedTypeImpl.create(this, descriptor);
// Add the MappedSuperclass to our Set of MappedSuperclasses
this.mappedSuperclasses.add(mappedSuperclassType);
String descriptorJavaType = mappedSuperclassType.getJavaTypeName();
if(null == descriptorJavaType) {
AbstractSessionLog.getLog().log(SessionLog.FINEST, SessionLog.METAMODEL, "metamodel_relationaldescriptor_javaclass_null_on_managedType",
descriptor, mappedSuperclassType);
}
// Add this MappedSuperclass to the Collection of Types
putType(descriptorJavaType, mappedSuperclassType);
// Add the MappedSuperclass to the Map of ManagedTypes
// So we can find hierarchies of the form [Entity --> MappedSuperclass(abstract) --> Entity]
this.managedTypes.put(descriptorJavaType, mappedSuperclassType);
}
}
/**
* INTERNAL:
*
* Initialize the JPA metamodel that wraps the EclipseLink JPA metadata created descriptors.
* Note: Since the types Map is lazy-loaded with key:value pairs - the designer and especially the user
* must realized that a particular BasicType may not be in the Map until it is referenced.
*
* Also note that a transient superclass (non-entity, non-mappedSuperclass)
* exists as a BasicType (it has no attributes), and that any inheriting Entity either
* directly subclassing or indirectly subclassing via a MappedSuperclass inheritance chain
* - does not pick up non-persistence fields that normally would be inherited.
* (The fields exist in Java but not in ORM:Metamodel)
* The transient class will have no JPA annotations.
*
* This is the second phase of metamodel initialization. It causes preindexed classes to have their
* attributes populated.
*/
public void initialize(ClassLoader classLoader) {
// Handle all IdentifiableTypes (after all ManagedTypes have been created)
// Assign all superType fields on all IdentifiableTypes (only after all managedType objects have been created)
for(ManagedTypeImpl> potentialIdentifiableType : managedTypes.values()) {
Class> aClass = potentialIdentifiableType.getJavaType(classLoader);
/**
* The superclass for top-level types is Object - however we set [null] as the supertype for root types.
* 1) We are constrained by the fact that the spec requires that a superType be an IdentifiableType.
* Since [Object] is not an Entity or MappedSuperclass - it fails this criteria - as it would be a BasicType
* because it has no @Entity or @MappedSuperclass annotation.
* 2) Another object space reasoning issue behind this is to separate the Java and Metamodel object spaces.
* In Java all types inherit from Object, however in the JPA Metamodel all types DO NOT inherit from a common type.
* Therefore in the metamodel top-level root types have a superType of null.
* See design issue discussion:
* http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_42:_20090709:_IdentifiableType.supertype_-_what_do_top-level_types_set_it_to
*/
// 303063: secondary check for a null javaType (metadata processing should never allow this to happen - however we must handle it here in case
// http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_101:_20100218:_Descriptor.javaClass_is_null_on_a_container_EM_for_a_specific_case
if(null == aClass) {
AbstractSessionLog.getLog().log(SessionLog.FINEST, SessionLog.METAMODEL, "metamodel_itentifiableType_javaclass_null_cannot_set_supertype",
potentialIdentifiableType.getDescriptor(), this);
} else {
Class> superclass = aClass.getSuperclass();
// explicitly set the superType to null (just in case it is initialized to a non-null value in a constructor)
IdentifiableType> identifiableTypeSuperclass = null;
if(potentialIdentifiableType.isIdentifiableType() && (superclass != ClassConstants.OBJECT && superclass != null)) {
// Get the Entity or MappedSuperclass
// A hierarchy of Entity --> Entity or Entity --> MappedSuperclass will be found
identifiableTypeSuperclass = (IdentifiableType>)managedTypes.get(superclass.getName());
// If there is no superclass (besides Object for a top level identifiable type) then keep the supertype set to null
// See design issue #42 - we return Object for top-level types (with no superclass) and null if the supertype was not set
// http://wiki.eclipse.org/EclipseLink/Development/JPA_2.0/metamodel_api#DI_42:_20090709:_IdentifiableType.supertype_-_what_do_top-level_types_set_it_to
((IdentifiableTypeImpl)potentialIdentifiableType).setSupertype(identifiableTypeSuperclass);
// set back pointer if mappedSuperclass
if(null != identifiableTypeSuperclass && ((IdentifiableTypeImpl)identifiableTypeSuperclass).isMappedSuperclass()) {
((MappedSuperclassTypeImpl)identifiableTypeSuperclass).addInheritingType(((IdentifiableTypeImpl)potentialIdentifiableType));
}
((IdentifiableTypeImpl)potentialIdentifiableType).setSupertype(identifiableTypeSuperclass);
}
}
}
//1 - process all non-mappedSuperclass types first so we pick up attribute types
//2 - process mappedSuperclass types and lookup collection attribute types on inheriting entity types when field is not set
/**
* Delayed-Initialization (process all mappings) of all Managed types
* (This includes all IdentifiableTypes = Entity and MappedSuperclass types).
* To avoid a ConcurrentModificationException on the types map, iterate a list instead of the Map values directly.
* The following code section may add BasicTypes to the types map.
*/
for(ManagedTypeImpl> managedType : new ArrayList>(managedTypes.values())) {
managedType.initialize();
}
// 3 - process all the Id attributes on each IdentifiableType
for(ManagedTypeImpl> potentialIdentifiableType : managedTypes.values()) {
if(potentialIdentifiableType.isIdentifiableType()) {
((IdentifiableTypeImpl>)potentialIdentifiableType).initializeIdAttributes();
}
}
// 338837: verify that the collections are not empty - this would mean entities did not make it into the search path
if((null == this.embeddables || this.embeddables.isEmpty()) &&
(null == this.managedTypes || this.managedTypes.isEmpty()) &&
(null == this.entities || this.entities.isEmpty())) {
AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.METAMODEL, "metamodel_type_collection_empty", null, true);
}
isInitialized = true;
}
/**
* Return the metamodel managed type representing the
* entity, mapped superclass, or embeddable class.
* @param clazz the type of the represented managed class
* @return the metamodel managed type
* @throws IllegalArgumentException if not a managed class
*/
@Override
public ManagedType managedType(Class clazz) {
String key = clazz == null ? null : clazz.getName();
ManagedTypeImpl> aType = this.managedTypes.get(key);
// Throw an IAE exception if the returned type is not a ManagedType
// For any clazz that resolves to a BasicType - use getType(clazz) in implementations when you are expecting a BasicType
if (aType == null) {
entityEmbeddableManagedTypeNotFound(managedTypes, null, clazz, "Managed", "ManagedType");
}
return (ManagedType) aType;
}
/**
* INTERNAL:
* Print out all the Type attributes in the Metamodel
*/
public void printAllTypes() {
if ((null == types) || types.isEmpty()) {
// 338837: verify that the collection is not empty - this would mean entities did not make it into the search path
AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.METAMODEL, "metamodel_type_collection_empty", null, true);
} else {
this.session.log(SessionLog.INFO, SessionLog.METAMODEL, "metamodel_print_type_header",this.types.size());
for (Type aType : this.types.values()) {
this.session.log(SessionLog.INFO, SessionLog.METAMODEL, "metamodel_print_type_value",aType);
}
}
}
/**
* INTERNAL:
* Return the string representation of the receiver.
*/
@Override
public String toString() {
StringBuffer aBuffer = new StringBuffer();
aBuffer.append(this.getClass().getSimpleName());
aBuffer.append("@");
aBuffer.append(hashCode());
aBuffer.append(" [");
if(null != this.types) {
aBuffer.append(" ");
aBuffer.append(this.types.size());
aBuffer.append(" Types: ");
//aBuffer.append(this.types.keySet());
}
if(null != this.managedTypes) {
aBuffer.append(", ");
aBuffer.append(this.managedTypes.size());
aBuffer.append(" ManagedTypes: ");
//aBuffer.append(this.managedTypes.keySet());
}
if(null != this.entities) {
aBuffer.append(", ");
aBuffer.append(this.entities.size());
aBuffer.append(" EntityTypes: ");
//aBuffer.append(this.entities.keySet());
}
if(null != this.mappedSuperclasses) {
aBuffer.append(", ");
aBuffer.append(this.mappedSuperclasses.size());
aBuffer.append(" MappedSuperclassTypes: ");
//aBuffer.append(this.mappedSuperclasses);
}
if(null != this.embeddables) {
aBuffer.append(", ");
aBuffer.append(this.embeddables.size());
aBuffer.append(" EmbeddableTypes: ");
//aBuffer.append(this.embeddables.keySet());
}
aBuffer.append("]");
return aBuffer.toString();
}
}