/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates, IBM Corporation. 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 * 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) * 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/02/2009-2.0 Guy Pelletier * - 278768: JPA 2.0 Association Override Join Table * 03/08/2010-2.1 Michael O'Brien * - 300051: JPA 2.0 Metamodel processing requires EmbeddedId validation moved higher from * EmbeddedIdAccessor.process() to MetadataDescriptor.addAccessor() so we * can better determine when to add the MAPPED_SUPERCLASS_RESERVED_PK_NAME * temporary PK field used to process MappedSuperclasses for the Metamodel API * during MetadataProject.addMetamodelMappedSuperclass() * 04/27/2010-2.1 Guy Pelletier * - 309856: MappedSuperclasses from XML are not being initialized properly * 06/14/2010-2.2 Guy Pelletier * - 264417: Table generation is incorrect for JoinTables in AssociationOverrides * 09/03/2010-2.2 Guy Pelletier * - 317286: DB column lenght not in sync between @Column and @JoinColumn * 12/17/2010-2.2 Guy Pelletier * - 330755: Nested embeddables can't be used as embedded ids * 12/23/2010-2.3 Guy Pelletier * - 331386: NPE when mapping chain of 2 multi-column relationships using JPA 2.0 and @EmbeddedId composite PK-FK * 03/24/2011-2.3 Guy Pelletier * - 337323: Multi-tenant with shared schema support (part 1) * 08/24/2016-2.6 Will Dazey * - 500145: Nested Embeddables with matching attribute names overwrite ******************************************************************************/ package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings; import java.util.Collection; import java.util.HashMap; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAnnotation; import org.eclipse.persistence.mappings.EmbeddableMapping; /** * An embedded id relationship 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 TopLink EJB 3.0 Reference Implementation */ public class EmbeddedIdAccessor extends EmbeddedAccessor { // We store map of fields that are the primary key and add them only at the // end of processing since they may change when processing attribute // overrides. They are mapped by attribute name. protected HashMap<String, DatabaseField> m_idFields = new HashMap<String, DatabaseField>(); protected HashMap<DatabaseField, MappingAccessor> m_idAccessors = new HashMap<DatabaseField, MappingAccessor>(); /** * INTERNAL: * Default constructor. */ public EmbeddedIdAccessor() { super("<embedded-id>"); } /** * INTERNAL: */ public EmbeddedIdAccessor(MetadataAnnotation embeddedId, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) { super(embeddedId, accessibleObject, classAccessor); } /** * INTERNAL: * Process an attribute override for an embedded object, that is, an * aggregate object mapping in EclipseLink. */ @Override protected void addFieldNameTranslation(EmbeddableMapping embeddableMapping, String overrideName, DatabaseField overrideField, MappingAccessor mappingAccessor) { super.addFieldNameTranslation(embeddableMapping, overrideName, overrideField, mappingAccessor); // Update our primary key field with the attribute override field. // The super class will ensure the correct field is on the metadata // column. m_idFields.put(overrideName, overrideField); } /** * INTERNAL: */ protected void addIdFieldFromAccessor(String attributeName, MappingAccessor accessor) { if (m_idFields.containsKey(attributeName)) { // It may be in our id fields map already if an attribute override // was specified on the embedded mapping. Make sure the existing id // field has its mapping accessor associated with it. m_idAccessors.put(m_idFields.get(attributeName), accessor); } else { DatabaseField field = accessor.getMapping().getField(); m_idFields.put(attributeName, field); m_idAccessors.put(field, accessor); } } /** * INTERNAL: */ protected void addIdFieldsFromAccessors(String parentAttribute, Collection<MappingAccessor> accessors) { // Go through all our mappings, the fields from those mappings will // make up the composite primary key. for (MappingAccessor accessor : accessors) { String attributeName = (parentAttribute == null) ? accessor.getAttributeName() : parentAttribute + "." + accessor.getAttributeName(); if (accessor.isBasic()) { addIdFieldFromAccessor(attributeName, accessor); } else if (accessor.isDerivedIdClass() || accessor.isEmbedded()) { // Recursively bury down on the embedded or derived id class accessors. addIdFieldsFromAccessors(attributeName, accessor.getReferenceAccessors()); } else { // EmbeddedId is solely a JPA feature, so we will not allow // the expansion of attributes for those types of Embeddable // classes beyond basics or derived ids as defined in the spec. throw ValidationException.invalidMappingForEmbeddedId(getAttributeName(), getJavaClass(), accessor.getAttributeName(), getReferenceDescriptor().getJavaClass()); } } } /** * INTERNAL: */ @Override public boolean equals(Object objectToCompare) { return super.equals(objectToCompare) && objectToCompare instanceof EmbeddedIdAccessor; } @Override public int hashCode() { return super.hashCode(); } /** * INTERNAL: */ @Override public boolean isEmbeddedId() { return true; } /** * INTERNAL: * Process an EmbeddedId metadata. */ @Override public void process() { // Process the embeddable and our embedded metadata. This must be // done now and before the calls below. super.process(); // After processing the embeddable class, we need to gather our primary // keys fields that we will eventually set on the owning descriptor. if (getReferenceAccessors().isEmpty()) { throw ValidationException.embeddedIdHasNoAttributes(getDescriptor().getJavaClass(), getReferenceDescriptor().getJavaClass(), getReferenceDescriptor().getClassAccessor().getAccessType()); } else { // Go through all our mappings, the fields from those mappings will // make up the composite primary key. addIdFieldsFromAccessors(null, getReferenceAccessors()); // Flag this id accessor as a JPA id mapping. getMapping().setIsJPAId(); // Set the embedded id metadata on all owning descriptors. for (MetadataDescriptor owningDescriptor : getOwningDescriptors()) { // Check if we already processed an Id or IdClass. if (owningDescriptor.hasPrimaryKeyFields()) { throw ValidationException.embeddedIdAndIdAnnotationFound(getJavaClass(), getAttributeName(), owningDescriptor.getIdAttributeName()); } // Set the PK class. owningDescriptor.setPKClass(getReferenceClass()); // Store the embeddedId attribute name. owningDescriptor.setEmbeddedIdAccessor(this); // Add all the fields from the embeddable as primary keys on the // owning metadata descriptor. for (DatabaseField field : m_idFields.values()) { if (! owningDescriptor.getPrimaryKeyFields().contains(field)) { // Set a table if one is not specified. Because embeddables // can be re-used we must deal with clones and not change // the original fields. DatabaseField clone = field.clone(); if (clone.getTableName().equals("")) { clone.setTable(owningDescriptor.getPrimaryTable()); } owningDescriptor.addPrimaryKeyField(clone, m_idAccessors.get(clone)); } } } } } }