Better handling of Objects in JMX MetaData (#10762)

* Send attributes as Map<String,String>
* JMX-annotated ErrorHandler, fixed usage of showMessageInTitle and removed showServlet.
* JMX-annotated Request.Handler.
* Added JMX test module to test improvements to JMX.

Co-authored-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Greg Wilkins 2023-10-24 01:03:28 +02:00 committed by GitHub
parent 2940528033
commit 14152c425b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 404 additions and 120 deletions

View File

@ -19,11 +19,16 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
@ -52,6 +57,8 @@ class MetaData
private static final MBeanConstructorInfo[] NO_CONSTRUCTORS = new MBeanConstructorInfo[0];
private static final MBeanOperationInfo[] NO_OPERATIONS = new MBeanOperationInfo[0];
private static final MBeanNotificationInfo[] NO_NOTIFICATIONS = new MBeanNotificationInfo[0];
private static final String[] NO_PARAMETERS = new String[0];
private static final Object[] NO_ARGUMENTS = new Object[0];
private final Map<String, AttributeInfo> _attributes = new HashMap<>();
private final Map<String, OperationInfo> _operations = new HashMap<>();
@ -224,24 +231,76 @@ class MetaData
return attributeName.substring(0, 1).toLowerCase(Locale.ENGLISH) + attributeName.substring(1);
}
private static boolean isManagedObject(Class<?> klass)
private static Type getManagedType(Type type)
{
if (klass.isArray())
klass = klass.getComponentType();
if (klass.isPrimitive())
return false;
while (klass != null)
if (type == null || Void.TYPE.equals(type))
return Void.TYPE;
if (type instanceof Class<?> clazz)
{
if (klass.isAnnotationPresent(ManagedObject.class))
return true;
klass = klass.getSuperclass();
if (clazz.isAnnotationPresent(ManagedObject.class))
return ObjectName.class;
if (clazz.isArray() && clazz.getComponentType().isAnnotationPresent(ManagedObject.class))
return ObjectName[].class;
}
return false;
if (type instanceof ParameterizedType parameterizedType &&
parameterizedType.getRawType() instanceof Class<?> clazz &&
Collection.class.isAssignableFrom(clazz))
{
Type[] genArgs = parameterizedType.getActualTypeArguments();
if (genArgs.length == 1 && genArgs[0] instanceof Class<?> klass && klass.isAnnotationPresent(ManagedObject.class))
return ObjectName[].class;
}
if (type.getTypeName().startsWith("org.eclipse.jetty."))
return String.class;
return type;
}
private static Object convertToManagedType(MBeanContainer mbeanContainer, Object object, Class<?> from, Type to)
{
if (object == null)
return null;
if (ObjectName.class.equals(to))
return mbeanContainer.findMBean(object);
if (ObjectName[].class.equals(to))
{
if (object instanceof Collection<?> collection)
{
int length = collection.size();
ObjectName[] names = new ObjectName[length];
int i = 0;
for (Object o : collection)
names[i++] = mbeanContainer.findMBean(o);
return names;
}
if (from.isArray())
{
int length = Array.getLength(object);
ObjectName[] names = new ObjectName[length];
for (int i = 0; i < length; ++i)
names[i] = mbeanContainer.findMBean(Array.get(object, i));
return names;
}
return null;
}
if (String.class.equals(to))
return String.valueOf(object);
return object;
}
private static String signature(String name, String[] params)
{
return String.format("%s(%s)", name, String.join(",", params));
return String.format("%s(%s)", name, String.join(",", params != null ? params : NO_PARAMETERS));
}
private static String signature(Method method)
@ -336,7 +395,7 @@ class MetaData
private final Method _getter;
private final Method _setter;
private final boolean _proxied;
private final boolean _convert;
private final Type _convert;
private final MBeanAttributeInfo _info;
private AttributeInfo(ManagedAttribute attribute, Method getter)
@ -353,13 +412,8 @@ class MetaData
_proxied = attribute.proxied();
Class<?> returnType = getter.getReturnType();
_convert = isManagedObject(returnType);
String signature = _convert
? returnType.isArray()
? ObjectName[].class.getName()
: ObjectName.class.getName()
: returnType.getName();
_convert = getManagedType(getter.getGenericReturnType());
String signature = _convert.getTypeName();
String description = attribute.value();
_info = new MBeanAttributeInfo(name, signature, description, true,
@ -374,19 +428,7 @@ class MetaData
if (_proxied || _getter.getDeclaringClass().isInstance(mbean))
target = mbean;
Object result = _getter.invoke(target);
if (result == null)
return null;
if (!_convert)
return result;
if (!_getter.getReturnType().isArray())
return mbean.findObjectName(result);
int length = Array.getLength(result);
ObjectName[] names = new ObjectName[length];
for (int i = 0; i < length; ++i)
{
names[i] = mbean.findObjectName(Array.get(result, i));
}
return names;
return convertToManagedType(mbean.getMBeanContainer(), result, _getter.getReturnType(), _convert);
}
catch (InvocationTargetException x)
{
@ -409,24 +451,26 @@ class MetaData
Object target = mbean.getManagedObject();
if (_proxied || _setter.getDeclaringClass().isInstance(mbean))
target = mbean;
if (!_convert || value == null)
{
_setter.invoke(target, value);
return;
}
if (!_getter.getReturnType().isArray())
{
if (ObjectName.class.equals(_convert))
value = mbean.findBean((ObjectName)value);
_setter.invoke(target, value);
return;
}
ObjectName[] names = (ObjectName[])value;
Object result = new Object[names.length];
for (int i = 0; i < names.length; ++i)
if (ObjectName[].class.equals(_convert))
{
Array.set(result, i, mbean.findBean(names[i]));
ObjectName[] names = (ObjectName[])value;
Object[] objects = new Object[names.length];
for (int i = 0; i < names.length; ++i)
objects[i] = mbean.findBean(names[i]);
if (List.class.isAssignableFrom(_setter.getReturnType()))
value = Arrays.asList(objects);
else if (Set.class.isAssignableFrom(_setter.getReturnType()))
value = new HashSet<>(Arrays.asList(objects));
else
value = objects;
}
_setter.invoke(target, result);
_setter.invoke(target, value);
}
catch (InvocationTargetException x)
{
@ -479,7 +523,7 @@ class MetaData
private final String _name;
private final Method _method;
private final boolean _proxied;
private final boolean _convert;
private final Type _convert;
private final MBeanOperationInfo _info;
private OperationInfo(ManagedOperation operation, Method method)
@ -490,13 +534,8 @@ class MetaData
_proxied = operation.proxied();
Class<?> returnType = method.getReturnType();
_convert = isManagedObject(returnType);
String returnSignature = _convert
? returnType.isArray()
? ObjectName[].class.getName()
: ObjectName.class.getName()
: returnType.getName();
_convert = getManagedType(method.getGenericReturnType());
String returnSignature = _convert.getTypeName();
String impactName = operation.impact();
int impact = MBeanOperationInfo.UNKNOWN;
@ -514,6 +553,7 @@ class MetaData
public Object invoke(Object[] args, ObjectMBean mbean) throws ReflectionException, MBeanException
{
args = args != null ? args : NO_ARGUMENTS;
if (LOG.isDebugEnabled())
LOG.debug("invoke {}.{}({}) {}", mbean, _info.getName(), Arrays.asList(args), _info);
try
@ -522,19 +562,7 @@ class MetaData
if (_proxied || _method.getDeclaringClass().isInstance(mbean))
target = mbean;
Object result = _method.invoke(target, args);
if (result == null)
return null;
if (!_convert)
return result;
if (!_method.getReturnType().isArray())
return mbean.findObjectName(result);
int length = Array.getLength(result);
ObjectName[] names = new ObjectName[length];
for (int i = 0; i < length; ++i)
{
names[i] = mbean.findObjectName(Array.get(result, i));
}
return names;
return convertToManagedType(mbean.getMBeanContainer(), result, _method.getReturnType(), _convert);
}
catch (InvocationTargetException x)
{
@ -556,9 +584,8 @@ class MetaData
Annotation[] parameterAnnotations = parametersAnnotations[i];
for (Annotation parameterAnnotation : parameterAnnotations)
{
if (parameterAnnotation instanceof Name)
if (parameterAnnotation instanceof Name name)
{
Name name = (Name)parameterAnnotation;
info = result[i] = new MBeanParameterInfo(name.value(), typeName, name.description());
break;
}

View File

@ -117,7 +117,7 @@ public interface Handler extends LifeCycle, Destroyable, Request.Handler
/**
* @return the {@code Server} associated with this {@code Handler}
*/
@ManagedAttribute(value = "the Server instance associated to this Handler", readonly = true)
@ManagedAttribute(value = "The Server instance associated to this Handler", readonly = true)
Server getServer();
/**

View File

@ -51,6 +51,8 @@ import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.thread.Invocable;
/**
@ -660,6 +662,7 @@ public interface Request extends Attributes, Content.Source
* <p>A handler for an HTTP request and response.</p>
* <p>The handling typically involves reading the request content (if any) and producing a response.</p>
*/
@ManagedObject
@FunctionalInterface
interface Handler extends Invocable
{
@ -699,6 +702,13 @@ public interface Request extends Attributes, Content.Source
* called and thus should attempt to complete the request as if a false had been returned.
*/
boolean handle(Request request, Response response, Callback callback) throws Exception;
@Override
@ManagedAttribute("The InvocationType of this Handler")
default InvocationType getInvocationType()
{
return InvocationType.BLOCKING;
}
}
/**

View File

@ -866,7 +866,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
/**
* @return Returns the base resource as a string.
*/
@ManagedAttribute("document root for context")
@ManagedAttribute(value = "document root for context", readonly = true)
public Resource getBaseResource()
{
return _baseResource;

View File

@ -49,6 +49,8 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,6 +60,7 @@ import org.slf4j.LoggerFactory;
* It is called by the {@link Response#writeError(Request, Response, Callback, int, String)}
* to generate an error page.
*/
@ManagedObject
public class ErrorHandler implements Request.Handler
{
// TODO This classes API needs to be majorly refactored/cleanup in jetty-10
@ -69,7 +72,6 @@ public class ErrorHandler implements Request.Handler
public static final Set<String> ERROR_METHODS = Set.of("GET", "POST", "HEAD");
public static final HttpField ERROR_CACHE_CONTROL = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
boolean _showServlet = true;
boolean _showStacks = true;
boolean _showMessageInTitle = true;
HttpField _cacheControl = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
@ -211,7 +213,7 @@ public class ErrorHandler implements Request.Handler
{
// write into the response aggregate buffer and flush it asynchronously.
// Looping to reduce size if buffer overflows
boolean showStacks = _showStacks;
boolean showStacks = isShowStacks();
while (true)
{
try
@ -294,7 +296,7 @@ public class ErrorHandler implements Request.Handler
writer.write("<title>Error ");
String status = Integer.toString(code);
writer.write(status);
if (message != null && !message.equals(status))
if (isShowMessageInTitle() && message != null && !message.equals(status))
{
writer.write(' ');
writer.write(StringUtil.sanitizeXmlString(message));
@ -353,8 +355,11 @@ public class ErrorHandler implements Request.Handler
{
writer.write("HTTP ERROR ");
writer.write(Integer.toString(code));
writer.write(' ');
writer.write(StringUtil.sanitizeXmlString(message));
if (isShowMessageInTitle())
{
writer.write(' ');
writer.write(StringUtil.sanitizeXmlString(message));
}
writer.write("\n");
writer.printf("URI: %s%n", request.getHttpURI());
writer.printf("STATUS: %s%n", code);
@ -433,6 +438,7 @@ public class ErrorHandler implements Request.Handler
*
* @return the cacheControl header to set on error responses.
*/
@ManagedAttribute("The value of the Cache-Control response header")
public String getCacheControl()
{
return _cacheControl == null ? null : _cacheControl.getValue();
@ -448,25 +454,10 @@ public class ErrorHandler implements Request.Handler
_cacheControl = cacheControl == null ? null : new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cacheControl);
}
/**
* @return True if the error page will show the Servlet that generated the error
*/
public boolean isShowServlet()
{
return _showServlet;
}
/**
* @param showServlet True if the error page will show the Servlet that generated the error
*/
public void setShowServlet(boolean showServlet)
{
_showServlet = showServlet;
}
/**
* @return True if stack traces are shown in the error pages
*/
@ManagedAttribute("Whether the error page shows the stack trace")
public boolean isShowStacks()
{
return _showStacks;
@ -480,6 +471,12 @@ public class ErrorHandler implements Request.Handler
_showStacks = showStacks;
}
@ManagedAttribute("Whether the error message is shown in the error page title")
public boolean isShowMessageInTitle()
{
return _showMessageInTitle;
}
/**
* Set if true, the error message appears in page title.
* @param showMessageInTitle if true, the error message appears in page title
@ -489,11 +486,6 @@ public class ErrorHandler implements Request.Handler
_showMessageInTitle = showMessageInTitle;
}
public boolean getShowMessageInTitle()
{
return _showMessageInTitle;
}
protected void write(Writer writer, String string) throws IOException
{
if (string == null)

View File

@ -38,15 +38,12 @@ public class ContextHandlerMBean extends Handler.AbstractMBean
}
@ManagedAttribute("Map of context attributes")
public Map<String, Object> getContextAttributes()
public Map<String, String> getContextAttributes()
{
Map<String, Object> map = new TreeMap<>();
Map<String, String> map = new TreeMap<>();
ContextHandler.ScopedContext context = getManagedObject().getContext();
for (String name : context.getAttributeNameSet())
{
Object value = context.getAttribute(name);
map.put(name, value);
}
map.put(name, String.valueOf(context.getAttribute(name)));
return map;
}

View File

@ -18,7 +18,6 @@ import java.util.List;
import org.eclipse.jetty.jmx.ObjectMBean;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
public class Handler
@ -45,9 +44,7 @@ public class Handler
String contextName = getContextName(contextHandler);
if (contextName == null)
contextName = contextHandler.getDisplayName();
if (contextName != null)
return contextName;
return null;
return contextName;
}
else
{
@ -86,11 +83,5 @@ public class Handler
return name;
}
@ManagedAttribute("The invocation type")
public String getInvocationType()
{
return getManagedObject().getInvocationType().name();
}
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-tests</artifactId>
<version>12.0.3-SNAPSHOT</version>
</parent>
<artifactId>jetty-test-jmx</artifactId>
<name>Core :: Tests :: JMX</name>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,232 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.test.jmx;
import java.lang.management.ManagementFactory;
import java.util.Set;
import javax.management.Attribute;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.Invocable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ServerJMXTest
{
private MBeanServer mbeanServer;
private MBeanContainer mbeanContainer;
private Server server;
private void start(Handler handler) throws Exception
{
mbeanServer = ManagementFactory.getPlatformMBeanServer();
mbeanContainer = new MBeanContainer(mbeanServer);
server = new Server();
server.addBean(mbeanContainer);
server.setHandler(handler);
server.start();
}
@AfterEach
public void dispose()
{
LifeCycle.stop(server);
server.removeBean(mbeanContainer);
}
@Test
public void testAnonymousAbstractHandler() throws Exception
{
start(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
callback.succeeded();
return true;
}
});
// Verify the Handler is there.
// It is an anonymous class of this test class something like ServerJMXTest$1,
// so its domain will be the package name of this test class.
Set<ObjectName> objectNames = mbeanServer.queryNames(ObjectName.getInstance(getClass().getPackageName() + ":*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName objectName = objectNames.iterator().next();
String invocationTypeValue = (String)mbeanServer.getAttribute(objectName, "invocationType");
assertDoesNotThrow(() -> Invocable.InvocationType.valueOf(invocationTypeValue));
Object serverValue = mbeanServer.getAttribute(objectName, "server");
assertInstanceOf(ObjectName.class, serverValue);
MBeanInfo mbeanInfo = mbeanServer.getMBeanInfo((ObjectName)serverValue);
assertNotNull(mbeanInfo);
assertEquals(server.getClass().getName(), mbeanInfo.getClassName());
}
@Test
public void testContextHandlerGetAttributes() throws Exception
{
ContextHandler context = new ContextHandler("/ctx");
context.setHandler(new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
callback.succeeded();
return true;
}
});
start(context);
Set<ObjectName> objectNames = mbeanServer.queryNames(ObjectName.getInstance(context.getClass().getPackageName() + ":*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName contextHandlerObjectName = objectNames.iterator().next();
// The ContextHandler MBean should report the contextPath as an ObjectName property.
String contextProperty = contextHandlerObjectName.getKeyProperty("context");
assertNotNull(contextProperty);
assertEquals(context.getContextPath(), "/" + contextProperty);
objectNames = mbeanServer.queryNames(ObjectName.getInstance(getClass().getPackageName() + ":*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName handlerObjectName = objectNames.iterator().next();
// Also the child Handler should have the contextPath as an ObjectName property.
String handlerProperty = handlerObjectName.getKeyProperty("context");
assertNotNull(handlerProperty);
assertEquals(contextProperty, handlerProperty);
// The child Handler should be available as ObjectName.
Object handlerValue = mbeanServer.getAttribute(contextHandlerObjectName, "handler");
assertInstanceOf(ObjectName.class, handlerValue);
assertEquals(handlerObjectName, handlerValue);
// The list of Handlers should be available as ObjectNames.
Object handlersValue = mbeanServer.getAttribute(contextHandlerObjectName, "handlers");
assertInstanceOf(ObjectName[].class, handlersValue);
ObjectName[] childrenObjectNames = (ObjectName[])handlersValue;
assertEquals(1, childrenObjectNames.length);
assertEquals(handlerObjectName, childrenObjectNames[0]);
}
@Test
public void testContextHandlerSetAttributes() throws Exception
{
ContextHandler context = new ContextHandler("/ctx");
Handler.Abstract handler = new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
callback.succeeded();
return true;
}
};
context.setHandler(handler);
context.setErrorHandler(new ErrorHandler());
start(context);
Set<ObjectName> objectNames = mbeanServer.queryNames(ObjectName.getInstance(context.getClass().getPackageName() + ":type=contexthandler,*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName contextHandlerObjectName = objectNames.iterator().next();
// Test simple setter.
String displayName = "test-displayName";
mbeanServer.setAttribute(contextHandlerObjectName, new Attribute("displayName", displayName));
Object displayNameValue = mbeanServer.getAttribute(contextHandlerObjectName, "displayName");
assertEquals(displayName, displayNameValue);
objectNames = mbeanServer.queryNames(ObjectName.getInstance(context.getClass().getPackageName() + ":type=errorhandler,*"), null);
assertEquals(1, objectNames.size());
ObjectName errorHandlerObjectName = objectNames.iterator().next();
Object errorHandlerValue = mbeanServer.getAttribute(contextHandlerObjectName, "errorHandler");
assertInstanceOf(ObjectName.class, errorHandlerValue);
assertEquals(errorHandlerObjectName, errorHandlerValue);
Object handlerValue = mbeanServer.getAttribute(contextHandlerObjectName, "handler");
assertInstanceOf(ObjectName.class, handlerValue);
// Test setting a JMX-converted attribute.
// Method setErrorHandler() should be able to take an ObjectName, lookup the
// correspondent Object and perform the invocation with the object on the target.
mbeanServer.setAttribute(contextHandlerObjectName, new Attribute("errorHandler", handlerValue));
// Verify that the JMX invocation performed what expected on the actual objects.
assertSame(handler, context.getErrorHandler());
}
@Test
public void testContextHandlerOperations() throws Exception
{
ContextHandler context = new ContextHandler("/ctx");
Handler.Abstract handler = new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback)
{
callback.succeeded();
return true;
}
};
context.setHandler(handler);
start(context);
Set<ObjectName> objectNames = mbeanServer.queryNames(ObjectName.getInstance(context.getClass().getPackageName() + ":*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName contextHandlerObjectName = objectNames.iterator().next();
// Stop and restart.
mbeanServer.invoke(contextHandlerObjectName, "stop", null, null);
mbeanServer.invoke(contextHandlerObjectName, "start", null, null);
// Assert that the tree structure remained as before.
assertSame(handler, context.getHandler());
assertTrue(handler.isStarted());
// The Handler MBean should have been unregistered and registered again.
objectNames = mbeanServer.queryNames(ObjectName.getInstance(getClass().getPackageName() + ":*"), null);
assertNotNull(objectNames);
assertEquals(1, objectNames.size());
ObjectName handlerObjectName = objectNames.iterator().next();
String dump = (String)mbeanServer.invoke(handlerObjectName, "dump", null, null);
assertNotNull(dump);
}
}

View File

@ -0,0 +1,2 @@
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.jmx.LEVEL=DEBUG

View File

@ -13,6 +13,7 @@
<modules>
<module>jetty-test-client-transports</module>
<module>jetty-test-jmx</module>
</modules>
<properties>

View File

@ -32,15 +32,12 @@ public class ContextHandlerMBean extends AbstractHandlerMBean
}
@ManagedAttribute("Map of context attributes")
public Map<String, Object> getContextAttributes()
public Map<String, String> getContextAttributes()
{
Map<String, Object> map = new HashMap<String, Object>();
Map<String, String> map = new HashMap<>();
Attributes attrs = ((ContextHandler)_managed).getAttributes();
for (String name : attrs.getAttributeNameSet())
{
Object value = attrs.getAttribute(name);
map.put(name, value);
}
map.put(name, String.valueOf(attrs.getAttribute(name)));
return map;
}