jdk/test/javax/management/Introspector/ResourceInjectionTest.java
author emcmanus
Wed, 09 Jul 2008 10:36:07 +0200
changeset 833 bfa2bef7517c
child 1247 b4c26443dee5
permissions -rw-r--r--
6323980: Annotations to simplify MBean development Reviewed-by: jfdenise, dfuchs

/*
 * Copyright 2007 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

/*
 * @test %M% %I%
 * @bug 6323980
 * @summary Test resource injection via @Resource
 * @author Eamonn McManus
 * @run main/othervm -ea ResourceInjectionTest
 */

import java.io.File;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import javax.annotation.Resource;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InstanceNotFoundException;
import javax.management.MBean;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MXBean;
import javax.management.MalformedObjectNameException;
import javax.management.ManagedAttribute;
import javax.management.ManagedOperation;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.SendNotification;
import javax.management.StandardEmitterMBean;
import javax.management.StandardMBean;
import javax.management.openmbean.MXBeanMappingFactory;

public class ResourceInjectionTest {
    private static MBeanServer mbs;
    private static final ObjectName objectName;
    static {
        try {
            objectName = new ObjectName("test:type=Test");
        } catch (MalformedObjectNameException e) {
            throw new RuntimeException(e);
        }
    }

    /* This is somewhat nasty.  In the current state of affairs, a
     * StandardEmitterMBean can only get the
     * MBeanServer to rewrite the source of a Notification from
     * the originating object's reference to its ObjectName IF
     * StandardEmitterMBean.getResource() returns a reference to the
     * wrapped object.  By default it doesn't, and you need to specify
     * the option below to make it do so.  We may hope that this is
     * obscure enough for users to run into it rarely if ever.
     */
    private static final StandardMBean.Options withWrappedVisible;
    private static final StandardMBean.Options withWrappedVisibleMX;
    static {
        withWrappedVisible = new StandardMBean.Options();
        withWrappedVisible.setWrappedObjectVisible(true);
        withWrappedVisibleMX = withWrappedVisible.clone();
        withWrappedVisibleMX.setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
    }

    @Retention(RetentionPolicy.RUNTIME)
    private static @interface ExpectException {
        Class<? extends Exception> value();
    }

    public static void main(String[] args) throws Exception {
        if (!ResourceInjectionTest.class.desiredAssertionStatus())
            throw new Exception("Test must be run with -ea");

        File policyFile = File.createTempFile("jmxperms", ".policy");
        policyFile.deleteOnExit();
        PrintWriter pw = new PrintWriter(policyFile);
        pw.println("grant {");
        pw.println("    permission javax.management.MBeanPermission \"*\", \"*\";");
        pw.println("    permission javax.management.MBeanServerPermission \"*\";");
        pw.println("    permission javax.management.MBeanTrustPermission \"*\";");
        pw.println("};");
        pw.close();

        System.setProperty("java.security.policy", policyFile.getAbsolutePath());
        System.setSecurityManager(new SecurityManager());

        String failure = null;

        for (Method m : ResourceInjectionTest.class.getDeclaredMethods()) {
            if (Modifier.isStatic(m.getModifiers()) &&
                    m.getName().startsWith("test") &&
                    m.getParameterTypes().length == 0) {
                ExpectException expexc = m.getAnnotation(ExpectException.class);
                mbs = MBeanServerFactory.newMBeanServer();
                try {
                    m.invoke(null);
                    if (expexc != null) {
                        failure =
                                m.getName() + " did not got expected exception " +
                                expexc.value().getName();
                        System.out.println(failure);
                    } else
                        System.out.println(m.getName() + " OK");
                } catch (InvocationTargetException ite) {
                    Throwable t = ite.getCause();
                    String prob = null;
                    if (expexc != null) {
                        if (expexc.value().isInstance(t)) {
                            System.out.println(m.getName() + " OK (got expected " +
                                    expexc.value().getName() + ")");
                        } else
                            prob = "got wrong exception";
                    } else
                        prob = "got exception";
                    if (prob != null) {
                        failure = m.getName() + ": " + prob + " " +
                                t.getClass().getName();
                        System.out.println(failure);
                        t.printStackTrace(System.out);
                    }
                }
            }
        }
        if (failure == null)
            System.out.println("TEST PASSED");
        else
            throw new Exception("TEST FAILED: " + failure);
    }

    private static interface Send {
        public void send();
    }

    // Test @Resource in MBean defined by annotations

