/******************************************************************************* * Copyright (c) 2011, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * 03/19/2009-2.0 dclarke - initial API start * 06/30/2009-2.0 mobrien - finish JPA Metadata API modifications in support * of the Metamodel implementation for EclipseLink 2.0 release involving * Map, ElementCollection and Embeddable types on MappedSuperclass descriptors * - 266912: JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API) * 11/05/2009-2.0 mobrien - DI 86: MapKey support when only generics * are used to determine the keyType for an IdClass that used an embeddable * 11/10/2009-2.0 mobrien - DI 98: Use keyMapping on MappedKeyMappedContainerPolicy * keep workaround for bug# 294765 for Basic keyType when MapKey annotation not specified. * keep workaround for bug# 294811 for Entity, Embeddable, Transient keyType support * when MapKey name attribute not specified (MapContainerPolicy) * add BasicMap support via DirectMapContainerPolicy * 13/10/2009-2.0 mobrien - 294765 - fix allows removal of workaround for * when MapKey annotation not specified * 06/14/2010-2.1 mobrien - 314906: getJavaType should return the * collection javaType C in <X,C,V) of <X, Map<K,V>, V> instead off the elementType V ******************************************************************************/ package org.eclipse.persistence.internal.jpa.metamodel; import java.util.Map; import javax.persistence.metamodel.MapAttribute; import javax.persistence.metamodel.Type; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.internal.queries.ContainerPolicy; import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; import org.eclipse.persistence.mappings.CollectionMapping; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.foundation.MapKeyMapping; /** * <p> * <b>Purpose</b>: Provides the implementation for the MapAttribute interface * of the JPA 2.0 Metamodel API (part of the JSR-317 EJB 3.1 Criteria API) * <p> * <b>Description</b>: * Instances of the type MapAttribute represent persistent Map-valued * attributes. * * @see javax.persistence.metamodel.MapAttribute * * @since EclipseLink 1.2 - JPA 2.0 * * @param <X> The type the represented Map belongs to * @param <K> The type of the key of the represented Map * @param <V> The type of the value of the represented Map */ public class MapAttributeImpl<X, K, V> extends PluralAttributeImpl<X, Map<K, V>, V> implements MapAttribute<X, K, V> { /** Item 54: DI 89: explicit UID will avoid performance hit runtime generation of one */ private static final long serialVersionUID = 5702748112869113135L; /** The key type that this Map type is based on **/ private Type<K> keyType; /** * INTERNAL: * @param managedType * @param mapping */ protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping) { this(managedType, mapping, false); } /** * INTERNAL: * Create a new MapAttribute instance. * The elementType field is instantiated in the superclass. * The keyType field is instantiated in this constructor by using one of the following * A) MapContainerPolicy by consulting the keyField or PK class * B) MappedKeyMapContainerPolicy by using the mapKeyTargetType on the keyMapping or the attributeClassification on the attributeAccessor * @param managedType - the owning type (EmbeddableTypes do not support mappings) * @param mapping - contains the mapping policy * @param validationEnabled - report errors in the metamodel */ protected MapAttributeImpl(ManagedTypeImpl<X> managedType, CollectionMapping mapping, boolean validationEnabled) { // Set the managedType (X or owning Type) - Note: EmbeddableTypes are only supported as Map keys here super(managedType, mapping, validationEnabled); // We need to set the keyType Type that represents the type of the Map key for this mapping ContainerPolicy policy = mapping.getContainerPolicy(); Class<?> javaClass = null; MapKeyMapping keyMapping = null; Object policyKeyType = null; /** * Note: the (at) sign for annotations has been replaced by the & sign for javadoc processing. * * We have the following policy structure and behavior * ContainerPolicy (A) * +=== InterfaceContainerPolicy (A) * +=== DirectMapContainerPolicy * +=== MapContainerPolicy (use keyField or PK class) * +=== MappedKeyMapContainerPolicy (use keyMapping.mapKeyTargetType or attributeClassification) * * Use Case segmentation for keyType A) MapContainerPolicy A1) keyField set (lazy loaded) UC2 - name attribute defines mapKey, generics are not required and are secondary &OneToMany(cascade=ALL, mappedBy="mappedEmployerUC2") &MapKey(name="name") private Map<String, HardwareDesigner> hardwareDesignersMapUC2; UC4 - name attribute defines mapKey, generics are not required &OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC4") &MapKey(name="name") private Map hardwareDesignersMapUC4; UC8 - mapKey defined via generics &OneToMany(cascade=ALL, mappedBy="mappedEmployerUC8") &MapKey // name attribute will default to "id" private Map<Integer, HardwareDesigner> hardwareDesignersMapUC8; A2) Use mapPolicy.elementDescriptor.cmppolicy.pkClass (since KeyField == null) UC10 - mapKey defined via generics and is a java class defined as an IdClass on the element(value) class - here Enclosure &OneToMany(mappedBy="computer", cascade=ALL, fetch=EAGER) &MapKey // key defaults to an instance of the composite pk class private Map<EnclosureIdClassPK, Enclosure> enclosures; &Entity &IdClass(EnclosureIdClassPK.class) public class Enclosure {} UC11 - or (get keyClass from mapping if the Id is a get() function) TBD - use reflection B) MappedKeyMapContainerPolicy B1) mapKeyTargetType set on the keyMapping - normal processing UC9 - mapKey defined by generics in the absence of a MapKey annotation &OneToMany(cascade=CascadeType.ALL, mappedBy="mappedManufacturerUC9") private Map<Board, Enclosure> enclosureByBoardMapUC9; UC13 - mapKey defined by generics in the absence of a MapKey name attribute (unidirectional M:1 becomes M:M) &MapKey // on Computer inverse private Map<EmbeddedPK, GalacticPosition> position; &ManyToOne(fetch=EAGER) // on GalacticPosition owner private Computer computer; B2) - secondary processing for Basic (DirectToField) mappings Use AttributeClassification (since keyMapping.attributeAccessor.attributeClass == null) UC1a - mapKey defined by generics in the absence of a MapKey annotation &OneToMany(cascade=ALL, mappedBy="mappedEmployerUC1a") private Map<String, HardwareDesigner> hardwareDesignersMapUC1a; UC7 - mapKey defined by generics in the absence of a MapKey annotation &OneToMany(targetEntity=HardwareDesigner.class, cascade=ALL, mappedBy="mappedEmployerUC7") private Map<String, HardwareDesigner> hardwareDesignersMapUC7; */ // Step 1: We check via the ContainerPolicy interface for a mapPolicy for the keyMapping (covers MappedKeyMapContainerPolicy and its superclass MapContainerPolicy if (policy.isMapPolicy()) { // check for Either a generic Map (MapContainerPolicy) or specific MappedKeyMapContainerPolicy subclass if (policy.isMappedKeyMapPolicy()) { // See UC9 // The cast below is unavoidable because getKeyMapping() is not overridden from the MapContainerPolicy superclass keyMapping = ((MappedKeyMapContainerPolicy)policy).getKeyMapping(); policyKeyType = keyMapping.getMapKeyTargetType(); } else { /** * Assume we have a MapContainerPolicy general superclass with a lazy-loaded keyType * or a DirectMapContainerPolicy using a &BasicMap * See UC2, UC4, UC8, UC13 (unidirectional ManyToOne becomes ManyToMany) * returns a Class<?> or descriptor (via AggregateObjectMapping) or null in 2 cases - * 1 - if the ContainerPolicy does not support maps * 2 - If the keyField is null (we handle this below by consulting the CMPPolicy) */ policyKeyType = policy.getKeyType(); } } /** * Step 2: We determine the java class from the policyKeyType (class or ClassDecriptor) * We also perform alternate keyType lookup for the case where * the name attribute is not specified in a MapKey annotation where * the map key is one of the following (via the MapContainerPolicy superclass of MappedKeyMapContainerPolicy) * - map key is an Entity with an IdClass * - map key is Java class that is defined as the IdClass of an Entity (above) */ if(null == policyKeyType) { // Pending reproduction case: @MapKey private Map<K,V> // no name column specified and the PK is defined by a method if(null == javaClass) { if(policy.isMappedKeyMapPolicy()) { // See UC10, UC11 javaClass = getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted( (MappedKeyMapContainerPolicy)policy); } } } else { // Process the policyKeyType normally if(policyKeyType instanceof ClassDescriptor) { // from AggregateObjectMapping javaClass = ((ClassDescriptor)policyKeyType).getJavaClass(); } else { javaClass = (Class<?>)policyKeyType; } } // Optional: catch any holes in our keyType logic (8 hits in clover coverage) if(null == javaClass) { javaClass = Object.class; } // Step 3: We wrap the java type in a Metamodel Type instance or retrieve an existing Type this.keyType = (Type<K>) getMetamodel().getType(javaClass); } /** * INTERNAL: * Default to the PK of the owning descriptor when no MapKey or MapKey:name attribute is specified. * Prerequisites: policy on the mapping is of type MappedKeyMapPolicy * @return */ private Class getOwningPKTypeWhenMapKeyAnnotationMissingOrDefaulted(MappedKeyMapContainerPolicy policy) { Class<?> javaClass = null;; MapKeyMapping mapKeyMapping = policy.getKeyMapping(); ClassDescriptor descriptor = ((DatabaseMapping)mapKeyMapping).getDescriptor(); // If the reference descriptor is null then we are on a direct mapping if(null != descriptor) { javaClass = ((DatabaseMapping)mapKeyMapping).getAttributeClassification(); if(null == javaClass) { // Default to the PK of the owning descriptor when no MapKey or MapKey:name attribute is specified if (descriptor.getCMPPolicy() != null) { javaClass = descriptor.getCMPPolicy().getPKClass(); } } } return javaClass; } /** * Return the collection type. * @return collection type */ @Override public CollectionType getCollectionType() { return CollectionType.MAP; } /** * Return the Java type of the represented attribute. * @return Java type */ @Override public Class getJavaType() { return Map.class; } /** * Return the Java type of the map key. * @return Java key type * @see MapAttribute */ public Class<K> getKeyJavaType() { return keyType.getJavaType(); } /** * Return the type representing the key type of the map. * @return type representing key type * @see MapAttribute */ public Type<K> getKeyType() { return this.keyType; } }