/*
 * Copyright 2002-2016 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
 *
 *      https://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.jmx.export.assembler;

import java.util.HashMap;
import java.util.Map;
import javax.management.Descriptor;
import javax.management.MBeanInfo;
import javax.management.MBeanParameterInfo;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;

import org.junit.Test;

import org.springframework.aop.framework.ProxyFactory;
import org.springframework.jmx.IJmxTestBean;
import org.springframework.jmx.JmxTestBean;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.metadata.JmxAttributeSource;
import org.springframework.jmx.support.ObjectNameManager;
import org.springframework.tests.aop.interceptor.NopInterceptor;

import static org.junit.Assert.*;

/**
 * @author Rob Harrop
 * @author Chris Beams
 */
public abstract class AbstractMetadataAssemblerTests extends AbstractJmxAssemblerTests {

	protected static final String QUEUE_SIZE_METRIC = "QueueSize";

	protected static final String CACHE_ENTRIES_METRIC = "CacheEntries";

	@Test
	public void testDescription() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		assertEquals("The descriptions are not the same", "My Managed Bean",
				info.getDescription());
	}

	@Test
	public void testAttributeDescriptionOnSetter() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = inf.getAttribute(AGE_ATTRIBUTE);
		assertEquals("The description for the age attribute is incorrect",
				"The Age Attribute", attr.getDescription());
	}

	@Test
	public void testAttributeDescriptionOnGetter() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = inf.getAttribute(NAME_ATTRIBUTE);
		assertEquals("The description for the name attribute is incorrect",
				"The Name Attribute", attr.getDescription());
	}

	/**
	 * Tests the situation where the attribute is only defined on the getter.
	 */
	@Test
	public void testReadOnlyAttribute() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = inf.getAttribute(AGE_ATTRIBUTE);
		assertFalse("The age attribute should not be writable", attr.isWritable());
	}

	@Test
	public void testReadWriteAttribute() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = inf.getAttribute(NAME_ATTRIBUTE);
		assertTrue("The name attribute should be writable", attr.isWritable());
		assertTrue("The name attribute should be readable", attr.isReadable());
	}

	/**
	 * Tests the situation where the property only has a getter.
	 */
	@Test
	public void testWithOnlySetter() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = inf.getAttribute("NickName");
		assertNotNull("Attribute should not be null", attr);
	}

	/**
	 * Tests the situation where the property only has a setter.
	 */
	@Test
	public void testWithOnlyGetter() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo attr = info.getAttribute("Superman");
		assertNotNull("Attribute should not be null", attr);
	}

	@Test
	public void testManagedResourceDescriptor() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		Descriptor desc = info.getMBeanDescriptor();

		assertEquals("Logging should be set to true", "true", desc.getFieldValue("log"));
		assertEquals("Log file should be jmx.log", "jmx.log", desc.getFieldValue("logFile"));
		assertEquals("Currency Time Limit should be 15", "15", desc.getFieldValue("currencyTimeLimit"));
		assertEquals("Persist Policy should be OnUpdate", "OnUpdate", desc.getFieldValue("persistPolicy"));
		assertEquals("Persist Period should be 200", "200", desc.getFieldValue("persistPeriod"));
		assertEquals("Persist Location should be foo", "./foo", desc.getFieldValue("persistLocation"));
		assertEquals("Persist Name should be bar", "bar.jmx", desc.getFieldValue("persistName"));
	}

	@Test
	public void testAttributeDescriptor() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		Descriptor desc = info.getAttribute(NAME_ATTRIBUTE).getDescriptor();

		assertEquals("Default value should be foo", "foo", desc.getFieldValue("default"));
		assertEquals("Currency Time Limit should be 20", "20", desc.getFieldValue("currencyTimeLimit"));
		assertEquals("Persist Policy should be OnUpdate", "OnUpdate", desc.getFieldValue("persistPolicy"));
		assertEquals("Persist Period should be 300", "300", desc.getFieldValue("persistPeriod"));
	}

	@Test
	public void testOperationDescriptor() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		Descriptor desc = info.getOperation("myOperation").getDescriptor();

		assertEquals("Currency Time Limit should be 30", "30", desc.getFieldValue("currencyTimeLimit"));
		assertEquals("Role should be \"operation\"", "operation", desc.getFieldValue("role"));
	}

	@Test
	public void testOperationParameterMetadata() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		ModelMBeanOperationInfo oper = info.getOperation("add");
		MBeanParameterInfo[] params = oper.getSignature();

		assertEquals("Invalid number of params", 2, params.length);
		assertEquals("Incorrect name for x param", "x", params[0].getName());
		assertEquals("Incorrect type for x param", int.class.getName(), params[0].getType());

		assertEquals("Incorrect name for y param", "y", params[1].getName());
		assertEquals("Incorrect type for y param", int.class.getName(), params[1].getType());
	}

	@Test
	public void testWithCglibProxy() throws Exception {
		IJmxTestBean tb = createJmxTestBean();
		ProxyFactory pf = new ProxyFactory();
		pf.setTarget(tb);
		pf.addAdvice(new NopInterceptor());
		Object proxy = pf.getProxy();

		MetadataMBeanInfoAssembler assembler = (MetadataMBeanInfoAssembler) getAssembler();

		MBeanExporter exporter = new MBeanExporter();
		exporter.setBeanFactory(getContext());
		exporter.setAssembler(assembler);

		String objectName = "spring:bean=test,proxy=true";

		Map<String, Object> beans = new HashMap<>();
		beans.put(objectName, proxy);
		exporter.setBeans(beans);
		start(exporter);

		MBeanInfo inf = getServer().getMBeanInfo(ObjectNameManager.getInstance(objectName));
		assertEquals("Incorrect number of operations", getExpectedOperationCount(), inf.getOperations().length);
		assertEquals("Incorrect number of attributes", getExpectedAttributeCount(), inf.getAttributes().length);

		assertTrue("Not included in autodetection", assembler.includeBean(proxy.getClass(), "some bean name"));
	}

	@Test
	public void testMetricDescription() throws Exception {
		ModelMBeanInfo inf = getMBeanInfoFromAssembler();
		ModelMBeanAttributeInfo metric = inf.getAttribute(QUEUE_SIZE_METRIC);
		ModelMBeanOperationInfo operation = inf.getOperation("getQueueSize");
		assertEquals("The description for the queue size metric is incorrect",
				"The QueueSize metric", metric.getDescription());
		assertEquals("The description for the getter operation of the queue size metric is incorrect",
				"The QueueSize metric", operation.getDescription());
	}

	@Test
	public void testMetricDescriptor() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		Descriptor desc = info.getAttribute(QUEUE_SIZE_METRIC).getDescriptor();
		assertEquals("Currency Time Limit should be 20", "20", desc.getFieldValue("currencyTimeLimit"));
		assertEquals("Persist Policy should be OnUpdate", "OnUpdate", desc.getFieldValue("persistPolicy"));
		assertEquals("Persist Period should be 300", "300", desc.getFieldValue("persistPeriod"));
		assertEquals("Unit should be messages", "messages",desc.getFieldValue("units"));
		assertEquals("Display Name should be Queue Size", "Queue Size",desc.getFieldValue("displayName"));
		assertEquals("Metric Type should be COUNTER", "COUNTER",desc.getFieldValue("metricType"));
		assertEquals("Metric Category should be utilization", "utilization",desc.getFieldValue("metricCategory"));
	}

	@Test
	public void testMetricDescriptorDefaults() throws Exception {
		ModelMBeanInfo info = getMBeanInfoFromAssembler();
		Descriptor desc = info.getAttribute(CACHE_ENTRIES_METRIC).getDescriptor();
		assertNull("Currency Time Limit should not be populated", desc.getFieldValue("currencyTimeLimit"));
		assertNull("Persist Policy should not be populated", desc.getFieldValue("persistPolicy"));
		assertNull("Persist Period should not be populated", desc.getFieldValue("persistPeriod"));
		assertNull("Unit should not be populated", desc.getFieldValue("units"));
		assertEquals("Display Name should be populated by default via JMX", CACHE_ENTRIES_METRIC,desc.getFieldValue("displayName"));
		assertEquals("Metric Type should be GAUGE", "GAUGE",desc.getFieldValue("metricType"));
		assertNull("Metric Category should not be populated", desc.getFieldValue("metricCategory"));
	}

	@Override
	protected abstract String getObjectName();

	@Override
	protected int getExpectedAttributeCount() {
		return 6;
	}

	@Override
	protected int getExpectedOperationCount() {
		return 9;
	}

	protected IJmxTestBean createJmxTestBean() {
		return new JmxTestBean();
	}

	@Override
	protected MBeanInfoAssembler getAssembler() {
		MetadataMBeanInfoAssembler assembler = new MetadataMBeanInfoAssembler();
		assembler.setAttributeSource(getAttributeSource());
		return assembler;
	}

	protected abstract JmxAttributeSource getAttributeSource();

}
