/******************************************************************************* * Copyright (c) 1998, 2015 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<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.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<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(); } }