blob: 6941f1de79654dbb086750c7ca452dbf4d8be7b2 [file] [log] [blame]
/*
* Copyright (c) 1998, 2020 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:
// Oracle - initial API and implementation from Oracle TopLink
// 05/16/2008-1.0M8 Guy Pelletier
// - 218084: Implement metadata merging functionality between mapping files
// 05/23/2008-1.0M8 Guy Pelletier
// - 211330: Add attributes-complete support to the EclipseLink-ORM.XML Schema
// 07/15/2008-1.0.1 Guy Pelletier
// - 240679: MappedSuperclass Id not picked when on get() method accessor
// 09/23/2008-1.1 Guy Pelletier
// - 241651: JPA 2.0 Access Type support
// 10/01/2008-1.1 Guy Pelletier
// - 249329: To remain JPA 1.0 compliant, any new JPA 2.0 annotations should be referenced by name
// 01/28/2009-2.0 Guy Pelletier
// - 248293: JPA 2.0 Element Collections (part 1)
// 02/06/2009-2.0 Guy Pelletier
// - 248293: JPA 2.0 Element Collections (part 2)
// 02/26/2009-2.0 Guy Pelletier
// - 264001: dot notation for mapped-by and order-by
// 03/27/2009-2.0 Guy Pelletier
// - 241413: JPA 2.0 Add EclipseLink support for Map type attributes
// 04/03/2009-2.0 Guy Pelletier
// - 241413: JPA 2.0 Add EclipseLink support for Map type attributes
// 04/24/2009-2.0 Guy Pelletier
// - 270011: JPA 2.0 MappedById support
// 06/25/2009-2.0 Michael O'Brien
// - 266912: change MappedSuperclass handling in stage2 to pre process accessors
// in support of the custom descriptors holding mappings required by the Metamodel
// 04/09/2010-2.1 Guy Pelletier
// - 307050: Add defaults for access methods of a VIRTUAL access type
// 05/04/2010-2.1 Guy Pelletier
// - 309373: Add parent class attribute to EclipseLink-ORM
// 05/14/2010-2.1 Guy Pelletier
// - 253083: Add support for dynamic persistence using ORM.xml/eclipselink-orm.xml
// 06/01/2010-2.1 Guy Pelletier
// - 315195: Add new property to avoid reading XML during the canonical model generation
// 06/14/2010-2.2 Guy Pelletier
// - 264417: Table generation is incorrect for JoinTables in AssociationOverrides
// 07/05/2010-2.1.1 Guy Pelletier
// - 317708: Exception thrown when using LAZY fetch on VIRTUAL mapping
// 09/16/2010-2.2 Guy Pelletier
// - 283028: Add support for letting an @Embeddable extend a @MappedSuperclass
// 12/01/2010-2.2 Guy Pelletier
// - 331234: xml-mapping-metadata-complete overriden by metadata-complete specification
// 12/02/2010-2.2 Guy Pelletier
// - 251554: ExcludeDefaultMapping annotation needed
// 03/24/2011-2.3 Guy Pelletier
// - 337323: Multi-tenant with shared schema support (part 1)
// 09/11/2017-2.1 Will Dazey
// - 520387: multiple owning descriptors for an embeddable are not set
package org.eclipse.persistence.internal.jpa.metadata.accessors.classes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.annotations.Cache;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor;
import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
import org.eclipse.persistence.internal.jpa.metadata.MetadataProject;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAnnotation;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_FIELD;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_PROPERTY;
/**
* INTERNAL:
* An embeddable accessor.
*
* Key notes:
* - any metadata mapped from XML to this class must be compared in the
* equals method.
* - any metadata mapped from XML to this class must be handled in the merge
* method. (merging is done at the accessor/mapping level)
* - any metadata mapped from XML to this class must be initialized in the
* initXMLObject method.
* - methods should be preserved in alphabetical order.
*
* @author Guy Pelletier
* @since EclipseLink 1.0
*/
public class EmbeddableAccessor extends ClassAccessor {
// Embedding accessors is a map of those classes that embed this embeddable.
// All embedding accessors are owning descriptors, but not vice versa.
private Map<String, ClassAccessor> m_embeddingAccessors = new HashMap<String, ClassAccessor>();
/**
* INTERNAL:
*/
public EmbeddableAccessor() {
super("<embeddable>");
}
/**
* INTERNAL:
*/
public EmbeddableAccessor(MetadataAnnotation annotation, MetadataClass cls, MetadataProject project) {
super(annotation, cls, project);
}
/**
* INTERNAL:
* Embedding accessors are those accessors that actually embed the
* embeddable class with an embedded mapping. We use this list to extract
* and validate the access type of this embeddable when the embeddable
* does not specify an explicit access type.
*/
protected void addEmbeddingAccessor(ClassAccessor embeddingAccessor) {
m_embeddingAccessors.put(embeddingAccessor.getJavaClassName(), embeddingAccessor);
}
/**
* INTERNAL:
*/
public void addEmbeddingAccessors(Map<String, ClassAccessor> embeddingAccessors) {
m_embeddingAccessors.putAll(embeddingAccessors);
}
/**
* INTERNAL:
*/
public void addOwningDescriptor(MetadataDescriptor owningDescriptor) {
getOwningDescriptors().add(owningDescriptor);
}
/**
* INTERNAL:
*/
public void addOwningDescriptors(List<MetadataDescriptor> owningDescriptors) {
getOwningDescriptors().addAll(owningDescriptors);
}
/**
* INTERNAL
* Ensure any embeddable classes that are discovered during pre-process
* are added to the project. The newly discovered embeddable accesors will
* also be pre-processed now as well.
*/
@Override
protected void addPotentialEmbeddableAccessor(MetadataClass potentialEmbeddableClass, ClassAccessor embeddingAccessor) {
if (potentialEmbeddableClass != null) {
// Get embeddable accessor will add the embeddable to the project
// if it is a valid embeddable. That is, if the class has an
// Embeddable annotation of the class is used as an IdClass for
// another entity within the persistence unit.
EmbeddableAccessor embeddableAccessor = getProject().getEmbeddableAccessor(potentialEmbeddableClass, true);
if (embeddableAccessor != null) {
embeddableAccessor.addEmbeddingAccessor(embeddingAccessor);
embeddableAccessor.addOwningDescriptors(getOwningDescriptors());
if(!embeddableAccessor.isPreProcessed()) {
embeddableAccessor.preProcess();
}
}
}
}
/**
* INTERNAL:
* Build a list of classes that are decorated with a MappedSuperclass
* annotation or that are tagged as a mapped-superclass in an XML document.
*
* This method will also do a couple other things as well since we are
* traversing the parent classes:
* - Build a map of generic types specified and will be used to resolve
* actual class types for mappings.
* - save mapped-superclass descriptors on the project for later use
* by the Metamodel API
*
* We don't support embeddable inheritance yet. When that is added, this
* method will need to change and in fact we may be able to re-use the
* existing discover method from EntityAccessor (with minor tweaks).
*/
protected void discoverMappedSuperclassesAndInheritanceParents(boolean addMappedSuperclassAccessors) {
// Clear any previous discovery.
clearMappedSuperclassesAndInheritanceParents();
MetadataClass parentClass = getJavaClass().getSuperclass();
List<String> genericTypes = getJavaClass().getGenericType();
while (parentClass != null && ! parentClass.isObject()) {
// Our parent might be a mapped superclass, check and add as needed.
addPotentialMappedSuperclass(parentClass, addMappedSuperclassAccessors);
// Resolve any generic types from the generic parent onto the
// current entity accessor.
resolveGenericTypes(genericTypes, parentClass);
// Grab the generic types from the parent class.
genericTypes = parentClass.getGenericType();
// Finally, get the next parent and keep processing ...
parentClass = parentClass.getSuperclass();
}
}
/**
* INTERNAL:
*/
public Map<String, ClassAccessor> getEmbeddingAccessors() {
return m_embeddingAccessors;
}
/**
* INTERNAL:
* So, here's the deal ... this method typically gets called when defaulting
* pk's name, primary table names etc. for various mappings. The problem
* however is that we go beyond the spec and allow more mappings to be
* specified on embeddable classes. For example, a M-M would default its
* join table and join columns using the info from its owning descriptor.
* The problem then is ... what to do when this embedabble has multiple
* owning descriptors? That is, is shared. Right now, their pk names from
* the owners better be same or mappings must be fully specified and not use
* any defaults. I think that is somewhat ok given we're going beyond the
* spec and TopLink doesn't even support it anyway???
*
* So the stance is, we'll allow the extra mappings on embeddables that are
* not shared, however on shared cases there are restrictions. Users should
* use mapped superclasses when they have a need to share complex
* embeddables.
*
* Or they can write customizers to modify their embeddable descriptors
* after initialize (after they have been cloned)
*
* Future: the metadata processing 'could' set all necessary (per owning
* descriptor) metadata and have the descriptor initialize code handle it.
* Metadata processing would process embeddable classes as it currently does
* for MappedSuperclasses. Clone them and process under each owning entity
* context. At descriptor initialize time, we would avoid cloning the
* aggregate descriptor and use the one metadata processing provided.
* Investigate further at a later date ...
*
* Callers to this method are ...
* BasicCollectionAccessor - processCollectionTable - defaults pk names from the owning descriptor.
* RelationshipAccessor - processJoinTable - defaults the join table name and the source field name
* OneToManyAccessor - processUnidirectionalOneToManyMapping - defaults the pk field and table.
* MappingAccessor - processAssociationOverride and updatePrimaryKeyField.
* ObjectAccessor - processId
*/
@Override
public MetadataDescriptor getOwningDescriptor() {
if (getOwningDescriptors() != null && getOwningDescriptors().size() > 0) {
// Return the first owning descriptor. In most cases this will be OK
// since in most cases there is only one.
return getOwningDescriptors().get(0);
}
return this.getDescriptor();
}
/**
* INTERNAL:
* Return true if this accessor represents an embeddable accessor.
*/
@Override
public boolean isEmbeddableAccessor() {
return true;
}
/**
* INTERNAL:
* The pre-process method is called during regular deployment and metadata
* processing.
*
* This method is called after each entity of the persistence unit has had
* an opportunity to pre-process itself first since we'll rely on owning
* entities for things like access type etc. The pre-process will run some
* validation.
*
* The order of processing is important, care must be taken if changes must
* be made.
*/
@Override
public void preProcess() {
// Perform the parent discovery process before processing any further.
discoverMappedSuperclassesAndInheritanceParents(true);
// Process the correct access type before any other processing.
processAccessType();
// Process a virtual class specification after determining access type.
processVirtualClass();
// Process the default access methods after determining access type.
processAccessMethods();
// Process a @Struct and @EIS annotation to create the correct type of descriptor.
processStruct();
processNoSql();
// Process our parents metadata after processing our own.
super.preProcess();
}
/**
* INTERNAL:
* The pre-process for canonical model method is called (and only called)
* during the canonical model generation. The use of this pre-process allows
* us to remove some items from the regular pre-process that do not apply
* to the canonical model generation.
*
* The order of processing is important, care must be taken if changes must
* be made.
*/
@Override
public void preProcessForCanonicalModel() {
// Perform the parent discovery process before processing any further.
discoverMappedSuperclassesAndInheritanceParents(false);
// Process our parents metadata after processing our own.
super.preProcessForCanonicalModel();
}
/**
* INTERNAL
* Sub classes (Entity and Embeddable) must override this method to control
* the metadata that is processed for their context.
*/
@Override
protected void preProcessMappedSuperclassMetadata(MappedSuperclassAccessor mappedSuperclass) {
// Process the global converters.
mappedSuperclass.processConverters();
// Add the accessors and converters from this mapped superclass.
mappedSuperclass.addAccessors();
}
/**
* INTERNAL:
* Process the metadata from this embeddable class.
*/
@Override
public void process() {
// If a Cache annotation is present throw an exception.
if (isAnnotationPresent(Cache.class)) {
throw ValidationException.cacheNotSupportedWithEmbeddable(getJavaClass());
}
// Process our parents metadata after processing our own.
super.process();
// Process the mapping accessors on this embeddable now.
processMappingAccessors();
}
/**
* INTERNAL:
* For VIRTUAL access we need to look for default access methods that we
* need to use with our mapping attributes.
*/
public void processAccessMethods() {
// If we use virtual access and do not have any access methods
// specified then get the default access methods from our owning
// entity.
if (hasAccessMethods()) {
getDescriptor().setDefaultAccessMethods(getAccessMethods());
} else {
// The embeddable does not define default access methods. We
// need to look at our owning entities and 1) validate that
// they all use the same default access methods and 2) grab
// the default access methods from them.
ClassAccessor embeddingAccessor = null;
for (ClassAccessor currentEmbeddingAccessor : m_embeddingAccessors.values()) {
if (embeddingAccessor == null) {
embeddingAccessor = currentEmbeddingAccessor;
continue;
}
if (! embeddingAccessor.getDescriptor().getDefaultAccessMethods().equals(currentEmbeddingAccessor.getDescriptor().getDefaultAccessMethods())) {
throw ValidationException.conflictingAccessMethodsForEmbeddable(getJavaClassName(), embeddingAccessor.getJavaClassName(), embeddingAccessor.getDescriptor().getDefaultAccessMethods(), currentEmbeddingAccessor.getJavaClassName(), currentEmbeddingAccessor.getDescriptor().getDefaultAccessMethods());
}
}
// Set the default access methods on the descriptor and log a
// message if there is an embedding accessor. Otherwise we'll
// use the default set on our descriptor.
if (embeddingAccessor != null) {
getDescriptor().setDefaultAccessMethods(embeddingAccessor.getDescriptor().getDefaultAccessMethods());
}
}
}
/**
* INTERNAL:
* Process the access type of this embeddable. If this embeddable is not
* embedded by at least one entity, it will not be processed. Therefore,
* embedding accessors can not be empty at this point.
*/
@Override
protected void processAccessType() {
// Validate that this embeddable is not used within entities with
// conflicting access types when the embeddable doesn't have its own
// explicit setting. When the embeddable is shared, the access type
// must be the same across the board.
if (! hasAccess()) {
ClassAccessor embeddingAccessor = null;
// The access type of the embeddable is determined from the class
// that is embedding it. If there are multiple embedding
// accessors we will validate that there are no conflicting types.
for (ClassAccessor currentEmbeddingAccessor : m_embeddingAccessors.values()) {
if (embeddingAccessor == null) {
embeddingAccessor = currentEmbeddingAccessor;
continue;
}
if (! embeddingAccessor.getAccessType().equals(currentEmbeddingAccessor.getAccessType())) {
throw ValidationException.conflictingAccessTypeForEmbeddable(getJavaClassName(), embeddingAccessor.getJavaClassName(), embeddingAccessor.getAccessType(), currentEmbeddingAccessor.getJavaClassName(), currentEmbeddingAccessor.getAccessType());
}
}
// Set the default access type on the descriptor and log a message
// that we are defaulting the access type for this embeddable.
if (embeddingAccessor == null) {
// We don't have an owning entity (only possible during
// canonical model generation) so look at the mapped
// superclasses. Ultimate default will be FIELD.
String defaultAccessType = JPA_ACCESS_FIELD;
for (MappedSuperclassAccessor mappedSuperclass : getMappedSuperclasses()) {
if (! mappedSuperclass.hasAccess()) {
if (mappedSuperclass.hasObjectRelationalFieldMappingAnnotationsDefined()) {
defaultAccessType = JPA_ACCESS_FIELD;
} else if (mappedSuperclass.hasObjectRelationalMethodMappingAnnotationsDefined()) {
defaultAccessType = JPA_ACCESS_PROPERTY;
}
break;
}
}
getDescriptor().setDefaultAccess(defaultAccessType);
} else {
// Use the access type from the embedding accessor.
getDescriptor().setDefaultAccess(embeddingAccessor.getAccessType());
}
getLogger().logConfigMessage(MetadataLogger.ACCESS_TYPE, getDescriptor().getDefaultAccess(), getJavaClass());
getDescriptor().setAccessTypeOnClassDescriptor(getAccessType());
}
}
/**
* INTERNAL
* From an embeddable we need pair down what we process as things like
* ID metadata does not apply.
*/
@Override
protected void processMappedSuperclassMetadata(MappedSuperclassAccessor mappedSuperclass) {
// Process the attribute override metadata.
mappedSuperclass.processAttributeOverrides();
// Process the association override metadata.
mappedSuperclass.processAssociationOverrides();
// Process the change tracking metadata.
mappedSuperclass.processChangeTracking();
// Process the customizer metadata.
mappedSuperclass.processCustomizer();
// Process the copy policy metadata.
mappedSuperclass.processCopyPolicy();
// Process the property metadata.
mappedSuperclass.processProperties();
}
}