| /* |
| * 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.lang.reflect.Proxy; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.IntegrityChecker; |
| import org.eclipse.persistence.indirection.ValueHolder; |
| import org.eclipse.persistence.indirection.ValueHolderInterface; |
| import org.eclipse.persistence.internal.descriptors.DescriptorIterator; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.MergeManager; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sessions.remote.ObjectDescriptor; |
| import org.eclipse.persistence.internal.sessions.remote.RemoteSessionController; |
| import org.eclipse.persistence.internal.sessions.remote.RemoteUnitOfWork; |
| import org.eclipse.persistence.queries.ObjectLevelReadQuery; |
| import org.eclipse.persistence.queries.ReadQuery; |
| import org.eclipse.persistence.sessions.remote.DistributedSession; |
| |
| /** |
| * <h2>ProxyIndirectionPolicy</h2> |
| * |
| * <p>Define the behavior for Proxy Indirection.</p> |
| * |
| * <p>Proxy Indirection uses the <CODE>Proxy</CODE> and <CODE>InvocationHandler</CODE> features |
| * of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy |
| * Indirection:</p> |
| * |
| * <ul> |
| * <li>The target class must implement at least one public interface</li> |
| * <li>The attribute on the source class must be typed as that public interface</li> |
| * </ul> |
| * |
| * In this policy, proxy objects are returned during object creation. When a message other than |
| * <CODE>toString</CODE> is called on the proxy the real object data is retrieved from the database. |
| * |
| * @see org.eclipse.persistence.internal.indirection.ProxyIndirectionHandler |
| * @author Rick Barkhouse |
| * @since TopLink 3.0 |
| */ |
| public class ProxyIndirectionPolicy extends BasicIndirectionPolicy { |
| private final Class<?>[] targetInterfaces; |
| |
| public ProxyIndirectionPolicy(Class<?>[] targetInterfaces) { |
| this.targetInterfaces = targetInterfaces; |
| } |
| |
| public ProxyIndirectionPolicy() { |
| this.targetInterfaces = new Class<?>[] { }; |
| } |
| |
| /** |
| * INTERNAL: |
| * Nothing required. |
| */ |
| @Override |
| public void initialize() { |
| // Nothing required |
| } |
| |
| /** |
| * Reset the wrapper used to store the value. |
| */ |
| @Override |
| public void reset(Object target) { |
| // Nothing required. |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if targetInterfaces is not empty. |
| */ |
| public boolean hasTargetInterfaces() { |
| return (targetInterfaces != null) && (targetInterfaces.length != 0); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value to be stored in the object's attribute. |
| * This will be a proxy object. |
| */ |
| @Override |
| public Object valueFromRow(Object object) { |
| ValueHolderInterface<?> valueHolder = new ValueHolder<>(object); |
| |
| return ProxyIndirectionHandler.newProxyInstance(object.getClass(), targetInterfaces, valueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value to be stored in the object's attribute. |
| * This will be a proxy object. |
| */ |
| @Override |
| public Object valueFromQuery(ReadQuery query, AbstractRecord row, AbstractSession session) { |
| ClassDescriptor descriptor = null; |
| try { |
| // Need an instance of the implementing class |
| //CR#3838 |
| descriptor = session.getDescriptor(query.getReferenceClass()); |
| if (descriptor.isDescriptorForInterface()) { |
| descriptor = descriptor.getInterfacePolicy().getChildDescriptors().get(0); |
| } |
| } catch (Exception e) { |
| return null; |
| } |
| ValueHolderInterface<?> valueHolder = new QueryBasedValueHolder<>(query, row, session); |
| |
| return ProxyIndirectionHandler.newProxyInstance(descriptor.getJavaClass(), targetInterfaces, valueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value to be stored in the object's attribute. |
| * This value is determined by invoking the appropriate method on the object and passing it the |
| * row and session. |
| */ |
| @Override |
| public Object valueFromMethod(Object object, AbstractRecord row, AbstractSession session) { |
| ValueHolderInterface<?> valueHolder = new TransformerBasedValueHolder<>(this.getTransformationMapping().getAttributeTransformer(), object, row, session); |
| |
| return ProxyIndirectionHandler.newProxyInstance(object.getClass(), targetInterfaces, valueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the value to be stored in the object's attribute. |
| * This value is determined by the batch query. * |
| * NOTE: Currently not supported anyway. |
| */ |
| @Override |
| public Object valueFromBatchQuery(ReadQuery batchQuery, AbstractRecord row, ObjectLevelReadQuery originalQuery, CacheKey parentCacheKey) { |
| Object object; |
| try { |
| // Need an instance of the implementing class |
| final ClassDescriptor cd = originalQuery.getDescriptor().isDescriptorForInterface() ? |
| originalQuery.getDescriptor().getInterfacePolicy().getChildDescriptors().get(0) : originalQuery.getDescriptor(); |
| object = PrivilegedAccessHelper.callDoPrivilegedWithException( |
| () -> PrivilegedAccessHelper.newInstanceFromClass(cd.getJavaClass()) |
| ); |
| } catch (Exception e) { |
| //org.eclipse.persistence.internal.helper.Helper.toDo("*** Should probably throw some sort of TopLink exception here. ***"); |
| e.printStackTrace(); |
| return null; |
| } |
| ValueHolderInterface<?> valueHolder = new BatchValueHolder<>(batchQuery, row, this.getForeignReferenceMapping(), originalQuery, parentCacheKey); |
| return ProxyIndirectionHandler.newProxyInstance(object.getClass(), targetInterfaces, valueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the specified object is instantiated. |
| */ |
| @Override |
| public boolean objectIsInstantiated(Object object) { |
| if (object instanceof Proxy) { |
| ProxyIndirectionHandler<?> handler = (ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(object); |
| ValueHolderInterface<?> valueHolder = handler.getValueHolder(); |
| return valueHolder.isInstantiated(); |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return whether the specified object can be instantiated without database access. |
| */ |
| @Override |
| public boolean objectIsEasilyInstantiated(Object object) { |
| if (object instanceof Proxy) { |
| ProxyIndirectionHandler<?> handler = (ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(object); |
| ValueHolderInterface<?> valueHolder = handler.getValueHolder(); |
| if (valueHolder instanceof DatabaseValueHolder) { |
| return ((DatabaseValueHolder<?>)valueHolder).isEasilyInstantiated(); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the null value of the appropriate attribute. That is, the field from the database is NULL, |
| * return what should be placed in the object's attribute as a result. |
| */ |
| @Override |
| public Object nullValueFromRow() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Replace the client value holder with the server value holder after copying some of the settings from |
| * the client value holder. |
| */ |
| @Override |
| public void mergeRemoteValueHolder(Object clientSideDomainObject, Object serverSideDomainObject, MergeManager mergeManager) { |
| getMapping().setAttributeValueInObject(clientSideDomainObject, serverSideDomainObject); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the "real" attribute value, as opposed to any wrapper. This will trigger the wrapper to |
| * instantiate the value. |
| */ |
| @Override |
| public Object getRealAttributeValueFromObject(Object obj, Object object) { |
| if (object instanceof Proxy) { |
| ProxyIndirectionHandler<?> handler = (ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(object); |
| ValueHolderInterface<?> valueHolder = handler.getValueHolder(); |
| return valueHolder.getValue(); |
| } else { |
| return object; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Given a proxy object, trigger the indirection and return the actual object represented by the proxy. |
| */ |
| public static Object getValueFromProxy(Object value) { |
| if (Proxy.isProxyClass(value.getClass())) { |
| return ((ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(value)).getValueHolder().getValue(); |
| } |
| return value; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the "real" value of the attribute to attributeValue. |
| */ |
| @Override |
| public void setRealAttributeValueInObject(Object target, Object attributeValue) { |
| this.getMapping().setAttributeValueInObject(target, attributeValue); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the original indirection object for a unit of work indirection object. |
| */ |
| @Override |
| public Object getOriginalIndirectionObject(Object unitOfWorkIndirectionObject, AbstractSession session) { |
| if (unitOfWorkIndirectionObject instanceof UnitOfWorkValueHolder) { |
| ValueHolderInterface<?> valueHolder = ((UnitOfWorkValueHolder<?>)unitOfWorkIndirectionObject).getWrappedValueHolder(); |
| if ((valueHolder == null) && session.isRemoteUnitOfWork()) { |
| RemoteSessionController controller = ((RemoteUnitOfWork)session).getParentSessionController(); |
| valueHolder = controller.getRemoteValueHolders().get(((UnitOfWorkValueHolder<?>)unitOfWorkIndirectionObject).getWrappedValueHolderRemoteID()); |
| } |
| return valueHolder; |
| } else { |
| return unitOfWorkIndirectionObject; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * An object has been serialized from the server to the client. Replace the transient attributes of the |
| * remote value holders with client-side objects. |
| */ |
| @Override |
| public void fixObjectReferences(Object object, Map<Object, ObjectDescriptor> objectDescriptors, Map<Object, Object> processedObjects, ObjectLevelReadQuery query, DistributedSession session) { |
| //org.eclipse.persistence.internal.helper.Helper.toDo("*** Something tells me this isn't going to work. *** [X]"); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the reference row for the reference object. This allows the new row to be built without |
| * instantiating the reference object. Return null if the object has already been instantiated. |
| */ |
| @Override |
| public AbstractRecord extractReferenceRow(Object referenceObject) { |
| if ((referenceObject == null) || !Proxy.isProxyClass(referenceObject.getClass())) { |
| return null; |
| } |
| |
| ProxyIndirectionHandler<?> handler = (ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(referenceObject); |
| ValueHolderInterface<?> valueHolder = handler.getValueHolder(); |
| |
| if (valueHolder.isInstantiated()) { |
| return null; |
| } else { |
| return ((DatabaseValueHolder<?>)valueHolder).getRow(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a clone of the attribute. |
| * @param buildDirectlyFromRow indicates that we are building the clone |
| * directly from a row as opposed to building the original from the |
| * row, putting it in the shared cache, and then cloning the original. |
| */ |
| @Override |
| @SuppressWarnings({"unchecked"}) |
| public Object cloneAttribute(Object attributeValue, Object original, CacheKey cacheKey, Object clone, Integer refreshCascade, AbstractSession cloningSession, boolean buildDirectlyFromRow) { |
| if (!(attributeValue instanceof Proxy)) { |
| boolean isExisting = !cloningSession.isUnitOfWork() || (((UnitOfWorkImpl)cloningSession).isObjectRegistered(clone) && (!((UnitOfWorkImpl)cloningSession).isOriginalNewObject(original))); |
| return this.getMapping().buildCloneForPartObject(attributeValue, original, null, clone, cloningSession, refreshCascade, isExisting, !buildDirectlyFromRow); |
| } |
| |
| ValueHolderInterface<Object> newValueHolder; |
| ProxyIndirectionHandler<Object> handler = (ProxyIndirectionHandler<Object>)Proxy.getInvocationHandler(attributeValue); |
| ValueHolderInterface<Object> oldValueHolder = handler.getValueHolder(); |
| |
| if (!buildDirectlyFromRow && cloningSession.isUnitOfWork() && ((UnitOfWorkImpl)cloningSession).isOriginalNewObject(original)) { |
| // CR#3156435 Throw a meaningful exception if a serialized/dead value holder is detected. |
| // This can occur if an existing serialized object is attempt to be registered as new. |
| if ((oldValueHolder instanceof DatabaseValueHolder) |
| && (! oldValueHolder.isInstantiated()) |
| && (((DatabaseValueHolder<?>) oldValueHolder).getSession() == null) |
| && (! ((DatabaseValueHolder<?>) oldValueHolder).isSerializedRemoteUnitOfWorkValueHolder())) { |
| throw DescriptorException.attemptToRegisterDeadIndirection(original, getMapping()); |
| } |
| newValueHolder = new ValueHolder<>(); |
| newValueHolder.setValue(this.getMapping().buildCloneForPartObject(oldValueHolder.getValue(), original, null, clone, cloningSession, refreshCascade, false, false)); |
| } else { |
| AbstractRecord row = null; |
| if (oldValueHolder instanceof DatabaseValueHolder) { |
| row = ((DatabaseValueHolder<?>)oldValueHolder).getRow(); |
| } |
| newValueHolder = this.getMapping().createCloneValueHolder(oldValueHolder, original, clone, row, cloningSession, buildDirectlyFromRow); |
| } |
| |
| return ProxyIndirectionHandler.newProxyInstance(attributeValue.getClass(), targetInterfaces, newValueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a backup clone of the attribute. |
| */ |
| @Override |
| @SuppressWarnings({"unchecked"}) |
| public Object backupCloneAttribute(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) { |
| if (!(attributeValue instanceof Proxy)) { |
| return this.getMapping().buildBackupCloneForPartObject(attributeValue, clone, backup, unitOfWork); |
| } |
| ProxyIndirectionHandler<Object> handler = (ProxyIndirectionHandler<Object>)Proxy.getInvocationHandler(attributeValue); |
| ValueHolderInterface<Object> unitOfWorkValueHolder = handler.getValueHolder(); |
| ValueHolderInterface<Object> backupValueHolder = null; |
| |
| if ((!(unitOfWorkValueHolder instanceof UnitOfWorkValueHolder)) || unitOfWorkValueHolder.isInstantiated()) { |
| backupValueHolder = (ValueHolderInterface<Object>) super.backupCloneAttribute(unitOfWorkValueHolder, clone, backup, unitOfWork); |
| } else { |
| // CR#2852176 Use a BackupValueHolder to handle replacing of the original. |
| backupValueHolder = new BackupValueHolder<>(unitOfWorkValueHolder); |
| ((UnitOfWorkValueHolder<Object>)unitOfWorkValueHolder).setBackupValueHolder(backupValueHolder); |
| } |
| |
| return ProxyIndirectionHandler.newProxyInstance(attributeValue.getClass(), targetInterfaces, backupValueHolder); |
| } |
| |
| /** |
| * INTERNAL: |
| * Iterate over the specified attribute value. |
| */ |
| @Override |
| public void iterateOnAttributeValue(DescriptorIterator iterator, Object attributeValue) { |
| if (attributeValue instanceof Proxy) { |
| ProxyIndirectionHandler<?> handler = (ProxyIndirectionHandler<?>)Proxy.getInvocationHandler(attributeValue); |
| ValueHolderInterface<?> valueHolder = handler.getValueHolder(); |
| |
| iterator.iterateValueHolderForMapping(valueHolder, this.getMapping()); |
| } else { |
| if (attributeValue != null) { |
| this.getMapping().iterateOnRealAttributeValue(iterator, attributeValue); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that the value of the attribute within an instantiated object is of the appropriate type for |
| * the indirection policy. In this case, the attribute must non-null and implement some public interface. |
| */ |
| @Override |
| public Object validateAttributeOfInstantiatedObject(Object attributeValue) { |
| if ((attributeValue != null) && (attributeValue.getClass().getInterfaces().length == 0) && attributeValue instanceof Proxy) { |
| //org.eclipse.persistence.internal.helper.Helper.toDo("*** Need a new DescriptorException here. ***"); |
| // throw DescriptorException.valueHolderInstantiationMismatch(attributeValue, this.getMapping()); |
| System.err.println("** ProxyIndirection attribute validation failed."); |
| } |
| return attributeValue; |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that attribute type is correct for the indirection policy. If it is incorrect, add an exception to the |
| * integrity checker. In this case, the attribute type must be contained in targetInterfaces. |
| */ |
| @Override |
| public void validateDeclaredAttributeType(Class<?> attributeType, IntegrityChecker checker) throws DescriptorException { |
| if (!isValidType(attributeType)) { |
| checker.handleError(DescriptorException.invalidAttributeTypeForProxyIndirection(attributeType, targetInterfaces, getMapping())); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that the return type of the attribute's get method is correct for the indirection policy. If it is |
| * incorrect, add an exception to the integrity checker. In this case, the return type must be a |
| * public interface. |
| */ |
| @Override |
| public void validateGetMethodReturnType(Class<?> returnType, IntegrityChecker checker) throws DescriptorException { |
| if (!isValidType(returnType)) { |
| checker.handleError(DescriptorException.invalidGetMethodReturnTypeForProxyIndirection(returnType, targetInterfaces, getMapping())); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that the parameter type of the attribute's set method is correct for the indirection policy. If it is |
| * incorrect, add an exception to the integrity checker. In this case, the parameter type must be a |
| * public interface. |
| */ |
| @Override |
| public void validateSetMethodParameterType(Class<?> parameterType, IntegrityChecker checker) throws DescriptorException { |
| if (!isValidType(parameterType)) { |
| checker.handleError(DescriptorException.invalidSetMethodParameterTypeForProxyIndirection(parameterType, targetInterfaces, getMapping())); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The method validateAttributeOfInstantiatedObject(Object attributeValue) fixes the value of the attributeValue |
| * in cases where it is null and indirection requires that it contain some specific data structure. Return whether this will happen. |
| * This method is used to help determine if indirection has been triggered |
| * @see #validateAttributeOfInstantiatedObject(Object attributeValue) |
| */ |
| @Override |
| public boolean isAttributeValueFullyBuilt(Object attributeValue){ |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that a class type is valid to use for the proxy. The class must be one of the |
| * interfaces in <CODE>targetInterfaces</CODE>. |
| */ |
| public boolean isValidType(Class<?> attributeType) { |
| if (!attributeType.isInterface()) { |
| return false; |
| } |
| for (int i = 0; i < targetInterfaces.length; i++) { |
| if (attributeType == targetInterfaces[i]) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |