/*
* 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
*
* 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.jmx.access;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.BindException;
import java.util.HashMap;
import java.util.Map;
import javax.management.Descriptor;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import org.junit.Test;
import org.springframework.jmx.AbstractMBeanServerTests;
import org.springframework.jmx.IJmxTestBean;
import org.springframework.jmx.JmxException;
import org.springframework.jmx.JmxTestBean;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.assembler.AbstractReflectiveMBeanInfoAssembler;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;
import org.springframework.util.SocketUtils;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
/**
* To run the tests in the class, set the following Java system property:
* {@code -DtestGroups=jmxmp}.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
*/
public class MBeanClientInterceptorTests extends AbstractMBeanServerTests {
protected static final String OBJECT_NAME = "spring:test=proxy";
protected JmxTestBean target;
protected boolean runTests = true;
@Override
public void onSetUp() throws Exception {
target = new JmxTestBean();
target.setAge(100);
target.setName("Rob Harrop");
MBeanExporter adapter = new MBeanExporter();
Map<String, Object> beans = new HashMap<>();
beans.put(OBJECT_NAME, target);
adapter.setServer(getServer());
adapter.setBeans(beans);
adapter.setAssembler(new ProxyTestAssembler());
start(adapter);
}
protected MBeanServerConnection getServerConnection() throws Exception {
return getServer();
}
protected IJmxTestBean getProxy() throws Exception {
MBeanProxyFactoryBean factory = new MBeanProxyFactoryBean();
factory.setServer(getServerConnection());
factory.setProxyInterface(IJmxTestBean.class);
factory.setObjectName(OBJECT_NAME);
factory.afterPropertiesSet();
return (IJmxTestBean) factory.getObject();
}
@Test
public void testProxyClassIsDifferent() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
assertTrue("The proxy class should be different than the base class", (proxy.getClass() != IJmxTestBean.class));
}
@Test
public void testDifferentProxiesSameClass() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy1 = getProxy();
IJmxTestBean proxy2 = getProxy();
assertNotSame("The proxies should NOT be the same", proxy1, proxy2);
assertSame("The proxy classes should be the same", proxy1.getClass(), proxy2.getClass());
}
@Test
public void testGetAttributeValue() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy1 = getProxy();
int age = proxy1.getAge();
assertEquals("The age should be 100", 100, age);
}
@Test
public void testSetAttributeValue() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
proxy.setName("Rob Harrop");
assertEquals("The name of the bean should have been updated", "Rob Harrop", target.getName());
}
@Test(expected = IllegalArgumentException.class)
public void testSetAttributeValueWithRuntimeException() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
proxy.setName("Juergen");
}
@Test(expected = ClassNotFoundException.class)
public void testSetAttributeValueWithCheckedException() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
proxy.setName("Juergen Class");
}
@Test(expected = IOException.class)
public void testSetAttributeValueWithIOException() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
proxy.setName("Juergen IO");
}
@Test(expected = InvalidInvocationException.class)
public void testSetReadOnlyAttribute() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
proxy.setAge(900);
}
@Test
public void testInvokeNoArgs() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
long result = proxy.myOperation();
assertEquals("The operation should return 1", 1, result);
}
@Test
public void testInvokeArgs() throws Exception {
assumeTrue(runTests);
IJmxTestBean proxy = getProxy();
int result = proxy.add(1, 2);
assertEquals("The operation should return 3", 3, result);
}
@Test(expected = InvalidInvocationException.class)
public void testInvokeUnexposedMethodWithException() throws Exception {
assumeTrue(runTests);
IJmxTestBean bean = getProxy();
bean.dontExposeMe();
}
@Test
public void testTestLazyConnectionToRemote() throws Exception {
assumeTrue(runTests);
Assume.group(TestGroup.JMXMP);
final int port = SocketUtils.findAvailableTcpPort();
JMXServiceURL url = new JMXServiceURL("service:jmx:jmxmp://localhost:" + port);
JMXConnectorServer connector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, getServer());
MBeanProxyFactoryBean factory = new MBeanProxyFactoryBean();
factory.setServiceUrl(url.toString());
factory.setProxyInterface(IJmxTestBean.class);
factory.setObjectName(OBJECT_NAME);
factory.setConnectOnStartup(false);
factory.setRefreshOnConnectFailure(true);
// should skip connection to the server
factory.afterPropertiesSet();
IJmxTestBean bean = (IJmxTestBean) factory.getObject();
// now start the connector
try {
connector.start();
}
catch (BindException ex) {
System.out.println("Skipping remainder of JMX LazyConnectionToRemote test because binding to local port ["
+ port + "] failed: " + ex.getMessage());
return;
}
// should now be able to access data via the lazy proxy
try {
assertEquals("Rob Harrop", bean.getName());
assertEquals(100, bean.getAge());
}
finally {
connector.stop();
}
try {
bean.getName();
}
catch (JmxException ex) {
// expected
}
connector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, getServer());
connector.start();
// should now be able to access data via the lazy proxy
try {
assertEquals("Rob Harrop", bean.getName());
assertEquals(100, bean.getAge());
}
finally {
connector.stop();
}
}
/*
public void testMXBeanAttributeAccess() throws Exception {
MBeanClientInterceptor interceptor = new MBeanClientInterceptor();
interceptor.setServer(ManagementFactory.getPlatformMBeanServer());
interceptor.setObjectName("java.lang:type=Memory");
interceptor.setManagementInterface(MemoryMXBean.class);
MemoryMXBean proxy = ProxyFactory.getProxy(MemoryMXBean.class, interceptor);
assertTrue(proxy.getHeapMemoryUsage().getMax() > 0);
}
public void testMXBeanOperationAccess() throws Exception {
MBeanClientInterceptor interceptor = new MBeanClientInterceptor();
interceptor.setServer(ManagementFactory.getPlatformMBeanServer());
interceptor.setObjectName("java.lang:type=Threading");
ThreadMXBean proxy = ProxyFactory.getProxy(ThreadMXBean.class, interceptor);
assertTrue(proxy.getThreadInfo(Thread.currentThread().getId()).getStackTrace() != null);
}
public void testMXBeanAttributeListAccess() throws Exception {
MBeanClientInterceptor interceptor = new MBeanClientInterceptor();
interceptor.setServer(ManagementFactory.getPlatformMBeanServer());
interceptor.setObjectName("com.sun.management:type=HotSpotDiagnostic");
HotSpotDiagnosticMXBean proxy = ProxyFactory.getProxy(HotSpotDiagnosticMXBean.class, interceptor);
assertFalse(proxy.getDiagnosticOptions().isEmpty());
}
*/
private static class ProxyTestAssembler extends AbstractReflectiveMBeanInfoAssembler {
@Override
protected boolean includeReadAttribute(Method method, String beanKey) {
return true;
}
@Override
protected boolean includeWriteAttribute(Method method, String beanKey) {
if ("setAge".equals(method.getName())) {
return false;
}
return true;
}
@Override
protected boolean includeOperation(Method method, String beanKey) {
if ("dontExposeMe".equals(method.getName())) {
return false;
}
return true;
}
@SuppressWarnings("unused")
protected String getOperationDescription(Method method) {
return method.getName();
}
@SuppressWarnings("unused")
protected String getAttributeDescription(PropertyDescriptor propertyDescriptor) {
return propertyDescriptor.getDisplayName();
}
@SuppressWarnings("unused")
protected void populateAttributeDescriptor(Descriptor descriptor, Method getter, Method setter) {
}
@SuppressWarnings("unused")
protected void populateOperationDescriptor(Descriptor descriptor, Method method) {
}
@SuppressWarnings({ "unused", "rawtypes" })
protected String getDescription(String beanKey, Class beanClass) {
return "";
}
@SuppressWarnings({ "unused", "rawtypes" })
protected void populateMBeanDescriptor(Descriptor mbeanDescriptor, String beanKey, Class beanClass) {
}
}
}