/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/* @test
 * @bug 8280642
 * @summary functional test for RMIClassLoader.loadProxyClass; test
 * ensures that the default RMI class loader provider implements
 * RMIClassLoader.loadProxyClass correctly.
 *
 * @author Laird Dornin
 *
 * @library ../../../testlibrary
 * @modules java.rmi/sun.rmi.registry
 *          java.rmi/sun.rmi.server
 *          java.rmi/sun.rmi.transport
 *          java.rmi/sun.rmi.transport.tcp
 * @build TestLibrary FnnClass FnnUnmarshal NonpublicInterface
 *     NonpublicInterface1 PublicInterface PublicInterface1
 * @run main/othervm/policy=security.policy
 *     -Djava.rmi.server.useCodebaseOnly=false LoadProxyClasses
 */

import java.rmi.server.RMIClassLoader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.MarshalledObject;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.Serializable;
import java.io.InvalidClassException;
import java.io.IOException;

import java.util.Arrays;
import java.util.zip.Checksum;

/**
 *  Invokes RMIClassLoader.loadProxyClass() to load a proxy class with
 *  multiple interfaces using RMI class unmarshalling.  Test is
 *  composed of cases which each unmarshal a proxy class in a
 *  different environment.  All of the cases create needed class
 *  loaders, load appropriate interfaces, create a proxy class that
 *  implements those interfaces, create a marshalled object from that
 *  proxy class, and finally call .get() on that object.  Get of the
 *  object should pass in some cases and fail in others.
 *
 *  1. Nonpublic interface loaded from the parent of the First
 *  Non-Null class Loader on the execution stack (FNNL).  Public
 *  interface loaded from grandparent of FNNL parent. Proxy class must
 *  be defined in non-null FNNL parent. Should succeed.
 *
 *  2. Nonpublic interface (java.util.zip.ZipConstants) and public
 *  interface (java.util.zip.CheckSum) loaded from bootclasspath,
 *  proxy class defined in null/boot class loader.  Should succeed.
 *
 *  3. Public interface classes loaded in FNNL are also available in
 *  RMI loader parent.  FNNL is grandparent of RMI loader. Proxy class
 *  must be defined in RMI class loader. Should succeed. public
 *  interface must be defined in FNNL.
 *
 *  4. Non-public interfaces have multiple class loaders. Should fail
 *  with a LinkageError.
 *
 *  5. Interface classes loaded from RMI class loader. Proxy class
 *  defined in RMI class loader.
 *
 *  6. Not all interfaces classes can be loaded from a single class
 *  loader; should fail with ClassNotFoundException.  All interface
 *  classes will exist (but not all interfaces will be available from
 *  one class loader).
 *
 *  7. prove that proxy loader has correct annotation.
 *
 *  8. REMIND: may want to add a case where the FNNL is null (This
 *  would be for class unmarshalling in the implemntation of a remote
 *  method invocation).
 */
public class LoadProxyClasses {

    private static URL publicUrl = null;

    public static boolean boomerangSemantics = false;

