blob: 24acbd9b78af67dc31dfc8438f8f817c2b973b5b [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.queries;
import java.lang.reflect.*;
import java.security.AccessController;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
/**
* <p><b>Purpose</b>:</p>
* Allows a class to be a <code>QueryRedirector</code> without implementing
* {@link QueryRedirector QueryRedirector}.
*
* <p><b>Description</b>:</p>
* Normally to define a Redirector a Class must implement <code>QueryRedirector</code> and
* the required {@link QueryRedirector#invokeQuery QueryRedirector.invokeQuery(DatabaseQuery, Record, Session)}.
* <p>
* To maintain transparency it is possible to instead only define a static
* method that takes the same arguments as <code>invokeQuery</code>.
* </p>
* An instance of <code>MethodBaseQueryRedirector</code> can be constructed, taking the name of that static
* method and the <code>Class</code> in which it is defined as parameters.
* <p>
* Whenever <code>invokeQuery</code> is called on this instance reflection will
* automatically be used to invoke the custom method instead.
* </p>
* <b>Advantages</b>:
* <ul>
* <li> The Redirector class and method name can be specified dynamically.
* <li> The class containing the <code>invokeQuery</code> method does not need to implement
* <code>QueryRedirector</code>.
* <li> The <code>invokeQuery</code> method can have any name.
* <li> The <code>invokeQuery</code> method can alternatively be defined to accept only
* <code>Session session</code> and <code>Vector arguments</code> as parameters.
* </ul>
* <b>Disadvantages</b>:
* <ul>
* <li> An extra step is added as the real <code>invokeQuery</code> method is called
* dynamically.
* </ul>
* <p><b>Example</b>:</p>
* <BLOCKQUOTE><PRE>
* // First create a named query, define a redirector for it, and add the query
* // to the query manager.
* ReadObjectQuery query = new ReadObjectQuery(Employee.class);
* query.setName("findEmployeeByAnEmployee");
* query.addArgument("employee");
*
* MethodBaseQueryRedirector redirector = new
* MethodBaseQueryRedirector(QueryRedirectorTest.class, "findEmployeeByAnEmployee");
* query.setRedirector(redirector);
* ClassDescriptor descriptor = getSession().getDescriptor(query.getReferenceClass());
* descriptor.getQueryManager().addQuery(query.getName(), query);
*
* // Now execute the query by name, passing in an Employee as an argument.
* Vector arguments = new Vector();
* arguments.addElement(employee);
* objectFromDatabase =
* getSession().executeQuery("findEmployeeByAnEmployee", Employee.class, arguments);
*
* // Note this Class does not implement QueryRedirector or method invokeQuery.
* public class QueryRedirectorTest {
* public static Object findEmployeeByAnEmployee(DatabaseQuery query, Record arguments, Session session) {
* ((ReadObjectQuery) query).setSelectionObject(arguments.get("employee"));
* return session.executeQuery(query);
* }
* }</PRE></BLOCKQUOTE>
*
* @see QueryRedirector
* @author James Sutherland
* @since TOPLink/Java 3.0
*/
public class MethodBaseQueryRedirector implements QueryRedirector {
protected Class methodClass;
protected String methodClassName;
protected String methodName;
protected transient Method method;
/**
* PUBLIC:
* Returns a new query redirector.
*/
public MethodBaseQueryRedirector() {
}
/**
* PUBLIC:
* Returns a new query redirector based on the static method in methodClass.
*/
public MethodBaseQueryRedirector(Class methodClass, String methodName) {
this.methodClass = methodClass;
this.methodName = methodName;
}
/**
* INTERNAL:
* Returns the static method.
*/
protected Method getMethod() {
return method;
}
/**
* PUBLIC:
* Returns the class to execute the static method on.
*/
public Class getMethodClass() {
return methodClass;
}
/**
* INTERNAL:
* Returns the class to execute the static method on.
*/
public String getMethodClassName() {
if ((methodClassName == null) && (methodClass != null)) {
methodClassName = methodClass.getName();
}
return methodClassName;
}
/**
* PUBLIC:
* Returns the name of the static method.
* This method must be public, static and have argument of DatabaseQuery, Vector, Session.
* @see #setMethodName
*/
public String getMethodName() {
return methodName;
}
/**
* INTERNAL:
* Set the method.
*/
protected void initializeMethod(DatabaseQuery query) throws QueryException {
if ((getMethodName() == null) || (getMethodClass() == null)) {
throw QueryException.redirectionClassOrMethodNotSet(query);
}
// Must check 3 possible argument sets for backward compatibility.
// The DatabaseQuery, Record, Session should be used, check last the throw correct exception.
// Check Session, Vector.
Class[] arguments = new Class[2];
arguments[0] = ClassConstants.SessionsSession_Class;
arguments[1] = ClassConstants.Vector_class;
try {
setMethod(Helper.getDeclaredMethod(getMethodClass(), getMethodName(), arguments));
} catch (Exception ignore) {
// Check DatabaseQuery, Record, Session.
arguments = new Class[3];
arguments[0] = ClassConstants.DatabaseQuery_Class;
arguments[1] = ClassConstants.Record_Class;
arguments[2] = ClassConstants.SessionsSession_Class;
try {
setMethod(Helper.getDeclaredMethod(getMethodClass(), getMethodName(), arguments));
} catch (Exception ignoreAgain) {
// Check DatabaseQuery, Record, Session.
arguments = new Class[3];
arguments[0] = ClassConstants.DatabaseQuery_Class;
arguments[1] = ClassConstants.Record_Class;
arguments[2] = ClassConstants.SessionsSession_Class;
try {
setMethod(Helper.getDeclaredMethod(getMethodClass(), getMethodName(), arguments));
} catch (Exception exception) {
throw QueryException.redirectionMethodNotDefinedCorrectly(getMethodClass(), getMethodName(), exception, query);
}
}
}
// Ensure the method is static.
if (!Modifier.isStatic(getMethod().getModifiers())) {
throw QueryException.redirectionMethodNotDefinedCorrectly(getMethodClass(), getMethodName(), null, query);
}
}
/**
* INTERNAL:
* Call the static method to execute the query.
*/
@Override
public Object invokeQuery(DatabaseQuery query, DataRecord arguments, Session session) {
if (getMethod() == null) {
initializeMethod(query);
}
// To different methods type are supported for backward compatibility.
// Check method types to call with correct arguments.
Object result = null;
if (getMethod().getParameterTypes().length == 3) {
Object[] argumentArray = new Object[3];
argumentArray[0] = query;
argumentArray[1] = arguments;
argumentArray[2] = session;
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
result = AccessController.doPrivileged(new PrivilegedMethodInvoker(getMethod(), null, argumentArray));
}else{
result = PrivilegedAccessHelper.invokeMethod(getMethod(), null, argumentArray);
}
} catch (Exception exception) {
throw QueryException.redirectionMethodError(exception, query);
}
} else {
Object[] argumentArray = new Object[2];
argumentArray[0] = session;
argumentArray[1] = ((AbstractRecord)arguments).getValues();
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
result = AccessController.doPrivileged(new PrivilegedMethodInvoker(getMethod(), null, argumentArray));
}else{
result = PrivilegedAccessHelper.invokeMethod(getMethod(), null, argumentArray);
}
} catch (Exception exception) {
throw QueryException.redirectionMethodError(exception, query);
}
}
return result;
}
/**
* INTERNAL:
* Sets the static method.
*/
protected void setMethod(Method newMethod) {
method = newMethod;
}
/**
* PUBLIC:
* Sets the class to execute the static method on.
*/
public void setMethodClass(Class newMethodClass) {
methodClass = newMethodClass;
}
/**
* INTERNAL:
* Sets the class to execute the static method on.
*/
public void setMethodClassName(String newMethodClassName) {
methodClassName = newMethodClassName;
}
/**
* PUBLIC:
* Sets the name of the static method.<p>
* This method must be public, static and have arguments of DatabaseQuery, Record, and Session.
* <p>
* The DatabaseQuery argument is the query that is currently being executed.
* <p>
* The Record will contain the Argument names added to the Query through addArgument(Sting) or, in the case
* of an Object query, the object attribute field names. These names will
* reference the argument values passed into the query, or in the case of an
* Object Query the values from the object.
* <p>
* The session argument is the session that the query is currently being executed on.
* <p>
* Alternatively the method can take only <code>(Session session, Vector arguments)</code>
* as parameters.
*/
public void setMethodName(String newMethodName) {
methodName = newMethodName;
}
}