org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy 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, 2018 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.*;
/**
* ProxyIndirectionPolicy
*
* Define the behavior for Proxy Indirection.
*
* Proxy Indirection uses the Proxy
and InvocationHandler
features
* of JDK 1.3 to provide "transparent indirection" for 1:1 relationships. In order to use Proxy
* Indirection:
*
*
* - The target class must implement at least one public interface
*
- The attribute on the source class must be typed as that public interface
*
*
* In this policy, proxy objects are returned during object creation. When a message other than
* toString
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.
*/
public void initialize() {
// Nothing required
}
/**
* Reset the wrapper used to store the value.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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)
&& (! ((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.
*/
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) backupValueHolder);
}
return ProxyIndirectionHandler.newProxyInstance(attributeValue.getClass(), targetInterfaces, backupValueHolder);
}
/**
* INTERNAL:
* Iterate over the specified attribute value.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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
* @param attributeValue
* @return
* @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 targetInterfaces
.
*/
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;
}
}