    @MBean
    public static class Annotated {
        @Resource
        private volatile MBeanServer mbeanServer;
        @Resource
        private volatile ObjectName myName;

        @ManagedAttribute
        public ObjectName getMyName() {
            return myName;
        }

        @ManagedOperation
        public void unregisterSelf()
        throws InstanceNotFoundException, MBeanRegistrationException {
            mbeanServer.unregisterMBean(myName);
        }
    }

    private static void testAnnotated() throws Exception {
        testMBean(new Annotated());
    }

    private static void testAnnotatedWrapped() throws Exception {
        testMBean(new StandardMBean(new Annotated(), null));
    }

    @MBean
    public static class AnnotatedSend extends Annotated implements Send {
        @Resource
        private volatile SendNotification sender;

        @ManagedOperation
        public void send() {
            sender.sendNotification(new Notification("type", this, 0L));
        }
    }

    private static void testAnnotatedSend() throws Exception {
        testMBean(new AnnotatedSend());
    }

    private static void testAnnotatedSendWrapped() throws Exception {
        testMBean(new StandardEmitterMBean(
                new AnnotatedSend(), null, withWrappedVisible, null));
    }

    // Test @Resource in MXBean defined by annotations

    @MXBean
    public static class AnnotatedMX {
        @Resource
        private volatile MBeanServer mbeanServer;
        @Resource
        private volatile ObjectName myName;

        @ManagedAttribute
        public ObjectName getMyName() {
            return myName;
        }

        @ManagedOperation
        public void unregisterSelf()
        throws InstanceNotFoundException, MBeanRegistrationException {
            mbeanServer.unregisterMBean(myName);
        }
    }

    private static void testAnnotatedMX() throws Exception {
        testMBean(new AnnotatedMX());
    }

    private static void testAnnotatedMXWrapped() throws Exception {
        testMBean(new StandardMBean(new AnnotatedMX(), null, true));
    }

    public static class AnnotatedMXSend extends AnnotatedMX implements Send {
        @Resource
        private volatile SendNotification sender;

        @ManagedOperation
        public void send() {
            sender.sendNotification(new Notification("type", this, 0L));
        }
    }

    private static void testAnnotatedMXSend() throws Exception {
        testMBean(new AnnotatedMXSend());
    }

    private static void testAnnotatedMXSendWrapped() throws Exception {
        testMBean(new StandardEmitterMBean(
                new AnnotatedMXSend(), null, withWrappedVisibleMX, null));
    }

    // Test @Resource in Standard MBean

    public static interface SimpleStandardMBean {
        public ObjectName getMyName();
        public void unregisterSelf() throws Exception;
    }

    public static class SimpleStandard implements SimpleStandardMBean {
        @Resource(type = MBeanServer.class)
        private volatile Object mbeanServer;
        @Resource(type = ObjectName.class)
        private volatile Object myName;

        public ObjectName getMyName() {
            return (ObjectName) myName;
        }

        public void unregisterSelf() throws Exception {
            ((MBeanServer) mbeanServer).unregisterMBean(getMyName());
        }
    }

    private static void testStandard() throws Exception {
        testMBean(new SimpleStandard());
    }

    private static void testStandardWrapped() throws Exception {
        testMBean(new StandardMBean(new SimpleStandard(), SimpleStandardMBean.class));
    }

    public static interface SimpleStandardSendMBean extends SimpleStandardMBean {
        public void send();
    }

    public static class SimpleStandardSend
            extends SimpleStandard implements SimpleStandardSendMBean {
        @Resource(type = SendNotification.class)
        private volatile Object sender;

        public void send() {
            ((SendNotification) sender).sendNotification(
                    new Notification("type", this, 0L));
        }
    }

    private static void testStandardSend() throws Exception {
        testMBean(new SimpleStandardSend());
    }

    private static void testStandardSendWrapped() throws Exception {
        testMBean(new StandardEmitterMBean(
                new SimpleStandardSend(), SimpleStandardSendMBean.class,
                withWrappedVisible, null));
    }

    // Test @Resource in MXBean

    public static interface SimpleMXBean {
        public ObjectName getMyName();
        public void unregisterSelf() throws Exception;
    }

    public static class SimpleMX implements SimpleMXBean {
        @Resource(type = MBeanServer.class)
        private volatile Object mbeanServer;
        @Resource(type = ObjectName.class)
        private volatile Object myName;

        public ObjectName getMyName() {
            return (ObjectName) myName;
        }