    public static void main(String[] args) {
        try {
            System.err.println("\nFunctional test to verify that RMI " +
                               "loads proxy classes correctly\n");

            /* install proxy interfaces */
            publicUrl =
                TestLibrary.installClassInCodebase("PublicInterface",
                                                   "public");
            URL publicUrl1 =
                TestLibrary.installClassInCodebase("PublicInterface1",
                                                   "public1");
            URL nonpublicUrl =
                TestLibrary.installClassInCodebase("NonpublicInterface",
                                                   "nonpublic", false);
            URL nonpublicUrl1 =
                TestLibrary.installClassInCodebase("NonpublicInterface1",
                                                   "nonpublic1", false);
            URL bothNonpublicUrl =
                TestLibrary.installClassInCodebase("NonpublicInterface",
                                                   "bothNonpublic");
            TestLibrary.installClassInCodebase("NonpublicInterface1",
                                               "bothNonpublic");
            URL fnnUrl =
                TestLibrary.installClassInCodebase("FnnClass", "fnn");

            TestLibrary.suggestSecurityManager(null);


            /* Case 1 */
            ClassLoader grandParentPublic =
                new URLClassLoader(new URL[] {publicUrl});
            ClassLoader parentNonpublic =
                new URLClassLoader(new URL[] {nonpublicUrl},
                                   grandParentPublic);
            URLClassLoader fnnLoader1 =
                new URLClassLoader(new URL[] {fnnUrl}, parentNonpublic);

            Class nonpublicInterface =
                fnnLoader1.loadClass("NonpublicInterface");
            Class publicInterface =
                fnnLoader1.loadClass("PublicInterface");

            Proxy proxy1 = (Proxy) Proxy.newProxyInstance(parentNonpublic,
                new Class[] {nonpublicInterface, publicInterface},
                new TestInvocationHandler());
            unmarshalProxyClass(proxy1, fnnLoader1, parentNonpublic, 1, null);



            /* Case 2 */
            Class zipConstantsClass =
                Class.forName("java.util.zip.ZipConstants");
            URLClassLoader fnnLoader2 =
                new URLClassLoader(new URL[] {fnnUrl});
            Proxy proxy2 = (Proxy) Proxy.newProxyInstance(null,
                new Class[] {zipConstantsClass, Checksum.class},
                new TestInvocationHandler());
            unmarshalProxyClass(proxy2, fnnLoader2,
                                (ClassLoader) null, 2, null);



            /* Case 3 */
            Thread currentThread = Thread.currentThread();
            ClassLoader fnnLoader3 = new URLClassLoader(
                new URL[] {publicUrl, fnnUrl});
            ClassLoader newCtxLoader =
                new URLClassLoader(new URL[] {publicUrl}, fnnLoader3);
            Class publicInterface3 =
                fnnLoader3.loadClass("PublicInterface");
            ClassLoader currentCtxLoader =
                currentThread.getContextClassLoader();
            currentThread.setContextClassLoader(newCtxLoader);

            Proxy proxy3 = (Proxy) Proxy.newProxyInstance(newCtxLoader,
                new Class[] {publicInterface3},
                new TestInvocationHandler());

            unmarshalProxyClass(proxy3, fnnLoader3, fnnLoader3,
                3, new Case3Checker());

            currentThread.setContextClassLoader(currentCtxLoader);



            /* Case 4 */
            ClassLoader bothNonpublicLoader =
                new URLClassLoader(new URL[] {bothNonpublicUrl});
            Class nonpublicInterface4a =
                bothNonpublicLoader.loadClass("NonpublicInterface");
            Class nonpublicInterface4b =
                bothNonpublicLoader.loadClass("NonpublicInterface1");
            Proxy proxy4 = (Proxy) Proxy.newProxyInstance(bothNonpublicLoader,
                new Class[] {nonpublicInterface4a, nonpublicInterface4b},
                new TestInvocationHandler());

            ClassLoader nonpublicLoaderA =
                new URLClassLoader(new URL[] {nonpublicUrl});
            ClassLoader nonpublicLoaderB =
                new URLClassLoader(new URL[] {nonpublicUrl1}, nonpublicLoaderA);
            currentCtxLoader =
                currentThread.getContextClassLoader();
            currentThread.setContextClassLoader(nonpublicLoaderB);

            InvalidClassException invalid = null;
            try {
                unmarshalProxyClass(proxy4, fnnLoader2, nonpublicLoaderB,
                                    4, null);
            } catch (InvalidClassException e) {
                invalid = e;
            }

            if (invalid == null) {
                TestLibrary.bomb("case4: InvalidClassException not thrown " +
                                 "when multiple nonpublic interfaces have \n" +
                                 "different class loaders");
            } else {
                System.err.println("\ncase4: InvalidClassException correctly " +
                                   "thrown \n when trying to load proxy " +
                                   "with multiple nonpublic interfaces in \n" +
                                   "  different class loaders");
            }
            currentThread.setContextClassLoader(currentCtxLoader);



            /* Case 5*/
            ClassLoader publicLoader =
                new URLClassLoader(new URL[] {publicUrl});
            Class publicInterface5 =
                publicLoader.loadClass("PublicInterface");
            Proxy proxy5 = (Proxy) Proxy.newProxyInstance(publicLoader,
                new Class[] {publicInterface5},
                new TestInvocationHandler());

            currentCtxLoader =
                currentThread.getContextClassLoader();
            currentThread.setContextClassLoader(publicLoader);
            unmarshalProxyClass(proxy5, fnnLoader2, publicLoader, 5,
                                new Case5Checker());
            currentThread.setContextClassLoader(currentCtxLoader);



            /* Case 6 */
            ClassLoader fnnLoader6 =
                new URLClassLoader(new URL[] {fnnUrl, publicUrl});
            ClassLoader publicLoader6 =
                new URLClassLoader(new URL[] {publicUrl1}, fnnLoader6);

            Class publicInterface6a =
                publicLoader6.loadClass("PublicInterface1");
            Class publicInterface6b =
                fnnLoader6.loadClass("PublicInterface");
            Proxy proxy6 = (Proxy) Proxy.newProxyInstance(publicLoader6,
                new Class[] {publicInterface6a, publicInterface6b},
                new TestInvocationHandler());
            ClassNotFoundException cnfe = null;
            try {
                unmarshalProxyClass(proxy6, fnnLoader6, publicLoader6, 6,
                                    null);
            } catch (ClassNotFoundException e) {
                cnfe = e;
            }
            if (cnfe == null) {
                TestLibrary.bomb("ClassNotFoundException not thrown " +
                                 "when not all proxy interfaces could " +
                                 " be found in a single class loader ");
            } else {
                System.err.println("Case6: ClassNotFoundException " +
                                   "correctly thrown when not all proxy" +
                                   " interfaces could be found in a " +
                                   "single class loader");
                cnfe.printStackTrace();
            }

            System.err.println("TEST PASSED");

        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            TestLibrary.bomb(e);
        }
    }


