org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction 180e602
/*******************************************************************************
* Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* 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)
******************************************************************************/
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 m_embeddingAccessors = new HashMap();
/**
* INTERNAL:
*/
public EmbeddableAccessor() {
super("");
}
/**
* 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 embeddingAccessors) {
m_embeddingAccessors.putAll(embeddingAccessors);
}
/**
* INTERNAL:
*/
public void addOwningDescriptor(MetadataDescriptor owningDescriptor) {
getOwningDescriptors().add(owningDescriptor);
}
/**
* INTERNAL:
*/
public void addOwningDescriptors(List 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.isPreProcessed()) {
embeddableAccessor.addEmbeddingAccessor(embeddingAccessor);
embeddableAccessor.addOwningDescriptors(getOwningDescriptors());
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 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 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();
}
}