        public void unregisterSelf() throws Exception {
            ((MBeanServer) mbeanServer).unregisterMBean(getMyName());
        }
    }

    private static void testMX() throws Exception {
        testMBean(new SimpleMX());
    }

    private static void testMXWrapped() throws Exception {
        testMBean(new StandardMBean(new SimpleMX(), SimpleMXBean.class, true));
    }

    public static interface SimpleMXBeanSend extends SimpleMXBean {
        public void send();
    }

    public MBeanServer getMbs() {
        return mbs;
    }

    public static class SimpleMXSend extends SimpleMX implements SimpleMXBeanSend {
        @Resource(type = SendNotification.class)
        private volatile Object sender;

        public void send() {
            ((SendNotification) sender).sendNotification(
                    new Notification("type", this, 0L));
        }
    }

    private static void testMXSend() throws Exception {
        testMBean(new SimpleMXSend());
    }

    private static void testMXSendWrapped() throws Exception {
        testMBean(new StandardEmitterMBean(
                new SimpleMXSend(), SimpleMXBeanSend.class,
                withWrappedVisibleMX, null));
    }

    // Test @Resource in Dynamic MBean

    private static class SimpleDynamic implements DynamicMBean {
        private MBeanServer mbeanServer;
        private ObjectName myName;

        @Resource
        private synchronized void setMBeanServer(MBeanServer mbs) {
            mbeanServer = mbs;
        }

        @Resource(type = ObjectName.class)
        private synchronized void setObjectName(Serializable name) {
            myName = (ObjectName) name;
        }

        public synchronized Object getAttribute(String attribute)
        throws AttributeNotFoundException {
            if (attribute.equals("MyName"))
                return myName;
            throw new AttributeNotFoundException(attribute);
        }

        public void setAttribute(Attribute attribute)
        throws AttributeNotFoundException {
            throw new AttributeNotFoundException(attribute.getName());
        }

        public synchronized AttributeList getAttributes(String[] attributes) {
            AttributeList list = new AttributeList();
            for (String name : attributes) {
                if (name.equals("MyName"))
                    list.add(new Attribute("MyName", myName));
            }
            return list;
        }

        public AttributeList setAttributes(AttributeList attributes) {
            return new AttributeList();
        }

        public synchronized Object invoke(
                String actionName, Object[] params, String[] signature)
        throws MBeanException, ReflectionException {
            if (actionName.equals("unregisterSelf") &&
                    (params == null || params.length == 0) &&
                    (signature == null || signature.length == 0)) {
                try {
                    mbeanServer.unregisterMBean(myName);
                    return null;
                } catch (Exception x) {
                    throw new MBeanException(x);
                }
            } else {
                Exception x = new NoSuchMethodException(
                        actionName + Arrays.toString(signature));
                throw new MBeanException(x);
            }
        }

        public MBeanInfo getMBeanInfo() {
            DynamicMBean mbean = new StandardMBean(
                    new SimpleStandard(), SimpleStandardMBean.class, false);
            return mbean.getMBeanInfo();
        }
    }

    private static void testDynamic() throws Exception {
        testMBean(new SimpleDynamic());
    }

    private static class SimpleDynamicSend extends SimpleDynamic {
        private SendNotification sender;

        @Resource
        private synchronized void setSender(SendNotification sender) {
            this.sender = sender;
        }

        @Override
        public synchronized Object invoke(
                String actionName, Object[] params, String[] signature)
        throws MBeanException, ReflectionException {
            if (actionName.equals("send")) {
                sender.sendNotification(new Notification("type", this, 0L));
                return null;
            } else
                return super.invoke(actionName, params, signature);
        }
    }

    private static void testDynamicSend() throws Exception {
        testMBean(new SimpleDynamicSend());
    }

    // Test that @Resource classes don't have to be public
    // They can even be defined within methods!
    // But you can't have any @ManagedAttributes or @ManagedOperations
    // in such MBeans so their utility is limited.

    private static void testNonPublic() throws Exception {
        @MBean
        class NonPublic {
            @Resource
            ObjectName myName;
        }
        assert !Modifier.isPublic(NonPublic.class.getModifiers());
        NonPublic mbean = new NonPublic();
        mbs.registerMBean(mbean, objectName);
        assert objectName.equals(mbean.myName);
    }

    // Test inheritance and multiple injections of the same value

