| /******************************************************************************* |
| * 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)); |
| } |
| } |
| } |
| } |
| } |
| } |