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:
parent
2940528033
commit
14152c425b
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.jmx.LEVEL=DEBUG
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<modules>
|
||||
<module>jetty-test-client-transports</module>
|
||||
<module>jetty-test-jmx</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue