| /* |
| * 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; |
| } |
| } |