org.eclipse.persistence.internal.indirection.WeavedObjectBasicIndirectionPolicy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.indirection;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.indirection.WeavedAttributeValueHolderInterface;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
/**
* A WeavedObjectBasicIndirectionPolicy is used by OneToOne mappings that are LAZY through weaving
* and which use Property(method) access.
*
* It extends BasicIndirection by providing the capability of calling the set method that was initially
* mapped in addition to the set method for the weaved valueholder in order to coordinate the value of the
* underlying property with the value stored in the valueholder
*
* @author Tom Ware
*/
public class WeavedObjectBasicIndirectionPolicy extends BasicIndirectionPolicy {
/** Name of the initial set method. */
protected String setMethodName = null;
/** Lazily initialized set method based on the set method name. */
protected transient Method setMethod = null;
/** Name of the initial get method. */
protected String getMethodName;
/** indicates whether the mapping has originally used method access */
protected boolean hasUsedMethodAccess;
/** Stores the actual type of the mapping if different from the reference type. Used for set method invocation*/
protected String actualTypeClassName = null;
public WeavedObjectBasicIndirectionPolicy(String getMethodName, String setMethodName, String actualTypeClassName, boolean hasUsedMethodAccess) {
super();
this.setMethodName = setMethodName;
this.getMethodName = getMethodName;
this.hasUsedMethodAccess = hasUsedMethodAccess;
this.actualTypeClassName = actualTypeClassName;
}
public String getActualTypeClassName() {
return actualTypeClassName;
}
/**
* Return the "real" attribute value, as opposed to any wrapper.
* This will trigger the wrapper to instantiate the value. In a weaved policy, this will
* also call the initial setter method to coordinate the values of the valueholder with
* the underlying data.
*/
@Override
public Object getRealAttributeValueFromObject(Object object, Object attribute) {
boolean wasInstantiated = attribute != null && attribute instanceof ValueHolderInterface && ((ValueHolderInterface)attribute).isInstantiated();
Object value = super.getRealAttributeValueFromObject(object, attribute);
// Provide the indirection policy with a callback that allows it to do any updates it needs as the result of getting the value.
if (!wasInstantiated && (value != attribute)) {
//if the attribute was already unwrapped then do not call this method
updateValueInObject(object, value, attribute);
}
return value;
}
/**
* This method will lazily initialize the set method
* Lazy initialization occurs to that we are not required to have a handle on
* the actual class that we are using until runtime. This helps to satisfy the
* weaving requirement that demands that we avoid loading domain classes into
* the main class loader until after weaving occurs.
*/
protected Method getSetMethod() {
if (setMethod == null) {
ForeignReferenceMapping sourceMapping = (ForeignReferenceMapping)mapping;
// The parameter type for the set method must always be the return type of the get method.
Class>[] parameterTypes = new Class>[1];
parameterTypes[0] = sourceMapping.getReferenceClass();
try {
setMethod = Helper.getDeclaredMethod(sourceMapping.getDescriptor().getJavaClass(), setMethodName, parameterTypes);
} catch (NoSuchMethodException e) {
if (actualTypeClassName != null) {
try {
// try the actual class of the field or property
parameterTypes[0] = Helper.getClassFromClasseName(actualTypeClassName, sourceMapping.getReferenceClass().getClassLoader());
setMethod = Helper.getDeclaredMethod(sourceMapping.getDescriptor().getJavaClass(), setMethodName, parameterTypes);
} catch (NoSuchMethodException nsme) {}
if (setMethod != null) {
return setMethod;
}
}
try {
// Get the set method type from the get method.
Method getMethod = Helper.getDeclaredMethod(sourceMapping.getDescriptor().getJavaClass(), getGetMethodName(), new Class>[0]);
parameterTypes[0] = getMethod.getReturnType();
setMethod = Helper.getDeclaredMethod(sourceMapping.getDescriptor().getJavaClass(), setMethodName, parameterTypes);
} catch (NoSuchMethodException e2) {
// As a last ditch effort, change the parameter type to Object.class.
// If the model uses generics:
// public T getStuntDouble()
// public void setStuntDouble(T)
// The weaved methods will be:
// public Object getStuntDouble() and
// public void setStuntDouble(Object)
try {
parameterTypes[0] = Object.class;
setMethod = Helper.getDeclaredMethod(sourceMapping.getDescriptor().getJavaClass(), setMethodName, parameterTypes);
} catch (NoSuchMethodException e3) {
// Throw the original exception.
throw DescriptorException.errorAccessingSetMethodOfEntity(sourceMapping.getDescriptor().getJavaClass(), setMethodName, sourceMapping.getDescriptor(), e);
}
}
}
}
return setMethod;
}
/**
* Coordinate the valueholder for this mapping with the underlying property by calling the
* initial setter method.
*/
public void updateValueInObject(Object object, Object value, Object attributeValue){
setRealAttributeValueInObject(object, value);
((WeavedAttributeValueHolderInterface)attributeValue).setIsCoordinatedWithProperty(true);
}
/**
* Set the value of the appropriate attribute of target to attributeValue.
* In this case, place the value inside the target's ValueHolder.
* Change tracking will be turned off when this method is called
*/
@Override
public void setRealAttributeValueInObject(Object target, Object attributeValue) {
setRealAttributeValueInObject(target, attributeValue, false);
}
/**
* Set the value of the appropriate attribute of target to attributeValue.
* In this case, place the value inside the target's ValueHolder.
* if trackChanges is true, set the value in the object as if the user was setting it. Allow change tracking to pick up the change.
*/
@Override
public void setRealAttributeValueInObject(Object target, Object attributeValue, boolean trackChanges) {
// If the target object is using change tracking, it must be disable first to avoid thinking the value changed.
PropertyChangeListener listener = null;
ChangeTracker trackedObject = null;
if (!trackChanges && target instanceof ChangeTracker) {
trackedObject = (ChangeTracker)target;
listener = trackedObject._persistence_getPropertyChangeListener();
trackedObject._persistence_setPropertyChangeListener(null);
}
Object[] parameters = new Object[1];
parameters[0] = attributeValue;
try {
PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.invokeMethod(getSetMethod(), target, parameters),
(ex) -> {
if (ex instanceof IllegalAccessException) {
return DescriptorException.illegalAccessWhileSettingValueThruMethodAccessor(setMethod.getName(), attributeValue, ex);
} else if (ex instanceof IllegalArgumentException) {
return DescriptorException.illegalArgumentWhileSettingValueThruMethodAccessor(setMethod.getName(), attributeValue, ex);
} else if (ex instanceof InvocationTargetException) {
return DescriptorException.targetInvocationWhileSettingValueThruMethodAccessor(setMethod.getName(), attributeValue, ex);
}
// This indicates unexpected problem in the code
return new RuntimeException(String.format(
"Invocation of %s setter method failed", setMethod.getName()), ex);
}
);
} finally {
if (!trackChanges && trackedObject != null) {
trackedObject._persistence_setPropertyChangeListener(listener);
}
}
}
public String getGetMethodName() {
return this.getMethodName;
}
public String getSetMethodName() {
return this.setMethodName;
}
public boolean hasUsedMethodAccess() {
return this.hasUsedMethodAccess;
}
@Override
public boolean isWeavedObjectBasicIndirectionPolicy() {
return true;
}
}