/*******************************************************************************
 * Copyright (c) 1998, 2008 Oracle. 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.security;

import java.security.*;
import java.lang.reflect.*;

/**
 * INTERNAL:
 * Privileged Access Helper provides a utility so all calls that require privileged access can use the same code.
 * 
 * Do privileged blocks can be used with a security manager to grant a code base (eclipselink.jar) access to certain
 * Java operations such as reflection.  Generally a security manager is not enabled in a JVM, so this is not an issue.
 * If a security manager is enabled, then either the application can be configured to have access to operations such as
 * reflection, or only EclipseLink can be given access.  If only EclipseLink is desired to be given access then
 * do privileged must be enabled through the System property "eclipselink.security.usedoprivileged"=true.
 * 
 * Note the usage of do privileged has major impacts on performance, so should normally be avoided.
 */
public class PrivilegedAccessHelper {
    private static boolean shouldCheckPrivilegedAccess = true;
    private static boolean shouldUsePrivilegedAccess = false;

    /**
     * Finding a field within a class potentially has to navigate through it's superclasses to eventually
     * find the field.  This method is called by the public getDeclaredField() method and does a recursive
     * search for the named field in the given classes or it's superclasses.
     */
    private static Field findDeclaredField(Class javaClass, String fieldName) throws NoSuchFieldException {
        try {
            return javaClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException ex) {
            Class superclass = javaClass.getSuperclass();
            if (superclass == null) {
                throw ex;
            } else {
                return findDeclaredField(superclass, fieldName);
            }
        }
    }

    /**
     * Finding a method within a class potentially has to navigate through it's superclasses to eventually
     * find the method.  This method is called by the public getDeclaredMethod() method and does a recursive
     * search for the named method in the given classes or it's superclasses.
     */
    private static Method findMethod(Class javaClass, String methodName, Class[] methodParameterTypes) throws NoSuchMethodException {
        try {
            return javaClass.getDeclaredMethod(methodName, methodParameterTypes);
        } catch (NoSuchMethodException ex) {
            Class superclass = javaClass.getSuperclass();
            if (superclass == null) {
                throw ex;
            } else {
                try{
                    return findMethod(superclass, methodName, methodParameterTypes);
                }catch (NoSuchMethodException lastEx){
                    throw ex;
                }
            }
        }
    }
    
    /**
     * Execute a java Class.forName().  Wrap the call in a doPrivileged block if necessary.
     * @param className
     */
    public static Class getClassForName(final String className) throws ClassNotFoundException {
        return Class.forName(className);
    }

    /**
     * Execute a java Class.forName() wrap the call in a doPrivileged block if necessary.
     */
    public static Class getClassForName(final String className, final boolean initialize, final ClassLoader loader) throws ClassNotFoundException {
        return Class.forName(className, initialize, loader);
    }

    /**
     * Gets the class loader for a given class.  Wraps the call in a privileged block if necessary
     */
    public static ClassLoader getClassLoaderForClass(final Class clazz) {
        return clazz.getClassLoader();
    }

    /**
     * Gets the class loader for a given class.  Wraps the call in a privileged block if necessary
     */
    public static ClassLoader privilegedGetClassLoaderForClass(final Class clazz) {
        try{
            return (ClassLoader) AccessController.doPrivileged(new PrivilegedGetClassLoaderForClass(clazz));
        }catch (PrivilegedActionException ex){
            throw (RuntimeException) ex.getCause();
        }
    }

    /**
     * Get the public constructor for the given class and given arguments and wrap it in doPrivileged if
     * necessary.  The shouldSetAccessible parameter allows the the setAccessible API to be called as well.
     * This option was added to avoid making multiple doPrivileged calls within InstantiationPolicy.
     * @param javaClass The class to get the Constructor for
     * @param args An array of classes representing the argument types of the constructor
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchMethodException
     */
    public static Constructor getConstructorFor(final Class javaClass, final Class[] args, final boolean shouldSetAccessible) throws NoSuchMethodException {
        Constructor result = javaClass.getConstructor(args);
        if (shouldSetAccessible) {
            result.setAccessible(true);
        }
        return result;
    }

    /**
     *  Get the context ClassLoader for a thread.  Wrap the call in a doPrivileged block if necessary.
     */
    public static ClassLoader getContextClassLoader(final Thread thread) {
        return thread.getContextClassLoader();
    }