    private static class ManyResources extends AnnotatedSend {
        @Resource
        private volatile ObjectName myName;  // same name as in parent!
        @Resource(type=ObjectName.class)
        private volatile Object myOtherName;
        private volatile ObjectName myThirdName;
        private volatile ObjectName myFourthName;
        private volatile int methodCalls;
        @Resource
        private volatile SendNotification send1;
        @Resource(type = SendNotification.class)
        private volatile Object send2;

        @Resource
        void setMyName(ObjectName name) {
            myThirdName = name;
            methodCalls++;
        }

        @Resource(type=ObjectName.class)
        private void setMyNameAgain(ObjectName name) {
            myFourthName = name;
            methodCalls++;
        }

        void check() {
            assert objectName.equals(myName) : myName;
            for (ObjectName name : new ObjectName[] {
                (ObjectName)myOtherName, myThirdName, myFourthName
            }) {
                assert myName == name : name;
            }
            assert methodCalls == 2 : methodCalls;
            assert send1 != null && send2 == send1;
        }
    }

    private static void testManyResources() throws Exception {
        ManyResources mr = new ManyResources();
        testMBean(mr);
        mr.check();
    }

    // Test that method override doesn't lead to multiple calls of the same method

    private static class ManyResourcesSub extends ManyResources {
        private boolean called;

        @Override
        @Resource
        void setMyName(ObjectName name) {
            super.setMyName(name);
            called = true;
        }

        void check2() {
            assert called;
        }
    }

    private static void testOverride() throws Exception {
        ManyResourcesSub mrs = new ManyResourcesSub();
        testMBean(mrs);
        mrs.check();
        mrs.check2();
    }

    // Test that @Resource is illegal on static fields

    @MBean
    public static class StaticResource {
        @Resource
        private static ObjectName name;
    }

    @ExpectException(NotCompliantMBeanException.class)
    private static void testStaticResource() throws Exception {
        testMBean(new StaticResource());
    }

    // Test that @Resource is illegal on static methods

    @MBean
    public static class StaticResourceMethod {
        @Resource
        private static void setObjectName(ObjectName name) {}
    }

    @ExpectException(NotCompliantMBeanException.class)
    private static void testStaticResourceMethod() throws Exception {
        testMBean(new StaticResourceMethod());
    }

    // Test that @Resource is illegal on methods that don't return void

    @MBean
    public static class NonVoidMethod {
        @Resource
        private String setObjectName(ObjectName name) {
            return "oops";
        }
    }

    @ExpectException(NotCompliantMBeanException.class)
    private static void testNonVoidMethod() throws Exception {
        testMBean(new NonVoidMethod());
    }

    // Test that @Resource is illegal on methods with no arguments

    @MBean
    public static class NoArgMethod {
        @Resource(type=ObjectName.class)
        private void setObjectName() {}
    }

    @ExpectException(NotCompliantMBeanException.class)
    private static void testNoArgMethod() throws Exception {
        testMBean(new NoArgMethod());
    }

    // Test that @Resource is illegal on methods with more than one argument

    @MBean
    public static class MultiArgMethod {
        @Resource
        private void setObjectName(ObjectName name, String what) {}
    }

    @ExpectException(NotCompliantMBeanException.class)
    private static void testMultiArgMethod() throws Exception {
        testMBean(new MultiArgMethod());
    }

    private static class CountListener implements NotificationListener {
        volatile int count;
        public void handleNotification(Notification notification, Object handback) {
            count++;
        }
    }

    private static void testMBean(Object mbean) throws Exception {
        mbs.registerMBean(mbean, objectName);

        final ObjectName name = (ObjectName) mbs.getAttribute(objectName, "MyName");
        assert objectName.equals(name) : name;

        if (mbean instanceof Send || mbean instanceof NotificationEmitter) {
            assert mbs.isInstanceOf(name, NotificationEmitter.class.getName());
            CountListener countL = new CountListener();
            mbs.addNotificationListener(name, countL, null, null);
            NotificationListener checkSource = new NotificationListener() {
                public void handleNotification(Notification n, Object h) {
                    assert n.getSource().equals(name) : n.getSource();
                }
            };
            mbs.addNotificationListener(name, checkSource, null, null);
            mbs.invoke(objectName, "send", null, null);
            assert countL.count == 1;
            mbs.removeNotificationListener(name, checkSource);
            mbs.removeNotificationListener(name, countL, null, null);
        }

        mbs.invoke(objectName, "unregisterSelf", null, null);
        assert !mbs.isRegistered(objectName);
    }
}