/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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> * * Define the behavior for Proxy Indirection.<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>The attribute on the source class must be typed as that public interface * </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. */ 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 <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; } }