/*
 * Copyright (c) 2003, 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 4950756
 * @summary Test that RequiredModelMBean.invoke will not invoke methods
 * from the RequiredModelMBean class itself if they are not in the
 * ModelMBeanInfo
 * @author Eamonn McManus
 * @run clean RequiredModelMBeanMethodTest
 * @run build RequiredModelMBeanMethodTest
 * @run main RequiredModelMBeanMethodTest
 */

import java.lang.reflect.*;
import javax.management.*;
import javax.management.modelmbean.*;

/*
 * We do the same test with a number of different operations:
 *
 * - A plain operation that is directed to the managed resource in the
 * usual way.  We give it some parameters so we can test that the
 * class loading logic for signature parameters is reasonable.
 *
 * - An operation (removeNotificationListener) that is directed to the
 * RequiredModelMBean itself.  We use this particular operation because
 * it will throw an exception, which allows us to check that it did in
 * fact execute.
 *
 * - An operation (load()) that has the same signature as a
 * RequiredModelMBean operation but is directed to the resource
 * because of a "class" field in the descriptor.
 *
 * - An operation (store()) that has the same signature as a
 * RequiredModelMBean operation but is directed to the resource
 * because of a "targetObject" field in the descriptor.
 *
 * In each case we check that the operation does not work if it is not
 * in the ModelMBeanInfo, and does work if it is.
 */
public class RequiredModelMBeanMethodTest {
    public static void main(String[] args) throws Exception {
        boolean ok = true;
        MBeanServer mbs = MBeanServerFactory.createMBeanServer();

        Descriptor tralalaDescriptor =
            new DescriptorSupport(new String[] {
                "name=tralala",
                "descriptorType=operation",
                "role=operation",
                "targetType=ObjectReference",
            });
        Method tralalaMethod =
            Resource.class.getMethod("tralala",
                                     new Class[] {int.class, Resource.class});
        ModelMBeanOperationInfo tralalaInfo =
            new ModelMBeanOperationInfo("tralala descr", tralalaMethod,
                                        tralalaDescriptor);

        Method remACNLMethod =
            RequiredModelMBean.class.getMethod("removeAttributeChangeNotificationListener",
                                               new Class[] {
                                                   NotificationListener.class,
                                                   String.class
                                               });
        ModelMBeanOperationInfo remACNLInfo =
            new ModelMBeanOperationInfo("remACNL descr", remACNLMethod);

        Descriptor loadDescriptor =
            new DescriptorSupport(new String[] {
                "name=load",
                "descriptorType=operation",
                "role=operation",
                "targetType=ObjectReference",
                "class=" + Resource.class.getName(),
            });
        ModelMBeanOperationInfo loadInfo =
            new ModelMBeanOperationInfo("load", "load descr",
                                        new MBeanParameterInfo[0],
                                        "void", ModelMBeanOperationInfo.ACTION,
                                        loadDescriptor);

        Descriptor storeDescriptor =
            new DescriptorSupport(new String[] {
                "name=store",
                "descriptorType=operation",
                "role=operation",
                "targetType=ObjectReference",
            });
        storeDescriptor.setField("targetObject", resource);
        ModelMBeanOperationInfo storeInfo =
            new ModelMBeanOperationInfo("store", "store descr",
                                        new MBeanParameterInfo[0],
                                        "void", ModelMBeanOperationInfo.ACTION,
                                        storeDescriptor);

        ModelMBeanInfo emptyMMBI =
            new ModelMBeanInfoSupport(Resource.class.getName(),
                                      "empty descr",
                                      null, null, null, null);
        ModelMBean emptyMMB = new RequiredModelMBean(emptyMMBI);
        emptyMMB.setManagedResource(resource, "ObjectReference");
        ObjectName emptyMMBName = new ObjectName("test:type=Empty");
        mbs.registerMBean(emptyMMB, emptyMMBName);

        System.out.println("Testing that we cannot call methods not in the " +
                           "ModelMBeanInfo");
        try {
            boolean thisok = test(mbs, emptyMMBName, false);
            if (thisok)
                System.out.println("...OK");
            else
                ok = false;
        } catch (Exception e) {
            System.out.println("TEST FAILED: Caught exception:");
            e.printStackTrace(System.out);
            ok = false;
        }

        ModelMBeanOperationInfo[] opInfos = {
            tralalaInfo, remACNLInfo, loadInfo, storeInfo,
        };
        ModelMBeanInfo fullMMBI =
            new ModelMBeanInfoSupport(Resource.class.getName(),
                                      "full descr",
                                      null, null, opInfos, null);
        ModelMBean fullMMB = new RequiredModelMBean(fullMMBI);
        fullMMB.setManagedResource(resource, "ObjectReference");
        ObjectName fullMMBName = new ObjectName("test:type=Full");
        mbs.registerMBean(fullMMB, fullMMBName);


        System.out.println();
        System.out.println("Testing that we can call methods in the " +
                           "ModelMBeanInfo");
        System.out.println("  and that \"class\" or \"targetObject\" in " +
                           "descriptor directs methods to resource");
        try {
            boolean thisok = test(mbs, fullMMBName, true);
            if (thisok)
                System.out.println("...OK");
            else
                ok = false;
        } catch (Exception e) {
            System.out.println("TEST FAILED: Caught exception:");
            e.printStackTrace(System.out);
            ok = false;
        }

        if (ok) {
            if (!resource.loadCalled || !resource.storeCalled) {
                System.out.println("TEST FAILED: not called:" +
                                   (resource.loadCalled ? "" : " load") +
                                   (resource.storeCalled ? "" : " store"));
                ok = false;
            }
        }

        // Test the invoke("class.method") form
        if (ok) {
            System.out.println("Testing invoke(\"class.method\")");
            resource.loadCalled = false;
            mbs.invoke(fullMMBName, Resource.class.getName() + ".load",
                       null, null);
            if (!resource.loadCalled) {
                System.out.println("TEST FAILED: load not called");
                ok = false;
            }
            try {
                mbs.invoke(fullMMBName,
                           RequiredModelMBean.class.getName() +
                           ".removeAttributeChangeNotificationListener",
                           new Object[] {boringListener, null},
                           new String[] {
                                   NotificationListener.class.getName(),
                                   String.class.getName(),
                           });
                System.out.println("TEST FAILED: removeNotificationListener" +
                                   " returned successfully but " +
                                   "should not have");
                        ok = false;
            } catch (MBeanException e) {
                final Exception target = e.getTargetException();
                if (target instanceof ListenerNotFoundException) {
                    // OK: there is no such listener
                } else
                    throw e;
            }
        }

        if (ok)
            System.out.println("Test passed");
        else {
            System.out.println("TEST FAILED");
            System.exit(1);
        }
    }

