/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package test.feature.jmx;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.*;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.junit.Test;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.plugin.context.MBeanExport;
import org.springframework.config.java.plugin.context.RegistrationPolicy;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.support.JmxUtils;

/**
 * Integration test for {@link MBeanExporter @MBeanExporter} annotation support.
 */
public class MBeanExporterTests {

    public @Test void testInterfaceBasedMBeanRegistration() throws Exception {
        new JavaConfigApplicationContext(MBeanConfig.class);

        MBeanServer server = JmxUtils.locateMBeanServer();
        assertThat(server, notNullValue());

        // the following will throw InstanceNotFoundException if the name cannot be found
        server.getMBeanInfo(new ObjectName("test.feature.jmx:name=monitor,type=MBeanExporterTests.Monitor"));
    }

    @Configuration
    @MBeanExport
    static class MBeanConfig {
        public @Bean Monitor monitor() {
            return new Monitor();
        }
    }

    static class Monitor implements MonitorMBean {
        public int getCount() {
            return 42;
        }
    }

    // MBean interfaces must have public visibility
    public interface MonitorMBean {
        int getCount();
    }

    // -------------------------------------------

    public @Test void testAnnotationBasedMBeanRegistration() throws Exception {
        new JavaConfigApplicationContext(AnnotationMBeanConfig.class);

        MBeanServer server = JmxUtils.locateMBeanServer();
        assertThat(server, notNullValue());

        // the following will throw InstanceNotFoundException if the name cannot be found
        server.getMBeanInfo(new ObjectName("test.feature.jmx:name=annotatedMBean,type=MBeanExporterTests.AnnotatedMBean"));
    }

    @Configuration
    @MBeanExport
    static class AnnotationMBeanConfig {
        public @Bean AnnotatedMBean annotatedMBean() {
            return new AnnotatedMBean();
        }
    }

    @ManagedResource
    static class AnnotatedMBean { }

    // -------------------------------------------

    public @Test void testCustomDomainMBeanRegistration() throws Exception {
        new JavaConfigApplicationContext(CustomDomainMBeanConfig.class);

        MBeanServer server = JmxUtils.locateMBeanServer();
        assertThat(server, notNullValue());

        // the following will throw InstanceNotFoundException if the name cannot be found
        server.getMBeanInfo(new ObjectName("custom.domain:name=myMBean,type=MBeanExporterTests.MyMBean"));
    }

    @Configuration
    @MBeanExport(defaultDomain="custom.domain")
    static class CustomDomainMBeanConfig {
        public @Bean MyMBean myMBean() {
            return new MyMBean();
        }
    }

    // -------------------------------------------

    public @Test void testDefaultRegistrationPolicy() throws Exception {
        // first time registration should succeed
        new JavaConfigApplicationContext(DefaultRegistrationMBeanConfig.class);

        try {
            // second time registration should fail
            new JavaConfigApplicationContext(DefaultRegistrationMBeanConfig.class);
            fail("should have thrown 'duplicate MBean' exception");
        } catch (Exception ex) {
            Throwable rootCause = ExceptionUtils.getRootCause(ex);
            assertThat(rootCause, instanceOf(InstanceAlreadyExistsException.class));
        }
    }

    @Configuration
    @MBeanExport
    static class DefaultRegistrationMBeanConfig {
        public @Bean MyMBean myMBean() {
            return new MyMBean();
        }
    }

    // -------------------------------------------

    private static final String ATTRIB_NAME = "Size";
    private static final int INITIAL_SIZE = 42;
    private static final int NEW_SIZE = 300;
    private static final ObjectName OBJECT_NAME;
    static {
        try { OBJECT_NAME = new ObjectName("test.feature.jmx:name=myMBean,type=MBeanExporterTests.MyMBean"); }
        catch (Exception ex) { throw new RuntimeException(ex); }
    }

    public @Test void testReplaceExistingRegistrationPolicy() throws Exception {
        registerMBeanTwiceAndAssertFinalValue(ReplaceExistingMBeanConfig.class, INITIAL_SIZE);
    }

    @Configuration
    @MBeanExport(registration=RegistrationPolicy.REPLACE_EXISTING)
    static class ReplaceExistingMBeanConfig {
        public @Bean MyMBean myMBean() {
            return new MyMBean();
        }
    }

    public @Test void testIgnoreExistingRegistrationPolicy() throws Exception {
        registerMBeanTwiceAndAssertFinalValue(IgnoreExistingMBeanConfig.class, NEW_SIZE);
    }

    @Configuration
    @MBeanExport(registration=RegistrationPolicy.IGNORE_EXISTING)
    static class IgnoreExistingMBeanConfig {
        public @Bean MyMBean myMBean() {
            return new MyMBean();
        }
    }

    private void registerMBeanTwiceAndAssertFinalValue(Class<?> configClass, int expected) throws MBeanException, AttributeNotFoundException,
            InstanceNotFoundException, ReflectionException, InvalidAttributeValueException, MBeanRegistrationException {
        // initial registration of MBean
        new JavaConfigApplicationContext(configClass);

        MBeanServer server = JmxUtils.locateMBeanServer();

        assertEquals(server.getAttribute(OBJECT_NAME, ATTRIB_NAME), INITIAL_SIZE);
        server.setAttribute(OBJECT_NAME, new Attribute(ATTRIB_NAME, NEW_SIZE));
        assertEquals(server.getAttribute(OBJECT_NAME, ATTRIB_NAME), NEW_SIZE);

        // second registration of MBean
        new JavaConfigApplicationContext(configClass);

        // should remain at new size, because we used REPLACE_EXISTING
        assertEquals(server.getAttribute(OBJECT_NAME, ATTRIB_NAME), expected);

        server.unregisterMBean(OBJECT_NAME);
    }


    // -------------------------------------------

    @ManagedResource
    public static class MyMBean {

        private int size = INITIAL_SIZE;

        public @ManagedAttribute void setSize(int size) { this.size = size; }
        public @ManagedAttribute int getSize() { return size; }
    }

}
