| /* |
| * 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: |
| // 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/25/2009-2.0 Guy Pelletier |
| // - 265359: JPA 2.0 Element Collections - Metadata processing portions |
| // 03/27/2009-2.0 Guy Pelletier |
| // - 241413: JPA 2.0 Add EclipseLink support for Map type attributes |
| // 06/02/2009-2.0 Guy Pelletier |
| // - 278768: JPA 2.0 Association Override Join Table |
| // 11/06/2009-2.0 Guy Pelletier |
| // - 286317: UniqueConstraint xml element is changing (plus couple other fixes, see bug) |
| // 03/08/2010-2.1 Guy Pelletier |
| // - 303632: Add attribute-type for mapping attributes to EclipseLink-ORM |
| // 03/29/2010-2.1 Guy Pelletier |
| // - 267217: Add Named Access Type to EclipseLink-ORM |
| // 04/27/2010-2.1 Guy Pelletier |
| // - 309856: MappedSuperclasses from XML are not being initialized properly |
| // 12/30/2010-2.3 Guy Pelletier |
| // - 312253: Descriptor exception with Embeddable on DDL gen |
| // 03/24/2011-2.3 Guy Pelletier |
| // - 337323: Multi-tenant with shared schema support (part 1) |
| // 07/03/2011-2.3.1 Guy Pelletier |
| // - 348756: m_cascadeOnDelete boolean should be changed to Boolean |
| // // 30/05/2012-2.4 Guy Pelletier |
| // - 354678: Temp classloader is still being used during metadata processing |
| // 10/25/2012-2.5 Guy Pelletier |
| // - 3746888: JPA 2.1 Converter support |
| // 11/28/2012-2.5 Guy Pelletier |
| // - 374688: JPA 2.1 Converter support |
| // 07/16/2013-2.5.1 Guy Pelletier |
| // - 412384: Applying Converter for parameterized basic-type for joda-time's DateTime does not work |
| package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings; |
| |
| import org.eclipse.persistence.annotations.BatchFetch; |
| import org.eclipse.persistence.annotations.CascadeOnDelete; |
| import org.eclipse.persistence.annotations.JoinFetch; |
| import org.eclipse.persistence.annotations.Noncacheable; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor; |
| import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger; |
| 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.internal.jpa.metadata.mappings.BatchFetchMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.tables.CollectionTableMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings; |
| |
| import org.eclipse.persistence.mappings.AggregateCollectionMapping; |
| import org.eclipse.persistence.mappings.AggregateMapping; |
| import org.eclipse.persistence.mappings.CollectionMapping; |
| import org.eclipse.persistence.mappings.ContainerMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.DirectCollectionMapping; |
| import org.eclipse.persistence.mappings.DirectMapMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.mappings.foundation.AbstractCompositeDirectCollectionMapping; |
| |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_FETCH_LAZY; |
| |
| /** |
| * An abstract direct collection accessor. |
| * |
| * Used to support DirectCollection, DirectMap, AggregateCollection through |
| * the ElementCollection, BasicCollection and BasicMap metadata. |
| * |
| * 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.2 |
| */ |
| public abstract class DirectCollectionAccessor extends DirectAccessor { |
| private BatchFetchMetadata m_batchFetch; |
| private Boolean m_cascadeOnDelete; |
| private Boolean m_nonCacheable; |
| private String m_joinFetch; |
| private CollectionTableMetadata m_collectionTable; |
| |
| /** |
| * INTERNAL: |
| */ |
| protected DirectCollectionAccessor(String xmlElement) { |
| super(xmlElement); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected DirectCollectionAccessor(MetadataAnnotation annotation, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) { |
| super(annotation, accessibleObject, classAccessor); |
| |
| // Set the fetch type. A basic map may have no annotation (will default). |
| if (annotation != null) { |
| setFetch(annotation.getAttributeString("fetch")); |
| } |
| |
| // Set the join fetch if one is present. |
| if (isAnnotationPresent(JoinFetch.class)) { |
| m_joinFetch = getAnnotation(JoinFetch.class).getAttributeString("value"); |
| } |
| |
| // Set the batch fetch if one is present. |
| if (isAnnotationPresent(BatchFetch.class)) { |
| m_batchFetch = new BatchFetchMetadata(getAnnotation(BatchFetch.class), this); |
| } |
| |
| // Set the cascade on delete if specified. |
| m_cascadeOnDelete = isAnnotationPresent(CascadeOnDelete.class); |
| |
| // Set the non cacheable if specified. |
| m_nonCacheable = isAnnotationPresent(Noncacheable.class); |
| |
| // Since BasicCollection and ElementCollection look for different |
| // collection tables, we will not initialize/look for one here. Those |
| // accessors will be responsible for loading their collection table. |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean equals(Object objectToCompare) { |
| if (super.equals(objectToCompare) && objectToCompare instanceof DirectCollectionAccessor) { |
| DirectCollectionAccessor directCollectionAccessor = (DirectCollectionAccessor) objectToCompare; |
| |
| if (! valuesMatch(m_joinFetch, directCollectionAccessor.getJoinFetch())) { |
| return false; |
| } |
| |
| if (! valuesMatch(m_batchFetch, directCollectionAccessor.getBatchFetch())) { |
| return false; |
| } |
| |
| if (! valuesMatch(m_cascadeOnDelete, directCollectionAccessor.getCascadeOnDelete())) { |
| return false; |
| } |
| |
| if (! valuesMatch(m_nonCacheable, directCollectionAccessor.getNonCacheable())) { |
| return false; |
| } |
| |
| return valuesMatch(m_collectionTable, directCollectionAccessor.getCollectionTable()); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| result = 31 * result + (m_batchFetch != null ? m_batchFetch.hashCode() : 0); |
| result = 31 * result + (m_cascadeOnDelete != null ? m_cascadeOnDelete.hashCode() : 0); |
| result = 31 * result + (m_nonCacheable != null ? m_nonCacheable.hashCode() : 0); |
| result = 31 * result + (m_joinFetch != null ? m_joinFetch.hashCode() : 0); |
| result = 31 * result + (m_collectionTable != null ? m_collectionTable.hashCode() : 0); |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public BatchFetchMetadata getBatchFetch() { |
| return m_batchFetch; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public Boolean getCascadeOnDelete() { |
| return m_cascadeOnDelete; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public CollectionTableMetadata getCollectionTable() { |
| return m_collectionTable; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected String getDefaultCollectionTableName() { |
| return getOwningDescriptor().getAlias() + "_" + getDefaultAttributeName(); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public String getDefaultFetchType() { |
| return JPA_FETCH_LAZY; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public String getJoinFetch() { |
| return m_joinFetch; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public String getPrivateOwned() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * The reference table in a direct collection case is the collection table. |
| */ |
| @Override |
| protected DatabaseTable getReferenceDatabaseTable() { |
| if (m_collectionTable == null) { |
| return null; |
| } |
| return m_collectionTable.getDatabaseTable(); |
| } |
| |
| /** |
| * INTERNAL: |
| * In a direct collection case, there is no notion of a reference |
| * descriptor, therefore we return this accessors descriptor. |
| */ |
| @Override |
| public MetadataDescriptor getReferenceDescriptor() { |
| return getDescriptor(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the converter name for a map key. |
| */ |
| protected abstract String getKeyConverter(); |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public Boolean getNonCacheable(){ |
| return m_nonCacheable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the converter name for a collection value. |
| * @see BasicMapAccessor for override details. An EclipseLink |
| * BasicMapAccessor can specify a value converter within the BasicMap |
| * metadata. Otherwise for all other cases, BasicCollectionAccessor and |
| * ElementCollectionAccessor, the value converter is returned from a Convert |
| * metadata specification. |
| */ |
| protected String getValueConverter() { |
| return getConvert(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor has a map key class specified. |
| * @see ElementCollectionAccessor |
| */ |
| protected boolean hasMapKeyClass() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void initXMLObject(MetadataAccessibleObject accessibleObject, XMLEntityMappings entityMappings) { |
| super.initXMLObject(accessibleObject, entityMappings); |
| |
| // Initialize single objects. |
| initXMLObject(m_collectionTable, accessibleObject); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| public Boolean isCascadeOnDelete() { |
| return m_cascadeOnDelete != null && m_cascadeOnDelete.booleanValue(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a direct collection mapping, |
| * which include basic collection, basic map and element collection |
| * accessors. |
| */ |
| @Override |
| public boolean isDirectCollection() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public boolean isNonCacheable() { |
| return m_nonCacheable != null && m_nonCacheable.booleanValue(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if the given class is a valid basic collection type. |
| */ |
| protected boolean isValidDirectCollectionType() { |
| return getAccessibleObject().isSupportedCollectionClass(getRawClass()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if the given class is a valid basic map type. |
| */ |
| protected boolean isValidDirectMapType() { |
| return getAccessibleObject().isSupportedMapClass(getRawClass()); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void process(DatabaseMapping mapping) { |
| // Add the mapping to the descriptor. |
| setMapping(mapping); |
| |
| // Set the attribute name. |
| mapping.setAttributeName(getAttributeName()); |
| |
| // Will check for PROPERTY access |
| setAccessorMethods(mapping); |
| |
| if (mapping instanceof CollectionMapping) { |
| CollectionMapping collectionMapping = (CollectionMapping)mapping; |
| |
| // Set the reference class name. |
| collectionMapping.setReferenceClassName(getReferenceClassName()); |
| |
| // Process join fetch type. |
| processJoinFetch(getJoinFetch(), collectionMapping); |
| |
| // Process the batch fetch if specified. |
| processBatchFetch(collectionMapping); |
| |
| // Process the collection table. |
| processCollectionTable(collectionMapping); |
| |
| // The spec. requires pessimistic lock to be extend-able to CollectionTable |
| collectionMapping.setShouldExtendPessimisticLockScope(true); |
| |
| // Set the cascade on delete if specified. |
| collectionMapping.setIsCascadeOnDeleteSetOnDatabase(isCascadeOnDelete()); |
| } else if (mapping instanceof AggregateMapping) { |
| AggregateMapping aggregateMapping = (AggregateMapping)mapping; |
| |
| // Set the reference class name. |
| aggregateMapping.setReferenceClassName(getReferenceClassName()); |
| } |
| |
| // Set the non cacheable if specified. |
| mapping.setIsCacheable(!isNonCacheable()); |
| |
| // Process a @ReturnInsert and @ReturnUpdate (to log a warning message) |
| processReturnInsertAndUpdate(); |
| |
| // Process any partitioning policies. |
| processPartitioning(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the batch fetch type on the foreign reference mapping. |
| */ |
| protected void processBatchFetch(ForeignReferenceMapping mapping) { |
| if (m_batchFetch != null) { |
| m_batchFetch.process(mapping); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a MetadataCollectionTable. Sub classes should call this to |
| * ensure a table is defaulted. |
| */ |
| protected void processCollectionTable(CollectionMapping mapping) { |
| // Check that we loaded a collection table otherwise default one. |
| if (m_collectionTable == null) { |
| m_collectionTable = new CollectionTableMetadata(this); |
| } |
| |
| // Process any table defaults and log warning messages. |
| processTable(m_collectionTable, getDefaultCollectionTableName()); |
| |
| // Set the reference table on the mapping. |
| if (isDirectEmbeddableCollection()) { |
| ((AggregateCollectionMapping) mapping).setDefaultSourceTable(m_collectionTable.getDatabaseTable()); |
| } else { |
| ((DirectCollectionMapping) mapping).setReferenceTable(m_collectionTable.getDatabaseTable()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void processDirectCollectionMapping() { |
| // Initialize our mapping. |
| DatabaseMapping mapping = getOwningDescriptor().getClassDescriptor().newDirectCollectionMapping(); |
| |
| // Process common direct collection metadata. This must be done |
| // before any field processing since field processing requires that |
| // the collection table be processed before hand. |
| process(mapping); |
| |
| processContainerPolicyAndIndirection((ContainerMapping) mapping); |
| |
| if (mapping instanceof DirectCollectionMapping) { |
| DirectCollectionMapping directCollectionMapping = (DirectCollectionMapping) mapping; |
| // Process the container and indirection policies. |
| |
| // Process the value column (we must process this field before the |
| // call to processConverter, since it may set a field classification) |
| directCollectionMapping.setDirectField(getDatabaseField(getReferenceDatabaseTable(), MetadataLogger.VALUE_COLUMN)); |
| |
| // To resolve any generic types (or respect an attribute type |
| // specification) we need to set the attribute classification on the |
| // mapping to ensure we do the right conversions. |
| if (hasAttributeType() || getAccessibleObject().isGenericCollectionType()) { |
| directCollectionMapping.setDirectFieldClassificationName(getJavaClassName(getReferenceClass())); |
| } |
| } else if (mapping.isAbstractCompositeDirectCollectionMapping()) { |
| ((AbstractCompositeDirectCollectionMapping) mapping).setField(getDatabaseField(getDescriptor().getPrimaryTable(), MetadataLogger.COLUMN)); |
| } |
| |
| // Process a converter for this mapping. We will look for a convert |
| // value. If none is found then we'll look for a JPA converter, that |
| // is, Enumerated, Lob and Temporal. With everything falling into |
| // a serialized mapping if no converter whatsoever is found. |
| processMappingValueConverter(mapping, getValueConverter(), getConverts(), getReferenceClass(), getReferenceClassWithGenerics()); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected void processDirectMapMapping() { |
| // Initialize and process common direct collection metadata. This must |
| // be done before any field processing since field processing requires |
| // that the collection table be processed before hand. |
| DirectMapMapping mapping = new DirectMapMapping(); |
| process(mapping); |
| |
| // Process the container and indirection policies. |
| processContainerPolicyAndIndirection(mapping); |
| |
| // Process the key column (we must process this field before the |
| // call to processConverter, since it may set a field classification) |
| mapping.setDirectKeyField(getDatabaseField(getReferenceDatabaseTable(), MetadataLogger.MAP_KEY_COLUMN)); |
| |
| // Only process the key converter if this is a basic map accessor. The |
| // key converter for an element collection case will be taken care of |
| // in the processContainerPolicyAndIndirection call above. |
| if (isBasicMap()) { |
| // To resolve any generic types (or respect an attribute type |
| // specification) we need to set the attribute classification on the |
| // mapping to ensure we do the right conversions. |
| if (hasAttributeType() || getAccessibleObject().isGenericCollectionType()) { |
| mapping.setDirectKeyFieldClassificationName(getJavaClassName(getMapKeyReferenceClass())); |
| } |
| |
| // Process a converter for the key column of this mapping. |
| processMappingKeyConverter(mapping, getKeyConverter(), null, getMapKeyReferenceClass(), getMapKeyReferenceClassWithGenerics()); |
| } |
| |
| // Process the value column (we must process this field before the call |
| // to processConverter, since it may set a field classification) |
| mapping.setDirectField(getDatabaseField(getReferenceDatabaseTable(), MetadataLogger.VALUE_COLUMN)); |
| |
| // To resolve any generic types (or respect an attribute type |
| // specification) we need to set the attribute classification on the |
| // mapping to ensure we do the right conversions. |
| if (hasAttributeType() || getAccessibleObject().isGenericCollectionType()) { |
| mapping.setDirectFieldClassificationName(getJavaClassName(getReferenceClass())); |
| } |
| |
| // Process a converter for value column of this mapping. |
| processMappingValueConverter(mapping, getValueConverter(), getConverts(), getReferenceClass(), getReferenceClassWithGenerics()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setBatchFetch(BatchFetchMetadata batchFetch) { |
| m_batchFetch = batchFetch; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setCascadeOnDelete(Boolean cascadeOnDelete) { |
| m_cascadeOnDelete = cascadeOnDelete; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setCollectionTable(CollectionTableMetadata collectionTable) { |
| m_collectionTable = collectionTable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setNonCacheable(Boolean nonCacheable){ |
| m_nonCacheable = nonCacheable; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setJoinFetch(String joinFetch) { |
| m_joinFetch = joinFetch; |
| } |
| } |