    /**
     * Get the constructor for the given class and given arguments (regardless of whether it is public
     * or private))and wrap it in doPrivileged if necessary.  The shouldSetAccessible parameter allows
     * the the setAccessible API to be called as well. This option was added to avoid making multiple
     * doPrivileged calls within InstantiationPolicy.
     * @param javaClass The class to get the Constructor for
     * @param args An array of classes representing the argument types of the constructor
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchMethodException
     */
    public static Constructor getDeclaredConstructorFor(final Class javaClass, final Class[] args, final boolean shouldSetAccessible) throws NoSuchMethodException {
        Constructor result = javaClass.getDeclaredConstructor(args);
        if (shouldSetAccessible) {
            result.setAccessible(true);
        }
        return result;
    }

    /**
     * Get a field in a class or its superclasses and wrap the call in doPrivileged if necessary.
     * The shouldSetAccessible parameter allows the the setAccessible API to be called as well.
     * This option was added to avoid making multiple doPrivileged calls within InstanceVariableAttributeAccessor.
     * @param javaClass The class to get the field from
     * @param fieldName The name of the field
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchFieldException
     */
    public static Field getField(final Class javaClass, final String fieldName, final boolean shouldSetAccessible) throws NoSuchFieldException {
        Field field = findDeclaredField(javaClass, fieldName);
        if (shouldSetAccessible) {
            field.setAccessible(true);
        }
        return field;
    }

    /**
     * Get a field actually declared in a class and wrap the call in doPrivileged if necessary.
     * The shouldSetAccessible parameter allows the the setAccessible API to be called as well.
     * This option was added to avoid making multiple doPrivileged calls within InstanceVariableAttributeAccessor.
     * @param javaClass The class to get the field from
     * @param fieldName The name of the field
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchFieldException
     */
    public static Field getDeclaredField(final Class javaClass, final String fieldName, final boolean shouldSetAccessible) throws NoSuchFieldException {
        Field field = javaClass.getDeclaredField(fieldName);
        if (shouldSetAccessible) {
            field.setAccessible(true);
        }
        return field;
    }

    /**
     * Get the list of fields in a class.  Wrap the call in doPrivileged if necessary
     * Excludes inherited fields.
     * @param clazz the class to get the fields from.
     */
    public static Field[] getDeclaredFields(final Class clazz) {
        return clazz.getDeclaredFields();
    }

    /**
     * Get the list of public fields in a class.  Wrap the call in doPrivileged if necessary
     * @param clazz the class to get the fields from.
     */
    public static Field[] getFields(final Class clazz) {
        return clazz.getFields();
    }

   /**
     * Return a method on a given class with the given method name and parameter 
     * types. This call will NOT traverse the superclasses. Wrap the call in 
     * doPrivileged if necessary.
     * @param method the class to get the method from
     * @param methodName the name of the method to get
     * @param methodParameters a list of classes representing the classes of the
     *  parameters of the method.
     */
    public static Method getDeclaredMethod(final Class clazz, final String methodName, final Class[] methodParameterTypes) throws NoSuchMethodException {
         return clazz.getDeclaredMethod(methodName, methodParameterTypes);
    }
    
    /**
     * Get a method declared in the given class. Wrap the call in doPrivileged 
     * if necessary. This call will traverse the superclasses. The 
     * shouldSetAccessible parameter allows the the setAccessible API to be 
     * called as well. This option was added to avoid making multiple 
     * doPrivileged calls within MethodBasedAttributeAccessor.
     * @param javaClass The class to get the method from
     * @param methodName The name of the method to get
     * @param methodParameterTypes A list of classes representing the classes of the parameters of the mthod
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchMethodException
     */
    public static Method getMethod(final Class javaClass, final String methodName, final Class[] methodParameterTypes, final boolean shouldSetAccessible) throws NoSuchMethodException {
        Method method = findMethod(javaClass, methodName, methodParameterTypes);
        if (shouldSetAccessible) {
            method.setAccessible(true);
        }
        return method;
    }
    
    /**
     * Get a public method declared in the given class. Wrap the call in doPrivileged if necessary. 
     * This call will traverse the superclasses. The shouldSetAccessible parameter allows the the 
     * setAccessible API to be called as well. This option was added to avoid making multiple 
     * doPrivileged calls within MethodBasedAttributeAccessor.
     * 
     * @param javaClass The class to get the method from
     * @param methodName The name of the method to get
     * @param methodParameterTypes A list of classes representing the classes of the parameters of the method
     * @param shouldSetAccessible whether or not to call the setAccessible API
     * @throws java.lang.NoSuchMethodException
     */
    public static Method getPublicMethod(final Class javaClass, final String methodName, final Class[] methodParameterTypes, final boolean shouldSetAccessible) throws NoSuchMethodException {
        // Return the (public) method - will traverse superclass(es) if necessary
        Method method = javaClass.getMethod(methodName, methodParameterTypes);
        if (shouldSetAccessible) {
            method.setAccessible(true);
        }
        return method;
    }