    private interface LoadChecker {
        void checkLoad(Proxy proxy, ClassLoader expectedLoader);
    }

    private static Proxy unmarshalProxyClass(Proxy proxy,
                                             ClassLoader fnnLoader,
                                             ClassLoader expectedLoader,
                                             int n,
                                             LoadChecker checker)
        throws ClassNotFoundException, IOException,
               InstantiationException, IllegalAccessException
    {
        FnnUnmarshal fnnUnmarshal = (FnnUnmarshal)
                fnnLoader.loadClass("FnnClass").newInstance();
        Proxy unmarshalled = (Proxy)
            fnnUnmarshal.unmarshal(new MarshalledObject(proxy));
        ClassLoader unmarshalledLoader =
            unmarshalled.getClass().getClassLoader();

        if (checker != null) {
            checker.checkLoad(unmarshalled, expectedLoader);
        } else {
            if (unmarshalledLoader != expectedLoader) {
                TestLibrary.bomb("case" + n + ": proxy class not " +
                                 "placed into incorrect loader: " +
                                 unmarshalledLoader);
            } else {
                System.err.println("\ncase" + n + ": proxy class correctly" +
                                   " placed into expected loader: " +
                                   expectedLoader);
            }
        }
        return proxy;
    }

    private static class Case3Checker implements LoadChecker {
        public void checkLoad(Proxy proxy, ClassLoader expectedLoader) {
            ClassLoader ifaceLoader =
                proxy.getClass().getInterfaces()[0].getClassLoader();
            ClassLoader proxyLoader = proxy.getClass().getClassLoader();

            boolean proxyOk = false;

            if (boomerangSemantics) {
                ClassLoader ctxLoader =
                    Thread.currentThread().getContextClassLoader();
                if (proxyLoader == ctxLoader) {
                    proxyOk = true;
                }
            } else if (proxyLoader.getClass().
                       getName().indexOf("sun.rmi") >= 0)
            {
                proxyOk = true;
            }

            if (proxyOk) {
                System.err.println("\ncase3: proxy loaded in" +
                                   " correct loader: " + proxyLoader +
                                   Arrays.asList(((URLClassLoader)
                                                 proxyLoader).getURLs()));
            } else {
                TestLibrary.bomb("case3: proxy class loaded in " +
                                 "incorrect loader: " + proxyLoader +
                                   Arrays.asList(((URLClassLoader)
                                                  proxyLoader).getURLs()));
            }

            if (ifaceLoader == expectedLoader) {
                System.err.println("case3: proxy interface loaded in" +
                                   " correct loader: " + ifaceLoader);
            } else {
                TestLibrary.bomb("public proxy interface loaded in " +
                                 "incorrect loader: " + ifaceLoader);
            }
        }
    }

    private static class Case5Checker implements LoadChecker {
        public void checkLoad(Proxy proxy, ClassLoader expectedLoader) {
            ClassLoader proxyLoader = proxy.getClass().getClassLoader();

            String proxyAnnotation =
                RMIClassLoader.getClassAnnotation(proxy.getClass());

            if ((proxyAnnotation == null) ||
                !proxyAnnotation.equals(publicUrl.toString()))
            {
                TestLibrary.bomb("proxy class had incorrect annotation: " +
                                 proxyAnnotation);
            } else {
                System.err.println("proxy class had correct annotation: " +
                                   proxyAnnotation);
            }

            boolean proxyOk = false;

            if (boomerangSemantics) {
                ClassLoader ctxLoader =
                    Thread.currentThread().getContextClassLoader();
                if (proxyLoader == ctxLoader) {
                    proxyOk = true;
                }
            } else if (proxyLoader.getClass().
                       getName().indexOf("sun.rmi") >= 0)
            {
                proxyOk = true;
            }

            if (proxyOk) {
                System.err.println("\ncase5: proxy loaded from" +
                                   " correct loader: " + proxyLoader);
            } else {
                TestLibrary.bomb("case5: proxy interface loaded from " +
                                 "incorrect loader: " + proxyLoader);
            }
        }
    }

    private static class TestInvocationHandler
        implements InvocationHandler, Serializable
    {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {return null;}
    }
}
