blob: e242265fb38cc2a171b72f8c31c87490ceca6f56 [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.queries;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.Iterator;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetMethod;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.CollectionMapping;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
/**
* <p><b>Purpose</b>: The abstract class for ContainerPolicy's whose container class implements
* a container interface.
*
* @see CollectionContainerPolicy
* @see MapContainerPolicy
*/
public abstract class InterfaceContainerPolicy extends ContainerPolicy {
/** The concrete container class. */
protected Class containerClass;
protected String containerClassName;
/** The method which will return a clone of an instance of the containerClass. */
protected transient Method cloneMethod;
/**
* INTERNAL:
* Construct a new policy.
*/
protected InterfaceContainerPolicy() {
super();
}
/**
* INTERNAL:
* Construct a new policy for the specified class.
*/
protected InterfaceContainerPolicy(Class containerClass) {
setContainerClass(containerClass);
}
/**
* INTERNAL:
* Construct a new policy for the specified class name.
*/
protected InterfaceContainerPolicy(String containerClassName) {
setContainerClassName(containerClassName);
}
/**
* INTERNAL:
* Return if the policy is equal to the other.
* By default if they are the same class, they are considered equal.
* This is used for query parse caching.
*/
@Override
public boolean equals(Object object) {
return super.equals(object) && getContainerClass().equals(((InterfaceContainerPolicy)object).getContainerClass());
}
@Override
public int hashCode() {
int result = super.hashCode();
Class containerClass = getContainerClass();
result = 31 * result + (containerClass != null ? containerClass.hashCode() : 0);
return result;
}
/**
* INTERNAL:
* Return a clone of the specified container.
*/
@Override
public Object cloneFor(Object container) {
if (container == null) {
return null;
}
Method cloneMethod;
Class javaClass = container.getClass();
if (javaClass == getContainerClass()) {
cloneMethod = getCloneMethod();
} else {
// container may be a superclass of the concrete container class
// so we have to use the right clone method...
cloneMethod = getCloneMethod(javaClass);
}
return invokeCloneMethodOn(cloneMethod, container);
}
/**
* INTERNAL:
* Convert all the class-name-based settings in this ContainerPolicy to actual class-based
* settings. This method is used when converting a project that has been built
* with class names to a project with classes.
*/
@Override
public void convertClassNamesToClasses(ClassLoader classLoader){
super.convertClassNamesToClasses(classLoader);
if (getContainerClassName() == null){
return;
}
Class containerClass = null;
try{
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
try {
containerClass = AccessController.doPrivileged(new PrivilegedClassForName(getContainerClassName(), true, classLoader));
} catch (PrivilegedActionException exception) {
throw ValidationException.classNotFoundWhileConvertingClassNames(getContainerClassName(), exception.getException());
}
} else {
containerClass = PrivilegedAccessHelper.getClassForName(getContainerClassName(), true, classLoader);
}
} catch (ClassNotFoundException exception) {
throw ValidationException.classNotFoundWhileConvertingClassNames(getContainerClassName(), exception);
}
setContainerClass(containerClass);
}
/**
* INTERNAL:
* Creates a CollectionChangeEvent for the container
*/
@Override
public CollectionChangeEvent createChangeEvent(Object collectionOwner, String propertyName, Object collectionChanged, Object elementChanged, int changeType, Integer index, boolean isChangeApplied) {
return new CollectionChangeEvent(collectionOwner, propertyName, collectionChanged, elementChanged, changeType, index, false, isChangeApplied);// make the remove change event fire.
}
/**
* INTERNAL:
* Create a query key that links to the map key
* InterfaceContainerPolicy does not support maps, so this method will return null
* subclasses will extend this method.
*/
public QueryKey createQueryKeyForMapKey() {
return null;
}
/**
* INTERNAL:
* Return the 'clone()' Method for the container class.
* Lazy initialization is used, so we can serialize these things.
*/
public Method getCloneMethod() {
if (cloneMethod == null) {
setCloneMethod(getCloneMethod(getContainerClass()));
}
return cloneMethod;
}
private static final class ClassWeakReference extends WeakReference<Class> {
private final int hash;
ClassWeakReference(Class referent) {
super(referent);
hash = referent.hashCode();
}
ClassWeakReference(Class referent, ReferenceQueue<Class> referenceQueue) {
super(referent, referenceQueue);
hash = referent.hashCode();
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof ClassWeakReference) {
return get() == ((ClassWeakReference) obj).get();
}
return false;
}
@Override
public String toString() {
Class referent = get();
return new StringBuilder("ClassWeakReference: ").append(referent == null ? null : referent).toString();
}
}
private static final ReferenceQueue<Class> refQueue = new ReferenceQueue<>();
private static final ConcurrentHashMap<ClassWeakReference, Method> cloneMethodCache = new ConcurrentHashMap<>();
/**
* INTERNAL:
* Return the 'clone()' Method for the specified class.
* Return null if the method does not exist anywhere in the hierarchy
*/
protected Method getCloneMethod(Class javaClass) {
for (Object key; (key = refQueue.poll()) != null;) {
cloneMethodCache.remove(key);
}
Method cloneMethod = cloneMethodCache.get(new ClassWeakReference(javaClass));
if (cloneMethod != null) {
return cloneMethod;
}
try {
// This must not be set "accessible" - clone() must be public, and some JVM's do not allow access to JDK classes.
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
cloneMethod = AccessController.doPrivileged(new PrivilegedGetMethod(javaClass, "clone", null, false));
} catch (PrivilegedActionException exception) {
throw QueryException.methodDoesNotExistInContainerClass("clone", javaClass);
}
} else {
cloneMethod = PrivilegedAccessHelper.getMethod(javaClass, "clone", null, false);
}
} catch (NoSuchMethodException ex) {
throw QueryException.methodDoesNotExistInContainerClass("clone", javaClass);
}
cloneMethodCache.put(new ClassWeakReference(javaClass, refQueue), cloneMethod);
return cloneMethod;
}
/**
* INTERNAL:
* Returns the container class to be used with this policy.
*/
@Override
public Class getContainerClass() {
return containerClass;
}
@Override
public String getContainerClassName() {
if ((containerClassName == null) && (containerClass != null)) {
containerClassName = containerClass.getName();
}
return containerClassName;
}
/**
* INTERNAL:
* Return the DatabaseField that represents the key in a DirectMapMapping. If the
* keyMapping is not a DirectMapping, this will return null.
*/
public DatabaseField getDirectKeyField(CollectionMapping mapping) {
return null;
}
public abstract Class getInterfaceType();
/**
* INTERNAL:
* Return whether the iterator has more objects,
*/
@Override
public boolean hasNext(Object iterator) {
return ((Iterator)iterator).hasNext();
}
/**
* INTERNAL:
* Invoke the specified clone method on the container,
* handling the necessary exceptions.
*/
protected Object invokeCloneMethodOn(Method method, Object container) {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
return AccessController.doPrivileged(new PrivilegedMethodInvoker(method, container, null));
} catch (PrivilegedActionException exception) {
Exception throwableException = exception.getException();
if (throwableException instanceof IllegalAccessException) {
throw QueryException.cannotAccessMethodOnObject(method, container);
} else {
throw QueryException.methodInvocationFailed(method, container, throwableException);
}
}
} else {
return PrivilegedAccessHelper.invokeMethod(method, container, null);
}
} catch (IllegalAccessException ex1) {
throw QueryException.cannotAccessMethodOnObject(method, container);
} catch (InvocationTargetException ex2) {
throw QueryException.methodInvocationFailed(method, container, ex2);
}
}
/**
* INTERNAL:
* Return whether a map key this container policy represents is an attribute
* By default this method will return false since only subclasses actually represent maps.
*/
public boolean isMapKeyAttribute(){
return false;
}
/**
* INTERNAL:
* Validate the container type.
*/
@Override
public boolean isValidContainerType(Class containerType) {
return Helper.classImplementsInterface(containerType, getInterfaceType());
}
/**
* INTERNAL:
* Return the next object on the queue.
* Valid for some subclasses only.
*/
@Override
protected Object next(Object iterator) {
return ((Iterator)iterator).next();
}
/**
* INTERNAL:
* Set the Method that will return a clone of an instance of the containerClass.
*/
public void setCloneMethod(Method cloneMethod) {
this.cloneMethod = cloneMethod;
}
/**
* INTERNAL:
* Set the class to use as the container.
*/
@Override
public void setContainerClass(Class containerClass) {
this.containerClass = containerClass;
initializeConstructor();
}
@Override
public void setContainerClassName(String containerClassName) {
this.containerClassName = containerClassName;
}
/**
* INTERNAL:
* Return a container populated with the contents of the specified Vector.
*/
@Override
public Object buildContainerFromVector(Vector vector, AbstractSession session) {
// PERF: If a Vector policy just return the original.
if (this.containerClass == ClassConstants.Vector_class) {
return vector;
}
return super.buildContainerFromVector(vector, session);
}
@Override
protected Object toStringInfo() {
return getContainerClass();
}
}