| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2018 IBM Corporation. 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: |
| // 05/16/2008-1.0M8 Guy Pelletier |
| // - 218084: Implement metadata merging functionality between mapping files |
| // 06/20/2008-1.0 Guy Pelletier |
| // - 232975: Failure when attribute type is generic |
| // 08/27/2008-1.1 Guy Pelletier |
| // - 211329: Add sequencing on non-id attribute(s) support to the EclipseLink-ORM.XML Schema |
| // 09/23/2008-1.1 Guy Pelletier |
| // - 241651: JPA 2.0 Access Type support |
| // 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 |
| // 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 |
| // 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. |
| // We handle undefined parameterized generic types for a MappedSuperclass defined |
| // Map field by returning Void in this case. |
| // 09/29/2009-2.0 Guy Pelletier |
| // - 282553: JPA 2.0 JoinTable support for OneToOne and ManyToOne |
| // 10/21/2009-2.0 Guy Pelletier |
| // - 290567: mappedbyid support incomplete |
| // 11/06/2009-2.0 Guy Pelletier |
| // - 286317: UniqueConstraint xml element is changing (plus couple other fixes, see bug) |
| // 01/22/2010-2.0.1 Guy Pelletier |
| // - 294361: incorrect generated table for element collection attribute overrides |
| // 01/26/2010-2.0.1 Guy Pelletier |
| // - 299893: @MapKeyClass does not work with ElementCollection |
| // 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/09/2010-2.1 Guy Pelletier |
| // - 307050: Add defaults for access methods of a VIRTUAL access type |
| // 04/27/2010-2.1 Guy Pelletier |
| // - 309856: MappedSuperclasses from XML are not being initialized properly |
| // 05/19/2010-2.1 Guy Pelletier |
| // - 313574: Lower case primary key column association does not work when upper casing flag is set to true |
| // 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 |
| // 08/20/2010-2.2 Guy Pelletier |
| // - 323252: Canonical model generator throws NPE on virtual 1-1 or M-1 mapping |
| // 09/03/2010-2.2 Guy Pelletier |
| // - 317286: DB column lenght not in sync between @Column and @JoinColumn |
| // 10/27/2010-2.2 Guy Pelletier |
| // - 328114: @AttributeOverride does not work with nested embeddables having attributes of the same name |
| // 12/01/2010-2.2 Guy Pelletier |
| // - 331234: xml-mapping-metadata-complete overriden by metadata-complete specification |
| // 03/24/2011-2.3 Guy Pelletier |
| // - 337323: Multi-tenant with shared schema support (part 1) |
| // 11/10/2011-2.4 Guy Pelletier |
| // - 357474: Address primaryKey option from tenant discriminator column |
| // // 30/05/2012-2.4 Guy Pelletier |
| // - 354678: Temp classloader is still being used during metadata processing |
| // 10/09/2012-2.5 Guy Pelletier |
| // - 374688: JPA 2.1 Converter support |
| // 10/25/2012-2.5 Guy Pelletier |
| // - 374688: JPA 2.1 Converter support |
| // 10/30/2012-2.5 Guy Pelletier |
| // - 374688: JPA 2.1 Converter support |
| // 11/19/2012-2.5 Guy Pelletier |
| // - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) |
| // 11/28/2012-2.5 Guy Pelletier |
| // - 374688: JPA 2.1 Converter support |
| // 12/07/2012-2.5 Guy Pelletier |
| // - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) |
| // 02/20/2013-2.5 Guy Pelletier |
| // - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) |
| // 07/16/2013-2.5.1 Guy Pelletier |
| // - 412384: Applying Converter for parameterized basic-type for joda-time's DateTime does not work |
| // 06/20/2014-2.5.2 Rick Curtis |
| // - 437760: AttributeOverride with no column name defined doesn't work. |
| // 07/01/2014-2.5.3 Rick Curtis |
| // - 375101: Date and Calendar should not require @Temporal. |
| package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings; |
| |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.EL_ACCESS_VIRTUAL; |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_FIELD; |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS_PROPERTY; |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_CONVERT; |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_CONVERTS; |
| import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_FETCH_EAGER; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.annotations.Convert; |
| import org.eclipse.persistence.annotations.JoinFetchType; |
| import org.eclipse.persistence.annotations.Properties; |
| import org.eclipse.persistence.annotations.Property; |
| import org.eclipse.persistence.annotations.ReturnInsert; |
| import org.eclipse.persistence.annotations.ReturnUpdate; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.internal.descriptors.VirtualAttributeAccessor; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy; |
| import org.eclipse.persistence.internal.indirection.WeavedObjectBasicIndirectionPolicy; |
| import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor; |
| import org.eclipse.persistence.internal.jpa.metadata.MetadataHelper; |
| import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.MetadataAccessor; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.PropertyMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor; |
| 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.accessors.objects.MetadataClass; |
| import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataMethod; |
| import org.eclipse.persistence.internal.jpa.metadata.columns.AssociationOverrideMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.columns.AttributeOverrideMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.columns.ColumnMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.columns.ForeignKeyMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.columns.JoinColumnMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.AbstractConverterMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.ClassInstanceMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.ConvertMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.EnumeratedMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.JSONMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.KryoMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.LobMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.SerializedMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.TemporalMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.converters.XMLMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.mappings.MapKeyMetadata; |
| import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings; |
| import org.eclipse.persistence.internal.mappings.converters.AttributeNamePrefix; |
| import org.eclipse.persistence.internal.mappings.converters.AttributeNameTokenizer; |
| import org.eclipse.persistence.internal.queries.CollectionContainerPolicy; |
| import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; |
| import org.eclipse.persistence.mappings.AggregateObjectMapping; |
| import org.eclipse.persistence.mappings.CollectionMapping; |
| import org.eclipse.persistence.mappings.ContainerMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.DirectMapMapping; |
| import org.eclipse.persistence.mappings.DirectToFieldMapping; |
| import org.eclipse.persistence.mappings.EmbeddableMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.mappings.OneToOneMapping; |
| import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; |
| import org.eclipse.persistence.mappings.foundation.MapComponentMapping; |
| import org.eclipse.persistence.mappings.foundation.MapKeyMapping; |
| |
| /** |
| * INTERNAL: |
| * An abstract mapping accessor. Holds common metadata for all mappings. |
| * |
| * 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 abstract class MappingAccessor extends MetadataAccessor { |
| |
| // Used for looking up attribute overrides for a map accessor. |
| /** Dot notation key prefix. */ |
| protected static final String KEY_DOT_NOTATION |
| = AttributeNamePrefix.KEY.getName() + AttributeNameTokenizer.SEPARATOR; |
| /** Dot notation value prefix. */ |
| protected static final String VALUE_DOT_NOTATION |
| = AttributeNamePrefix.VALUE.getName() + AttributeNameTokenizer.SEPARATOR; |
| |
| private final static String DEFAULT_MAP_KEY_COLUMN_SUFFIX = "_KEY"; |
| |
| private ClassAccessor m_classAccessor; |
| private DatabaseMapping m_mapping; |
| private DatabaseMapping m_overrideMapping; |
| private Map<String, PropertyMetadata> m_properties = new HashMap<String, PropertyMetadata>(); |
| private String m_attributeType; |
| protected ColumnMetadata m_field; |
| |
| /** |
| * INTERNAL: |
| */ |
| protected MappingAccessor(MetadataAnnotation annotation, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) { |
| super(annotation, accessibleObject, classAccessor.getDescriptor(), classAccessor.getProject()); |
| |
| // We must keep a reference to the class accessors where this |
| // mapping accessor is defined. We need it to determine access types. |
| m_classAccessor = classAccessor; |
| |
| // Once the class accessor is initialized, we can look for an explicit |
| // access type specification. |
| initAccess(); |
| |
| // Any mapping type may have a field for EIS/NoSQL data. |
| if (isAnnotationPresent("org.eclipse.persistence.nosql.annotations.Field")) { |
| m_field = new ColumnMetadata(getAnnotation("org.eclipse.persistence.nosql.annotations.Field"), this); |
| } |
| |
| // Set the converts if some are present. |
| // Process all the converts first. |
| if (isAnnotationPresent(JPA_CONVERTS)) { |
| for (Object convert : getAnnotation(JPA_CONVERTS).getAttributeArray("value")) { |
| addConvertMetadata(new ConvertMetadata((MetadataAnnotation) convert, this)); |
| } |
| } |
| |
| // Process the single convert second. |
| if (isAnnotationPresent(JPA_CONVERT)) { |
| addConvertMetadata(new ConvertMetadata(getAnnotation(JPA_CONVERT), this)); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected MappingAccessor(String xmlElement) { |
| super(xmlElement); |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses that support converts need to override this method otherwise |
| * an exception will be thrown from those accessors that do not support them |
| * when a user has defined them on that accessor. |
| */ |
| protected void addConvert(ConvertMetadata convert) { |
| throw ValidationException.invalidMappingForConvert(getJavaClassName(), getAttributeName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a JPA convert annotation to the converts list. If it is a map key |
| * convert, pass it on to the map key converts list. |
| */ |
| protected void addConvertMetadata(ConvertMetadata convert) { |
| if (convert.isForMapKey()) { |
| // This isForMapKey call will remove the key prefix when there is one. |
| addMapKeyConvert(convert); |
| } else { |
| addConvert(convert); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process an attribute override for either an embedded object mapping, or |
| * an element collection mapping containing embeddable objects. |
| */ |
| protected void addFieldNameTranslation(EmbeddableMapping embeddableMapping, String overrideName, DatabaseField overrideField, MappingAccessor aggregatesAccessor) { |
| DatabaseMapping aggregatesMapping = aggregatesAccessor.getMapping(); |
| DatabaseField aggregatesMappingField = aggregatesMapping.getField(); |
| |
| // If we are specifying an attribute override to an id field that is |
| // within the embeddable we must update the primary key field on the |
| // owning descriptor. |
| if (aggregatesAccessor.isId()) { |
| updatePrimaryKeyField(aggregatesAccessor, overrideField); |
| } |
| |
| if (overrideName.indexOf('.') > -1) { |
| // Set the nested field name translation on the mapping. Nested |
| // (dot notation) overrides are initialized slightly different then |
| // core field name translations which are based on column names. In |
| // JPA we need to rely and differentiate based on the override |
| // (attribute) name. |
| embeddableMapping.addNestedFieldTranslation(overrideName, overrideField, aggregatesMappingField.getName()); |
| } else { |
| // Set the field name translation on the mapping. |
| embeddableMapping.addFieldTranslation(overrideField, aggregatesMappingField.getName()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses that support converts need to override this method otherwise |
| * an exception will be thrown from those accessors that do not support them |
| * when a user has defined them on that accessor. |
| */ |
| protected void addMapKeyConvert(ConvertMetadata convert) { |
| throw ValidationException.invalidMappingForMapKeyConvert(getJavaClassName(), getAttributeName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true is this accessor is a derived id accessor. |
| * @see ObjectAccessor |
| */ |
| public boolean derivesId() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * For merging and overriding to work properly, all ORMetadata must be able |
| * to compare themselves for metadata equality. |
| */ |
| @Override |
| public boolean equals(Object objectToCompare) { |
| if (super.equals(objectToCompare) && objectToCompare instanceof MappingAccessor) { |
| MappingAccessor mappingAccessor = (MappingAccessor) objectToCompare; |
| |
| // For extra safety compare that the owning class accessors of these |
| // mapping accessors are the same. |
| if (! valuesMatch(getClassAccessor(), mappingAccessor.getClassAccessor())) { |
| return false; |
| } |
| |
| if (! valuesMatch(m_field, mappingAccessor.getField())) { |
| return false; |
| } |
| |
| return valuesMatch(getAttributeType(), mappingAccessor.getAttributeType()); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| ClassAccessor classAccessor = getClassAccessor(); |
| String attributeType = getAttributeType(); |
| result = 31 * result + (classAccessor != null ? classAccessor.hashCode() : 0); |
| result = 31 * result + (attributeType != null ? attributeType.hashCode() : 0); |
| result = 31 * result + (m_field != null ? m_field.hashCode() : 0); |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the annotation if it exists. |
| */ |
| @Override |
| protected MetadataAnnotation getAnnotation(String annotation) { |
| return getAccessibleObject().getAnnotation(annotation, getClassAccessor()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the list of association overrides into a map, merging and |
| * overriding any association overrides where necessary with descriptor |
| * level association overrides. |
| */ |
| protected Map<String, AssociationOverrideMetadata> getAssociationOverrides(List<AssociationOverrideMetadata> associationOverrides) { |
| // TODO: Be nice to look for duplicates within the same list. |
| Map<String, AssociationOverrideMetadata> associationOverridesMap = new HashMap<String, AssociationOverrideMetadata>(); |
| |
| for (AssociationOverrideMetadata associationOverride : associationOverrides) { |
| String name = associationOverride.getName(); |
| |
| // An association override from a sub-entity class will name its |
| // association override slightly different in that it will have |
| // one extra dot notation at the front. E.G. A mapped superclass |
| // that defines an embedded attribute named 'record' can define |
| // association overrides directly on the mapping, that is, |
| // 'date'. Whereas from an entity class to override 'date' on |
| // 'record', the attribute name will be 'record.date' |
| String dotNotationName = getAttributeName() + "." + name; |
| if (getClassAccessor().isMappedSuperclass() && getDescriptor().hasAssociationOverrideFor(dotNotationName)) { |
| getLogger().logConfigMessage(MetadataLogger.IGNORE_ASSOCIATION_OVERRIDE, name, getAttributeName(), getClassAccessor().getJavaClassName(), getJavaClassName()); |
| associationOverridesMap.put(name, getDescriptor().getAssociationOverrideFor(dotNotationName)); |
| } else { |
| associationOverridesMap.put(name, associationOverride); |
| } |
| } |
| |
| // Now add every other descriptor association override that didn't |
| // override a mapping level one (if we are processing a mapping from |
| // a mapped superclass level). We'll check the attribute names match |
| // and rip off the extra qualifying when adding it to the override map. |
| if (getClassAccessor().isMappedSuperclass()) { |
| for (AssociationOverrideMetadata associationOverride : getDescriptor().getAssociationOverrides()) { |
| String name = associationOverride.getName(); |
| String attributeName = name; |
| String overrideName = name; |
| int indexOfFirstDot = name.indexOf('.'); |
| |
| if (indexOfFirstDot > -1) { |
| attributeName = name.substring(0, indexOfFirstDot); |
| overrideName = name.substring(indexOfFirstDot + 1); |
| } |
| |
| if (attributeName.equals(getAttributeName()) && ! associationOverridesMap.containsKey(attributeName)) { |
| associationOverridesMap.put(overrideName, associationOverride); |
| } |
| } |
| } |
| |
| return associationOverridesMap; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the attribute name for this accessor. This is typically the |
| * attribute name on the accessible object (i.e., field or property name), |
| * however, if access-methods have been specified, use the name attribute |
| * that was specified in XML. (e.g. basic name="sin") and not the property |
| * name of the get method from the access-methods specification. |
| */ |
| @Override |
| public String getAttributeName() { |
| if (hasAccessMethods()) { |
| return getName(); |
| } else { |
| return getAccessibleObject().getAttributeName(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the attribute override for this accessor. |
| */ |
| protected AttributeOverrideMetadata getAttributeOverride(String loggingCtx) { |
| if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) { |
| return getDescriptor().getAttributeOverrideFor(KEY_DOT_NOTATION + getAttributeName()); |
| } else if (loggingCtx.equals(MetadataLogger.VALUE_COLUMN)) { |
| if (getDescriptor().hasAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName())) { |
| return getDescriptor().getAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName()); |
| } |
| } |
| return getDescriptor().getAttributeOverrideFor(getAttributeName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the list of attribute overrides into a map, merging and |
| * overriding any attribute overrides where necessary with descriptor |
| * level attribute overrides. |
| */ |
| protected Map<String, AttributeOverrideMetadata> getAttributeOverrides(List<AttributeOverrideMetadata> attributeOverrides) { |
| // TODO: Be nice to look for duplicates within the same list. |
| HashMap<String, AttributeOverrideMetadata> attributeOverridesMap = new HashMap<String, AttributeOverrideMetadata>(); |
| |
| for (AttributeOverrideMetadata attributeOverride : attributeOverrides) { |
| String name = attributeOverride.getName(); |
| |
| // An attribute override from a sub-entity class will name its |
| // attribute override slightly different in that it will have one |
| // extra dot notation at the front. E.G. A mapped superclass that |
| // defines an embedded attribute named 'record' can define attribute |
| // overrides directly on the mapping, that is, 'date'. Whereas |
| // from an entity class to override 'date' on 'record', the |
| // attribute name will be 'record.date' |
| String dotNotationName = getAttributeName() + "." + name; |
| if (getClassAccessor().isMappedSuperclass() && getDescriptor().hasAttributeOverrideFor(dotNotationName)) { |
| getLogger().logConfigMessage(MetadataLogger.IGNORE_ATTRIBUTE_OVERRIDE, name, getAttributeName(), getClassAccessor().getJavaClassName(), getJavaClassName()); |
| attributeOverridesMap.put(name, getDescriptor().getAttributeOverrideFor(dotNotationName)); |
| } else { |
| attributeOverridesMap.put(name, attributeOverride); |
| } |
| } |
| |
| // Now add every other descriptor association override that didn't |
| // override a mapping level one (if we are processing a mapping from |
| // a mapped superclass level). We'll check the attribute names match |
| // and rip off the extra qualifying when adding it to the override map. |
| if (getClassAccessor().isMappedSuperclass()) { |
| for (AttributeOverrideMetadata attributeOverride : getDescriptor().getAttributeOverrides()) { |
| String name = attributeOverride.getName(); |
| String attributeName = name; |
| String overrideName = name; |
| int indexOfFirstDot = name.indexOf('.'); |
| |
| if (indexOfFirstDot > -1) { |
| attributeName = name.substring(0, indexOfFirstDot); |
| overrideName = name.substring(indexOfFirstDot + 1); |
| } |
| |
| if (attributeName.equals(getAttributeName()) && ! attributeOverridesMap.containsKey(attributeName)) { |
| attributeOverridesMap.put(overrideName, attributeOverride); |
| } |
| } |
| } |
| |
| return attributeOverridesMap; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. Those accessors that do not require a separate |
| * attribute-type specification for VIRTUAL accessors should override this |
| * method. For example, one-to-one and many-to-one will its target-entity. |
| * variable-one-to-one will use its target-interface. |
| */ |
| public String getAttributeType() { |
| return m_attributeType; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the class accessor on which this mapping was defined. |
| */ |
| public ClassAccessor getClassAccessor(){ |
| return m_classAccessor; |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses should override this method to return the appropriate |
| * column for their mapping. |
| * @see BasicAccessor |
| * @see BasicCollectionAccessor |
| * @see BasicMapAccessor |
| * @see ElementCollectionAccessor |
| * @see CollectionAccessor |
| */ |
| protected ColumnMetadata getColumn(String loggingCtx) { |
| return m_field == null ? new ColumnMetadata(this) : m_field; |
| } |
| |
| /** |
| * INTERNAL: |
| * Given the potential converts return them for processing unless there |
| * are overrides available from the descriptor. |
| */ |
| protected List<ConvertMetadata> getConverts(List<ConvertMetadata> potentialConverts) { |
| if (getDescriptor().hasConverts(getAttributeName())) { |
| return getDescriptor().getConverts(getAttributeName()); |
| } else { |
| return potentialConverts; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process column metadata details into a database field. This will set |
| * correct metadata and log defaulting messages to the user. It also looks |
| * for an attribute override. |
| * |
| * This method will call getColumn() which assumes the subclasses will |
| * return the appropriate ColumnMetadata to process based on the context |
| * provided. |
| * |
| * @see BasicCollectionAccessor |
| * @see BasicMapAccessor |
| */ |
| protected DatabaseField getDatabaseField(DatabaseTable defaultTable, String loggingCtx) { |
| // Check if we have an attribute override first, otherwise process for a column |
| ColumnMetadata column = hasAttributeOverride(loggingCtx) ? getAttributeOverride(loggingCtx).getColumn() : getColumn(loggingCtx); |
| |
| // Get the actual database field and apply any defaults. |
| DatabaseField field = column.getDatabaseField(); |
| |
| // Make sure there is a table name on the field. |
| if (! field.hasTableName()) { |
| field.setTable(defaultTable); |
| } |
| |
| // We must check the flag before blindly setting it on the table since |
| // global flag may be false where this fields table may explicitly use |
| // delimiters. |
| if (getProject().useDelimitedIdentifier()) { |
| field.getTable().setUseDelimiters(true); |
| } |
| |
| // Set the correct field name, defaulting and logging when necessary. |
| String defaultName = getDefaultAttributeName(); |
| |
| // If this is for a map key column, append a suffix. |
| if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) { |
| defaultName += DEFAULT_MAP_KEY_COLUMN_SUFFIX; |
| } |
| |
| setFieldName(field, defaultName, loggingCtx); |
| |
| // Store all the fields for this descriptor. This will allow re-use |
| // and easy lookup of referenced column names. |
| getDescriptor().addField(field); |
| |
| return field; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected String getDefaultFetchType() { |
| return JPA_FETCH_EAGER; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the default table to hold the foreign key of a MapKey when |
| * and Entity is used as the MapKey |
| */ |
| protected DatabaseTable getDefaultTableForEntityMapKey(){ |
| return getReferenceDescriptor().getPrimaryTable(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the enumerated metadata for this accessor. |
| * @see DirectAccessor |
| * @see ElementCollectionAccessor |
| * @see CollectionAccessor |
| */ |
| public EnumeratedMetadata getEnumerated(boolean isForMapKey) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public ColumnMetadata getField() { |
| return m_field; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the foreign key to use with this mapping accessor. This method |
| * will look for association overrides and use the foreign key from it |
| * instead (do as the association override says). |
| */ |
| protected ForeignKeyMetadata getForeignKey(ForeignKeyMetadata potentialForeignKey, MetadataDescriptor descriptor) { |
| if (getDescriptor().hasAssociationOverrideFor(getAttributeName())) { |
| ForeignKeyMetadata foreignKey = getDescriptor().getAssociationOverrideFor(getAttributeName()).getForeignKey(); |
| |
| // Have to init the foreign key with items that could potentially |
| // be required during processing here. |
| if (foreignKey != null) { |
| foreignKey.setProject(descriptor.getProject()); |
| } |
| |
| return foreignKey; |
| } else { |
| return potentialForeignKey; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the get method name of a method accessor. Note, this method |
| * should not be called when processing field access. |
| */ |
| public String getGetMethodName() { |
| return hasAccessMethods() ? getAccessMethods().getGetMethodName() : getAccessibleObjectName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the join columns to use with this mapping accessor. This method |
| * will look for association overrides and use those instead if some are |
| * available. This method will validate the join columns and default |
| * any where necessary. |
| */ |
| protected List<JoinColumnMetadata> getJoinColumns(List<JoinColumnMetadata> potentialJoinColumns, MetadataDescriptor descriptor) { |
| if (getDescriptor().hasAssociationOverrideFor(getAttributeName())) { |
| return getJoinColumnsAndValidate(getDescriptor().getAssociationOverrideFor(getAttributeName()).getJoinColumns(), descriptor); |
| } else { |
| return getJoinColumnsAndValidate(potentialJoinColumns, descriptor); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This method will validate the join columns and default any where |
| * necessary. |
| */ |
| protected List<JoinColumnMetadata> getJoinColumnsAndValidate(List<JoinColumnMetadata> joinColumns, MetadataDescriptor referenceDescriptor) { |
| if (joinColumns.isEmpty()) { |
| if (referenceDescriptor.hasCompositePrimaryKey()) { |
| // Add a default one for each part of the composite primary |
| // key. Foreign and primary key to have the same name. |
| for (DatabaseField primaryKeyField : referenceDescriptor.getPrimaryKeyFields()) { |
| // Multitenant primary key fields will be dealt with below so avoid adding here. |
| if (! primaryKeyField.isPrimaryKey()) { |
| JoinColumnMetadata joinColumn = new JoinColumnMetadata(); |
| joinColumn.setReferencedColumnName(primaryKeyField.getName()); |
| joinColumn.setName(primaryKeyField.getName()); |
| joinColumn.setProject(referenceDescriptor.getProject()); |
| joinColumns.add(joinColumn); |
| } |
| } |
| } else { |
| // Add a default one for the single case, not setting any |
| // foreign and primary key names. They will default based |
| // on which accessor is using them. |
| JoinColumnMetadata jcm = new JoinColumnMetadata(); |
| jcm.setProject(referenceDescriptor.getProject()); |
| joinColumns.add(jcm); |
| } |
| } else { |
| // Need to update any join columns that use a foreign key name |
| // for the primary key name. E.G. User specifies the renamed id |
| // field name from a primary key join column as the primary key in |
| // an inheritance subclass. |
| for (JoinColumnMetadata joinColumn : joinColumns) { |
| // Doing this could potentially change a value entered in XML. |
| // However, in this case I think that is ok since in theory we |
| // are writing out the correct value that EclipseLink needs to |
| // form valid queries. |
| String referencedColumnName = joinColumn.getReferencedColumnName(); |
| // The referenced column name in a variable one to one case is a |
| // query key name and not a column name so bypass any of this |
| // code. |
| if (referencedColumnName != null && !isVariableOneToOne()) { |
| DatabaseField referencedField = new DatabaseField(); |
| setFieldName(referencedField, referencedColumnName); |
| joinColumn.setReferencedColumnName(referenceDescriptor.getPrimaryKeyJoinColumnAssociation(referencedField).getName()); |
| } |
| } |
| } |
| |
| // Multitenant entities. Go through and add any multitenant primary key |
| // fields that need to be added. The user may or may not specify them |
| // in metadata. |
| if (referenceDescriptor.hasSingleTableMultitenant() && joinColumns.size() != referenceDescriptor.getPrimaryKeyFields().size()) { |
| Map<String, List<DatabaseField>> referenceTenantFields = referenceDescriptor.getSingleTableMultitenantFields(); |
| |
| // If we are multitenant then we can sync up on relationship fields |
| // using the context property. |
| Map<String, List<DatabaseField>> tenantFields = getDescriptor().hasSingleTableMultitenant() ? getDescriptor().getSingleTableMultitenantFields() : null; |
| |
| for (String contextProperty : referenceTenantFields.keySet()) { |
| List<DatabaseField> referenceFields = referenceTenantFields.get(contextProperty); |
| |
| for (DatabaseField referenceField : referenceFields) { |
| // Only if it is a primary key field, otherwise we don't |
| // care about it. |
| if (referenceField.isPrimaryKey()) { |
| JoinColumnMetadata jcm = new JoinColumnMetadata(); |
| // This join column must be read only. |
| jcm.setInsertable(false); |
| jcm.setUpdatable(false); |
| |
| // If we have a related context property, look up a |
| // field that matches from it. |
| if (tenantFields != null && tenantFields.containsKey(contextProperty)) { |
| // This is going to return a list just pick the first |
| // field (they populate the same value so doesn't really |
| // matter which one we pick. |
| jcm.setName(tenantFields.get(contextProperty).get(0).getName()); |
| } else { |
| // We don't have a match, use the same name. |
| jcm.setName(referenceField.getName()); |
| } |
| |
| jcm.setReferencedColumnName(referenceField.getName()); |
| jcm.setProject(referenceDescriptor.getProject()); |
| joinColumns.add(jcm); |
| } |
| } |
| } |
| } |
| |
| // Now run some validation. |
| if (referenceDescriptor.hasCompositePrimaryKey()) { |
| // The number of join columns should equal the number of primary key fields. |
| if (joinColumns.size() != referenceDescriptor.getPrimaryKeyFields().size()) { |
| throw ValidationException.incompleteJoinColumnsSpecified(getAnnotatedElement(), getJavaClass()); |
| } |
| |
| // All the primary and foreign key field names should be specified. |
| for (JoinColumnMetadata joinColumn : joinColumns) { |
| if (joinColumn.isPrimaryKeyFieldNotSpecified() || joinColumn.isForeignKeyFieldNotSpecified()) { |
| throw ValidationException.incompleteJoinColumnsSpecified(getAnnotatedElement(), getJavaClass()); |
| } |
| } |
| } |
| |
| return joinColumns; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the lob metadata for this accessor. |
| * @see DirectAccessor |
| */ |
| public LobMetadata getLob(boolean isForMapKey) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping that this accessor is associated to. |
| */ |
| public DatabaseMapping getMapping(){ |
| return (m_overrideMapping == null) ? m_mapping : m_overrideMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the owning descriptor of this accessor. |
| */ |
| public MetadataDescriptor getOwningDescriptor() { |
| return getClassAccessor().getOwningDescriptor(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the owning descriptors of this accessor. In most cases this is |
| * a single descriptor. Multiples can only exist when dealing with |
| * accessors for an embeddable that is shared. |
| */ |
| public List<MetadataDescriptor> getOwningDescriptors() { |
| return getClassAccessor().getOwningDescriptors(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the map key if this mapping accessor employs one. Those accessors |
| * that support it should override this method. |
| * @see CollectionAccessor |
| * @see ElementCollectionAccessor |
| */ |
| public MapKeyMetadata getMapKey() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Given the potential converts return them for processing unless there |
| * are overrides available from the descriptor. |
| */ |
| protected List<ConvertMetadata> getMapKeyConverts(List<ConvertMetadata> potentialMapKeyConverts) { |
| if (getDescriptor().hasMapKeyConverts(getAttributeName())) { |
| return getDescriptor().getMapKeyConverts(getAttributeName()); |
| } else { |
| return potentialMapKeyConverts; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the map key reference class for this accessor if applicable. It |
| * will try to extract a reference class from a generic specification. |
| * Parameterized generic keys on a MappedSuperclass will return void.class. |
| * If no generics are used, then it will return void.class. This avoids NPE's |
| * when processing JPA converters that can default (Enumerated and Temporal) |
| * based on the reference class. |
| */ |
| public MetadataClass getMapKeyReferenceClass() { |
| // First check if we are a mapped key map accessor and return its map |
| // key class if specified. Otherwise continue on to extract it from |
| // a generic specification. We do this to avoid going to the class |
| // with is needed for dynamic persistence. |
| if (isMappedKeyMapAccessor()) { |
| MetadataClass mapKeyClass = ((MappedKeyMapAccessor) this).getMapKeyClass(); |
| if (mapKeyClass != null && ! mapKeyClass.isVoid()) { |
| return mapKeyClass; |
| } |
| } |
| |
| if (isMapAccessor()) { |
| MetadataClass referenceClass = getAccessibleObject().getMapKeyClass(getDescriptor()); |
| |
| if (referenceClass == null) { |
| throw ValidationException.unableToDetermineMapKeyClass(getAttributeName(), getJavaClass()); |
| } |
| |
| // 266912: Use of parameterized generic types like Map<X,Y> |
| // inherits from class<T> in a MappedSuperclass field will cause |
| // referencing issues - as in we are unable to determine the correct |
| // type for T. A workaround for this is to detect when we are in |
| // this state and return a standard top level class. An invalid |
| // class will be of the form MetadataClass.m_name="T" |
| if (getDescriptor().isMappedSuperclass()) { |
| // Determine whether we are directly referencing a class or |
| // using a parameterized generic reference by trying to load the |
| // class and catching any validationException. If we do not get |
| // an exception on getClass then the referenceClass.m_name is |
| // valid and should be directly returned |
| try { |
| MetadataHelper.getClassForName(referenceClass.getName(), getMetadataFactory().getLoader()); |
| } catch (ValidationException exception) { |
| // Default to Void for parameterized types |
| // Ideally we would need a MetadataClass.isParameterized() to inform us instead. |
| return getMetadataClass(Void.class); |
| } |
| } |
| |
| return referenceClass; |
| } else { |
| return getMetadataClass(void.class); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the map key reference class name |
| */ |
| public String getMapKeyReferenceClassName() { |
| return getMapKeyReferenceClass().getName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the map key reference class for this accessor if applicable. It |
| * will try to extract a reference class from a generic specification. |
| * If no generics are used, then it will return void.class. This avoids NPE's |
| * when processing JPA converters that can default (Enumerated and Temporal) |
| * based on the reference class. |
| * |
| * Future: this method is where we would provide a more explicit reference |
| * class to support an auto-apply jpa converter. Per the spec auto-apply |
| * converters are applied against basics only. |
| */ |
| public MetadataClass getMapKeyReferenceClassWithGenerics() { |
| return getMapKeyReferenceClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the raw class for this accessor. |
| * E.g. For an accessor with a type of java.util.Collection<Employee>, this |
| * method will return java.util.Collection. To check for the attribute |
| * type we must go through the method calls since some accessors define |
| * the attribute type through a target entity specification. Do not access |
| * the m_attributeType variable directly in this method. |
| */ |
| public MetadataClass getRawClass() { |
| if (hasAttributeType()) { |
| // If the class doesn't exist the factory we'll just return a |
| // generic MetadataClass |
| return getMetadataClass(getAttributeType()); |
| } else { |
| return getAccessibleObject().getRawClass(getDescriptor()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the raw class with any generic specifications for this accessor. |
| * E.g. For an accessor with a type of java.util.Collection<Employee>, this |
| * method will return java.util.CollectionEmployee. To check for the |
| * attribute type we must go through the method calls since some accessors |
| * define the attribute type through a target entity specification. Do not |
| * access the m_attributeType variable directly in this method. |
| */ |
| public MetadataClass getRawClassWithGenerics() { |
| if (hasAttributeType()) { |
| // If the class doesn't exist the factory we'll just return a |
| // generic MetadataClass |
| return getMetadataClass(getAttributeType()); |
| } else { |
| return getAccessibleObject().getRawClassWithGenerics(getDescriptor()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping accessors associated with the reference descriptor. |
| */ |
| public Collection<MappingAccessor> getReferenceAccessors() { |
| return getReferenceDescriptor().getMappingAccessors(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference class for this accessor. By default the reference |
| * class is the raw class. Some accessors may need to override this |
| * method to drill down further. That is, try to extract a reference class |
| * from generics. |
| */ |
| public MetadataClass getReferenceClass() { |
| return getRawClass(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference class for this accessor. By default the reference |
| * class is the raw class. Some accessors may need to override this |
| * method to drill down further. That is, try to extract a reference class |
| * from generics. |
| */ |
| public MetadataClass getReferenceClassWithGenerics() { |
| return getRawClassWithGenerics(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Attempts to return a reference class from a generic specification. Note, |
| * this method may return null. |
| */ |
| public MetadataClass getReferenceClassFromGeneric() { |
| return getAccessibleObject().getReferenceClassFromGeneric(getDescriptor()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference class name for this accessor. |
| */ |
| public String getReferenceClassName() { |
| return getReferenceClass().getName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference descriptors table. By default it is the primary |
| * key table off the reference descriptor. Subclasses that care to return |
| * a different class should override this method. |
| * @see DirectCollectionAccessor |
| * @see ManyToManyAccessor |
| */ |
| protected DatabaseTable getReferenceDatabaseTable() { |
| return getReferenceDescriptor().getPrimaryKeyTable(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference metadata descriptor for this accessor. |
| */ |
| public MetadataDescriptor getReferenceDescriptor() { |
| ClassAccessor accessor = getProject().getAccessor(getReferenceClassName()); |
| |
| if (accessor == null) { |
| throw ValidationException.classNotListedInPersistenceUnit(getReferenceClassName()); |
| } |
| |
| return accessor.getDescriptor(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the set method name of a method accessor. Note, this method |
| * should not be called when processing field access. |
| */ |
| public String getSetMethodName() { |
| return hasAccessMethods() ? getAccessMethods().getSetMethodName() : ((MetadataMethod) getAccessibleObject()).getSetMethodName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the temporal metadata for this accessor. |
| * @see DirectAccessor |
| * @see CollectionAccessor |
| */ |
| public TemporalMetadata getTemporal(boolean isForMapKey) { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: Set the temporal metadata for this accessor. |
| * |
| * @see DirectAccessor |
| * @see CollectionAccessor |
| */ |
| protected void setTemporal(TemporalMetadata metadata, boolean isForMapKey) { |
| |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if we have an attribute override for this accessor. |
| */ |
| protected boolean hasAttributeOverride(String loggingCtx) { |
| if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) { |
| return getDescriptor().hasAttributeOverrideFor(KEY_DOT_NOTATION + getAttributeName()); |
| } else if (loggingCtx.equals(MetadataLogger.VALUE_COLUMN)) { |
| if (getDescriptor().hasAttributeOverrideFor(VALUE_DOT_NOTATION + getAttributeName())) { |
| return true; |
| } |
| } |
| |
| return getDescriptor().hasAttributeOverrideFor(getAttributeName()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Those accessors that do not require a separate attribute-type |
| * specification for VIRTUAL accessors should override this method. For |
| * example, one-to-one and many-to-one will its target-entity. |
| * variable-one-to-one will use its target-interface. |
| */ |
| public boolean hasAttributeType() { |
| return m_attributeType != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor has temporal metadata. |
| * @see DirectAccessor |
| * @see ElementCollectionAccessor |
| * @see CollectionAccessor |
| */ |
| protected boolean hasEnumerated(boolean isForMapKey) { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor has lob metadata. |
| * @see DirectAccessor |
| * @see ElementCollectionAccessor |
| * @see CollectionAccessor |
| */ |
| protected boolean hasLob(boolean isForMapKey) { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Method should be overridden by those accessors that accept and use a map |
| * key. |
| * @see CollectionAccessor |
| * @see ElementCollectionAccessor |
| * @see BasicMapAccessor |
| */ |
| public boolean hasMapKey() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Method to check if this accessor has a ReturnInsert annotation. |
| */ |
| protected boolean hasReturnInsert() { |
| return isAnnotationPresent(ReturnInsert.class); |
| } |
| |
| /** |
| * INTERNAL: |
| * Method to check if this accessor has a ReturnUpdate annotation. |
| */ |
| protected boolean hasReturnUpdate() { |
| return isAnnotationPresent(ReturnUpdate.class); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor has temporal metadata. |
| * @see DirectAccessor |
| * @see ElementCollectionAccessor |
| * @see CollectionAccessor |
| */ |
| public boolean hasTemporal(boolean isForMapKey) { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Init an xml mapping accessor with its necessary components. |
| */ |
| public void initXMLMappingAccessor(ClassAccessor classAccessor) { |
| m_classAccessor = classAccessor; |
| setEntityMappings(classAccessor.getEntityMappings()); |
| initXMLAccessor(classAccessor.getDescriptor(), classAccessor.getProject()); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void initXMLObject(MetadataAccessibleObject accessibleObject, XMLEntityMappings entityMappings) { |
| super.initXMLObject(accessibleObject, entityMappings); |
| |
| initXMLObject(m_field, accessibleObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the specified annotation is present on the annotated |
| * element for this accessor. Method checks against the metadata complete |
| * flag. |
| */ |
| @Override |
| public boolean isAnnotationPresent(String annotation) { |
| return getAccessibleObject().isAnnotationPresent(annotation, getClassAccessor()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a basic mapping. |
| */ |
| public boolean isBasic() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a basic collection mapping. |
| */ |
| public boolean isBasicCollection() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a basic map mapping. |
| */ |
| public boolean isBasicMap() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor is a derived id class accessor. |
| */ |
| public boolean isDerivedIdClass(){ |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a direct collection mapping, |
| * which include basic collection, basic map and element collection |
| * accessors. |
| */ |
| public boolean isDirectCollection() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents an element collection that |
| * contains embeddable objects. |
| */ |
| public boolean isDirectEmbeddableCollection() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a collection accessor. |
| */ |
| public boolean isCollectionAccessor() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents an aggregate mapping. |
| */ |
| public boolean isEmbedded() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents an aggregate id mapping. |
| */ |
| public boolean isEmbeddedId() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this represents an enum type mapping. Will return true |
| * if the accessor's reference class is an enum or if enumerated metadata |
| * exists. |
| */ |
| protected boolean isEnumerated(MetadataClass referenceClass, boolean isForMapKey) { |
| return hasEnumerated(isForMapKey) || EnumeratedMetadata.isValidEnumeratedType(referenceClass); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor is part of the id. |
| */ |
| public boolean isId(){ |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a BLOB/CLOB mapping. |
| */ |
| protected boolean isLob(MetadataClass referenceClass, boolean isForMapKey) { |
| return hasLob(isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a m-m relationship. |
| */ |
| public boolean isManyToMany() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a m-1 relationship. |
| */ |
| public boolean isManyToOne() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor uses a Map. |
| */ |
| public boolean isMapAccessor() { |
| return getAccessibleObject().isSupportedMapClass(getRawClass()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor is a mapped key map accessor. It is a |
| * map key accessor for two reasons, it's a map and it does not have |
| * a map key specified. NOTE: we can't check for a map key class since |
| * one may not have been explicitly specified. In this case, a generic |
| * value must be set and we check for one when adding accessors (and in |
| * turn set the map key class at that point) |
| */ |
| public boolean isMappedKeyMapAccessor() { |
| return MappedKeyMapAccessor.class.isAssignableFrom(getClass()) && isMapAccessor() && ! hasMapKey(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor is a multitenant id mapping. |
| */ |
| public boolean isMultitenantId() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a 1-m relationship. |
| */ |
| public boolean isOneToMany() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a 1-1 relationship. |
| */ |
| public boolean isOneToOne() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true is the given class is primitive wrapper type. |
| */ |
| protected boolean isPrimitiveWrapperClass(MetadataClass cls) { |
| return cls.extendsClass(Number.class) || |
| cls.isClass(Boolean.class) || |
| cls.isClass(Character.class) || |
| cls.isClass(String.class) || |
| cls.extendsClass(java.math.BigInteger.class) || |
| cls.extendsClass(java.math.BigDecimal.class) || |
| cls.extendsClass(java.util.Date.class) || |
| cls.extendsClass(java.util.Calendar.class); |
| } |
| |
| protected boolean isTimeClass(MetadataClass cls) { |
| return cls.extendsClass(java.time.LocalDateTime.class) || |
| cls.extendsClass(java.time.LocalDate.class) || |
| cls.extendsClass(java.time.LocalTime.class) || |
| cls.extendsClass(java.time.OffsetDateTime.class) || |
| cls.extendsClass(java.time.OffsetTime.class); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor has been processed. If there is a mapping |
| * set, we have processed this accessor. |
| */ |
| @Override |
| public boolean isProcessed() { |
| return m_mapping != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor method represents a relationship. |
| */ |
| public boolean isRelationship() { |
| return isManyToOne() || isManyToMany() || isOneToMany() || isOneToOne() || isVariableOneToOne(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a serialized mapping. |
| */ |
| public boolean isSerialized(MetadataClass referenceClass, boolean isForMapKey) { |
| return isValidSerializedType(referenceClass); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this represents a temporal type mapping. |
| */ |
| protected boolean isTemporal(MetadataClass referenceClass, boolean isForMapKey) { |
| return hasTemporal(isForMapKey) || TemporalMetadata.isValidTemporalType(referenceClass); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a transient mapping. |
| */ |
| public boolean isTransient() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if the given class is valid for SerializedObjectMapping. |
| */ |
| protected boolean isValidSerializedType(MetadataClass cls) { |
| if (cls.isPrimitive()) { |
| return false; |
| } |
| |
| if (isPrimitiveWrapperClass(cls)) { |
| return false; |
| } |
| |
| if (LobMetadata.isValidLobType(cls)) { |
| return false; |
| } |
| |
| if (TemporalMetadata.isValidTemporalType(cls)) { |
| return false; |
| } |
| |
| if (isTimeClass(cls)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if this accessor represents a variable one to one mapping. |
| */ |
| public boolean isVariableOneToOne() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Process an association override for either an embedded object mapping, |
| * or a map mapping (element-collection, 1-M and M-M) containing an |
| * embeddable object as the value or key. |
| * This method should be implemented in those accessors that support |
| * association overrides. An exception is thrown otherwise the association |
| * is called against an unsupported accessor/relationship. |
| */ |
| protected void processAssociationOverride(AssociationOverrideMetadata associationOverride, EmbeddableMapping embeddableMapping, MetadataDescriptor owningDescriptor) { |
| throw ValidationException.invalidEmbeddableAttributeForAssociationOverride(getJavaClass(), getAttributeName(), associationOverride.getName(), associationOverride.getLocation()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the association overrides for the given embeddable mapping which |
| * is either an embedded or element collection mapping. Association |
| * overrides are used to specify different keys to a shared mapping. |
| */ |
| protected void processAssociationOverrides(List<AssociationOverrideMetadata> associationOverrides, EmbeddableMapping embeddableMapping, MetadataDescriptor embeddableDescriptor) { |
| // Get the processible map of association overrides. This will take dot |
| // notation overrides into consideration (from a sub-entity to a mapped |
| // superclass accessor) and merge the lists. Once the map is returned, |
| // use the map keys as the attribute name and not the name from the |
| // individual association override since they could still contain dot |
| // notation names meaning you will not find their respective mapping |
| // accessor on the embeddable descriptor. |
| Map<String, AssociationOverrideMetadata> mergedAssociationOverrides = getAssociationOverrides(associationOverrides); |
| |
| for (String attributeName : mergedAssociationOverrides.keySet()) { |
| AssociationOverrideMetadata associationOverride = mergedAssociationOverrides.get(attributeName); |
| // The getAccessorFor call will take care of any sub dot notation |
| // attribute names when looking for the mapping. It will traverse |
| // the embeddable chain. |
| MappingAccessor mappingAccessor = embeddableDescriptor.getMappingAccessor(attributeName); |
| |
| if (mappingAccessor == null) { |
| throw ValidationException.embeddableAssociationOverrideNotFound(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName()); |
| } else { |
| mappingAccessor.processAssociationOverride(associationOverride, embeddableMapping, getOwningDescriptor()); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the attribute overrides for the given embedded mapping. Attribute |
| * overrides are used to apply the correct field name translations of direct |
| * fields. Note an embedded object mapping may be supported as the map key |
| * to an element-collection, 1-M and M-M mapping. |
| */ |
| protected void processAttributeOverrides(List<AttributeOverrideMetadata> attributeOverrides, AggregateObjectMapping aggregateObjectMapping, MetadataDescriptor embeddableDescriptor) { |
| // Get the processible map of attribute overrides. This will take dot |
| // notation overrides into consideration (from a sub-entity to a mapped |
| // superclass accessor) and merge the lists. Once the map is returned, |
| // use the map keys as the attribute name and not the name from the |
| // individual attribute override since they could still contain dot |
| // notation names meaning you will not find their respective mapping |
| // accessor on the embeddable descriptor. |
| Map<String, AttributeOverrideMetadata> mergedAttributeOverrides = getAttributeOverrides(attributeOverrides); |
| |
| for (String attributeName : mergedAttributeOverrides.keySet()) { |
| AttributeOverrideMetadata attributeOverride = mergedAttributeOverrides.get(attributeName); |
| // The getMappingForAttributeName call will take care of any sub dot |
| // notation attribute names when looking for the mapping. It will |
| // traverse the embeddable chain. |
| MappingAccessor mappingAccessor = embeddableDescriptor.getMappingAccessor(attributeName); |
| |
| String colName = attributeOverride.getColumn().getName(); |
| if (colName == null || colName.isEmpty()) { |
| String prevName = mappingAccessor.getDefaultAttributeName(); |
| attributeOverride.getColumn().setName(prevName); |
| } |
| |
| if (mappingAccessor == null) { |
| throw ValidationException.embeddableAttributeOverrideNotFound(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName()); |
| } else if (! mappingAccessor.isBasic()) { |
| throw ValidationException.invalidEmbeddableAttributeForAttributeOverride(embeddableDescriptor.getJavaClass(), attributeName, getJavaClass(), getAttributeName()); |
| } else { |
| // Get databasefield() takes care of any delimited/uppercasing on the column. |
| addFieldNameTranslation(aggregateObjectMapping, attributeName, attributeOverride.getColumn().getDatabaseField(), mappingAccessor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the map metadata if this is a valid map accessor. Will return |
| * the map key method name that should be use, null otherwise. |
| * @see CollectionAccessor |
| * @see ElementCollectionAccessor |
| */ |
| protected void processContainerPolicyAndIndirection(ContainerMapping mapping) { |
| if (isMappedKeyMapAccessor()) { |
| // If we are a map key map accessor then the following is true: |
| // 1 - we implement the mapped key map accessor interface |
| // 2 - we are a map accessor |
| // 3 - there is no map key metadata specified |
| processMapKeyClass(mapping, (MappedKeyMapAccessor) this); |
| } else if (isMapAccessor()) { |
| // If we are not a mapped key map accessor, but a map accessor, |
| // we need a map key metadata object to process. Default one if |
| // one is not provided. |
| MapKeyMetadata mapKey = getMapKey(); |
| if (mapKey == null) { |
| setIndirectionPolicy(mapping, new MapKeyMetadata().process(mapping, this), usesIndirection()); |
| } else { |
| setIndirectionPolicy(mapping, mapKey.process(mapping, this), usesIndirection()); |
| } |
| } else { |
| // Set the indirection policy on the mapping. |
| setIndirectionPolicy(mapping, null, usesIndirection()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a Convert annotation or convert element to apply to specified |
| * EclipseLink converter (Converter, TypeConverter, ObjectTypeConverter) |
| * to the given mapping. |
| */ |
| protected void processConvert(DatabaseMapping mapping, String converterName, MetadataClass referenceClass, boolean isForMapKey, boolean hasConverts) { |
| if (converterName.equals(Convert.SERIALIZED)) { |
| processSerialized(mapping, referenceClass, isForMapKey); |
| } else if (converterName.equals(Convert.CLASS_INSTANCE)){ |
| new ClassInstanceMetadata().process(mapping, this, referenceClass, isForMapKey); |
| } else if (converterName.equals(Convert.XML)){ |
| new XMLMetadata().process(mapping, this, referenceClass, isForMapKey); |
| } else if (converterName.equals(Convert.JSON)){ |
| new JSONMetadata().process(mapping, this, referenceClass, isForMapKey); |
| } else if (converterName.equals(Convert.KRYO)){ |
| new KryoMetadata().process(mapping, this, referenceClass, isForMapKey); |
| } else { |
| AbstractConverterMetadata converter = getProject().getConverter(converterName); |
| |
| if (converter == null) { |
| throw ValidationException.converterNotFound(getJavaClass(), converterName, getAnnotatedElement()); |
| } else { |
| // Process the converter for this mapping. |
| converter.process(mapping, this, referenceClass, isForMapKey); |
| } |
| } |
| |
| // This was an old requirement from PM, that if an EclipseLink |
| // convert was specified with a JPA converter that we log a warning |
| // message that we're overriding the JPA converter. |
| |
| if (hasEnumerated(isForMapKey)) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_ENUMERATED, getJavaClass(), getAnnotatedElement()); |
| } |
| |
| if (hasLob(isForMapKey)) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_LOB, getJavaClass(), getAnnotatedElement()); |
| } |
| |
| if (hasTemporal(isForMapKey)) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_TEMPORAL, getJavaClass(), getAnnotatedElement()); |
| } |
| |
| if (isValidSerializedType(referenceClass)) { |
| getLogger().logConfigMessage(MetadataLogger.IGNORE_SERIALIZED, getJavaClass(), getAnnotatedElement()); |
| } |
| |
| // Check for the new JPA 2.1 convert metadata. |
| if (hasConverts) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_CONVERTS, getJavaClass(), getAnnotatedElement()); |
| } |
| |
| if (getProject().hasAutoApplyConverter(referenceClass)) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_AUTO_APPLY_CONVERTER, getJavaClass(), getAnnotatedElement()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the JPA defined convert(s) |
| */ |
| protected void processConverts(List<ConvertMetadata> converts, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) { |
| if (converts != null) { |
| for (ConvertMetadata convert : converts) { |
| convert.process(mapping, referenceClass, getClassAccessor(), isForMapKey); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected AbstractDirectMapping processDirectMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) { |
| AbstractDirectMapping keyMapping = new DirectToFieldMapping(); |
| |
| // Get the map key field, defaulting and looking for attribute |
| // overrides. Set the field before applying a converter. |
| DatabaseField mapKeyField = getDatabaseField(getReferenceDatabaseTable(), MetadataLogger.MAP_KEY_COLUMN); |
| keyMapping.setField(mapKeyField); |
| keyMapping.setIsReadOnly(mapKeyField.isReadOnly()); |
| keyMapping.setAttributeClassificationName(mappedKeyMapAccessor.getMapKeyClass().getName()); |
| keyMapping.setDescriptor(getDescriptor().getClassDescriptor()); |
| |
| // Process a convert key or jpa converter for the map key if specified. |
| processMappingKeyConverter(keyMapping, mappedKeyMapAccessor.getMapKeyConvert(), mappedKeyMapAccessor.getMapKeyConverts(), mappedKeyMapAccessor.getMapKeyClass(), mappedKeyMapAccessor.getMapKeyClassWithGenerics()); |
| |
| return keyMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected AggregateObjectMapping processEmbeddableMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) { |
| AggregateObjectMapping keyMapping = new AggregateObjectMapping(); |
| MetadataClass mapKeyClass = mappedKeyMapAccessor.getMapKeyClass(); |
| keyMapping.setReferenceClassName(mapKeyClass.getName()); |
| |
| // The embeddable accessor must be processed by now. If it is not then |
| // we are in trouble since by the time we get here, we are too late in |
| // the cycle to process embeddable classes and their accessors. See |
| // MetadataProject processStage3(), processEmbeddableMappingAccessors. |
| // At this stage all class accessors (embeddable, entity and mapped |
| // superclass) have to have been processed to gather all their |
| // relational and embedded mappings. |
| EmbeddableAccessor mapKeyAccessor = getProject().getEmbeddableAccessor(mapKeyClass); |
| |
| // Ensure the reference descriptor is marked as an embeddable collection. |
| mapKeyAccessor.getDescriptor().setIsEmbeddable(); |
| |
| // Process the attribute overrides for this may key embeddable. |
| processAttributeOverrides(mappedKeyMapAccessor.getMapKeyAttributeOverrides(), keyMapping, mapKeyAccessor.getDescriptor()); |
| |
| // Process the association overrides for this may key embeddable. |
| processAssociationOverrides(mappedKeyMapAccessor.getMapKeyAssociationOverrides(), keyMapping, mapKeyAccessor.getDescriptor()); |
| |
| // Process a convert key or jpa converter for the map key if specified. |
| processConverts(getMapKeyConverts(mappedKeyMapAccessor.getMapKeyConverts()), keyMapping, mappedKeyMapAccessor.getMapKeyClass(), true); |
| |
| keyMapping.setDescriptor(getDescriptor().getClassDescriptor()); |
| |
| return keyMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the map key to be an entity class. |
| */ |
| protected OneToOneMapping processEntityMapKeyClass(MappedKeyMapAccessor mappedKeyMapAccessor) { |
| String mapKeyClassName = mappedKeyMapAccessor.getMapKeyClass().getName(); |
| |
| // Create the one to one map key mapping. |
| OneToOneMapping keyMapping = new OneToOneMapping(); |
| keyMapping.setReferenceClassName(mapKeyClassName); |
| keyMapping.dontUseIndirection(); |
| keyMapping.setDescriptor(getDescriptor().getClassDescriptor()); |
| |
| // Process the map key join columns. |
| EntityAccessor mapKeyAccessor = getProject().getEntityAccessor(mapKeyClassName); |
| MetadataDescriptor mapKeyClassDescriptor = mapKeyAccessor.getDescriptor(); |
| |
| // If the fk field (name) is not specified, it defaults to the |
| // concatenation of the following: the name of the referencing |
| // relationship property or field of the referencing entity or |
| // embeddable; "_"; "KEY" |
| String defaultFKFieldName = getAttributeName() + DEFAULT_MAP_KEY_COLUMN_SUFFIX; |
| |
| // Get the join columns (directly or through an association override), |
| // init them and validate. |
| List<JoinColumnMetadata> joinColumns = getJoinColumns(mappedKeyMapAccessor.getMapKeyJoinColumns(), mapKeyClassDescriptor); |
| |
| // Get the foreign key (directly or through an association override) and |
| // make sure it is initialized for processing. |
| ForeignKeyMetadata foreignKey = getForeignKey(mappedKeyMapAccessor.getMapKeyForeignKey(), mapKeyClassDescriptor); |
| |
| // Now process the foreign key relationship metadata. |
| processForeignKeyRelationship(keyMapping, joinColumns, foreignKey, mapKeyClassDescriptor, defaultFKFieldName, getDefaultTableForEntityMapKey()); |
| |
| return keyMapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * Process an Enumerated setting. The method may still be called if no |
| * Enumerated metadata has been specified but the accessor's reference |
| * class is a valid enumerated type. |
| */ |
| protected void processEnumerated(EnumeratedMetadata enumerated, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) { |
| if (enumerated == null) { |
| enumerated = new EnumeratedMetadata(this); |
| } |
| |
| enumerated.process(mapping, this, referenceClass, isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the indirection (aka fetch type) |
| */ |
| protected void processIndirection(ForeignReferenceMapping mapping) { |
| boolean usesIndirection = usesIndirection(); |
| |
| // Lazy is not disabled until descriptor initialization (OneToOneMapping preInitialize), |
| // as it cannot be known if weaving occurred until then. |
| String actualAttributeType = getAttributeType(); |
| if (getAccessibleObject() != null){ |
| actualAttributeType = getAccessibleObject().getType(); |
| } |
| |
| if (usesIndirection && usesPropertyAccess()) { |
| mapping.setIndirectionPolicy(new WeavedObjectBasicIndirectionPolicy(getGetMethodName(), getSetMethodName(), actualAttributeType, true)); |
| } else if (usesIndirection && usesFieldAccess()) { |
| mapping.setIndirectionPolicy(new WeavedObjectBasicIndirectionPolicy(Helper.getWeavedGetMethodName(mapping.getAttributeName()), Helper.getWeavedSetMethodName(mapping.getAttributeName()), actualAttributeType, false)); |
| } else { |
| mapping.setUsesIndirection(usesIndirection); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the mapping join fetch type. |
| */ |
| protected void processJoinFetch(String joinFetch, ForeignReferenceMapping mapping) { |
| if (joinFetch == null) { |
| mapping.setJoinFetch(ForeignReferenceMapping.NONE); |
| } else if (joinFetch.equals(JoinFetchType.INNER.name())) { |
| mapping.setJoinFetch(ForeignReferenceMapping.INNER_JOIN); |
| } else { |
| mapping.setJoinFetch(ForeignReferenceMapping.OUTER_JOIN); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a lob specification. The lob must be specified to process and |
| * create a lob type mapping. |
| */ |
| protected void processLob(LobMetadata lob, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) { |
| lob.process(mapping, this, referenceClass, isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a map key class for the given map key map accessor. |
| */ |
| protected void processMapKeyClass(ContainerMapping mapping, MappedKeyMapAccessor mappedKeyMapAccessor) { |
| MapKeyMapping keyMapping; |
| MetadataClass mapKeyClass = mappedKeyMapAccessor.getMapKeyClass(); |
| |
| if (getProject().hasEntity(mapKeyClass)) { |
| keyMapping = processEntityMapKeyClass(mappedKeyMapAccessor); |
| } else if (getProject().hasEmbeddable(mapKeyClass)) { |
| keyMapping = processEmbeddableMapKeyClass(mappedKeyMapAccessor); |
| } else { |
| keyMapping = processDirectMapKeyClass(mappedKeyMapAccessor); |
| } |
| |
| Class containerClass; |
| if (mapping instanceof ForeignReferenceMapping) { |
| if (usesIndirection()) { |
| containerClass = ClassConstants.IndirectMap_Class; |
| ((ForeignReferenceMapping) mapping).setIndirectionPolicy(new TransparentIndirectionPolicy()); |
| } else { |
| containerClass = java.util.Hashtable.class; |
| ((ForeignReferenceMapping) mapping).dontUseIndirection(); |
| } |
| } else { |
| containerClass = java.util.Hashtable.class; |
| } |
| |
| MappedKeyMapContainerPolicy policy = new MappedKeyMapContainerPolicy(containerClass); |
| policy.setKeyMapping(keyMapping); |
| policy.setValueMapping((MapComponentMapping) mapping); |
| mapping.setContainerPolicy(policy); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a convert value which specifies the name of an EclipseLink |
| * converter to process with this accessor's mapping. |
| */ |
| protected void processMappingConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics, boolean isForMapKey) { |
| boolean hasConverts = (converts != null && ! converts.isEmpty()); |
| |
| // A convert value is an EclipseLink extension and it takes precedence |
| // over all JPA converters so check for it first. |
| if (convertValue != null && ! convertValue.equals(Convert.NONE)) { |
| processConvert(mapping, convertValue, referenceClass, isForMapKey, hasConverts); |
| } else if (hasConverts) { |
| // If we have JPA converts, apply them. |
| processConverts(converts, mapping, referenceClass, isForMapKey); |
| } else if (getProject().hasAutoApplyConverter(referenceClassWithGenerics)) { |
| // If no convert is specified and there exist an auto-apply |
| // converter for the reference class, apply it. |
| getProject().getAutoApplyConverter(referenceClassWithGenerics).process(mapping, isForMapKey, null); |
| } else { |
| // Check for original JPA converters. Check for an enum first since |
| // it will fall into a serializable mapping otherwise since enums |
| // are serialized. |
| if (isEnumerated(referenceClass, isForMapKey)) { |
| processEnumerated(getEnumerated(isForMapKey), mapping, referenceClass, isForMapKey); |
| } else if (isLob(referenceClass, isForMapKey)) { |
| processLob(getLob(isForMapKey), mapping, referenceClass, isForMapKey); |
| } else if (isTemporal(referenceClass, isForMapKey)) { |
| processTemporal(getTemporal(isForMapKey), mapping, referenceClass, isForMapKey); |
| } else if (isSerialized(referenceClass, isForMapKey)) { |
| processSerialized(mapping, referenceClass, isForMapKey); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a mapping key converter either from an EclipseLink convert |
| * specification or a JPA converter specification (map-key-convert, |
| * map-key-temporal, map-key-enumerated) to be applied to the given mapping. |
| */ |
| protected void processMappingKeyConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics) { |
| processMappingConverter(mapping, convertValue, getMapKeyConverts(converts), referenceClass, referenceClassWithGenerics, true); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a convert value which specifies the name of an EclipseLink |
| * converter to process with this accessor's mapping. |
| */ |
| protected void processMappingValueConverter(DatabaseMapping mapping, String convertValue, List<ConvertMetadata> converts, MetadataClass referenceClass, MetadataClass referenceClassWithGenerics) { |
| processMappingConverter(mapping, convertValue, getConverts(converts), referenceClass, referenceClassWithGenerics, false); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process the join columns for the owning side of a one to one mapping. |
| * The default pk and fk field names are used only with single primary key |
| * entities. The processor should never get as far as to use them with |
| * entities that have a composite primary key (validation exception will be |
| * thrown). |
| */ |
| protected void processForeignKeyRelationship(ForeignReferenceMapping mapping, List<JoinColumnMetadata> joinColumns, ForeignKeyMetadata foreignKey, MetadataDescriptor referenceDescriptor, String defaultFKFieldName, DatabaseTable defaultFKTable) { |
| // We need to know if all the mappings are read-only so we can determine |
| // if we use target foreign keys to represent read-only parts of the |
| // join, or if we simply set the whole mapping as read-only |
| boolean allReadOnly = true; |
| Map<DatabaseField, DatabaseField> fields = new HashMap<DatabaseField, DatabaseField>(); |
| List<String> sourceFields = new ArrayList<String>(); |
| List<String> targetFields = new ArrayList<String>(); |
| DatabaseTable targetTable = null; |
| |
| // Build our fk->pk associations. |
| for (JoinColumnMetadata joinColumn : joinColumns) { |
| // Look up the primary key field from the referenced column name. |
| DatabaseField pkField = getReferencedField(joinColumn.getReferencedColumnName(), referenceDescriptor, MetadataLogger.PK_COLUMN); |
| |
| // The foreign key should be built using the primary key field |
| // since it will contain extra metadata that can not be specified |
| // in the join column. This will keep the pk and fk fields in sync. |
| DatabaseField fkField = joinColumn.getForeignKeyField(pkField); |
| setFieldName(fkField, defaultFKFieldName, MetadataLogger.FK_COLUMN); |
| |
| // Set the table name if one is not already set. |
| if (! fkField.hasTableName()) { |
| fkField.setTable(defaultFKTable); |
| } |
| |
| fields.put(fkField, pkField); |
| sourceFields.add(fkField.getName()); |
| targetFields.add(pkField.getName()); |
| if (targetTable == null) { |
| targetTable = pkField.getTable(); |
| } |
| allReadOnly = allReadOnly && fkField.isReadOnly(); |
| } |
| |
| DatabaseTable foreignKeyTable = null; |
| |
| // Apply the fields to the mapping based on what we found. |
| for (DatabaseField fkField : fields.keySet()) { |
| DatabaseField pkField = fields.get(fkField); |
| |
| if (allReadOnly || ! fkField.isReadOnly()) { |
| // Add a source foreign key to the mapping. |
| mapping.addForeignKeyField(fkField, pkField); |
| } else { |
| // This is a read-only join column that is part of a set of join |
| // columns that are not all read only - hence this is not a |
| // read-only mapping, but instead uses a target foreign key |
| // field to enable the read-only functionality |
| mapping.addTargetForeignKeyField(pkField, fkField); |
| } |
| |
| // Set the foreign key table to the first fk's table. |
| if (foreignKeyTable == null) { |
| foreignKeyTable = fkField.getTable(); |
| } |
| } |
| |
| // If all the join columns are read-only then set the mapping as read only. |
| mapping.setIsReadOnly(allReadOnly); |
| |
| // Process the foreign key if one is provided. Right now this assumes |
| // and supports the foreign keys all being on the same table. It covers |
| // the spec case (on the source table) and our extended support when |
| // the fk's are on the target table as well. |
| if (foreignKey != null) { |
| foreignKey.process(foreignKeyTable, sourceFields, targetFields, targetTable); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Adds properties to the mapping. |
| */ |
| protected void processProperties(DatabaseMapping mapping) { |
| // If we were loaded from XML use the properties loaded from there |
| // only. Otherwise look for annotations. |
| if (loadedFromXML()) { |
| for (PropertyMetadata property : getProperties()) { |
| processProperty(mapping, property); |
| } |
| } else { |
| // Look for annotations. |
| MetadataAnnotation properties = getAnnotation(Properties.class); |
| if (properties != null) { |
| for (Object property : properties.getAttributeArray("value")) { |
| processProperty(mapping, new PropertyMetadata((MetadataAnnotation) property, this)); |
| } |
| } |
| |
| MetadataAnnotation property = getAnnotation(Property.class); |
| if (property != null) { |
| processProperty(mapping, new PropertyMetadata(property, this)); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Adds properties to the mapping. They can only come from one place, |
| * therefore if we add the same one twice we know to throw an exception. |
| */ |
| protected void processProperty(DatabaseMapping mapping, PropertyMetadata property) { |
| if (property.shouldOverride(m_properties.get(property.getName()))) { |
| m_properties.put(property.getName(), property); |
| mapping.addUnconvertedProperty(property.getName(), property.getValue(), getJavaClassName(property.getValueType())); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses should call this method if they want the warning message or |
| * override the method if they want/support different behavior. |
| * @see BasicAccessor |
| */ |
| protected void processReturnInsert() { |
| if (hasReturnInsert()) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_RETURN_INSERT_ANNOTATION, getAnnotatedElement()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses should call this method if they want the warning message. |
| */ |
| protected void processReturnInsertAndUpdate() { |
| processReturnInsert(); |
| processReturnUpdate(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Subclasses should call this method if they want the warning message or |
| * override the method if they want/support different behavior. |
| * @see BasicAccessor |
| */ |
| protected void processReturnUpdate() { |
| if (hasReturnUpdate()) { |
| getLogger().logWarningMessage(MetadataLogger.IGNORE_RETURN_UPDATE_ANNOTATION, getAnnotatedElement()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a potential serializable attribute. If the class implements |
| * the Serializable interface then set a SerializedObjectConverter on |
| * the mapping. |
| */ |
| protected void processSerialized(DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) { |
| new SerializedMetadata(this).process(mapping, this, referenceClass, isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a potential serializable attribute. If the class implements |
| * the Serializable interface then set a SerializedObjectConverter on |
| * the mapping. |
| */ |
| protected void processSerialized(DatabaseMapping mapping, MetadataClass referenceClass, MetadataClass classification, boolean isForMapKey) { |
| new SerializedMetadata(this).process(mapping, this, referenceClass, classification, isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Process a temporal type accessor. |
| */ |
| protected void processTemporal(TemporalMetadata temporal, DatabaseMapping mapping, MetadataClass referenceClass, boolean isForMapKey) { |
| if (temporal == null) { |
| // We need to have a temporal type on either a basic mapping or the key to a collection |
| // mapping. Since the temporal type was not specified, per the JPA spec we *should* throw an |
| // exception.. but lets be a little nice to our users and default to timestamp. |
| MetadataAnnotation annotation = new MetadataAnnotation(); |
| annotation.setName("jakarta.persistence.Temporal"); |
| annotation.addAttribute("value", "TIMESTAMP"); |
| temporal = new TemporalMetadata(annotation, this); |
| |
| // This call handles both @Temporal and @MapKeyTemporal |
| setTemporal(temporal, isForMapKey); |
| } |
| |
| temporal.process(mapping, this, referenceClass, isForMapKey); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the getter and setter access methods for this accessor. |
| */ |
| protected void setAccessorMethods(DatabaseMapping mapping) { |
| if (usesPropertyAccess() || usesVirtualAccess()) { |
| if (usesVirtualAccess()) { |
| mapping.setAttributeAccessor(new VirtualAttributeAccessor()); |
| } |
| |
| mapping.setGetMethodName(getGetMethodName()); |
| mapping.setSetMethodName(getSetMethodName()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setAttributeType(String attributeType) { |
| m_attributeType = attributeType; |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the class accessor for this mapping accessor. |
| */ |
| public void setClassAccessor(ClassAccessor classAccessor) { |
| m_classAccessor = classAccessor; |
| } |
| |
| /** |
| * INTERNAL: |
| * Used for OX mapping. |
| */ |
| public void setField(ColumnMetadata column) { |
| m_field = column; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the correct indirection policy on a collection mapping. Method |
| * assume that the reference class has been set on the mapping before |
| * calling this method. |
| */ |
| protected void setIndirectionPolicy(ContainerMapping mapping, String mapKey, boolean usesIndirection) { |
| MetadataClass rawClass = getRawClass(); |
| boolean containerPolicySet = false; |
| if (usesIndirection && (mapping instanceof ForeignReferenceMapping)) { |
| containerPolicySet = true; |
| CollectionMapping collectionMapping = (CollectionMapping)mapping; |
| if (rawClass.isClass(Map.class)) { |
| if (collectionMapping.isDirectMapMapping()) { |
| ((DirectMapMapping) mapping).useTransparentMap(); |
| } else { |
| collectionMapping.useTransparentMap(mapKey); |
| } |
| } else if (rawClass.isClass(List.class)) { |
| collectionMapping.useTransparentList(); |
| } else if (rawClass.isClass(Collection.class)) { |
| collectionMapping.useTransparentCollection(); |
| } else if (rawClass.isClass(Set.class)) { |
| collectionMapping.useTransparentSet(); |
| } else { |
| getLogger().logWarningMessage(MetadataLogger.WARNING_INVALID_COLLECTION_USED_ON_LAZY_RELATION, getJavaClass(), getAnnotatedElement(), rawClass); |
| processIndirection((ForeignReferenceMapping)mapping); |
| containerPolicySet = false; |
| } |
| } else { |
| if (mapping instanceof CollectionMapping) { |
| ((CollectionMapping)mapping).dontUseIndirection(); |
| } |
| } |
| if (!containerPolicySet) { |
| if (rawClass.isClass(Map.class)) { |
| if (mapping instanceof DirectMapMapping) { |
| ((DirectMapMapping) mapping).useMapClass(java.util.Hashtable.class); |
| } else { |
| mapping.useMapClass(java.util.Hashtable.class, mapKey); |
| } |
| } else if (rawClass.isClass(Set.class)) { |
| // This will cause it to use a CollectionContainerPolicy type |
| mapping.useCollectionClass(java.util.HashSet.class); |
| } else if (rawClass.isClass(List.class)) { |
| // This will cause a ListContainerPolicy type to be used or |
| // OrderedListContainerPolicy if ordering is specified. |
| mapping.useCollectionClass(java.util.Vector.class); |
| } else if (rawClass.isClass(Collection.class)) { |
| // Force CollectionContainerPolicy type to be used with a |
| // collection implementation. |
| mapping.setContainerPolicy(new CollectionContainerPolicy(java.util.Vector.class)); |
| } else { |
| // Use the supplied collection class type if its not an interface |
| if (mapKey == null || mapKey.equals("")){ |
| if (rawClass.isList()) { |
| mapping.useListClassName(rawClass.getName()); |
| } else { |
| mapping.useCollectionClassName(rawClass.getName()); |
| } |
| } else { |
| mapping.useMapClassName(rawClass.getName(), mapKey); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * This will do three things: |
| * 1 - process any common level metadata for all mappings. |
| * 2 - add the mapping to the internal descriptor. |
| * 3 - store the actual database mapping associated with this accessor. |
| * |
| * Calling this method is a must for all mapping accessors since it will |
| * help to: |
| * 1 - determine if the accessor has been processed, and |
| * 2 - sub processing will may need access to the mapping to set its |
| * metadata. |
| */ |
| protected void setMapping(DatabaseMapping mapping) { |
| if (! isMultitenantId()) { |
| // Before adding the mapping to the descriptor, process the |
| // properties for this mapping (if any). Avoid this is we are |
| // multitenant id accessor which is derived from primary key |
| // tenant discriminator columns on the class where any properties |
| // defined there do not apply to this mapping. |
| processProperties(mapping); |
| } |
| |
| // Add the mapping to the class descriptor. |
| getDescriptor().getClassDescriptor().addMapping(mapping); |
| |
| // Keep a reference back to this mapping for quick look up. |
| m_mapping = mapping; |
| } |
| |
| /** |
| * INTERNAL: |
| * An override mapping is created when an association override is specified |
| * to a relationship accessor on an embeddable class. For any non-owning |
| * relationship accessor referring to this accessor will need its override |
| * mapping and not the original mapping from the embeddable so that it can |
| * populate the right metadata. |
| */ |
| protected void setOverrideMapping(DatabaseMapping mapping) { |
| m_overrideMapping = mapping; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public String toString() { |
| return getAnnotatedElementName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Update the primary key field on the owning descriptor the override field |
| * given. |
| */ |
| protected void updatePrimaryKeyField(MappingAccessor idAccessor, DatabaseField overrideField) { |
| getOwningDescriptor().removePrimaryKeyField(idAccessor.getMapping().getField()); |
| getOwningDescriptor().addPrimaryKeyField(overrideField, idAccessor); |
| } |
| |
| /** |
| * INTERNAL: |
| * @see RelationshipAccessor |
| * @see DirectAccessor |
| */ |
| protected boolean usesIndirection() { |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if this mapping or class uses property access. In an |
| * inheritance hierarchy, the subclasses inherit their access type from |
| * the parent (unless there is an explicit access setting). |
| */ |
| public boolean usesPropertyAccess() { |
| if (hasAccess()) { |
| return getAccess().equals(JPA_ACCESS_PROPERTY); |
| } else { |
| return hasAccessMethods() ? !usesVirtualAccess() : m_classAccessor.usesPropertyAccess(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if this mapping or class uses virtual access. In an |
| * inheritance hierarchy, the subclasses inherit their access type from |
| * the parent (unless there is an explicit access setting). |
| */ |
| public boolean usesVirtualAccess() { |
| if (hasAccess()) { |
| return getAccess().equals(EL_ACCESS_VIRTUAL); |
| } else { |
| return m_classAccessor.usesVirtualAccess(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns true if this mapping or class uses property field. In an |
| * inheritance hierarchy, the subclasses inherit their access type from |
| * the parent (unless there is an explicit access setting). |
| */ |
| public boolean usesFieldAccess() { |
| if (hasAccess()) { |
| return getAccess().equals(JPA_ACCESS_FIELD); |
| } else { |
| return m_classAccessor.usesFieldAccess(); |
| } |
| } |
| } |