blob: 0ff6a5eafaaefe31425022531699a9d85d96b217 [file] [log] [blame]
/*
* 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.security.AccessController;
import org.eclipse.persistence.internal.descriptors.DescriptorIterator;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import java.util.*;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
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.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.queries.ReadQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.sessions.remote.DistributedSession;
import org.eclipse.persistence.internal.sessions.remote.*;
/**
* <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 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
ClassDescriptor d = originalQuery.getDescriptor();
if (d.isDescriptorForInterface()) {
d = originalQuery.getDescriptor().getInterfacePolicy().getChildDescriptors().get(0);
}
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
object = AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(d.getJavaClass()));
}else{
object = PrivilegedAccessHelper.newInstanceFromClass(d.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 objectDescriptors, Map 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
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 newValueHolder;
ProxyIndirectionHandler handler = (ProxyIndirectionHandler)Proxy.getInvocationHandler(attributeValue);
ValueHolderInterface 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
public Object backupCloneAttribute(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
if (!(attributeValue instanceof Proxy)) {
return this.getMapping().buildBackupCloneForPartObject(attributeValue, clone, backup, unitOfWork);
}
ProxyIndirectionHandler handler = (ProxyIndirectionHandler)Proxy.getInvocationHandler(attributeValue);
ValueHolderInterface<?> unitOfWorkValueHolder = handler.getValueHolder();
ValueHolderInterface<?> backupValueHolder = null;
if ((!(unitOfWorkValueHolder instanceof UnitOfWorkValueHolder)) || unitOfWorkValueHolder.isInstantiated()) {
backupValueHolder = (ValueHolderInterface<?>) super.backupCloneAttribute(unitOfWorkValueHolder, clone, backup, unitOfWork);
} else {
// CR#2852176 Use a BackupValueHolder to handle replacing of the original.
backupValueHolder = new BackupValueHolder(unitOfWorkValueHolder);
((UnitOfWorkValueHolder)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;
}
}