blob: 6b1622b15fa32f763e2632cd9666b149f2e4ece9 [file] [log] [blame]
/*
* 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:
// 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.List;
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.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;
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;
/**
* <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
*
*/
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
*
* @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
*
*/
public void setForceUpdate(boolean shouldForceUpdate) {
this.forceUpdate = 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.
*
*/
public void setUpdateAllFields(boolean shouldUpdatAllFields) {
this.updateAllFields = 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
*/
@Override
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.
*/
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();
ObjectBuilder objectBuilder = getDescriptor().getObjectBuilder();
//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 = objectBuilder.getMappingForField(accessor.getDatabaseField());
if (! mapping.isAggregateObjectMapping() && ! mapping.isAbstractDirectMapping()) {
// Found a mapping for the database field but it's not an aggregate (embedded ID) or a direct mapping so maybe the mappings
// have been mapped multiple times. Check read-only mappings for dtf or aggregate
List<DatabaseMapping> readOnlyMappings = objectBuilder.getReadOnlyMappingsForField(accessor.getDatabaseField());
for (DatabaseMapping readOnlyMapping : readOnlyMappings) {
if (readOnlyMapping.isAggregateObjectMapping() || readOnlyMapping.isDirectToFieldMapping()) {
mapping = readOnlyMapping;
break;
}
}
}
}
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()) {
DatabaseMapping nestedMapping = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(accessor.getDatabaseField());
if (nestedMapping == null) {
//must be a read only mapping
List<DatabaseMapping> readOnlyMappings = mapping.getReferenceDescriptor().getObjectBuilder().getReadOnlyMappingsForField(accessor.getDatabaseField());
for (DatabaseMapping readOnlyMapping : readOnlyMappings) {
if (readOnlyMapping.isAbstractDirectMapping()) {
mapping = readOnlyMapping;
break;
}
}
} else {
mapping = nestedMapping;
}
}
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<>();
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.
*
*/
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 {
String getAttributeName();
DatabaseField getDatabaseField();
DatabaseMapping getMapping();
Object getValue(Object object, AbstractSession session);
void setValue(Object object, Object value);
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;
}
@Override
public String getAttributeName() {
return attributeName;
}
@Override
public DatabaseField getDatabaseField() {
return this.databaseField;
}
@Override
public DatabaseMapping getMapping(){
return this.mapping;
}
@Override
public Object getValue(Object object, AbstractSession session) {
return object;
}
@Override
public boolean isNestedAccessor() {
return false;
}
@Override
public void setValue(Object object, Object value) {
// WIP - do nothing for now???
}
}
}