/*
 * Copyright 2013 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 org.springframework.boot.actuate.endpoint.jmx;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.management.MBeanInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.junit.After;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.util.ObjectUtils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Tests for {@link EndpointMBeanExporter}
 *
 * @author Christian Dupuis
 */
public class EndpointMBeanExporterTests {

	GenericApplicationContext context = null;

	@After
	public void close() {
		if (this.context != null) {
			this.context.close();
		}
	}

	@Test
	public void testRegistrationOfOneEndpoint() throws Exception {
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition("endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		MBeanInfo mbeanInfo = mbeanExporter.getServer().getMBeanInfo(
				getObjectName("endpoint1", this.context));
		assertNotNull(mbeanInfo);
		assertEquals(3, mbeanInfo.getOperations().length);
		assertEquals(3, mbeanInfo.getAttributes().length);
	}

	@Test
	public void testRegistrationTwoEndpoints() throws Exception {
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition("endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.registerBeanDefinition("endpoint2", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				getObjectName("endpoint1", this.context)));
		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				getObjectName("endpoint2", this.context)));
	}

	@Test
	public void testRegistrationWithDifferentDomain() throws Exception {
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition(
				"endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class, null,
						new MutablePropertyValues(Collections.singletonMap("domain",
								"test-domain"))));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				getObjectName("test-domain", "endpoint1", false, this.context)));
	}

	@Test
	public void testRegistrationWithDifferentDomainAndIdentity() throws Exception {
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put("domain", "test-domain");
		properties.put("ensureUniqueRuntimeObjectNames", true);
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition("endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class, null,
						new MutablePropertyValues(properties)));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				getObjectName("test-domain", "endpoint1", true, this.context)));
	}

	@Test
	public void testRegistrationWithDifferentDomainAndIdentityAndStaticNames()
			throws Exception {
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put("domain", "test-domain");
		properties.put("ensureUniqueRuntimeObjectNames", true);
		Properties staticNames = new Properties();
		staticNames.put("key1", "value1");
		staticNames.put("key2", "value2");
		properties.put("objectNameStaticProperties", staticNames);
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition("endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class, null,
						new MutablePropertyValues(properties)));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				ObjectNameManager.getInstance(getObjectName("test-domain", "endpoint1",
						true, this.context).toString()
						+ ",key1=value1,key2=value2")));
	}

	@Test
	public void testRegistrationWithParentContext() throws Exception {
		this.context = new GenericApplicationContext();
		this.context.registerBeanDefinition("endpointMbeanExporter",
				new RootBeanDefinition(EndpointMBeanExporter.class));
		this.context.registerBeanDefinition("endpoint1", new RootBeanDefinition(
				TestEndpoint.class));
		GenericApplicationContext parent = new GenericApplicationContext();

		this.context.setParent(parent);
		parent.refresh();
		this.context.refresh();

		MBeanExporter mbeanExporter = this.context.getBean(EndpointMBeanExporter.class);

		assertNotNull(mbeanExporter.getServer().getMBeanInfo(
				getObjectName("endpoint1", this.context)));

		parent.close();
	}

	private ObjectName getObjectName(String beanKey, GenericApplicationContext context)
			throws MalformedObjectNameException {
		return getObjectName("org.springframework.boot", beanKey, false, context);
	}

	private ObjectName getObjectName(String domain, String beanKey,
			boolean includeIdentity, ApplicationContext applicationContext)
			throws MalformedObjectNameException {
		if (includeIdentity) {
			return ObjectNameManager
					.getInstance(String.format("%s:type=Endpoint,name=%s,identity=%s",
							domain, beanKey, ObjectUtils
									.getIdentityHexString(applicationContext
											.getBean(beanKey))));
		}
		else {
			return ObjectNameManager.getInstance(String.format(
					"%s:type=Endpoint,name=%s", domain, beanKey));
		}
	}

	public static class TestEndpoint extends AbstractEndpoint<String> {

		public TestEndpoint() {
			super("test");
		}

		@Override
		public String invoke() {
			return "hello world";
		}
	}

}
