/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates, IBM Corporation. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 12/17/2010-2.2 Guy Pelletier * - 330755: Nested embeddables can't be used as embedded ids * 11/10/2011-2.4 Guy Pelletier * - 357474: Address primaryKey option from tenant discriminator column * 14/05/2012-2.4 Guy Pelletier * - 376603: Provide for table per tenant support for multitenant applications * 03/23/2016-2.6_WAS Will Dazey * - 490114: Add support for PersistenceUnitUtil.getIdentifier with nested embeddables in EmbeddedId class ******************************************************************************/ package org.eclipse.persistence.descriptors; import java.io.Serializable; import java.security.AccessController; import java.util.HashSet; import java.util.Set; import org.eclipse.persistence.annotations.CacheKeyType; import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.ObjectReferenceMapping; import org.eclipse.persistence.mappings.converters.Converter; import org.eclipse.persistence.mappings.foundation.AbstractColumnMapping; import org.eclipse.persistence.queries.UpdateObjectQuery; import org.eclipse.persistence.internal.descriptors.ObjectBuilder; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.identitymaps.CacheId; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass; import org.eclipse.persistence.internal.sessions.AbstractSession; /** * <p> * <b>Description</b>: Place holder for CMP specific information. This class can be set on the ClassDescriptor. * * @see org.eclipse.persistence.descriptors.PessimisticLockingPolicy * * @since TopLink 10.1.3 */ public class CMPPolicy implements java.io.Serializable, Cloneable { protected Boolean forceUpdate; protected Boolean updateAllFields; /** Allow the bean to always be locked as it enters a new transaction. */ protected PessimisticLockingPolicy pessimisticLockingPolicy; /** Class originally mapped, before anything was generated. */ protected Class mappedClass; protected ClassDescriptor descriptor; /** The object deferral level. This controls when objects changes will be sent to the Database. */ protected int modificationDeferralLevel = ALL_MODIFICATIONS; /** defer no changes */ public static final int NONE = 0; /** defer updates */ public static final int UPDATE_MODIFICATIONS = 1; /** defer all modifications, inserts and deletes included (default) */ public static final int ALL_MODIFICATIONS = 2; /** This setting will allow customers to control when Toplink will issue the insert SQL for CMP beans. */ protected int nonDeferredCreateTime = UNDEFINED; /** undefined if it is non-deferred issue sql at create */ public static final int UNDEFINED = 0; /** issue SQL after ejbCreate but before ejbPostCreate */ public static final int AFTER_EJBCREATE = 1; /** issue SQL after ejbPostCreate */ public static final int AFTER_EJBPOSTCREATE = 2; public CMPPolicy() { this.forceUpdate = null; this.updateAllFields = null; } /** * ADVANCED: * This setting is only available for CMP beans that are not being deferred. * Using it will allow TopLink to determine if the INSERT SQL should be sent to * the database before or after the postCreate call. */ public int getNonDeferredCreateTime() { return this.nonDeferredCreateTime; } /** * PUBLIC: * Return the policy for bean pessimistic locking * @see org.eclipse.persistence.descriptors.PessimisticLockingPolicy */ public PessimisticLockingPolicy getPessimisticLockingPolicy() { return pessimisticLockingPolicy; } /** * ADVANCED: * This can be set to control when changes to objects are submitted to the database * This is only applicable to TopLink's CMP implementation and not available within * the core. */ public void setDeferModificationsUntilCommit(int deferralLevel) { this.modificationDeferralLevel = deferralLevel; } /** * PUBLIC: * Define the mapped class. This is the class which was originally mapped in the MW * * @param newMappedClass */ public void setMappedClass(Class newMappedClass) { mappedClass = newMappedClass; } /** * PUBLIC: * Answer the mapped class. This is the class which was originally mapped in the MW * */ public Class getMappedClass() { return mappedClass; } /** * ADVANCED: * This setting is only available for CMP beans that are not being deferred. * Using it will allow TopLink to determine if the INSERT SQL should be sent to * the database before or after the postCreate call. */ public void setNonDeferredCreateTime(int createTime) { this.nonDeferredCreateTime = createTime; } /** * PUBLIC: * Configure bean pessimistic locking * * @param policy * @see org.eclipse.persistence.descriptors.PessimisticLockingPolicy */ public void setPessimisticLockingPolicy(PessimisticLockingPolicy policy) { pessimisticLockingPolicy = policy; } /** * PUBLIC: * Return true if bean pessimistic locking is configured */ public boolean hasPessimisticLockingPolicy() { return pessimisticLockingPolicy != null; } /** * ADVANCED: * This can be used to control when changes to objects are submitted to the database * This is only applicable to TopLink's CMP implementation and not available within * the core. */ public int getDeferModificationsUntilCommit() { return this.modificationDeferralLevel; } /** * ADVANCED: * Return true if descriptor is set to always update all registered objects of this type */ public boolean getForceUpdate() { // default to false return (Boolean.TRUE.equals(this.forceUpdate)); } /** * ADVANCED: * Configure whether TopLink should always update all registered objects of * this type. NOTE: if set to true, then updateAllFields must also be set * to true * * @param shouldForceUpdate */ public void setForceUpdate(boolean shouldForceUpdate) { this.forceUpdate = Boolean.valueOf(shouldForceUpdate); } /** * ADVANCED: * Return true if descriptor is set to update all fields for an object of this * type when an update occurs. */ public boolean getUpdateAllFields() { // default to false return Boolean.TRUE.equals(this.updateAllFields); } /** * ADVANCED: * Configure whether TopLink should update all fields for an object of this * type when an update occurs. * * @param shouldUpdatAllFields */ public void setUpdateAllFields(boolean shouldUpdatAllFields) { this.updateAllFields = Boolean.valueOf(shouldUpdatAllFields); } /** * INTERNAL: * return internal tri-state value so we can decide whether to inherit or not at init time. */ public Boolean internalGetForceUpdate() { return this.forceUpdate; } /** * INTERNAL: * return internal tri-state value so we can decide whether to inherit or not at init time. */ public Boolean internalGetUpdateAllFields() { return this.updateAllFields; } /** * INTERNAL: * internal method to set the tri-state value. This is done in InheritancePolicy at init time. */ public void internalSetForceUpdate(Boolean newForceUpdateValue) { this.forceUpdate = newForceUpdateValue; } /** * INTERNAL: * internal method to set the tri-state value. This is done in InheritancePolicy at init time. */ public void internalSetUpdateAllFields(Boolean newUpdateAllFieldsValue) { this.updateAllFields = newUpdateAllFieldsValue; } /** * INTERNAL: * Initialize the CMPPolicy settings. */ public void initialize(ClassDescriptor descriptor, AbstractSession session) throws DescriptorException { // updateAllFields is true so set custom query in DescriptorQueryManager // to force full SQL. Don't overwrite a user defined query if (this.getUpdateAllFields() && !descriptor.getQueryManager().hasUpdateQuery()) { descriptor.getQueryManager().setUpdateQuery(new UpdateObjectQuery()); } // make sure updateAllFields is set if forceUpdate is true if (this.getForceUpdate() && !this.getUpdateAllFields()) { throw DescriptorException.updateAllFieldsNotSet(descriptor); } } /** * INTERNAL: * Initialize the CMPPolicy settings for remote sessions. */ public void remoteInitialize(ClassDescriptor descriptor, AbstractSession session) throws DescriptorException { } /** * INTERNAL: * @return Returns the owningDescriptor. */ public ClassDescriptor getDescriptor() { return descriptor; } /** * INTERNAL: * @param owningDescriptor The owningDescriptor to set. */ public void setDescriptor(ClassDescriptor owningDescriptor) { this.descriptor = owningDescriptor; } /** * INTERNAL: * Recursive method to set a field value in the given key instance. */ protected void setFieldValue(KeyElementAccessor accessor, Object keyInstance, DatabaseMapping mapping, AbstractSession session, int[] elementIndex, Object ... keyElements) { if (mapping.isAggregateMapping()) { Object nestedObject = mapping.getRealAttributeValueFromObject(keyInstance, session); if (nestedObject == null) { nestedObject = getClassInstance(mapping.getReferenceDescriptor().getJavaClass()); mapping.setRealAttributeValueInObject(keyInstance, nestedObject); } // keep drilling down the nested mappings ... setFieldValue(accessor, nestedObject, mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(accessor.getDatabaseField()), session, elementIndex, keyElements); } else { Object fieldValue = null; if (mapping.isAbstractColumnMapping()) { fieldValue = keyElements[elementIndex[0]]; Converter converter = ((AbstractColumnMapping) mapping).getConverter(); if (converter != null){ fieldValue = converter.convertDataValueToObjectValue(fieldValue, session); } ++elementIndex[0]; } else if (mapping.isObjectReferenceMapping()) { // what if mapping comes from derived ID. need to get the derived mapping. // get reference descriptor and extract pk from target cmp policy fieldValue = mapping.getReferenceDescriptor().getCMPPolicy().createPrimaryKeyInstanceFromPrimaryKeyValues(session, elementIndex, keyElements); } accessor.setValue(keyInstance, fieldValue); } } /** * INTERNAL: * Return if this policy is for CMP3. */ public boolean isCMP3Policy() { return false; } /** * INTERNAL: * Clone the CMPPolicy */ public CMPPolicy clone() { try { return (CMPPolicy) super.clone(); } catch (CloneNotSupportedException exception) { throw new InternalError(exception.getMessage()); } } /** * INTERNAL: * Convert all the class-name-based settings in this object to actual class-based * settings. This method is used when converting a project that has been built * with class names to a project with classes. * @param classLoader */ public void convertClassNamesToClasses(ClassLoader classLoader){ } /** * INTERNAL: * Create an instance of the composite primary key class for the key object. */ public Object createPrimaryKeyInstanceFromId(Object key, AbstractSession session) { if (this.descriptor.getCachePolicy().getCacheKeyType() == CacheKeyType.CACHE_ID) { return createPrimaryKeyInstanceFromPrimaryKeyValues(session, new int[]{0}, ((CacheId)key).getPrimaryKey()); } else { return createPrimaryKeyInstanceFromPrimaryKeyValues(session, new int[]{0}, key); } } /** * INTERNAL: * Create an instance of the composite primary key class for the key object. * Yes the elementIndex looks strange but this is just a simple way to get the index to be pass-by-reference */ public Object createPrimaryKeyInstanceFromPrimaryKeyValues(AbstractSession session, int[] elementIndex, Object ... keyElements ) { Object keyInstance = null; KeyElementAccessor[] pkElementArray = getKeyClassFields(); if (isSingleKey(pkElementArray)) { for (KeyElementAccessor accessor: pkElementArray){ DatabaseMapping mapping = getDescriptor().getObjectBuilder().getMappingForAttributeName(accessor.getAttributeName()); if (mapping != null && !mapping.isMultitenantPrimaryKeyMapping()){ if (mapping.isAbstractColumnMapping()) { Converter converter = ((AbstractColumnMapping) mapping).getConverter(); if (converter != null){ return converter.convertDataValueToObjectValue(keyElements[elementIndex[0]], session); } keyInstance = keyElements[elementIndex[0]]; } else if (mapping.isObjectReferenceMapping()) { // what if mapping comes from derived ID. need to get the derived mapping. //get reference descriptor and extract pk from target cmp policy keyInstance = mapping.getReferenceDescriptor().getCMPPolicy().createPrimaryKeyInstanceFromPrimaryKeyValues(session, elementIndex, keyElements); } ++elementIndex[0]; // remove processed key in case keys are complex and derived } if (keyInstance != null){ return keyInstance; } } } else { keyInstance = getPKClassInstance(); //get clone of Key so we can remove values. for (int index = 0; index < pkElementArray.length; index++) { KeyElementAccessor accessor = pkElementArray[index]; DatabaseMapping mapping = getDescriptor().getObjectBuilder().getMappingForAttributeName(accessor.getAttributeName()); if (mapping == null) { mapping = getDescriptor().getObjectBuilder().getMappingForField(accessor.getDatabaseField()); } if (accessor.isNestedAccessor()) { // Need to recursively build all the nested objects. setFieldValue(accessor, keyInstance, mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(accessor.getDatabaseField()), session, elementIndex, keyElements); } else { // Not nested but may be a single layer aggregate so check. if (mapping.isAggregateMapping()) { mapping = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(accessor.getDatabaseField()); } setFieldValue(accessor, keyInstance, mapping, session, elementIndex, keyElements); } } } return keyInstance; } /** * INTERNAL: * Create an instance of the Id class or value from the object. */ public Object createPrimaryKeyInstance(Object object, AbstractSession session) { KeyElementAccessor[] pkElementArray = this.getKeyClassFields(); ObjectBuilder builder = getDescriptor().getObjectBuilder(); if (pkElementArray.length == 1 && pkElementArray[0] instanceof KeyIsElementAccessor){ DatabaseMapping mapping = builder.getMappingForAttributeName(pkElementArray[0].getAttributeName()); Object fieldValue = mapping.getRealAttributeValueFromObject(object, session); if (mapping.isObjectReferenceMapping()){ fieldValue = mapping.getReferenceDescriptor().getCMPPolicy().createPrimaryKeyInstance(fieldValue, session); } return fieldValue; } Object keyInstance = getPKClassInstance(); Set<ObjectReferenceMapping> usedObjectReferenceMappings = new HashSet<ObjectReferenceMapping>(); for (int index = 0; index < pkElementArray.length; index++) { Object keyObj = object; KeyElementAccessor accessor = pkElementArray[index]; DatabaseField field = accessor.getDatabaseField(); DatabaseMapping mapping = builder.getMappingForField(field); Object nestedKeyInstance = keyInstance; // With session validation, the mapping shouldn't be null at this // point, don't bother checking. if (!mapping.isObjectReferenceMapping() || !usedObjectReferenceMappings.contains(mapping)){ while (mapping.isAggregateObjectMapping()) { keyObj = mapping.getRealAttributeValueFromObject(keyObj, session); mapping = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(field); //Check for embedded Id values if (mapping.isAggregateMapping()) { Object nestedObject = mapping.getRealAttributeValueFromObject(nestedKeyInstance, session); if (nestedObject == null) { nestedObject = getClassInstance(mapping.getReferenceDescriptor().getJavaClass()); } mapping.setRealAttributeValueInObject(nestedKeyInstance, nestedObject); nestedKeyInstance = nestedObject; } } Object fieldValue = mapping.getRealAttributeValueFromObject(keyObj, session); if (mapping.isObjectReferenceMapping()){ fieldValue = mapping.getReferenceDescriptor().getCMPPolicy().createPrimaryKeyInstance(fieldValue, session); usedObjectReferenceMappings.add((ObjectReferenceMapping)mapping); } accessor.setValue(nestedKeyInstance, fieldValue); } } return keyInstance; } /** * INTERNAL: * Return a new instance of the class provided. */ public Object getClassInstance(Class cls) { if (cls != null){ try { if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ return AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(cls)); } else { return org.eclipse.persistence.internal.security.PrivilegedAccessHelper.newInstanceFromClass(cls); } } catch (Exception e) { throw ValidationException.reflectiveExceptionWhileCreatingClassInstance(cls.getName(), e); } } return null; } /** * INTERNAL: */ public Object getPKClassInstance() { // TODO fix this exception so that it is more descriptive // This method only works in CMP3Policy but was added here for separation // of components throw new RuntimeException("Should not get here."); } /** * INTERNAL: */ public Class getPKClass() { // TODO fix this exception so that it is more descriptive // This method only works in CMP3Policy but was added here for separation // of components throw new RuntimeException("Should not get here."); } /** * INTERNAL: * Use the key to create a EclipseLink primary key. * If the key is simple (direct mapped) then just add it to a vector, * otherwise must go through the inefficient process of copying the key into the bean * and extracting the key from the bean. */ public Object createPrimaryKeyFromId(Object key, AbstractSession session) { // TODO fix this exception so that it is more descriptive // This method only works in CMP3Policy but was added here for separation // of components throw new RuntimeException("Should not get here."); } /** * INTERNAL: * Use the key to create a bean and initialize its primary key fields. * Note: If is a compound PK then a primary key object is being used. * This method should only be used for 'templates' when executing * queries. The bean built will not be given an EntityContext and should * not be used as an actual entity bean. * * @param key Object the primary key to use for initializing the bean's * corresponding pk fields * @return Object */ public Object createBeanUsingKey(Object key, AbstractSession session) { // TODO fix this exception so that it is more descriptive // This method only works in CMP3Policy but was added here for separation // of components throw new RuntimeException("Should not get here."); } /** * INTERNAL: * @return Returns the keyClassFields. */ protected KeyElementAccessor[] getKeyClassFields() { // TODO fix this exception so that it is more descriptive // This method only works in CMP3Policy but was added here for separation // of components throw new RuntimeException("Should not get here."); } /** * Check to see if there is a single key element. Iterate through the list of primary key elements * and count only keys that are not part of the Multitenant identifier. * * @param pkElementArray * @return */ protected boolean isSingleKey(KeyElementAccessor[] pkElementArray){ if ((pkElementArray.length == 1) && (pkElementArray[0] instanceof KeyIsElementAccessor)) { return true; } boolean foundFirstElement = false; for (KeyElementAccessor accessor: pkElementArray){ if (!(accessor instanceof KeyIsElementAccessor)){ return false; } if (!accessor.getMapping().isMultitenantPrimaryKeyMapping()){ if (foundFirstElement){ return false; } foundFirstElement = true; } } return true; } /** * INTERNAL: * This is the interface used to encapsulate the the type of key class element */ protected static interface KeyElementAccessor { public String getAttributeName(); public DatabaseField getDatabaseField(); public DatabaseMapping getMapping(); public Object getValue(Object object, AbstractSession session); public void setValue(Object object, Object value); public boolean isNestedAccessor(); } /** * INTERNAL: * This class will be used when the keyClass is a primitive */ protected static final class KeyIsElementAccessor implements KeyElementAccessor, Serializable { protected String attributeName; protected DatabaseField databaseField; protected DatabaseMapping mapping; public KeyIsElementAccessor(String attributeName, DatabaseField databaseField, DatabaseMapping mapping) { this.attributeName = attributeName; this.databaseField = databaseField; this.mapping = mapping; } public String getAttributeName() { return attributeName; } public DatabaseField getDatabaseField() { return this.databaseField; } public DatabaseMapping getMapping(){ return this.mapping; } public Object getValue(Object object, AbstractSession session) { return object; } public boolean isNestedAccessor() { return false; } public void setValue(Object object, Object value) { // WIP - do nothing for now??? } } }