    private static boolean test(MBeanServer mbs, ObjectName name,
                                boolean shouldWork)
            throws Exception {

        boolean ok = true;

        final String[] names = {
            "tralala",
            "removeAttributeChangeNotificationListener",
            "load",
            "store",
        };

        for (int i = 0; i < 4; i++) {
            boolean thisok = true;
            try {
                switch (i) {
                case 0:
                    String tralala = (String)
                        mbs.invoke(name, names[i],
                                   new Object[] {new Integer(5), resource},
                                   new String[] {"int",
                                                 Resource.class.getName()});
                    if (!"tralala".equals(tralala)) {
                        System.out.println("TEST FAILED: tralala returned: " +
                                           tralala);
                        thisok = false;
                    }
                    break;
                case 1:
                    try {
                        mbs.invoke(name,
                                   names[i],
                                   new Object[] {boringListener, null},
                                   new String[] {
                                       NotificationListener.class.getName(),
                                       String.class.getName(),
                                   });
                        System.out.println("TEST FAILED: " + names[i] +
                                           " returned successfully but " +
                                           "should not have");
                        thisok = false;
                    } catch (MBeanException e) {
                        final Exception target = e.getTargetException();
                        if (target instanceof ListenerNotFoundException) {
                            // OK: there is no such listener
                        } else
                            throw e;
                    }
                    break;
                case 2:
                case 3:
                    mbs.invoke(name,
                               names[i],
                               new Object[0],
                               new String[0]);
                    break;
                default:
                    throw new AssertionError();
                }

                thisok = shouldWork;
                if (!shouldWork) {
                    System.out.println("TEST FAILED: " + names[i] +
                                       " worked but should not");
                }
            } catch (MBeanException e) {
                if (shouldWork) {
                    System.out.println("TEST FAILED: " + names[i] + ": " + e);
                    e.printStackTrace(System.out);
                    thisok = false;
                } else {
                    Exception target = e.getTargetException();
                    if (!(target instanceof ServiceNotFoundException)) {
                        System.out.println("TEST FAILED: " + names[i] +
                                           ": wrong exception: " + target);
                        thisok = false;
                    }
                }
            } catch (Exception e) {
                System.out.println("TEST FAILED: " + names[i] + ": " + e);
                e.printStackTrace(System.out);
                thisok = false;
            }

            if (thisok)
                System.out.println("OK: " + names[i]);
            else
                ok = false;
        }

        return ok;
    }

    public static class Resource {
        public String tralala(int x, Resource y) {
            if (x != 5 || y != this)
                return "wrong params: " + x + " " + y;
            return "tralala";
        }

        public void load() {
            loadCalled = true;
        }

        public void store() {
            storeCalled = true;
        }

        boolean loadCalled, storeCalled;
    }

    private static Resource resource = new Resource();

    private static NotificationListener boringListener =
        new NotificationListener() {
            public void handleNotification(Notification n, Object h) {
            }
        };
}