    /**
     * Get the list of methods in a class. Wrap the call in doPrivileged if 
     * necessary. Excludes inherited methods.
     * @param clazz the class to get the methods from.
     */
    public static Method[] getDeclaredMethods(final Class clazz) {
        return clazz.getDeclaredMethods();
    }
    
    /**
     * Get the return type for a given method. Wrap the call in doPrivileged if necessary.
     * @param field
     */
    public static Class getFieldType(final Field field) {
        return field.getType();
    }

    /**
     * Get the line separator character.
     * Previous versions of TopLink always did this in a privileged block so we will continue to do so.
     */
    public static String getLineSeparator() {
        if (shouldUsePrivilegedAccess()) {
            return (String)AccessController.doPrivileged(new PrivilegedAction() {
                    public Object run() {
                        return System.getProperty("file.separator");
                    }
                });
        } else {
            return org.eclipse.persistence.internal.helper.Helper.cr();
        }
    }

    /**
     * Get the list of parameter types for a given method.  Wrap the call in doPrivileged if necessary.
     * @param method The method to get the parameter types of
     */
    public static Class[] getMethodParameterTypes(final Method method) {
        return method.getParameterTypes();
    }

    /**
     * Get the return type for a given method. Wrap the call in doPrivileged if necessary.
     * @param method
     */
    public static Class getMethodReturnType(final Method method) {
        return method.getReturnType();
    }
    
    /**
     * Get the list of methods in a class. Wrap the call in doPrivileged if 
     * necessary. This call will traverse the superclasses.
     * @param clazz the class to get the methods from.
     */
    public static Method[] getMethods(final Class clazz) {
        return clazz.getMethods();
    }
    
    /**
     * Get the value of the given field in the given object.
     */
    public static Object getValueFromField(final Field field, final Object object) throws IllegalAccessException {
        return field.get(object);
    }

    /**
     * Construct an object with the given Constructor and the given array of arguments.  Wrap the call in a
     * doPrivileged block if necessary.
     */
    public static Object invokeConstructor(final Constructor constructor, final Object[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        return constructor.newInstance(args);
    }

    /**
     * Invoke the givenMethod on a givenObject. Assumes method does not take
     * parameters. Wrap in a doPrivileged block if necessary.
     */
    public static Object invokeMethod(final Method method, final Object object) throws IllegalAccessException, InvocationTargetException {
        return invokeMethod(method, object, (Object[]) null);
    }
    
    /**
     * Invoke the givenMethod on a givenObject using the array of parameters given.  Wrap in a doPrivileged block
     * if necessary.
     */
    public static Object invokeMethod(final Method method, final Object object, final Object[] parameters) throws IllegalAccessException, InvocationTargetException {
        // Ensure the method is accessible.
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        return method.invoke(object, parameters);
    }

    /**
     * Get a new instance of a class using the default constructor.  Wrap the call in a privileged block
     * if necessary.
     */
    public static Object newInstanceFromClass(final Class clazz) throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }

    /**
     * Set the value of a given field in the given object with the value given.  Wrap the call in a privileged block
     * if necessary.
     */
    public static void setValueInField(final Field field, final Object object, final Object value) throws IllegalAccessException {
        field.set(object, value);
    }

    /**
     * This method checks to see if calls should be made to doPrivileged.
     * It will only return true if a security manager is enabled,
     * and the "eclipselink.security.usedoprivileged" property is set.
     * <p>
     * Note: it is not possible to run EclipseLink using doPrivileged blocks when there is no SecurityManager
     * enabled.
     */
    public static boolean shouldUsePrivilegedAccess() {
        // We will only detect whether to use doPrivileged once.
        if (shouldCheckPrivilegedAccess) {
            if (System.getSecurityManager() != null) {    
                Boolean privilegedPropertySet = (Boolean)AccessController.doPrivileged(new PrivilegedAction() {
                        public Object run() {
                            String usePrivileged = System.getProperty("eclipselink.security.usedoprivileged");
                            return (usePrivileged != null) && usePrivileged.equalsIgnoreCase("true");
                        }
                    });
                shouldUsePrivilegedAccess = privilegedPropertySet.booleanValue();
            } else {
                shouldUsePrivilegedAccess = false;
            }
            shouldCheckPrivilegedAccess = false;
        }
        return shouldUsePrivilegedAccess;
    }
}
