Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
8213a95a7b
|
@ -9,3 +9,4 @@
|
|||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.alpn.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.jmx.LEVEL=DEBUG
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
|
@ -117,6 +118,17 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kerby</groupId>
|
||||
<artifactId>kerb-simplekdc</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
|
|
|
@ -49,7 +49,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
private final int maxContentLength;
|
||||
private final ResponseNotifier notifier;
|
||||
|
||||
private static final Pattern CHALLENGE_PATTERN = Pattern.compile("(?<schemeOnly>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)|(?:(?<scheme>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s+)?(?:(?<token68>[a-zA-Z0-9\\-._~+\\/]+=*)|(?<paramName>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s*=\\s*(?:(?<paramValue>.*)))");
|
||||
private static final Pattern CHALLENGE_PATTERN = Pattern.compile("(?<schemeOnly>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)|(?:(?<scheme>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s+)?(?:(?<token68>[a-zA-Z0-9\\-._~+/]+=*)|(?<paramName>[!#$%&'*+\\-.^_`|~0-9A-Za-z]+)\\s*=\\s*(?:(?<paramValue>.*)))");
|
||||
|
||||
protected AuthenticationProtocolHandler(HttpClient client, int maxContentLength)
|
||||
{
|
||||
|
@ -122,7 +122,6 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
return headerInfos;
|
||||
}
|
||||
|
||||
|
||||
private class AuthenticationListener extends BufferingResponseListener
|
||||
{
|
||||
private AuthenticationListener()
|
||||
|
@ -225,13 +224,12 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
copyIfAbsent(request, newRequest, HttpHeader.AUTHORIZATION);
|
||||
copyIfAbsent(request, newRequest, HttpHeader.PROXY_AUTHORIZATION);
|
||||
|
||||
newRequest.onResponseSuccess(r -> client.getAuthenticationStore().addAuthenticationResult(authnResult));
|
||||
|
||||
AfterAuthenticationListener listener = new AfterAuthenticationListener(authnResult);
|
||||
Connection connection = (Connection)request.getAttributes().get(Connection.class.getName());
|
||||
if (connection != null)
|
||||
connection.send(newRequest, null);
|
||||
connection.send(newRequest, listener);
|
||||
else
|
||||
newRequest.send(null);
|
||||
newRequest.send(listener);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -298,4 +296,20 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private class AfterAuthenticationListener extends Response.Listener.Adapter
|
||||
{
|
||||
private final Authentication.Result authenticationResult;
|
||||
|
||||
private AfterAuthenticationListener(Authentication.Result authenticationResult)
|
||||
{
|
||||
this.authenticationResult = authenticationResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
client.getAuthenticationStore().addAuthenticationResult(authenticationResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
/**
|
||||
* <p>Implementation of the SPNEGO (or "Negotiate") authentication defined in RFC 4559.</p>
|
||||
* <p>A {@link #getUserName() user} is logged in via JAAS (either via userName/password or
|
||||
* via userName/keyTab) once only.</p>
|
||||
* <p>For every request that needs authentication, a {@link GSSContext} is initiated and
|
||||
* later established after reading the response from the server.</p>
|
||||
* <p>Applications should create objects of this class and add them to the
|
||||
* {@link AuthenticationStore} retrieved from the {@link HttpClient}
|
||||
* via {@link HttpClient#getAuthenticationStore()}.</p>
|
||||
*/
|
||||
public class SPNEGOAuthentication extends AbstractAuthentication
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SPNEGOAuthentication.class);
|
||||
private static final String NEGOTIATE = HttpHeader.NEGOTIATE.asString();
|
||||
|
||||
private final GSSManager gssManager = GSSManager.getInstance();
|
||||
private String userName;
|
||||
private String userPassword;
|
||||
private Path userKeyTabPath;
|
||||
private String serviceName;
|
||||
private boolean useTicketCache;
|
||||
private Path ticketCachePath;
|
||||
private boolean renewTGT;
|
||||
|
||||
public SPNEGOAuthentication(URI uri)
|
||||
{
|
||||
super(uri, ANY_REALM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return NEGOTIATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the user name of the user to login
|
||||
*/
|
||||
public String getUserName()
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userName user name of the user to login
|
||||
*/
|
||||
public void setUserName(String userName)
|
||||
{
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the password of the user to login
|
||||
*/
|
||||
public String getUserPassword()
|
||||
{
|
||||
return userPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userPassword the password of the user to login
|
||||
* @see #setUserKeyTabPath(Path)
|
||||
*/
|
||||
public void setUserPassword(String userPassword)
|
||||
{
|
||||
this.userPassword = userPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the keyTab file with the user credentials
|
||||
*/
|
||||
public Path getUserKeyTabPath()
|
||||
{
|
||||
return userKeyTabPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userKeyTabPath the path of the keyTab file with the user credentials
|
||||
* @see #setUserPassword(String)
|
||||
*/
|
||||
public void setUserKeyTabPath(Path userKeyTabPath)
|
||||
{
|
||||
this.userKeyTabPath = userKeyTabPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the service to use
|
||||
*/
|
||||
public String getServiceName()
|
||||
{
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serviceName the name of the service to use
|
||||
*/
|
||||
public void setServiceName(String serviceName)
|
||||
{
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether to use the ticket cache during login
|
||||
*/
|
||||
public boolean isUseTicketCache()
|
||||
{
|
||||
return useTicketCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param useTicketCache whether to use the ticket cache during login
|
||||
* @see #setTicketCachePath(Path)
|
||||
*/
|
||||
public void setUseTicketCache(boolean useTicketCache)
|
||||
{
|
||||
this.useTicketCache = useTicketCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the ticket cache file
|
||||
*/
|
||||
public Path getTicketCachePath()
|
||||
{
|
||||
return ticketCachePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ticketCachePath the path of the ticket cache file
|
||||
* @see #setUseTicketCache(boolean)
|
||||
*/
|
||||
public void setTicketCachePath(Path ticketCachePath)
|
||||
{
|
||||
this.ticketCachePath = ticketCachePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether to renew the ticket granting ticket
|
||||
*/
|
||||
public boolean isRenewTGT()
|
||||
{
|
||||
return renewTGT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renewTGT whether to renew the ticket granting ticket
|
||||
*/
|
||||
public void setRenewTGT(boolean renewTGT)
|
||||
{
|
||||
this.renewTGT = renewTGT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result authenticate(Request request, ContentResponse response, HeaderInfo headerInfo, Attributes context)
|
||||
{
|
||||
SPNEGOContext spnegoContext = (SPNEGOContext)context.getAttribute(SPNEGOContext.ATTRIBUTE);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Authenticate with context {}", spnegoContext);
|
||||
if (spnegoContext == null)
|
||||
{
|
||||
spnegoContext = login();
|
||||
context.setAttribute(SPNEGOContext.ATTRIBUTE, spnegoContext);
|
||||
}
|
||||
|
||||
String b64Input = headerInfo.getBase64();
|
||||
byte[] input = b64Input == null ? new byte[0] : Base64.getDecoder().decode(b64Input);
|
||||
byte[] output = Subject.doAs(spnegoContext.subject, initGSSContext(spnegoContext, request.getHost(), input));
|
||||
String b64Output = output == null ? null : new String(Base64.getEncoder().encode(output));
|
||||
|
||||
// The result cannot be used for subsequent requests,
|
||||
// so it always has a null URI to avoid being cached.
|
||||
return new SPNEGOResult(null, b64Output);
|
||||
}
|
||||
|
||||
private SPNEGOContext login()
|
||||
{
|
||||
try
|
||||
{
|
||||
// First login via JAAS using the Kerberos AS_REQ call, with a client user.
|
||||
// This will populate the Subject with the client user principal and the TGT.
|
||||
String user = getUserName();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Logging in user {}", user);
|
||||
CallbackHandler callbackHandler = new PasswordCallbackHandler();
|
||||
LoginContext loginContext = new LoginContext("", null, callbackHandler, new SPNEGOConfiguration());
|
||||
loginContext.login();
|
||||
Subject subject = loginContext.getSubject();
|
||||
|
||||
SPNEGOContext spnegoContext = new SPNEGOContext();
|
||||
spnegoContext.subject = subject;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Initialized {}", spnegoContext);
|
||||
return spnegoContext;
|
||||
}
|
||||
catch (LoginException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private PrivilegedAction<byte[]> initGSSContext(SPNEGOContext spnegoContext, String host, byte[] bytes)
|
||||
{
|
||||
return () ->
|
||||
{
|
||||
try
|
||||
{
|
||||
// The call to initSecContext with the service name will
|
||||
// trigger the Kerberos TGS_REQ call, asking for the SGT,
|
||||
// which will be added to the Subject credentials because
|
||||
// initSecContext() is called from within Subject.doAs().
|
||||
GSSContext gssContext = spnegoContext.gssContext;
|
||||
if (gssContext == null)
|
||||
{
|
||||
String principal = getServiceName() + "@" + host;
|
||||
GSSName serviceName = gssManager.createName(principal, GSSName.NT_HOSTBASED_SERVICE);
|
||||
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
|
||||
gssContext = gssManager.createContext(serviceName, spnegoOid, null, GSSContext.INDEFINITE_LIFETIME);
|
||||
spnegoContext.gssContext = gssContext;
|
||||
gssContext.requestMutualAuth(true);
|
||||
}
|
||||
byte[] result = gssContext.initSecContext(bytes, 0, bytes.length);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} {}", gssContext.isEstablished() ? "Initialized" : "Initializing", gssContext);
|
||||
return result;
|
||||
}
|
||||
catch (GSSException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class SPNEGOResult implements Result
|
||||
{
|
||||
private final URI uri;
|
||||
private final HttpHeader header;
|
||||
private final String value;
|
||||
|
||||
public SPNEGOResult(URI uri, String token)
|
||||
{
|
||||
this(uri, HttpHeader.AUTHORIZATION, token);
|
||||
}
|
||||
|
||||
public SPNEGOResult(URI uri, HttpHeader header, String token)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.header = header;
|
||||
this.value = NEGOTIATE + (token == null ? "" : " " + token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI()
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Request request)
|
||||
{
|
||||
request.header(header, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SPNEGOContext
|
||||
{
|
||||
private static final String ATTRIBUTE = SPNEGOContext.class.getName();
|
||||
|
||||
private Subject subject;
|
||||
private GSSContext gssContext;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[context=%s]", getClass().getSimpleName(), hashCode(), gssContext);
|
||||
}
|
||||
}
|
||||
|
||||
private class PasswordCallbackHandler implements CallbackHandler
|
||||
{
|
||||
@Override
|
||||
public void handle(Callback[] callbacks) throws IOException
|
||||
{
|
||||
PasswordCallback callback = Arrays.stream(callbacks)
|
||||
.filter(PasswordCallback.class::isInstance)
|
||||
.map(PasswordCallback.class::cast)
|
||||
.findAny()
|
||||
.filter(c -> c.getPrompt().contains(getUserName()))
|
||||
.orElseThrow(IOException::new);
|
||||
callback.setPassword(getUserPassword().toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class SPNEGOConfiguration extends Configuration
|
||||
{
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
|
||||
{
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
if (LOG.isDebugEnabled())
|
||||
options.put("debug", "true");
|
||||
options.put("refreshKrb5Config", "true");
|
||||
options.put("principal", getUserName());
|
||||
options.put("isInitiator", "true");
|
||||
Path keyTabPath = getUserKeyTabPath();
|
||||
if (keyTabPath != null)
|
||||
{
|
||||
options.put("doNotPrompt", "true");
|
||||
options.put("useKeyTab", "true");
|
||||
options.put("keyTab", keyTabPath.toAbsolutePath().toString());
|
||||
options.put("storeKey", "true");
|
||||
}
|
||||
boolean useTicketCache = isUseTicketCache();
|
||||
if (useTicketCache)
|
||||
{
|
||||
options.put("useTicketCache", "true");
|
||||
Path ticketCachePath = getTicketCachePath();
|
||||
if (ticketCachePath != null)
|
||||
options.put("ticketCache", ticketCachePath.toAbsolutePath().toString());
|
||||
options.put("renewTGT", String.valueOf(isRenewTGT()));
|
||||
}
|
||||
|
||||
String moduleClass = "com.sun.security.auth.module.Krb5LoginModule";
|
||||
AppConfigurationEntry config = new AppConfigurationEntry(moduleClass, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
||||
return new AppConfigurationEntry[]{config};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
|
||||
{
|
||||
|
@ -39,6 +38,5 @@ public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
|
|||
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
Log.getRootLogger().info("EMPTY service {}",target);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client.ssl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
@ -54,9 +49,13 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
/* This whole test is very specific to how TLS < 1.3 works.
|
||||
* Starting in Java 11, TLS/1.3 is now enabled by default.
|
||||
*/
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
// This whole test is very specific to how TLS < 1.3 works.
|
||||
// Starting in Java 11, TLS/1.3 is now enabled by default.
|
||||
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
|
||||
public class SslBytesClientTest extends SslBytesTest
|
||||
{
|
||||
|
|
|
@ -18,12 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.client.ssl;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||
import static org.junit.jupiter.api.condition.OS.WINDOWS;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
|
@ -72,20 +66,31 @@ import org.eclipse.jetty.server.ServerConnector;
|
|||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.JavaVersion;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.condition.OS.LINUX;
|
||||
import static org.junit.jupiter.api.condition.OS.WINDOWS;
|
||||
|
||||
// This whole test is very specific to how TLS < 1.3 works.
|
||||
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
|
||||
public class SslBytesServerTest extends SslBytesTest
|
||||
{
|
||||
private final AtomicInteger sslFills = new AtomicInteger();
|
||||
|
@ -101,8 +106,6 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
private SimpleProxy proxy;
|
||||
private Runnable idleHook;
|
||||
|
||||
// This whole test is very specific to how TLS < 1.3 works.
|
||||
@DisabledOnJre( JRE.JAVA_11 )
|
||||
@BeforeEach
|
||||
public void init() throws Exception
|
||||
{
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
|
||||
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
||||
import org.eclipse.jetty.client.EmptyServerHandler;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.security.ConfigurableSpnegoLoginService;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.security.HashLoginService;
|
||||
import org.eclipse.jetty.security.authentication.AuthorizationService;
|
||||
import org.eclipse.jetty.security.authentication.ConfigurableSpnegoAuthenticator;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
// Apparently only JDK 11 is able to run these tests.
|
||||
// See for example: https://bugs.openjdk.java.net/browse/JDK-8202439
|
||||
// where apparently the compiler gets the AES CPU instructions wrong.
|
||||
@DisabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10})
|
||||
public class SPNEGOAuthenticationTest extends AbstractHttpClientServerTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SPNEGOAuthenticationTest.class);
|
||||
|
||||
static
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
|
||||
System.setProperty("sun.security.jgss.debug", "true");
|
||||
System.setProperty("sun.security.krb5.debug", "true");
|
||||
System.setProperty("sun.security.spnego.debug", "true");
|
||||
}
|
||||
}
|
||||
|
||||
private Path testDirPath = MavenTestingUtils.getTargetTestingPath(SPNEGOAuthenticationTest.class.getSimpleName());
|
||||
private String clientName = "spnego_client";
|
||||
private String clientPassword = "spnego_client_pwd";
|
||||
private String serviceName = "srvc";
|
||||
private String serviceHost = "localhost";
|
||||
private String realm = "jetty.org";
|
||||
private Path realmPropsPath = MavenTestingUtils.getTestResourcePath("realm.properties");
|
||||
private Path serviceKeyTabPath = testDirPath.resolve("service.keytab");
|
||||
private Path clientKeyTabPath = testDirPath.resolve("client.keytab");
|
||||
private SimpleKdcServer kdc;
|
||||
private ConfigurableSpnegoAuthenticator authenticator;
|
||||
|
||||
@BeforeEach
|
||||
public void prepare() throws Exception
|
||||
{
|
||||
IO.delete(testDirPath.toFile());
|
||||
Files.createDirectories(testDirPath);
|
||||
System.setProperty("java.security.krb5.conf", testDirPath.toAbsolutePath().toString());
|
||||
|
||||
kdc = new SimpleKdcServer();
|
||||
kdc.setAllowUdp(false);
|
||||
kdc.setAllowTcp(true);
|
||||
kdc.setKdcRealm(realm);
|
||||
kdc.setWorkDir(testDirPath.toFile());
|
||||
kdc.init();
|
||||
|
||||
kdc.createAndExportPrincipals(serviceKeyTabPath.toFile(), serviceName + "/" + serviceHost);
|
||||
kdc.createPrincipal(clientName + "@" + realm, clientPassword);
|
||||
kdc.exportPrincipal(clientName, clientKeyTabPath.toFile());
|
||||
kdc.start();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("KDC started on port {}", kdc.getKdcTcpPort());
|
||||
String krb5 = Files.readAllLines(testDirPath.resolve("krb5.conf")).stream()
|
||||
.filter(line -> !line.startsWith("#"))
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
LOG.debug("krb5.conf{}{}", System.lineSeparator(), krb5);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (kdc != null)
|
||||
kdc.stop();
|
||||
}
|
||||
|
||||
private void startSPNEGO(Scenario scenario, Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
server.setSessionIdManager(new DefaultSessionIdManager(server));
|
||||
HashLoginService authorizationService = new HashLoginService(realm, realmPropsPath.toString());
|
||||
ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService(realm, AuthorizationService.from(authorizationService, ""));
|
||||
loginService.addBean(authorizationService);
|
||||
loginService.setKeyTabPath(serviceKeyTabPath);
|
||||
loginService.setServiceName(serviceName);
|
||||
loginService.setHostName(serviceHost);
|
||||
server.addBean(loginService);
|
||||
|
||||
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
|
||||
Constraint constraint = new Constraint();
|
||||
constraint.setAuthenticate(true);
|
||||
constraint.setRoles(new String[]{"**"}); //allow any authenticated user
|
||||
ConstraintMapping mapping = new ConstraintMapping();
|
||||
mapping.setPathSpec("/secure");
|
||||
mapping.setConstraint(constraint);
|
||||
securityHandler.addConstraintMapping(mapping);
|
||||
authenticator = new ConfigurableSpnegoAuthenticator();
|
||||
securityHandler.setAuthenticator(authenticator);
|
||||
securityHandler.setLoginService(loginService);
|
||||
securityHandler.setHandler(handler);
|
||||
|
||||
SessionHandler sessionHandler = new SessionHandler();
|
||||
sessionHandler.setHandler(securityHandler);
|
||||
start(scenario, sessionHandler);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testPasswordSPNEGOAuthentication(Scenario scenario) throws Exception
|
||||
{
|
||||
testSPNEGOAuthentication(scenario, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testKeyTabSPNEGOAuthentication(Scenario scenario) throws Exception
|
||||
{
|
||||
testSPNEGOAuthentication(scenario, true);
|
||||
}
|
||||
|
||||
private void testSPNEGOAuthentication(Scenario scenario, boolean useKeyTab) throws Exception
|
||||
{
|
||||
startSPNEGO(scenario, new EmptyServerHandler());
|
||||
authenticator.setAuthenticationDuration(Duration.ZERO);
|
||||
|
||||
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
|
||||
|
||||
// Request without Authentication causes a 401
|
||||
Request request = client.newRequest(uri).path("/secure");
|
||||
ContentResponse response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertNotNull(response);
|
||||
assertEquals(401, response.getStatus());
|
||||
|
||||
// Add authentication.
|
||||
SPNEGOAuthentication authentication = new SPNEGOAuthentication(uri);
|
||||
authentication.setUserName(clientName + "@" + realm);
|
||||
if (useKeyTab)
|
||||
authentication.setUserKeyTabPath(clientKeyTabPath);
|
||||
else
|
||||
authentication.setUserPassword(clientPassword);
|
||||
authentication.setServiceName(serviceName);
|
||||
AuthenticationStore authenticationStore = client.getAuthenticationStore();
|
||||
authenticationStore.addAuthentication(authentication);
|
||||
|
||||
// Request with authentication causes a 401 (no previous successful authentication) + 200
|
||||
request = client.newRequest(uri).path("/secure");
|
||||
response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatus());
|
||||
// Authentication results for SPNEGO cannot be cached.
|
||||
Authentication.Result authnResult = authenticationStore.findAuthenticationResult(uri);
|
||||
assertNull(authnResult);
|
||||
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
client.getRequestListeners().add(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
requests.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
// The server has infinite authentication duration, so
|
||||
// subsequent requests will be preemptively authorized.
|
||||
request = client.newRequest(uri).path("/secure");
|
||||
response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(1, requests.get());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testAuthenticationExpiration(Scenario scenario) throws Exception
|
||||
{
|
||||
startSPNEGO(scenario, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
IO.readBytes(request.getInputStream());
|
||||
}
|
||||
});
|
||||
long timeout = 1000;
|
||||
authenticator.setAuthenticationDuration(Duration.ofMillis(timeout));
|
||||
|
||||
URI uri = URI.create(scenario.getScheme() + "://localhost:" + connector.getLocalPort());
|
||||
|
||||
// Add authentication.
|
||||
SPNEGOAuthentication authentication = new SPNEGOAuthentication(uri);
|
||||
authentication.setUserName(clientName + "@" + realm);
|
||||
authentication.setUserPassword(clientPassword);
|
||||
authentication.setServiceName(serviceName);
|
||||
AuthenticationStore authenticationStore = client.getAuthenticationStore();
|
||||
authenticationStore.addAuthentication(authentication);
|
||||
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
client.getRequestListeners().add(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
requests.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
Request request = client.newRequest(uri).path("/secure");
|
||||
Response response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertEquals(200, response.getStatus());
|
||||
// Expect 401 + 200.
|
||||
assertEquals(2, requests.get());
|
||||
|
||||
requests.set(0);
|
||||
request = client.newRequest(uri).path("/secure");
|
||||
response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertEquals(200, response.getStatus());
|
||||
// Authentication not expired on server, expect 200 only.
|
||||
assertEquals(1, requests.get());
|
||||
|
||||
// Let authentication expire.
|
||||
Thread.sleep(2 * timeout);
|
||||
|
||||
requests.set(0);
|
||||
request = client.newRequest(uri).path("/secure");
|
||||
response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertEquals(200, response.getStatus());
|
||||
// Authentication expired, expect 401 + 200.
|
||||
assertEquals(2, requests.get());
|
||||
|
||||
// Let authentication expire again.
|
||||
Thread.sleep(2 * timeout);
|
||||
|
||||
requests.set(0);
|
||||
ByteArrayInputStream input = new ByteArrayInputStream("hello_world".getBytes(StandardCharsets.UTF_8));
|
||||
request = client.newRequest(uri).method("POST").path("/secure").content(new InputStreamContentProvider(input));
|
||||
response = request.timeout(15, TimeUnit.SECONDS).send();
|
||||
assertEquals(200, response.getStatus());
|
||||
// Authentication expired, but POSTs are allowed.
|
||||
assertEquals(1, requests.get());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=INFO
|
||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# Format is <user>:<password>,<roles>
|
||||
basic:basic
|
||||
digest:digest
|
||||
spnego_client:,admin
|
||||
|
|
|
@ -19,18 +19,27 @@
|
|||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanRegistrationException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.modelmbean.ModelMBean;
|
||||
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.Container;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
|
@ -45,15 +54,190 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
@ManagedObject("The component that registers beans as MBeans")
|
||||
public class MBeanContainer implements Container.InheritedListener, Dumpable, Destroyable
|
||||
{
|
||||
private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName());
|
||||
private final static ConcurrentMap<String, AtomicInteger> __unique = new ConcurrentHashMap<>();
|
||||
private static final Logger LOG = Log.getLogger(MBeanContainer.class.getName());
|
||||
private static final ConcurrentMap<String, AtomicInteger> __unique = new ConcurrentHashMap<>();
|
||||
private static final Container ROOT = new ContainerLifeCycle();
|
||||
|
||||
private final MBeanServer _mbeanServer;
|
||||
private final boolean _useCacheForOtherClassLoaders;
|
||||
private final ConcurrentMap<Class, MetaData> _metaData = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Object, Container> _beans = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Object, ObjectName> _mbeans = new ConcurrentHashMap<>();
|
||||
private String _domain = null;
|
||||
|
||||
/**
|
||||
* Constructs MBeanContainer
|
||||
*
|
||||
* @param server instance of MBeanServer for use by container
|
||||
*/
|
||||
public MBeanContainer(MBeanServer server)
|
||||
{
|
||||
this(server, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs MBeanContainer
|
||||
*
|
||||
* @param server instance of MBeanServer for use by container
|
||||
* @param cacheOtherClassLoaders If true, MBeans from other classloaders (eg WebAppClassLoader) will be cached.
|
||||
* The cache is never flushed, so this should be false if some classloaders do not live forever.
|
||||
*/
|
||||
public MBeanContainer(MBeanServer server, boolean cacheOtherClassLoaders)
|
||||
{
|
||||
_mbeanServer = server;
|
||||
_useCacheForOtherClassLoaders = cacheOtherClassLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve instance of MBeanServer used by container
|
||||
*
|
||||
* @return instance of MBeanServer
|
||||
*/
|
||||
public MBeanServer getMBeanServer()
|
||||
{
|
||||
return _mbeanServer;
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "Whether to use the cache for MBeans loaded by other ClassLoaders", readonly = true)
|
||||
public boolean isUseCacheForOtherClassLoaders()
|
||||
{
|
||||
return _useCacheForOtherClassLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set domain to be used to add MBeans
|
||||
*
|
||||
* @param domain domain name
|
||||
*/
|
||||
public void setDomain(String domain)
|
||||
{
|
||||
_domain = domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve domain name used to add MBeans
|
||||
*
|
||||
* @return domain name
|
||||
*/
|
||||
@ManagedAttribute("The default ObjectName domain")
|
||||
public String getDomain()
|
||||
{
|
||||
return _domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates an ObjectMBean for the given object.</p>
|
||||
* <p>Attempts to create an ObjectMBean for the object by searching the package
|
||||
* and class name space. For example an object of the type:</p>
|
||||
* <pre>
|
||||
* class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
|
||||
* </pre>
|
||||
* <p>then this method would look for the following classes:</p>
|
||||
* <ul>
|
||||
* <li>com.acme.jmx.MyClassMBean</li>
|
||||
* <li>com.acme.util.jmx.BaseClassMBean</li>
|
||||
* <li>org.eclipse.jetty.jmx.ObjectMBean</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param o The object
|
||||
* @return A new instance of an MBean for the object or null.
|
||||
*/
|
||||
public Object mbeanFor(Object o)
|
||||
{
|
||||
return mbeanFor(this, o);
|
||||
}
|
||||
|
||||
static Object mbeanFor(MBeanContainer container, Object o)
|
||||
{
|
||||
if (o == null)
|
||||
return null;
|
||||
Object mbean = findMetaData(container, o.getClass()).newInstance(o);
|
||||
if (mbean instanceof ObjectMBean)
|
||||
((ObjectMBean)mbean).setMBeanContainer(container);
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("MBean for {} is {}", o, mbean);
|
||||
if (mbean instanceof ObjectMBean)
|
||||
{
|
||||
MBeanInfo info = ((ObjectMBean)mbean).getMBeanInfo();
|
||||
for (Object a : info.getAttributes())
|
||||
LOG.debug(" {}", a);
|
||||
for (Object a : info.getOperations())
|
||||
LOG.debug(" {}", a);
|
||||
}
|
||||
}
|
||||
return mbean;
|
||||
}
|
||||
|
||||
static MetaData findMetaData(MBeanContainer container, Class<?> klass)
|
||||
{
|
||||
if (klass == null)
|
||||
return null;
|
||||
MetaData metaData = getMetaData(container, klass);
|
||||
if (metaData != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Found cached {}", metaData);
|
||||
return metaData;
|
||||
}
|
||||
return newMetaData(container, klass);
|
||||
}
|
||||
|
||||
private static MetaData getMetaData(MBeanContainer container, Class<?> klass)
|
||||
{
|
||||
return container == null ? null : container._metaData.get(klass);
|
||||
}
|
||||
|
||||
private static MetaData newMetaData(MBeanContainer container, Class<?> klass)
|
||||
{
|
||||
if (klass == null)
|
||||
return null;
|
||||
if (klass == Object.class)
|
||||
return new MetaData(klass, null, null, Collections.emptyList());
|
||||
|
||||
List<MetaData> interfaces = Arrays.stream(klass.getInterfaces())
|
||||
.map(intf -> findMetaData(container, intf))
|
||||
.collect(Collectors.toList());
|
||||
MetaData metaData = new MetaData(klass, findConstructor(klass), findMetaData(container, klass.getSuperclass()), interfaces);
|
||||
|
||||
if (container != null)
|
||||
{
|
||||
if (container.isUseCacheForOtherClassLoaders() || klass.getClassLoader() == container.getClass().getClassLoader())
|
||||
{
|
||||
MetaData existing = container._metaData.putIfAbsent(klass, metaData);
|
||||
if (existing != null)
|
||||
metaData = existing;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Cached {}", metaData);
|
||||
}
|
||||
}
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
private static Constructor<?> findConstructor(Class<?> klass)
|
||||
{
|
||||
String pName = klass.getPackage().getName();
|
||||
String cName = klass.getName().substring(pName.length() + 1);
|
||||
String mName = pName + ".jmx." + cName + "MBean";
|
||||
try
|
||||
{
|
||||
Class<?> mbeanClass = Loader.loadClass(mName);
|
||||
Constructor<?> constructor = ModelMBean.class.isAssignableFrom(mbeanClass)
|
||||
? mbeanClass.getConstructor()
|
||||
: mbeanClass.getConstructor(Object.class);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Found MBean wrapper: {} for {}", mName, klass.getName());
|
||||
return constructor;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("MBean wrapper not found: {} for {}", mName, klass.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup an object name by instance
|
||||
*
|
||||
|
@ -81,47 +265,6 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs MBeanContainer
|
||||
*
|
||||
* @param server instance of MBeanServer for use by container
|
||||
*/
|
||||
public MBeanContainer(MBeanServer server)
|
||||
{
|
||||
_mbeanServer = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve instance of MBeanServer used by container
|
||||
*
|
||||
* @return instance of MBeanServer
|
||||
*/
|
||||
public MBeanServer getMBeanServer()
|
||||
{
|
||||
return _mbeanServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set domain to be used to add MBeans
|
||||
*
|
||||
* @param domain domain name
|
||||
*/
|
||||
public void setDomain(String domain)
|
||||
{
|
||||
_domain = domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve domain name used to add MBeans
|
||||
*
|
||||
* @return domain name
|
||||
*/
|
||||
public String getDomain()
|
||||
{
|
||||
return _domain;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void beanAdded(Container parent, Object obj)
|
||||
{
|
||||
|
@ -154,14 +297,13 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De
|
|||
try
|
||||
{
|
||||
// Create an MBean for the object.
|
||||
Object mbean = ObjectMBean.mbeanFor(obj);
|
||||
Object mbean = mbeanFor(obj);
|
||||
if (mbean == null)
|
||||
return;
|
||||
|
||||
ObjectName objectName = null;
|
||||
if (mbean instanceof ObjectMBean)
|
||||
{
|
||||
((ObjectMBean)mbean).setMBeanContainer(this);
|
||||
objectName = ((ObjectMBean)mbean).getObjectName();
|
||||
}
|
||||
|
||||
|
@ -256,7 +398,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De
|
|||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
ContainerLifeCycle.dumpObject(out,this);
|
||||
ContainerLifeCycle.dumpObject(out, this);
|
||||
ContainerLifeCycle.dump(out, indent, _mbeans.entrySet());
|
||||
}
|
||||
|
||||
|
@ -269,6 +411,7 @@ public class MBeanContainer implements Container.InheritedListener, Dumpable, De
|
|||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
_metaData.clear();
|
||||
_mbeans.values().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(this::unregister);
|
||||
|
|
|
@ -0,0 +1,566 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.management.Attribute;
|
||||
import javax.management.AttributeNotFoundException;
|
||||
import javax.management.MBeanAttributeInfo;
|
||||
import javax.management.MBeanConstructorInfo;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanNotificationInfo;
|
||||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.ReflectionException;
|
||||
import javax.management.modelmbean.ModelMBean;
|
||||
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.annotation.ManagedOperation;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
class MetaData
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(MetaData.class);
|
||||
private static final MBeanAttributeInfo[] NO_ATTRIBUTES = new MBeanAttributeInfo[0];
|
||||
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 final Map<String, AttributeInfo> _attributes = new HashMap<>();
|
||||
private final Map<String, OperationInfo> _operations = new HashMap<>();
|
||||
private final Class<?> _klass;
|
||||
private final MetaData _parent;
|
||||
private final List<MetaData> _interfaces;
|
||||
private final Constructor<?> _constructor;
|
||||
private final MBeanInfo _info;
|
||||
|
||||
MetaData(Class<?> klass, Constructor<?> constructor, MetaData parent, List<MetaData> interfaces)
|
||||
{
|
||||
_klass = klass;
|
||||
_parent = parent;
|
||||
_interfaces = interfaces;
|
||||
_constructor = constructor;
|
||||
if (_constructor != null)
|
||||
parseMethods(klass, _constructor.getDeclaringClass());
|
||||
else
|
||||
parseMethods(klass);
|
||||
_info = buildMBeanInfo(klass);
|
||||
}
|
||||
|
||||
Object newInstance(Object bean)
|
||||
{
|
||||
Object mbean;
|
||||
if (_constructor != null)
|
||||
mbean = newInstance(_constructor, bean);
|
||||
else if (_parent != null)
|
||||
mbean = _parent.newInstance(bean);
|
||||
else
|
||||
mbean = new ObjectMBean(bean);
|
||||
return mbean;
|
||||
}
|
||||
|
||||
MBeanInfo getMBeanInfo()
|
||||
{
|
||||
return _info;
|
||||
}
|
||||
|
||||
Object getAttribute(String name, ObjectMBean mbean) throws AttributeNotFoundException, ReflectionException, MBeanException
|
||||
{
|
||||
AttributeInfo info = findAttribute(name);
|
||||
if (info == null)
|
||||
throw new AttributeNotFoundException(name);
|
||||
return info.getAttribute(mbean);
|
||||
}
|
||||
|
||||
void setAttribute(Attribute attribute, ObjectMBean mbean) throws AttributeNotFoundException, ReflectionException, MBeanException
|
||||
{
|
||||
if (attribute == null)
|
||||
return;
|
||||
String name = attribute.getName();
|
||||
AttributeInfo info = findAttribute(name);
|
||||
if (info == null)
|
||||
throw new AttributeNotFoundException(name);
|
||||
info.setAttribute(attribute.getValue(), mbean);
|
||||
}
|
||||
|
||||
private AttributeInfo findAttribute(String name)
|
||||
{
|
||||
if (name == null)
|
||||
return null;
|
||||
|
||||
AttributeInfo result = null;
|
||||
for (MetaData intf : _interfaces)
|
||||
{
|
||||
AttributeInfo r = intf.findAttribute(name);
|
||||
if (r != null)
|
||||
result = r;
|
||||
}
|
||||
|
||||
if (_parent != null)
|
||||
{
|
||||
AttributeInfo r = _parent.findAttribute(name);
|
||||
if (r != null)
|
||||
result = r;
|
||||
}
|
||||
|
||||
AttributeInfo r = _attributes.get(name);
|
||||
if (r != null)
|
||||
result = r;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Object invoke(String name, String[] params, Object[] args, ObjectMBean mbean) throws ReflectionException, MBeanException
|
||||
{
|
||||
String signature = signature(name, params);
|
||||
OperationInfo info = findOperation(signature);
|
||||
if (info == null)
|
||||
throw new ReflectionException(new NoSuchMethodException(signature));
|
||||
return info.invoke(args, mbean);
|
||||
}
|
||||
|
||||
private OperationInfo findOperation(String signature)
|
||||
{
|
||||
OperationInfo result = null;
|
||||
for (MetaData intf : _interfaces)
|
||||
{
|
||||
OperationInfo r = intf.findOperation(signature);
|
||||
if (r != null)
|
||||
result = r;
|
||||
}
|
||||
|
||||
if (_parent != null)
|
||||
{
|
||||
OperationInfo r = _parent.findOperation(signature);
|
||||
if (r != null)
|
||||
result = r;
|
||||
}
|
||||
|
||||
OperationInfo r = _operations.get(signature);
|
||||
if (r != null)
|
||||
result = r;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object newInstance(Constructor<?> constructor, Object bean)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object mbean = constructor.getParameterCount() == 0 ? constructor.newInstance() : constructor.newInstance(bean);
|
||||
if (mbean instanceof ModelMBean)
|
||||
((ModelMBean)mbean).setManagedResource(bean, "objectReference");
|
||||
return mbean;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseMethods(Class<?>... classes)
|
||||
{
|
||||
for (Class<?> klass : classes)
|
||||
{
|
||||
// Only work on the public method of the class, not of the hierarchy.
|
||||
for (Method method : klass.getDeclaredMethods())
|
||||
{
|
||||
if (!Modifier.isPublic(method.getModifiers()))
|
||||
continue;
|
||||
ManagedAttribute attribute = method.getAnnotation(ManagedAttribute.class);
|
||||
if (attribute != null)
|
||||
{
|
||||
AttributeInfo info = new AttributeInfo(attribute, method);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Found attribute {} for {}: {}", info._name, klass.getName(), info);
|
||||
_attributes.put(info._name, info);
|
||||
}
|
||||
ManagedOperation operation = method.getAnnotation(ManagedOperation.class);
|
||||
if (operation != null)
|
||||
{
|
||||
OperationInfo info = new OperationInfo(operation, method);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Found operation {} for {}: {}", info._name, klass.getName(), info);
|
||||
_operations.put(info._name, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String toAttributeName(String methodName)
|
||||
{
|
||||
String attributeName = methodName;
|
||||
if (methodName.startsWith("get") || methodName.startsWith("set"))
|
||||
attributeName = attributeName.substring(3);
|
||||
else if (methodName.startsWith("is"))
|
||||
attributeName = attributeName.substring(2);
|
||||
return attributeName.substring(0, 1).toLowerCase(Locale.ENGLISH) + attributeName.substring(1);
|
||||
}
|
||||
|
||||
private static boolean isManagedObject(Class<?> klass)
|
||||
{
|
||||
if (klass.isArray())
|
||||
klass = klass.getComponentType();
|
||||
if (klass.isPrimitive())
|
||||
return false;
|
||||
while (klass != null)
|
||||
{
|
||||
if (klass.isAnnotationPresent(ManagedObject.class))
|
||||
return true;
|
||||
klass = klass.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String signature(String name, String[] params)
|
||||
{
|
||||
return String.format("%s(%s)", name, String.join(",", params));
|
||||
}
|
||||
|
||||
private static String signature(Method method)
|
||||
{
|
||||
String signature = Arrays.stream(method.getParameterTypes())
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(","));
|
||||
return String.format("%s(%s)", method.getName(), signature);
|
||||
}
|
||||
|
||||
private MBeanInfo buildMBeanInfo(Class<?> klass)
|
||||
{
|
||||
ManagedObject managedObject = klass.getAnnotation(ManagedObject.class);
|
||||
String description = managedObject == null ? "" : managedObject.value();
|
||||
|
||||
Map<String, MBeanAttributeInfo> attributeInfos = new HashMap<>();
|
||||
collectMBeanAttributeInfos(attributeInfos);
|
||||
|
||||
Map<String, MBeanOperationInfo> operationInfos = new HashMap<>();
|
||||
collectMBeanOperationInfos(operationInfos);
|
||||
|
||||
MBeanInfo mbeanInfo = _parent == null ? null : _parent.getMBeanInfo();
|
||||
MBeanAttributeInfo[] attributes = attributeInfos.values().toArray(NO_ATTRIBUTES);
|
||||
MBeanConstructorInfo[] constructors = mbeanInfo == null ? NO_CONSTRUCTORS : mbeanInfo.getConstructors();
|
||||
MBeanOperationInfo[] operations = operationInfos.values().toArray(NO_OPERATIONS);
|
||||
MBeanNotificationInfo[] notifications = mbeanInfo == null ? NO_NOTIFICATIONS : mbeanInfo.getNotifications();
|
||||
return new MBeanInfo(klass.getName(), description, attributes, constructors, operations, notifications);
|
||||
}
|
||||
|
||||
private void collectMBeanAttributeInfos(Map<String, MBeanAttributeInfo> attributeInfos)
|
||||
{
|
||||
// Start with interfaces, overwrite with superClass, then overwrite with local attributes.
|
||||
for (MetaData intf : _interfaces)
|
||||
intf.collectMBeanAttributeInfos(attributeInfos);
|
||||
if (_parent != null)
|
||||
{
|
||||
MBeanAttributeInfo[] parentAttributes = _parent.getMBeanInfo().getAttributes();
|
||||
for (MBeanAttributeInfo parentAttribute : parentAttributes)
|
||||
attributeInfos.put(parentAttribute.getName(), parentAttribute);
|
||||
}
|
||||
for (Map.Entry<String, AttributeInfo> entry : _attributes.entrySet())
|
||||
attributeInfos.put(entry.getKey(), entry.getValue()._info);
|
||||
}
|
||||
|
||||
private void collectMBeanOperationInfos(Map<String, MBeanOperationInfo> operationInfos)
|
||||
{
|
||||
// Start with interfaces, overwrite with superClass, then overwrite with local operations.
|
||||
for (MetaData intf : _interfaces)
|
||||
intf.collectMBeanOperationInfos(operationInfos);
|
||||
if (_parent != null)
|
||||
{
|
||||
MBeanOperationInfo[] parentOperations = _parent.getMBeanInfo().getOperations();
|
||||
for (MBeanOperationInfo parentOperation : parentOperations)
|
||||
{
|
||||
String signature = signature(parentOperation.getName(), Arrays.stream(parentOperation.getSignature()).map(MBeanParameterInfo::getType).toArray(String[]::new));
|
||||
operationInfos.put(signature, parentOperation);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, OperationInfo> entry : _operations.entrySet())
|
||||
operationInfos.put(entry.getKey(), entry.getValue()._info);
|
||||
}
|
||||
|
||||
private static MBeanException toMBeanException(InvocationTargetException x)
|
||||
{
|
||||
Throwable cause = x.getCause();
|
||||
if (cause instanceof Exception)
|
||||
return new MBeanException((Exception)cause);
|
||||
else
|
||||
return new MBeanException(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s, attrs=%s, opers=%s]", getClass().getSimpleName(), hashCode(),
|
||||
_klass.getName(), _attributes.keySet(), _operations.keySet());
|
||||
}
|
||||
|
||||
private static class AttributeInfo
|
||||
{
|
||||
private final String _name;
|
||||
private final Method _getter;
|
||||
private final Method _setter;
|
||||
private final boolean _proxied;
|
||||
private final boolean _convert;
|
||||
private final MBeanAttributeInfo _info;
|
||||
|
||||
private AttributeInfo(ManagedAttribute attribute, Method getter)
|
||||
{
|
||||
String name = attribute.name();
|
||||
if ("".equals(name))
|
||||
name = toAttributeName(getter.getName());
|
||||
_name = name;
|
||||
|
||||
_getter = getter;
|
||||
|
||||
boolean readOnly = attribute.readonly();
|
||||
_setter = readOnly ? null : findSetter(attribute, getter, name);
|
||||
|
||||
_proxied = attribute.proxied();
|
||||
|
||||
Class<?> returnType = getter.getReturnType();
|
||||
_convert = isManagedObject(returnType);
|
||||
String signature = _convert ?
|
||||
returnType.isArray() ? ObjectName[].class.getName() : ObjectName.class.getName() :
|
||||
returnType.getName();
|
||||
|
||||
String description = attribute.value();
|
||||
_info = new MBeanAttributeInfo(name, signature, description, true,
|
||||
_setter != null, getter.getName().startsWith("is"));
|
||||
}
|
||||
|
||||
Object getAttribute(ObjectMBean mbean) throws ReflectionException, MBeanException
|
||||
{
|
||||
try
|
||||
{
|
||||
Object target = mbean.getManagedObject();
|
||||
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;
|
||||
}
|
||||
catch (InvocationTargetException x)
|
||||
{
|
||||
throw toMBeanException(x);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
throw new ReflectionException(x);
|
||||
}
|
||||
}
|
||||
|
||||
void setAttribute(Object value, ObjectMBean mbean) throws ReflectionException, MBeanException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("setAttribute {}.{}={} {}", mbean, _info.getName(), value, _info);
|
||||
try
|
||||
{
|
||||
if (_setter == null)
|
||||
return;
|
||||
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())
|
||||
{
|
||||
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)
|
||||
Array.set(result, i, mbean.findBean(names[i]));
|
||||
_setter.invoke(target, result);
|
||||
}
|
||||
catch (InvocationTargetException x)
|
||||
{
|
||||
throw toMBeanException(x);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
throw new ReflectionException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private Method findSetter(ManagedAttribute attribute, Method getter, String name)
|
||||
{
|
||||
String setterName = attribute.setter();
|
||||
if ("".equals(setterName))
|
||||
setterName = "set" + name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
|
||||
|
||||
Method setter = null;
|
||||
Class<?> klass = getter.getDeclaringClass();
|
||||
for (Method method : klass.getMethods())
|
||||
{
|
||||
if (method.getName().equals(setterName) && method.getParameterCount() == 1)
|
||||
{
|
||||
if (setter != null)
|
||||
{
|
||||
LOG.info("Multiple setters for mbean attribute {} in {}", name, klass);
|
||||
continue;
|
||||
}
|
||||
if (!getter.getReturnType().equals(method.getParameterTypes()[0]))
|
||||
{
|
||||
LOG.info("Getter/setter type mismatch for mbean attribute {} in {}", name, klass);
|
||||
continue;
|
||||
}
|
||||
setter = method;
|
||||
}
|
||||
}
|
||||
|
||||
return setter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s,proxied=%b,convert=%b,info=%s]", getClass().getSimpleName(), hashCode(),
|
||||
_name, _proxied, _convert, _info);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OperationInfo
|
||||
{
|
||||
private final String _name;
|
||||
private final Method _method;
|
||||
private final boolean _proxied;
|
||||
private final boolean _convert;
|
||||
private final MBeanOperationInfo _info;
|
||||
|
||||
private OperationInfo(ManagedOperation operation, Method method)
|
||||
{
|
||||
_name = signature(method);
|
||||
|
||||
_method = method;
|
||||
|
||||
_proxied = operation.proxied();
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
_convert = isManagedObject(returnType);
|
||||
String returnSignature = _convert ?
|
||||
returnType.isArray() ? ObjectName[].class.getName() : ObjectName.class.getName() :
|
||||
returnType.getName();
|
||||
|
||||
String impactName = operation.impact();
|
||||
int impact = MBeanOperationInfo.UNKNOWN;
|
||||
if ("ACTION".equals(impactName))
|
||||
impact = MBeanOperationInfo.ACTION;
|
||||
else if ("INFO".equals(impactName))
|
||||
impact = MBeanOperationInfo.INFO;
|
||||
else if ("ACTION_INFO".equals(impactName))
|
||||
impact = MBeanOperationInfo.ACTION_INFO;
|
||||
|
||||
String description = operation.value();
|
||||
MBeanParameterInfo[] parameterInfos = parameters(method.getParameterTypes(), method.getParameterAnnotations());
|
||||
_info = new MBeanOperationInfo(method.getName(), description, parameterInfos, returnSignature, impact);
|
||||
}
|
||||
|
||||
public Object invoke(Object[] args, ObjectMBean mbean) throws ReflectionException, MBeanException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("invoke {}.{}({}) {}", mbean, _info.getName(), Arrays.asList(args), _info);
|
||||
try
|
||||
{
|
||||
Object target = mbean.getManagedObject();
|
||||
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;
|
||||
}
|
||||
catch (InvocationTargetException x)
|
||||
{
|
||||
throw toMBeanException(x);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
throw new ReflectionException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private static MBeanParameterInfo[] parameters(Class<?>[] parameterTypes, Annotation[][] parametersAnnotations)
|
||||
{
|
||||
MBeanParameterInfo[] result = new MBeanParameterInfo[parameterTypes.length];
|
||||
for (int i = 0; i < parametersAnnotations.length; i++)
|
||||
{
|
||||
MBeanParameterInfo info = null;
|
||||
String typeName = parameterTypes[i].getName();
|
||||
Annotation[] parameterAnnotations = parametersAnnotations[i];
|
||||
for (Annotation parameterAnnotation : parameterAnnotations)
|
||||
{
|
||||
if (parameterAnnotation instanceof Name)
|
||||
{
|
||||
Name name = (Name)parameterAnnotation;
|
||||
info = result[i] = new MBeanParameterInfo(name.value(), typeName, name.description());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (info == null)
|
||||
result[i] = new MBeanParameterInfo("p" + i, typeName, "");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s,proxied=%b,convert=%b]", getClass().getSimpleName(), hashCode(),
|
||||
_name, _proxied, _convert);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,42 +18,15 @@
|
|||
|
||||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
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 javax.management.Attribute;
|
||||
import javax.management.AttributeList;
|
||||
import javax.management.AttributeNotFoundException;
|
||||
import javax.management.DynamicMBean;
|
||||
import javax.management.MBeanAttributeInfo;
|
||||
import javax.management.MBeanConstructorInfo;
|
||||
import javax.management.MBeanException;
|
||||
import javax.management.MBeanInfo;
|
||||
import javax.management.MBeanNotificationInfo;
|
||||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.ReflectionException;
|
||||
import javax.management.modelmbean.ModelMBean;
|
||||
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.annotation.ManagedOperation;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
@ -72,119 +45,11 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class ObjectMBean implements DynamicMBean
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ObjectMBean.class);
|
||||
private static final Class<?>[] OBJ_ARG = new Class[]{Object.class};
|
||||
private static final String OBJECT_NAME_CLASS = ObjectName.class.getName();
|
||||
private static final String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName();
|
||||
|
||||
protected Object _managed;
|
||||
private MBeanInfo _info;
|
||||
private Map<String, Method> _getters = new HashMap<>();
|
||||
private Map<String, Method> _setters = new HashMap<>();
|
||||
private Map<String, Method> _methods = new HashMap<>();
|
||||
// set of attributes mined from influence hierarchy
|
||||
private Set<String> _attributes = new HashSet<>();
|
||||
// set of attributes that are automatically converted to ObjectName
|
||||
// as they represent other managed beans which can be linked to
|
||||
private Set<String> _convert = new HashSet<>();
|
||||
private ClassLoader _loader;
|
||||
protected final Object _managed;
|
||||
private MetaData _metaData;
|
||||
private MBeanContainer _mbeanContainer;
|
||||
|
||||
/**
|
||||
* <p>Creates an ObjectMBean for the given object.</p>
|
||||
* <p>Attempts to create an ObjectMBean for the object by searching the package
|
||||
* and class name space. For example an object of the type:</p>
|
||||
* <pre>
|
||||
* class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
|
||||
* </pre>
|
||||
* <p>then this method would look for the following classes:</p>
|
||||
* <ul>
|
||||
* <li>com.acme.jmx.MyClassMBean</li>
|
||||
* <li>com.acme.util.jmx.BaseClassMBean</li>
|
||||
* <li>org.eclipse.jetty.jmx.ObjectMBean</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param o The object
|
||||
* @return A new instance of an MBean for the object or null.
|
||||
*/
|
||||
public static Object mbeanFor(Object o)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> oClass = o.getClass();
|
||||
while (oClass != null)
|
||||
{
|
||||
String pName = oClass.getPackage().getName();
|
||||
String cName = oClass.getName().substring(pName.length() + 1);
|
||||
String mName = pName + ".jmx." + cName + "MBean";
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> mClass;
|
||||
try
|
||||
{
|
||||
// Look for an MBean class from the same loader that loaded the original class
|
||||
mClass = (Object.class.equals(oClass)) ? oClass = ObjectMBean.class : Loader.loadClass(oClass, mName);
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
// Not found, so if not the same as the thread context loader, try that.
|
||||
if (Thread.currentThread().getContextClassLoader() == oClass.getClassLoader())
|
||||
throw e;
|
||||
LOG.ignore(e);
|
||||
mClass = Loader.loadClass(oClass, mName);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ObjectMBean: mbeanFor {} mClass={}", o, mClass);
|
||||
|
||||
Object mbean = null;
|
||||
try
|
||||
{
|
||||
Constructor<?> constructor = mClass.getConstructor(OBJ_ARG);
|
||||
mbean = constructor.newInstance(o);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
if (ModelMBean.class.isAssignableFrom(mClass))
|
||||
{
|
||||
mbean = mClass.getDeclaredConstructor().newInstance();
|
||||
((ModelMBean)mbean).setManagedResource(o, "objectReference");
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("mbeanFor {} is {}", o, mbean);
|
||||
|
||||
return mbean;
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
// The code below was modified to fix bugs 332200 and JETTY-1416
|
||||
// The issue was caused by additional information added to the
|
||||
// message after the class name when running in Apache Felix,
|
||||
// as well as before the class name when running in JBoss.
|
||||
if (e.getMessage().contains(mName))
|
||||
LOG.ignore(e);
|
||||
else
|
||||
LOG.warn(e);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
}
|
||||
|
||||
oClass = oClass.getSuperclass();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.ignore(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ObjectMBean wrapping the given {@code managedObject}.
|
||||
*
|
||||
|
@ -193,7 +58,6 @@ public class ObjectMBean implements DynamicMBean
|
|||
public ObjectMBean(Object managedObject)
|
||||
{
|
||||
_managed = managedObject;
|
||||
_loader = Thread.currentThread().getContextClassLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,177 +121,34 @@ public class ObjectMBean implements DynamicMBean
|
|||
return this._mbeanContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MBeanInfo getMBeanInfo()
|
||||
/**
|
||||
* @param o the object to wrap as MBean
|
||||
* @return a new instance of an MBean for the object or null if the MBean cannot be created
|
||||
* @deprecated Use {@link MBeanContainer#mbeanFor(Object)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Object mbeanFor(Object o)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_info == null)
|
||||
{
|
||||
String desc = null;
|
||||
List<MBeanAttributeInfo> attributes = new ArrayList<>();
|
||||
List<MBeanOperationInfo> operations = new ArrayList<>();
|
||||
|
||||
// Find list of classes that can influence the mbean
|
||||
Class<?> o_class = _managed.getClass();
|
||||
List<Class<?>> influences = new ArrayList<>();
|
||||
influences.add(this.getClass()); // always add MBean itself
|
||||
influences = findInfluences(influences, _managed.getClass());
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Influence Count: {}", influences.size());
|
||||
|
||||
// Process Type Annotations
|
||||
ManagedObject primary = o_class.getAnnotation(ManagedObject.class);
|
||||
|
||||
if (primary != null)
|
||||
{
|
||||
desc = primary.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("No @ManagedObject declared on {}", _managed.getClass());
|
||||
}
|
||||
|
||||
// For each influence
|
||||
for (Class<?> oClass : influences)
|
||||
{
|
||||
ManagedObject typeAnnotation = oClass.getAnnotation(ManagedObject.class);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Influenced by: " + oClass.getCanonicalName());
|
||||
|
||||
if (typeAnnotation == null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Annotations not found for: {}", oClass.getCanonicalName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process Method Annotations
|
||||
|
||||
for (Method method : oClass.getDeclaredMethods())
|
||||
{
|
||||
ManagedAttribute methodAttributeAnnotation = method.getAnnotation(ManagedAttribute.class);
|
||||
|
||||
if (methodAttributeAnnotation != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Attribute Annotation found for: {}", method.getName());
|
||||
MBeanAttributeInfo mai = defineAttribute(method, methodAttributeAnnotation);
|
||||
if (mai != null)
|
||||
attributes.add(mai);
|
||||
}
|
||||
|
||||
ManagedOperation methodOperationAnnotation = method.getAnnotation(ManagedOperation.class);
|
||||
|
||||
if (methodOperationAnnotation != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Method Annotation found for: {}", method.getName());
|
||||
MBeanOperationInfo oi = defineOperation(method, methodOperationAnnotation);
|
||||
if (oi != null)
|
||||
operations.add(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_info = new MBeanInfo(o_class.getName(),
|
||||
desc,
|
||||
attributes.toArray(new MBeanAttributeInfo[attributes.size()]),
|
||||
new MBeanConstructorInfo[0],
|
||||
operations.toArray(new MBeanOperationInfo[operations.size()]),
|
||||
new MBeanNotificationInfo[0]);
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
throw e;
|
||||
}
|
||||
return _info;
|
||||
return MBeanContainer.mbeanFor(null, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) throws AttributeNotFoundException, ReflectionException
|
||||
public MBeanInfo getMBeanInfo()
|
||||
{
|
||||
Method getter = _getters.get(name);
|
||||
if (getter == null)
|
||||
throw new AttributeNotFoundException(name);
|
||||
return metaData().getMBeanInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) throws AttributeNotFoundException, ReflectionException, MBeanException
|
||||
{
|
||||
ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
Object o = _managed;
|
||||
if (getter.getDeclaringClass().isInstance(this))
|
||||
o = this; // mbean method
|
||||
|
||||
// get the attribute
|
||||
Object r = getter.invoke(o, (java.lang.Object[])null);
|
||||
|
||||
// convert to ObjectName if the type has the @ManagedObject annotation
|
||||
if (r != null)
|
||||
{
|
||||
if (r.getClass().isArray())
|
||||
{
|
||||
if (r.getClass().getComponentType().isAnnotationPresent(ManagedObject.class))
|
||||
{
|
||||
ObjectName[] on = new ObjectName[Array.getLength(r)];
|
||||
for (int i = 0; i < on.length; i++)
|
||||
on[i] = _mbeanContainer.findMBean(Array.get(r, i));
|
||||
r = on;
|
||||
}
|
||||
}
|
||||
else if (r instanceof Collection<?>)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> c = (Collection<Object>)r;
|
||||
if (!c.isEmpty() && c.iterator().next().getClass().isAnnotationPresent(ManagedObject.class))
|
||||
{
|
||||
// check the first thing out
|
||||
ObjectName[] on = new ObjectName[c.size()];
|
||||
int i = 0;
|
||||
for (Object obj : c)
|
||||
{
|
||||
on[i++] = _mbeanContainer.findMBean(obj);
|
||||
}
|
||||
r = on;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Class<?> clazz = r.getClass();
|
||||
while (clazz != null)
|
||||
{
|
||||
if (clazz.isAnnotationPresent(ManagedObject.class))
|
||||
{
|
||||
ObjectName mbean = _mbeanContainer.findMBean(r);
|
||||
|
||||
if (mbean != null)
|
||||
{
|
||||
return mbean;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
return metaData().getAttribute(name, this);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
finally
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new AttributeNotFoundException(e.toString());
|
||||
}
|
||||
catch (InvocationTargetException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new ReflectionException(new Exception(e.getCause()));
|
||||
Thread.currentThread().setContextClassLoader(prevLoader);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,408 +162,77 @@ public class ObjectMBean implements DynamicMBean
|
|||
{
|
||||
results.add(new Attribute(name, getAttribute(name)));
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(Attribute attr) throws AttributeNotFoundException, ReflectionException
|
||||
public void setAttribute(Attribute attribute) throws AttributeNotFoundException, ReflectionException, MBeanException
|
||||
{
|
||||
if (attr == null)
|
||||
return;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("setAttribute " + _managed + ":" + attr.getName() + "=" + attr.getValue());
|
||||
Method setter = _setters.get(attr.getName());
|
||||
if (setter == null)
|
||||
throw new AttributeNotFoundException(attr.getName());
|
||||
|
||||
ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
Object o = _managed;
|
||||
if (setter.getDeclaringClass().isInstance(this))
|
||||
o = this;
|
||||
|
||||
// get the value
|
||||
Object value = attr.getValue();
|
||||
|
||||
// convert from ObjectName if need be
|
||||
if (value != null && _convert.contains(attr.getName()))
|
||||
{
|
||||
if (value.getClass().isArray())
|
||||
{
|
||||
Class<?> t = setter.getParameterTypes()[0].getComponentType();
|
||||
Object na = Array.newInstance(t, Array.getLength(value));
|
||||
for (int i = Array.getLength(value); i-- > 0; )
|
||||
Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i)));
|
||||
value = na;
|
||||
}
|
||||
else
|
||||
value = _mbeanContainer.findBean((ObjectName)value);
|
||||
}
|
||||
|
||||
// do the setting
|
||||
setter.invoke(o, value);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new AttributeNotFoundException(e.toString());
|
||||
}
|
||||
catch (InvocationTargetException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new ReflectionException(new Exception(e.getCause()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeList setAttributes(AttributeList attrs)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("setAttributes");
|
||||
|
||||
AttributeList results = new AttributeList(attrs.size());
|
||||
for (Object element : attrs)
|
||||
{
|
||||
try
|
||||
{
|
||||
Attribute attr = (Attribute)element;
|
||||
setAttribute(attr);
|
||||
results.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("ObjectMBean:invoke " + name);
|
||||
|
||||
StringBuilder builder = new StringBuilder(name);
|
||||
builder.append("(");
|
||||
if (signature != null)
|
||||
for (int i = 0; i < signature.length; i++)
|
||||
builder.append(i > 0 ? "," : "").append(signature[i]);
|
||||
builder.append(")");
|
||||
String methodKey = builder.toString();
|
||||
|
||||
ClassLoader old_loader = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(_loader);
|
||||
Method method = _methods.get(methodKey);
|
||||
if (method == null)
|
||||
throw new NoSuchMethodException(methodKey);
|
||||
|
||||
Object o = _managed;
|
||||
|
||||
if (method.getDeclaringClass().isInstance(this))
|
||||
o = this;
|
||||
|
||||
return method.invoke(o, params);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new ReflectionException(e);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new MBeanException(e);
|
||||
}
|
||||
catch (InvocationTargetException e)
|
||||
{
|
||||
LOG.warn(Log.EXCEPTION, e);
|
||||
throw new ReflectionException(new Exception(e.getCause()));
|
||||
metaData().setAttribute(attribute, this);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setContextClassLoader(old_loader);
|
||||
Thread.currentThread().setContextClassLoader(prevLoader);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Class<?>> findInfluences(List<Class<?>> influences, Class<?> aClass)
|
||||
@Override
|
||||
public AttributeList setAttributes(AttributeList attributes)
|
||||
{
|
||||
if (aClass != null)
|
||||
AttributeList results = new AttributeList(attributes.size());
|
||||
for (Attribute attribute : attributes.asList())
|
||||
{
|
||||
if (!influences.contains(aClass))
|
||||
try
|
||||
{
|
||||
// This class is a new influence
|
||||
influences.add(aClass);
|
||||
setAttribute(attribute);
|
||||
results.add(new Attribute(attribute.getName(), getAttribute(attribute.getName())));
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
}
|
||||
|
||||
// So are the super classes
|
||||
influences = findInfluences(influences, aClass.getSuperclass());
|
||||
|
||||
// So are the interfaces
|
||||
Class<?>[] ifs = aClass.getInterfaces();
|
||||
for (int i = 0; ifs != null && i < ifs.length; i++)
|
||||
influences = findInfluences(influences, ifs[i]);
|
||||
}
|
||||
return influences;
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Defines an attribute for the managed object using the annotation attributes.</p>
|
||||
*
|
||||
* @param method the method on the managed objec
|
||||
* @param attributeAnnotation the annotation with the attribute metadata
|
||||
* @return an MBeanAttributeInfo with the attribute metadata
|
||||
*/
|
||||
private MBeanAttributeInfo defineAttribute(Method method, ManagedAttribute attributeAnnotation)
|
||||
@Override
|
||||
public Object invoke(String name, Object[] params, String[] signature) throws ReflectionException, MBeanException
|
||||
{
|
||||
// determine the name of the managed attribute
|
||||
String name = attributeAnnotation.name();
|
||||
|
||||
if ("".equals(name))
|
||||
name = toVariableName(method.getName());
|
||||
|
||||
if (_attributes.contains(name))
|
||||
return null; // we have an attribute named this already
|
||||
|
||||
String description = attributeAnnotation.value();
|
||||
boolean readonly = attributeAnnotation.readonly();
|
||||
boolean onMBean = attributeAnnotation.proxied();
|
||||
|
||||
// determine if we should convert
|
||||
Class<?> return_type = method.getReturnType();
|
||||
|
||||
// get the component type
|
||||
Class<?> component_type = return_type;
|
||||
while (component_type.isArray())
|
||||
component_type = component_type.getComponentType();
|
||||
|
||||
// Test to see if the returnType or any of its super classes are managed objects
|
||||
boolean convert = isAnnotationPresent(component_type, ManagedObject.class);
|
||||
|
||||
String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
|
||||
Class<?> oClass = onMBean ? this.getClass() : _managed.getClass();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("defineAttribute {} {}:{}:{}:{}", name, onMBean, readonly, oClass, description);
|
||||
|
||||
Method setter = null;
|
||||
|
||||
// dig out a setter if one exists
|
||||
if (!readonly)
|
||||
{
|
||||
String declaredSetter = attributeAnnotation.setter();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("DeclaredSetter: {}", declaredSetter);
|
||||
|
||||
for (Method method1 : oClass.getMethods())
|
||||
{
|
||||
if (!Modifier.isPublic(method1.getModifiers()))
|
||||
continue;
|
||||
|
||||
if (!"".equals(declaredSetter))
|
||||
{
|
||||
// look for a declared setter
|
||||
if (method1.getName().equals(declaredSetter) && method1.getParameterCount() == 1)
|
||||
{
|
||||
if (setter != null)
|
||||
{
|
||||
LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
|
||||
continue;
|
||||
}
|
||||
setter = method1;
|
||||
if (!component_type.equals(method1.getParameterTypes()[0]))
|
||||
{
|
||||
LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
|
||||
continue;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Declared Setter: " + declaredSetter);
|
||||
}
|
||||
}
|
||||
|
||||
// look for a setter
|
||||
if (method1.getName().equals("set" + uName) && method1.getParameterCount() == 1)
|
||||
{
|
||||
if (setter != null)
|
||||
{
|
||||
LOG.warn("Multiple setters for mbean attr {} in {}", name, oClass);
|
||||
continue;
|
||||
}
|
||||
setter = method1;
|
||||
if (!return_type.equals(method1.getParameterTypes()[0]))
|
||||
LOG.warn("Type conflict for mbean attr {} in {}", name, oClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (convert)
|
||||
{
|
||||
if (component_type.isPrimitive() && !component_type.isArray())
|
||||
{
|
||||
LOG.warn("Cannot convert mbean primitive {}", name);
|
||||
return null;
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("passed convert checks {} for type {}", name, component_type);
|
||||
}
|
||||
|
||||
ClassLoader prevLoader = Thread.currentThread().getContextClassLoader();
|
||||
try
|
||||
{
|
||||
// Remember the methods
|
||||
_getters.put(name, method);
|
||||
_setters.put(name, setter);
|
||||
|
||||
MBeanAttributeInfo info;
|
||||
if (convert)
|
||||
{
|
||||
_convert.add(name);
|
||||
info = new MBeanAttributeInfo(name,
|
||||
component_type.isArray() ? OBJECT_NAME_ARRAY_CLASS : OBJECT_NAME_CLASS,
|
||||
description,
|
||||
true,
|
||||
setter != null,
|
||||
method.getName().startsWith("is"));
|
||||
}
|
||||
else
|
||||
{
|
||||
info = new MBeanAttributeInfo(name, description, method, setter);
|
||||
}
|
||||
|
||||
_attributes.add(name);
|
||||
|
||||
return info;
|
||||
return metaData().invoke(name, signature, params, this);
|
||||
}
|
||||
catch (Exception e)
|
||||
finally
|
||||
{
|
||||
LOG.warn(e);
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
Thread.currentThread().setContextClassLoader(prevLoader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Defines an operation for the managed object using the annotation attributes.</p>
|
||||
*
|
||||
* @param method the method on the managed object
|
||||
* @param methodAnnotation the annotation with the operation metadata
|
||||
* @return an MBeanOperationInfo with the operation metadata
|
||||
*/
|
||||
private MBeanOperationInfo defineOperation(Method method, ManagedOperation methodAnnotation)
|
||||
ObjectName findObjectName(Object bean)
|
||||
{
|
||||
String description = methodAnnotation.value();
|
||||
boolean onMBean = methodAnnotation.proxied();
|
||||
|
||||
// determine if we should convert
|
||||
Class<?> returnType = method.getReturnType();
|
||||
|
||||
if (returnType.isArray())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("returnType is array, get component type");
|
||||
returnType = returnType.getComponentType();
|
||||
}
|
||||
|
||||
boolean convert = false;
|
||||
if (returnType.isAnnotationPresent(ManagedObject.class))
|
||||
convert = true;
|
||||
|
||||
String impactName = methodAnnotation.impact();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("defineOperation {} {}:{}:{}", method.getName(), onMBean, impactName, description);
|
||||
|
||||
try
|
||||
{
|
||||
// Resolve the impact
|
||||
int impact = MBeanOperationInfo.UNKNOWN;
|
||||
if ("UNKNOWN".equals(impactName))
|
||||
impact = MBeanOperationInfo.UNKNOWN;
|
||||
else if ("ACTION".equals(impactName))
|
||||
impact = MBeanOperationInfo.ACTION;
|
||||
else if ("INFO".equals(impactName))
|
||||
impact = MBeanOperationInfo.INFO;
|
||||
else if ("ACTION_INFO".equals(impactName))
|
||||
impact = MBeanOperationInfo.ACTION_INFO;
|
||||
else
|
||||
LOG.warn("Unknown impact '" + impactName + "' for " + method);
|
||||
|
||||
Annotation[][] allParameterAnnotations = method.getParameterAnnotations();
|
||||
Class<?>[] methodTypes = method.getParameterTypes();
|
||||
MBeanParameterInfo[] pInfo = new MBeanParameterInfo[allParameterAnnotations.length];
|
||||
|
||||
for (int i = 0; i < allParameterAnnotations.length; ++i)
|
||||
{
|
||||
Annotation[] parameterAnnotations = allParameterAnnotations[i];
|
||||
for (Annotation anno : parameterAnnotations)
|
||||
{
|
||||
if (anno instanceof Name)
|
||||
{
|
||||
Name nameAnnotation = (Name)anno;
|
||||
pInfo[i] = new MBeanParameterInfo(nameAnnotation.value(), methodTypes[i].getName(), nameAnnotation.description());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(method.getName());
|
||||
builder.append("(");
|
||||
for (int i = 0; i < methodTypes.length; ++i)
|
||||
{
|
||||
builder.append(methodTypes[i].getName());
|
||||
if (i != methodTypes.length - 1)
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append(")");
|
||||
String signature = builder.toString();
|
||||
|
||||
Class<?> returnClass = method.getReturnType();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Method Cache: " + signature);
|
||||
|
||||
if (_methods.containsKey(signature))
|
||||
return null; // we have an operation for this already
|
||||
|
||||
_methods.put(signature, method);
|
||||
if (convert)
|
||||
_convert.add(signature);
|
||||
|
||||
return new MBeanOperationInfo(method.getName(), description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("Operation '" + method + "'", e);
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
return _mbeanContainer.findMBean(bean);
|
||||
}
|
||||
|
||||
protected String toVariableName(String methodName)
|
||||
Object findBean(ObjectName objectName)
|
||||
{
|
||||
String variableName = methodName;
|
||||
if (methodName.startsWith("get") || methodName.startsWith("set"))
|
||||
variableName = variableName.substring(3);
|
||||
else if (methodName.startsWith("is"))
|
||||
variableName = variableName.substring(2);
|
||||
return variableName.substring(0, 1).toLowerCase(Locale.ENGLISH) + variableName.substring(1);
|
||||
return _mbeanContainer.findBean(objectName);
|
||||
}
|
||||
|
||||
protected boolean isAnnotationPresent(Class<?> clazz, Class<? extends Annotation> annotation)
|
||||
MetaData metaData()
|
||||
{
|
||||
Class<?> test = clazz;
|
||||
while (test != null)
|
||||
{
|
||||
if (test.isAnnotationPresent(annotation))
|
||||
return true;
|
||||
else
|
||||
test = test.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
if (_metaData == null)
|
||||
_metaData = MBeanContainer.findMetaData(_mbeanContainer, _managed.getClass());
|
||||
return _metaData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.eclipse.jetty.util.annotation.ManagedOperation;
|
|||
@ManagedObject(value = "Test the mbean extended stuff")
|
||||
public class DerivedExtended extends Derived
|
||||
{
|
||||
|
||||
private String doodle4 = "doodle4";
|
||||
|
||||
@ManagedAttribute(value = "The doodle4 name of something", name = "doodle4", setter = "setDoodle4")
|
||||
|
|
|
@ -18,24 +18,23 @@
|
|||
|
||||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.acme.Managed;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import com.acme.Managed;
|
||||
import org.eclipse.jetty.util.component.Container;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class MBeanContainerTest
|
||||
{
|
||||
private MBeanContainer mbeanContainer;
|
||||
|
@ -54,27 +53,21 @@ public class MBeanContainerTest
|
|||
@Test
|
||||
public void testMakeName()
|
||||
{
|
||||
// given
|
||||
beanName = "mngd:bean";
|
||||
|
||||
// when
|
||||
beanName = mbeanContainer.makeName(beanName);
|
||||
|
||||
// then
|
||||
assertEquals("mngd_bean", beanName, "Bean name should be mngd_bean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindBean()
|
||||
{
|
||||
// given
|
||||
managed = getManaged();
|
||||
|
||||
// when
|
||||
objectName = mbeanContainer.findMBean(managed);
|
||||
assertNotNull(objectName);
|
||||
|
||||
// then
|
||||
assertEquals(managed, mbeanContainer.findBean(objectName), "Bean must be added");
|
||||
assertNull(mbeanContainer.findBean(null), "It must return null as there is no bean with the name null");
|
||||
}
|
||||
|
@ -104,40 +97,31 @@ public class MBeanContainerTest
|
|||
@Test
|
||||
public void testDomain()
|
||||
{
|
||||
// given
|
||||
String domain = "Test";
|
||||
|
||||
// when
|
||||
mbeanContainer.setDomain(domain);
|
||||
|
||||
// then
|
||||
assertEquals(domain, mbeanContainer.getDomain(), "Domain name must be Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanAdded() throws Exception
|
||||
public void testBeanAdded()
|
||||
{
|
||||
// given
|
||||
setBeanAdded();
|
||||
|
||||
// when
|
||||
objectName = mbeanContainer.findMBean(managed);
|
||||
|
||||
// then
|
||||
assertTrue(mbeanServer.isRegistered(objectName), "Bean must have been registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanAddedNullCheck() throws Exception
|
||||
public void testBeanAddedNullCheck()
|
||||
{
|
||||
// given
|
||||
setBeanAdded();
|
||||
Integer mbeanCount = mbeanServer.getMBeanCount();
|
||||
|
||||
// when
|
||||
mbeanContainer.beanAdded(null, null);
|
||||
|
||||
// then
|
||||
assertEquals(mbeanCount, mbeanServer.getMBeanCount(), "MBean count must not change after beanAdded(null, null) call");
|
||||
}
|
||||
|
||||
|
@ -150,15 +134,12 @@ public class MBeanContainerTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testBeanRemoved() throws Exception
|
||||
public void testBeanRemoved()
|
||||
{
|
||||
// given
|
||||
setUpBeanRemoved();
|
||||
|
||||
// when
|
||||
mbeanContainer.beanRemoved(null, managed);
|
||||
|
||||
// then
|
||||
assertNull(mbeanContainer.findMBean(managed), "Bean shouldn't be registered with container as we removed the bean");
|
||||
}
|
||||
|
||||
|
@ -200,30 +181,24 @@ public class MBeanContainerTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDestroy() throws Exception
|
||||
public void testDestroy()
|
||||
{
|
||||
// given
|
||||
setUpDestroy();
|
||||
|
||||
// when
|
||||
objectName = mbeanContainer.findMBean(managed);
|
||||
mbeanContainer.destroy();
|
||||
|
||||
// then
|
||||
assertFalse(mbeanContainer.getMBeanServer().isRegistered(objectName), "Unregistered bean - managed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDestroyInstanceNotFoundException() throws Exception
|
||||
{
|
||||
// given
|
||||
setUpDestroy();
|
||||
|
||||
// when
|
||||
objectName = mbeanContainer.findMBean(managed);
|
||||
mbeanContainer.getMBeanServer().unregisterMBean(objectName);
|
||||
|
||||
// then
|
||||
assertFalse(mbeanContainer.getMBeanServer().isRegistered(objectName), "Unregistered bean - managed");
|
||||
// this flow covers InstanceNotFoundException. Actual code just eating
|
||||
// the exception. i.e Actual code just printing the stacktrace, whenever
|
||||
|
|
|
@ -18,12 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.acme.Derived;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
import javax.management.Attribute;
|
||||
|
@ -31,198 +25,127 @@ import javax.management.MBeanInfo;
|
|||
import javax.management.MBeanOperationInfo;
|
||||
import javax.management.MBeanParameterInfo;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import com.acme.Derived;
|
||||
import com.acme.Managed;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ObjectMBeanTest
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ObjectMBeanTest.class);
|
||||
|
||||
private static MBeanContainer container;
|
||||
private MBeanContainer container;
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception
|
||||
public void before()
|
||||
{
|
||||
container = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception
|
||||
public void after()
|
||||
{
|
||||
container.destroy();
|
||||
container = null;
|
||||
}
|
||||
|
||||
/*
|
||||
* this test uses the com.acme.Derived test classes
|
||||
*/
|
||||
@Test
|
||||
public void testMetaDataCaching()
|
||||
{
|
||||
Derived derived = new Derived();
|
||||
ObjectMBean derivedMBean = (ObjectMBean)container.mbeanFor(derived);
|
||||
ObjectMBean derivedMBean2 = (ObjectMBean)container.mbeanFor(derived);
|
||||
assertNotSame(derivedMBean, derivedMBean2);
|
||||
assertSame(derivedMBean.metaData(), derivedMBean2.metaData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivedAttributes() throws Exception
|
||||
{
|
||||
Derived derived = new Derived();
|
||||
ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
|
||||
Managed managed = derived.getManagedInstance();
|
||||
ObjectMBean derivedMBean = (ObjectMBean)container.mbeanFor(derived);
|
||||
ObjectMBean managedMBean = (ObjectMBean)container.mbeanFor(managed);
|
||||
|
||||
ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
|
||||
mbean.setMBeanContainer(container);
|
||||
managed.setMBeanContainer(container);
|
||||
container.beanAdded(null, derived);
|
||||
container.beanAdded(null, managed);
|
||||
|
||||
container.beanAdded(null,derived);
|
||||
container.beanAdded(null,derived.getManagedInstance());
|
||||
MBeanInfo derivedInfo = derivedMBean.getMBeanInfo();
|
||||
assertNotNull(derivedInfo);
|
||||
MBeanInfo managedInfo = managedMBean.getMBeanInfo();
|
||||
assertNotNull(managedInfo);
|
||||
|
||||
MBeanInfo toss = managed.getMBeanInfo();
|
||||
assertEquals("com.acme.Derived", derivedInfo.getClassName(), "name does not match");
|
||||
assertEquals("Test the mbean stuff", derivedInfo.getDescription(), "description does not match");
|
||||
assertEquals(6, derivedInfo.getAttributes().length, "attribute count does not match");
|
||||
assertEquals("Full Name", derivedMBean.getAttribute("fname"), "attribute values does not match");
|
||||
|
||||
assertNotNull(mbean.getMBeanInfo());
|
||||
|
||||
MBeanInfo info = mbean.getMBeanInfo();
|
||||
|
||||
assertEquals("com.acme.Derived", info.getClassName(), "name does not match");
|
||||
assertEquals("Test the mbean stuff", info.getDescription(), "description does not match");
|
||||
|
||||
// for ( MBeanAttributeInfo i : info.getAttributes())
|
||||
// {
|
||||
// LOG.debug(i.toString());
|
||||
// }
|
||||
|
||||
/*
|
||||
* 2 attributes from lifecycle and 2 from Derived and 1 from MBean
|
||||
*/
|
||||
assertEquals(6, info.getAttributes().length, "attribute count does not match");
|
||||
|
||||
assertEquals("Full Name", mbean.getAttribute("fname"), "attribute values does not match");
|
||||
|
||||
mbean.setAttribute(new Attribute("fname","Fuller Name"));
|
||||
|
||||
assertEquals("Fuller Name", mbean.getAttribute("fname"), "set attribute value does not match");
|
||||
|
||||
assertEquals("goop", mbean.getAttribute("goop"), "proxy attribute values do not match");
|
||||
|
||||
// Thread.sleep(100000);
|
||||
derivedMBean.setAttribute(new Attribute("fname", "Fuller Name"));
|
||||
assertEquals("Fuller Name", derivedMBean.getAttribute("fname"), "set attribute value does not match");
|
||||
assertEquals("goop", derivedMBean.getAttribute("goop"), "proxy attribute values do not match");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivedOperations() throws Exception
|
||||
{
|
||||
Derived derived = new Derived();
|
||||
ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
|
||||
ObjectMBean mbean = (ObjectMBean)container.mbeanFor(derived);
|
||||
|
||||
mbean.setMBeanContainer(container);
|
||||
|
||||
container.beanAdded(null,derived);
|
||||
container.beanAdded(null, derived);
|
||||
|
||||
MBeanInfo info = mbean.getMBeanInfo();
|
||||
|
||||
assertEquals(5, info.getOperations().length, "operation count does not match");
|
||||
|
||||
MBeanOperationInfo[] opinfos = info.getOperations();
|
||||
MBeanOperationInfo[] operationInfos = info.getOperations();
|
||||
boolean publish = false;
|
||||
boolean doodle = false;
|
||||
boolean good = false;
|
||||
for (int i = 0; i < opinfos.length; ++i)
|
||||
for (MBeanOperationInfo operationInfo : operationInfos)
|
||||
{
|
||||
MBeanOperationInfo opinfo = opinfos[i];
|
||||
|
||||
if ("publish".equals(opinfo.getName()))
|
||||
if ("publish".equals(operationInfo.getName()))
|
||||
{
|
||||
publish = true;
|
||||
assertEquals("publish something", opinfo.getDescription(), "description doesn't match");
|
||||
assertEquals("publish something", operationInfo.getDescription(), "description doesn't match");
|
||||
}
|
||||
|
||||
if ("doodle".equals(opinfo.getName()))
|
||||
if ("doodle".equals(operationInfo.getName()))
|
||||
{
|
||||
doodle = true;
|
||||
assertEquals("Doodle something", opinfo.getDescription(), "description doesn't match");
|
||||
|
||||
MBeanParameterInfo[] pinfos = opinfo.getSignature();
|
||||
|
||||
assertEquals("A description of the argument", pinfos[0].getDescription(), "parameter description doesn't match");
|
||||
assertEquals("doodle", pinfos[0].getName(), "parameter name doesn't match");
|
||||
assertEquals("Doodle something", operationInfo.getDescription(), "description doesn't match");
|
||||
MBeanParameterInfo[] parameterInfos = operationInfo.getSignature();
|
||||
assertEquals("A description of the argument", parameterInfos[0].getDescription(), "parameter description doesn't match");
|
||||
assertEquals("doodle", parameterInfos[0].getName(), "parameter name doesn't match");
|
||||
}
|
||||
|
||||
// This is a proxied operation on the JMX wrapper
|
||||
if ("good".equals(opinfo.getName()))
|
||||
// This is a proxied operation on the MBean wrapper.
|
||||
if ("good".equals(operationInfo.getName()))
|
||||
{
|
||||
good = true;
|
||||
|
||||
assertEquals("test of proxy operations", opinfo.getDescription(), "description does not match");
|
||||
assertEquals("not bad",mbean.invoke("good",new Object[] {}, new String[] {}), "execution contexts wrong");
|
||||
assertEquals("test of proxy operations", operationInfo.getDescription(), "description does not match");
|
||||
assertEquals("not bad", mbean.invoke("good", new Object[]{}, new String[]{}), "execution contexts wrong");
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(publish, "publish operation was not not found");
|
||||
assertTrue(doodle, "doodle operation was not not found");
|
||||
assertTrue(good, "good operation was not not found");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivedObjectAttributes() throws Exception
|
||||
public void testMethodNameMining()
|
||||
{
|
||||
Derived derived = new Derived();
|
||||
ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
|
||||
|
||||
ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
|
||||
mbean.setMBeanContainer(container);
|
||||
managed.setMBeanContainer(container);
|
||||
|
||||
assertNotNull(mbean.getMBeanInfo());
|
||||
|
||||
container.beanAdded(null,derived);
|
||||
container.beanAdded(null,derived.getManagedInstance());
|
||||
container.beanAdded(null,mbean);
|
||||
container.beanAdded(null,managed);
|
||||
|
||||
// Managed managedInstance = (Managed)mbean.getAttribute("managedInstance");
|
||||
// assertNotNull(managedInstance);
|
||||
// assertEquals("foo", managedInstance.getManaged(), "managed instance returning nonsense");
|
||||
|
||||
assertEquals("fullName", MetaData.toAttributeName("getFullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("getfullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("isFullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("isfullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("setFullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("setfullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("FullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("fullName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("ignore, used in testing jconsole atm")
|
||||
public void testThreadPool() throws Exception
|
||||
{
|
||||
|
||||
Derived derived = new Derived();
|
||||
ObjectMBean mbean = (ObjectMBean)ObjectMBean.mbeanFor(derived);
|
||||
|
||||
ObjectMBean managed = (ObjectMBean)ObjectMBean.mbeanFor(derived.getManagedInstance());
|
||||
mbean.setMBeanContainer(container);
|
||||
managed.setMBeanContainer(container);
|
||||
|
||||
QueuedThreadPool qtp = new QueuedThreadPool();
|
||||
|
||||
ObjectMBean bqtp = (ObjectMBean)ObjectMBean.mbeanFor(qtp);
|
||||
|
||||
bqtp.getMBeanInfo();
|
||||
|
||||
container.beanAdded(null,derived);
|
||||
container.beanAdded(null,derived.getManagedInstance());
|
||||
container.beanAdded(null,mbean);
|
||||
container.beanAdded(null,managed);
|
||||
container.beanAdded(null,qtp);
|
||||
|
||||
Thread.sleep(10000000);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodNameMining() throws Exception
|
||||
{
|
||||
ObjectMBean mbean = new ObjectMBean(new Derived());
|
||||
|
||||
assertEquals("fullName",mbean.toVariableName("getFullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("getfullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("isFullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("isfullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("setFullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("setfullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("FullName"));
|
||||
assertEquals("fullName",mbean.toVariableName("fullName"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.jmx;
|
||||
|
||||
import com.acme.Derived;
|
||||
import com.acme.DerivedExtended;
|
||||
import com.acme.DerivedManaged;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -33,68 +29,33 @@ import javax.management.MBeanException;
|
|||
import javax.management.MBeanInfo;
|
||||
import javax.management.ReflectionException;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import com.acme.Derived;
|
||||
import com.acme.DerivedExtended;
|
||||
import com.acme.DerivedManaged;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class ObjectMBeanUtilTest
|
||||
{
|
||||
|
||||
private ObjectMBean objectMBean;
|
||||
|
||||
private DerivedExtended derivedExtended;
|
||||
|
||||
private MBeanContainer container;
|
||||
|
||||
private MBeanInfo objectMBeanInfo;
|
||||
|
||||
private Object mBean;
|
||||
|
||||
private String value;
|
||||
|
||||
private Attribute attribute;
|
||||
|
||||
private AttributeList attributes;
|
||||
|
||||
private ObjectMBean mBeanDerivedManaged;
|
||||
|
||||
private Derived[] derivedes;
|
||||
|
||||
private ArrayList<Derived> aliasNames;
|
||||
|
||||
private DerivedManaged derivedManaged;
|
||||
|
||||
private static final int EMPTY = 0;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass()
|
||||
{
|
||||
Logger ombLog = Log.getLogger(ObjectMBean.class);
|
||||
if (ombLog instanceof StdErrLog && !ombLog.isDebugEnabled())
|
||||
((StdErrLog)ombLog).setHideStacks(true);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass()
|
||||
{
|
||||
Logger ombLog = Log.getLogger(ObjectMBean.class);
|
||||
if (ombLog instanceof StdErrLog)
|
||||
((StdErrLog)ombLog).setHideStacks(false);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setUp()
|
||||
{
|
||||
derivedExtended = new DerivedExtended();
|
||||
objectMBean = new ObjectMBean(derivedExtended);
|
||||
container = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||
objectMBean.setMBeanContainer(container);
|
||||
derivedExtended = new DerivedExtended();
|
||||
objectMBean = (ObjectMBean)container.mbeanFor(derivedExtended);
|
||||
objectMBeanInfo = objectMBean.getMBeanInfo();
|
||||
}
|
||||
|
||||
|
@ -112,131 +73,93 @@ public class ObjectMBeanUtilTest
|
|||
@Test
|
||||
public void testMbeanForNullCheck()
|
||||
{
|
||||
// when
|
||||
mBean = ObjectMBean.mbeanFor(null);
|
||||
|
||||
// then
|
||||
Object mBean = container.mbeanFor(null);
|
||||
assertNull(mBean, "As we are passing null value the output should be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAttributeReflectionException() throws Exception
|
||||
public void testGetAttributeMBeanException() throws Exception
|
||||
{
|
||||
// given
|
||||
setUpGetAttribute("doodle4","charu");
|
||||
Attribute attribute = new Attribute("doodle4", "charu");
|
||||
objectMBean.setAttribute(attribute);
|
||||
|
||||
// when
|
||||
ReflectionException e = assertThrows(ReflectionException.class, ()-> {
|
||||
objectMBean.getAttribute("doodle4");
|
||||
});
|
||||
MBeanException e = assertThrows(MBeanException.class, () -> objectMBean.getAttribute("doodle4"));
|
||||
|
||||
// then
|
||||
assertNotNull(e, "An InvocationTargetException must have occurred by now as doodle4() internally throwing exception");
|
||||
}
|
||||
|
||||
private void setUpGetAttribute(String property, String value) throws Exception
|
||||
{
|
||||
Attribute attribute = new Attribute(property,value);
|
||||
objectMBean.setAttribute(attribute);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAttributeAttributeNotFoundException() throws Exception
|
||||
public void testGetAttributeAttributeNotFoundException()
|
||||
{
|
||||
// when
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, ()->{
|
||||
objectMBean.getAttribute("ffname");
|
||||
});
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, () -> objectMBean.getAttribute("ffname"));
|
||||
|
||||
// then
|
||||
assertNotNull(e, "An AttributeNotFoundException must have occurred by now as there is no " + "attribute with the name ffname in bean");
|
||||
assertNotNull(e, "An AttributeNotFoundException must have occurred by now as there is no attribute with the name ffname in bean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributeWithCorrectAttrName() throws Exception
|
||||
{
|
||||
// given
|
||||
setUpGetAttribute("fname","charu");
|
||||
Attribute attribute = new Attribute("fname", "charu");
|
||||
objectMBean.setAttribute(attribute);
|
||||
|
||||
// when
|
||||
value = (String)objectMBean.getAttribute("fname");
|
||||
String value = (String)objectMBean.getAttribute("fname");
|
||||
|
||||
// then
|
||||
assertEquals("charu", value, "Attribute(fname) value must be equl to charu");
|
||||
assertEquals("charu", value, "Attribute(fname) value must be equal to charu");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributeNullCheck() throws Exception
|
||||
{
|
||||
// given
|
||||
objectMBean.setAttribute(null);
|
||||
|
||||
// when
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, ()->{
|
||||
objectMBean.getAttribute(null);
|
||||
});
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, () -> objectMBean.getAttribute(null));
|
||||
|
||||
// then
|
||||
assertNotNull(e,"An AttributeNotFoundException must have occurred by now as there is no attribute with the name null");
|
||||
assertNotNull(e, "An AttributeNotFoundException must have occurred by now as there is no attribute with the name null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributeAttributeWithWrongAttrName() throws Exception
|
||||
public void testSetAttributeAttributeWithWrongAttrName()
|
||||
{
|
||||
// given
|
||||
attribute = new Attribute("fnameee","charu");
|
||||
attribute = new Attribute("fnameee", "charu");
|
||||
|
||||
// when
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, ()->{
|
||||
objectMBean.setAttribute(attribute);
|
||||
});
|
||||
AttributeNotFoundException e = assertThrows(AttributeNotFoundException.class, () -> objectMBean.setAttribute(attribute));
|
||||
|
||||
// then
|
||||
assertNotNull(e, "An AttributeNotFoundException must have occurred by now as there is no attribute " + "with the name ffname in bean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributesWithCorrectValues() throws Exception
|
||||
public void testSetAttributesWithCorrectValues()
|
||||
{
|
||||
// given
|
||||
attributes = getAttributes("fname","vijay");
|
||||
attributes = objectMBean.setAttributes(attributes);
|
||||
AttributeList attributes = getAttributes("fname", "vijay");
|
||||
objectMBean.setAttributes(attributes);
|
||||
|
||||
// when
|
||||
attributes = objectMBean.getAttributes(new String[]
|
||||
{ "fname" });
|
||||
attributes = objectMBean.getAttributes(new String[]{"fname"});
|
||||
|
||||
// then
|
||||
assertEquals(1, attributes.size());
|
||||
assertEquals("vijay", ((Attribute)(attributes.get(0))).getValue(), "Fname value must be equal to vijay");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributesForArrayTypeAttribue() throws Exception
|
||||
public void testSetAttributesForArrayTypeAttribute() throws Exception
|
||||
{
|
||||
// given
|
||||
derivedes = getArrayTypeAttribute();
|
||||
Derived[] deriveds = getArrayTypeAttribute();
|
||||
|
||||
// when
|
||||
derivedManaged.setAddresses(derivedes);
|
||||
derivedManaged.setAddresses(deriveds);
|
||||
mBeanDerivedManaged.getMBeanInfo();
|
||||
|
||||
// then
|
||||
assertNotNull(mBeanDerivedManaged.getAttribute("addresses"), "Address object shouldn't be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributesForCollectionTypeAttribue() throws Exception
|
||||
{
|
||||
// given
|
||||
aliasNames = getCollectionTypeAttribute();
|
||||
ArrayList<Derived> aliasNames = new ArrayList<>(Arrays.asList(getArrayTypeAttribute()));
|
||||
|
||||
// when
|
||||
derivedManaged.setAliasNames(aliasNames);
|
||||
mBeanDerivedManaged.getMBeanInfo();
|
||||
|
||||
// then
|
||||
assertNotNull(mBeanDerivedManaged.getAttribute("aliasNames"), "Address object shouldn't be null");
|
||||
assertNull(mBeanDerivedManaged.getAttribute("derived"), "Derived object shouldn't registerd with container so its value will be null");
|
||||
assertNull(mBeanDerivedManaged.getAttribute("derived"), "Derived object shouldn't registered with container so its value will be null");
|
||||
}
|
||||
|
||||
private Derived[] getArrayTypeAttribute()
|
||||
|
@ -246,119 +169,74 @@ public class ObjectMBeanUtilTest
|
|||
MBeanContainer mBeanDerivedManagedContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
|
||||
mBeanDerivedManaged.setMBeanContainer(mBeanDerivedManagedContainer);
|
||||
Derived derived0 = new Derived();
|
||||
mBeanDerivedManagedContainer.beanAdded(null,derived0);
|
||||
Derived[] derivedes = new Derived[3];
|
||||
mBeanDerivedManagedContainer.beanAdded(null, derived0);
|
||||
Derived[] deriveds = new Derived[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
derivedes[i] = new Derived();
|
||||
}
|
||||
derivedManaged.setAddresses(derivedes);
|
||||
deriveds[i] = new Derived();
|
||||
derivedManaged.setAddresses(deriveds);
|
||||
mBeanDerivedManaged.getMBeanInfo();
|
||||
ArrayList<Derived> aliasNames = new ArrayList<Derived>(Arrays.asList(derivedes));
|
||||
ArrayList<Derived> aliasNames = new ArrayList<>(Arrays.asList(deriveds));
|
||||
derivedManaged.setAliasNames(aliasNames);
|
||||
return derivedes;
|
||||
}
|
||||
|
||||
private ArrayList<Derived> getCollectionTypeAttribute()
|
||||
{
|
||||
ArrayList<Derived> aliasNames = new ArrayList<Derived>(Arrays.asList(getArrayTypeAttribute()));
|
||||
return aliasNames;
|
||||
return deriveds;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetAttributesException()
|
||||
{
|
||||
// given
|
||||
attributes = getAttributes("fnameee","charu");
|
||||
AttributeList attributes = getAttributes("fnameee", "charu");
|
||||
|
||||
// when
|
||||
attributes = objectMBean.setAttributes(attributes);
|
||||
|
||||
// then
|
||||
// Original code eating the exception and returning zero size list
|
||||
assertEquals(EMPTY,attributes.size(),"As there is no attribute with the name fnameee, this should return empty");
|
||||
assertEquals(0, attributes.size(), "As there is no attribute with the name fnameee, this should return empty");
|
||||
}
|
||||
|
||||
private AttributeList getAttributes(String name, String value)
|
||||
{
|
||||
Attribute attribute = new Attribute(name,value);
|
||||
Attribute attribute = new Attribute(name, value);
|
||||
AttributeList attributes = new AttributeList();
|
||||
attributes.add(attribute);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeMBeanException() throws Exception
|
||||
public void testInvokeMBeanException()
|
||||
{
|
||||
// given
|
||||
setMBeanInfoForInvoke();
|
||||
ReflectionException e = assertThrows(ReflectionException.class, () -> objectMBean.invoke("doodle2", new Object[0], new String[0]));
|
||||
|
||||
// when
|
||||
MBeanException e = assertThrows(MBeanException.class, ()->{
|
||||
objectMBean.invoke("doodle2",new Object[] {},new String[] {});
|
||||
});
|
||||
|
||||
// then
|
||||
assertNotNull(e, "An MBeanException must have occurred by now as doodle2() in Derived bean throwing exception");
|
||||
assertNotNull(e, "An ReflectionException must have occurred by now as doodle2() in Derived bean is private");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeReflectionException() throws Exception
|
||||
public void testInvokeReflectionException()
|
||||
{
|
||||
// given
|
||||
setMBeanInfoForInvoke();
|
||||
MBeanException e = assertThrows(MBeanException.class, () -> objectMBean.invoke("doodle1", new Object[0], new String[0]));
|
||||
|
||||
// when
|
||||
ReflectionException e = assertThrows(ReflectionException.class, ()->{
|
||||
objectMBean.invoke("doodle1",new Object[] {},new String[] {});
|
||||
});
|
||||
|
||||
// then
|
||||
assertNotNull(e, "ReflectionException is null");
|
||||
assertNotNull(e, "MBeanException is null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvoke() throws Exception
|
||||
{
|
||||
// given
|
||||
setMBeanInfoForInvoke();
|
||||
String value = (String)objectMBean.invoke("good", new Object[0], new String[0]);
|
||||
|
||||
// when
|
||||
value = (String)objectMBean.invoke("good",new Object[] {},new String[] {});
|
||||
|
||||
// then
|
||||
assertEquals("not bad", value, "Method(good) invocation on objectMBean must return not bad");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokeNoSuchMethodException() throws Exception
|
||||
public void testInvokeNoSuchMethodException()
|
||||
{
|
||||
// given
|
||||
setMBeanInfoForInvoke();
|
||||
// DerivedMBean contains a managed method with the name good,
|
||||
// we must call this method without any arguments.
|
||||
ReflectionException e = assertThrows(ReflectionException.class, () ->
|
||||
objectMBean.invoke("good", new Object[0], new String[]{"int aone"}));
|
||||
|
||||
// when
|
||||
// DerivedMBean contains a managed method with the name good,we must
|
||||
// call this method without any arguments
|
||||
ReflectionException e = assertThrows(ReflectionException.class, ()->{
|
||||
objectMBean.invoke("good",new Object[] {},new String[]
|
||||
{ "int aone" });
|
||||
});
|
||||
|
||||
// then
|
||||
assertNotNull(e, "An ReflectionException must have occurred by now as we cannot call a methow with wrong signature");
|
||||
|
||||
}
|
||||
|
||||
private void setMBeanInfoForInvoke()
|
||||
{
|
||||
objectMBean = (ObjectMBean)ObjectMBean.mbeanFor(derivedExtended);
|
||||
container.beanAdded(null,derivedExtended);
|
||||
objectMBean.getMBeanInfo();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToVariableName()
|
||||
public void testToAttributeName()
|
||||
{
|
||||
assertEquals("fullName",objectMBean.toVariableName("isfullName"));
|
||||
assertEquals("fullName", MetaData.toAttributeName("isfullName"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,27 +20,25 @@ package org.eclipse.jetty.jmx;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.eclipse.jetty.util.log.jmx.LogMBean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.openpojo.reflection.impl.PojoClassFactory;
|
||||
import com.openpojo.validation.Validator;
|
||||
import com.openpojo.validation.ValidatorBuilder;
|
||||
import com.openpojo.validation.test.impl.GetterTester;
|
||||
import com.openpojo.validation.test.impl.SetterTester;
|
||||
import org.eclipse.jetty.util.log.jmx.LogMBean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/*
|
||||
* This class tests all the getters and setters for a given list of classes.
|
||||
*/
|
||||
public class PojoTest
|
||||
{
|
||||
|
||||
private Validator validator;
|
||||
|
||||
@Test
|
||||
public void testOpenPojo()
|
||||
{
|
||||
validator = ValidatorBuilder.create().with(new SetterTester()).with(new GetterTester()).build();
|
||||
List<Class> classes = Arrays.asList(MBeanContainer.class,ObjectMBean.class,LogMBean.class);
|
||||
Validator validator = ValidatorBuilder.create().with(new SetterTester()).with(new GetterTester()).build();
|
||||
List<Class> classes = Arrays.asList(MBeanContainer.class, ObjectMBean.class, LogMBean.class);
|
||||
for (Class clazz : classes)
|
||||
{
|
||||
validator.validate(PojoClassFactory.getPojoClass(clazz));
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
org.eclipse.jetty.jmx.LEVEL=INFO
|
||||
#org.eclipse.jetty.jmx.LEVEL=DEBUG
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<bundle-symbolic-name>${project.groupId}.boot.test.osgi</bundle-symbolic-name>
|
||||
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
|
||||
<assembly-directory>target/distribution</assembly-directory>
|
||||
<exam.version>4.11.0</exam.version>
|
||||
<exam.version>4.12.0</exam.version>
|
||||
<url.version>2.5.2</url.version>
|
||||
<injection.bundle.version>1.0</injection.bundle.version>
|
||||
<skipTests>true</skipTests>
|
||||
|
@ -45,15 +45,6 @@
|
|||
<artifactId>pax-exam-junit4</artifactId>
|
||||
<version>${exam.version}</version>
|
||||
<scope>test</scope>
|
||||
<!-- not anymore as others tests use junit 5 -->
|
||||
<!--
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
-->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ops4j.pax.exam</groupId>
|
||||
|
@ -410,13 +401,11 @@
|
|||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
<version>${asm.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<version>${asm.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -443,6 +432,14 @@
|
|||
<mavenRepoPath>${settings.localRepository}</mavenRepoPath>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
<!-- paxexam still using junit 4 so we have to force the provider here -->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.surefire</groupId>
|
||||
<artifactId>surefire-junit47</artifactId>
|
||||
<version>${maven.surefire.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.servicemix.tooling</groupId>
|
||||
|
|
|
@ -18,16 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.proxy;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpFieldsMatchers.containsHeader;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -73,7 +63,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpContentResponse;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
|
@ -96,7 +85,6 @@ import org.eclipse.jetty.servlet.FilterHolder;
|
|||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
|
@ -105,7 +93,15 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpFieldsMatchers.containsHeader;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ProxyServletTest
|
||||
{
|
||||
|
@ -1400,8 +1396,8 @@ public class ProxyServletTest
|
|||
// Wait more than the idle timeout to break the connection.
|
||||
Thread.sleep(2 * idleTimeout);
|
||||
|
||||
assertTrue(serverLatch.await(555, TimeUnit.SECONDS));
|
||||
assertTrue(clientLatch.await(555, TimeUnit.SECONDS));
|
||||
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.file.Path;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import javax.security.auth.login.LoginContext;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.security.authentication.AuthorizationService;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.ietf.jgss.GSSContext;
|
||||
import org.ietf.jgss.GSSCredential;
|
||||
import org.ietf.jgss.GSSException;
|
||||
import org.ietf.jgss.GSSManager;
|
||||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
/**
|
||||
* <p>A configurable (as opposed to using system properties) SPNEGO LoginService.</p>
|
||||
* <p>At startup, this LoginService will login via JAAS the service principal, composed
|
||||
* of the {@link #getServiceName() service name} and the {@link #getHostName() host name},
|
||||
* for example {@code HTTP/wonder.com}, using a {@code keyTab} file as the service principal
|
||||
* credentials.</p>
|
||||
* <p>Upon receiving a HTTP request, the server tries to authenticate the client
|
||||
* calling {@link #login(String, Object, ServletRequest)} where the GSS APIs are used to
|
||||
* verify client tokens and (perhaps after a few round-trips) a {@code GSSContext} is
|
||||
* established.</p>
|
||||
*/
|
||||
public class ConfigurableSpnegoLoginService extends ContainerLifeCycle implements LoginService
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ConfigurableSpnegoLoginService.class);
|
||||
|
||||
private final GSSManager _gssManager = GSSManager.getInstance();
|
||||
private final String _realm;
|
||||
private final AuthorizationService _authorizationService;
|
||||
private IdentityService _identityService = new DefaultIdentityService();
|
||||
private String _serviceName;
|
||||
private Path _keyTabPath;
|
||||
private String _hostName;
|
||||
private SpnegoContext _context;
|
||||
|
||||
public ConfigurableSpnegoLoginService(String realm, AuthorizationService authorizationService)
|
||||
{
|
||||
_realm = realm;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the realm name
|
||||
*/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return _realm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the keyTab file containing service credentials
|
||||
*/
|
||||
public Path getKeyTabPath()
|
||||
{
|
||||
return _keyTabPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyTabFile the path of the keyTab file containing service credentials
|
||||
*/
|
||||
public void setKeyTabPath(Path keyTabFile)
|
||||
{
|
||||
_keyTabPath = keyTabFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the service name, typically "HTTP"
|
||||
* @see #getHostName()
|
||||
*/
|
||||
public String getServiceName()
|
||||
{
|
||||
return _serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serviceName the service name
|
||||
* @see #setHostName(String)
|
||||
*/
|
||||
public void setServiceName(String serviceName)
|
||||
{
|
||||
_serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the host name of the service
|
||||
* @see #setServiceName(String)
|
||||
*/
|
||||
public String getHostName()
|
||||
{
|
||||
return _hostName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hostName the host name of the service
|
||||
*/
|
||||
public void setHostName(String hostName)
|
||||
{
|
||||
_hostName = hostName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (_hostName == null)
|
||||
_hostName = InetAddress.getLocalHost().getCanonicalHostName();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Retrieving credentials for service {}/{}", getServiceName(), getHostName());
|
||||
LoginContext loginContext = new LoginContext("", null, null, new SpnegoConfiguration());
|
||||
loginContext.login();
|
||||
Subject subject = loginContext.getSubject();
|
||||
_context = Subject.doAs(subject, newSpnegoContext(subject));
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
private PrivilegedAction<SpnegoContext> newSpnegoContext(Subject subject)
|
||||
{
|
||||
return () ->
|
||||
{
|
||||
try
|
||||
{
|
||||
GSSName serviceName = _gssManager.createName(getServiceName() + "@" + getHostName(), GSSName.NT_HOSTBASED_SERVICE);
|
||||
Oid kerberosOid = new Oid("1.2.840.113554.1.2.2");
|
||||
Oid spnegoOid = new Oid("1.3.6.1.5.5.2");
|
||||
Oid[] mechanisms = new Oid[]{kerberosOid, spnegoOid};
|
||||
GSSCredential serviceCredential = _gssManager.createCredential(serviceName, GSSCredential.DEFAULT_LIFETIME, mechanisms, GSSCredential.ACCEPT_ONLY);
|
||||
SpnegoContext context = new SpnegoContext();
|
||||
context._subject = subject;
|
||||
context._serviceCredential = serviceCredential;
|
||||
return context;
|
||||
}
|
||||
catch (GSSException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserIdentity login(String username, Object credentials, ServletRequest req)
|
||||
{
|
||||
Subject subject = _context._subject;
|
||||
HttpServletRequest request = (HttpServletRequest)req;
|
||||
HttpSession httpSession = request.getSession(false);
|
||||
GSSContext gssContext = null;
|
||||
if (httpSession != null)
|
||||
{
|
||||
GSSContextHolder holder = (GSSContextHolder)httpSession.getAttribute(GSSContextHolder.ATTRIBUTE);
|
||||
gssContext = holder == null ? null : holder.gssContext;
|
||||
}
|
||||
if (gssContext == null)
|
||||
gssContext = Subject.doAs(subject, newGSSContext());
|
||||
|
||||
byte[] input = Base64.getDecoder().decode((String)credentials);
|
||||
byte[] output = Subject.doAs(_context._subject, acceptGSSContext(gssContext, input));
|
||||
String token = Base64.getEncoder().encodeToString(output);
|
||||
|
||||
String userName = toUserName(gssContext);
|
||||
// Save the token in the principal so it can be sent in the response.
|
||||
SpnegoUserPrincipal principal = new SpnegoUserPrincipal(userName, token);
|
||||
if (gssContext.isEstablished())
|
||||
{
|
||||
if (httpSession != null)
|
||||
httpSession.removeAttribute(GSSContextHolder.ATTRIBUTE);
|
||||
|
||||
UserIdentity roles = _authorizationService.getUserIdentity(request, userName);
|
||||
return new SpnegoUserIdentity(subject, principal, roles);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The GSS context is not established yet, save it into the HTTP session.
|
||||
if (httpSession == null)
|
||||
httpSession = request.getSession(true);
|
||||
GSSContextHolder holder = new GSSContextHolder(gssContext);
|
||||
httpSession.setAttribute(GSSContextHolder.ATTRIBUTE, holder);
|
||||
|
||||
// Return an unestablished UserIdentity.
|
||||
return new SpnegoUserIdentity(subject, principal, null);
|
||||
}
|
||||
}
|
||||
|
||||
private PrivilegedAction<GSSContext> newGSSContext()
|
||||
{
|
||||
return () ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return _gssManager.createContext(_context._serviceCredential);
|
||||
}
|
||||
catch (GSSException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PrivilegedAction<byte[]> acceptGSSContext(GSSContext gssContext, byte[] token)
|
||||
{
|
||||
return () ->
|
||||
{
|
||||
try
|
||||
{
|
||||
return gssContext.acceptSecContext(token, 0, token.length);
|
||||
}
|
||||
catch (GSSException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String toUserName(GSSContext gssContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
String name = gssContext.getSrcName().toString();
|
||||
int at = name.indexOf('@');
|
||||
if (at < 0)
|
||||
return name;
|
||||
return name.substring(0, at);
|
||||
}
|
||||
catch (GSSException x)
|
||||
{
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserIdentity user)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityService getIdentityService()
|
||||
{
|
||||
return _identityService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentityService(IdentityService identityService)
|
||||
{
|
||||
_identityService = identityService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(UserIdentity user)
|
||||
{
|
||||
}
|
||||
|
||||
private class SpnegoConfiguration extends Configuration
|
||||
{
|
||||
@Override
|
||||
public AppConfigurationEntry[] getAppConfigurationEntry(String name)
|
||||
{
|
||||
String principal = getServiceName() + "/" + getHostName();
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
if (LOG.isDebugEnabled())
|
||||
options.put("debug", "true");
|
||||
options.put("doNotPrompt", "true");
|
||||
options.put("refreshKrb5Config", "true");
|
||||
options.put("principal", principal);
|
||||
options.put("useKeyTab", "true");
|
||||
Path keyTabPath = getKeyTabPath();
|
||||
if (keyTabPath != null)
|
||||
options.put("keyTab", keyTabPath.toAbsolutePath().toString());
|
||||
// This option is required to store the service credentials in
|
||||
// the Subject, so that it can be later used by acceptSecContext().
|
||||
options.put("storeKey", "true");
|
||||
options.put("isInitiator", "false");
|
||||
String moduleClass = "com.sun.security.auth.module.Krb5LoginModule";
|
||||
AppConfigurationEntry config = new AppConfigurationEntry(moduleClass, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
|
||||
return new AppConfigurationEntry[]{config};
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpnegoContext
|
||||
{
|
||||
private Subject _subject;
|
||||
private GSSCredential _serviceCredential;
|
||||
}
|
||||
|
||||
private static class GSSContextHolder implements Serializable
|
||||
{
|
||||
public static final String ATTRIBUTE = GSSContextHolder.class.getName();
|
||||
|
||||
private transient final GSSContext gssContext;
|
||||
|
||||
private GSSContextHolder(GSSContext gssContext)
|
||||
{
|
||||
this.gssContext = gssContext;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,10 @@ import org.ietf.jgss.GSSManager;
|
|||
import org.ietf.jgss.GSSName;
|
||||
import org.ietf.jgss.Oid;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ConfigurableSpnegoLoginService} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.eclipse.jetty.security;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
|
@ -27,18 +26,17 @@ import org.eclipse.jetty.server.UserIdentity;
|
|||
|
||||
public class SpnegoUserIdentity implements UserIdentity
|
||||
{
|
||||
private Subject _subject;
|
||||
private Principal _principal;
|
||||
private List<String> _roles;
|
||||
private final Subject _subject;
|
||||
private final Principal _principal;
|
||||
private final UserIdentity _roleDelegate;
|
||||
|
||||
public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
|
||||
public SpnegoUserIdentity(Subject subject, Principal principal, UserIdentity roleDelegate)
|
||||
{
|
||||
_subject = subject;
|
||||
_principal = principal;
|
||||
_roles = roles;
|
||||
_roleDelegate = roleDelegate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Subject getSubject()
|
||||
{
|
||||
|
@ -54,7 +52,11 @@ public class SpnegoUserIdentity implements UserIdentity
|
|||
@Override
|
||||
public boolean isUserInRole(String role, Scope scope)
|
||||
{
|
||||
return _roles.contains(role);
|
||||
return _roleDelegate != null && _roleDelegate.isUserInRole(role, scope);
|
||||
}
|
||||
|
||||
public boolean isEstablished()
|
||||
{
|
||||
return _roleDelegate != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
package org.eclipse.jetty.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import java.util.Base64;
|
||||
|
||||
public class SpnegoUserPrincipal implements Principal
|
||||
{
|
||||
|
@ -28,13 +27,13 @@ public class SpnegoUserPrincipal implements Principal
|
|||
private byte[] _token;
|
||||
private String _encodedToken;
|
||||
|
||||
public SpnegoUserPrincipal( String name, String encodedToken )
|
||||
public SpnegoUserPrincipal(String name, String encodedToken)
|
||||
{
|
||||
_name = name;
|
||||
_encodedToken = encodedToken;
|
||||
}
|
||||
|
||||
public SpnegoUserPrincipal( String name, byte[] token )
|
||||
public SpnegoUserPrincipal(String name, byte[] token)
|
||||
{
|
||||
_name = name;
|
||||
_token = token;
|
||||
|
@ -48,19 +47,15 @@ public class SpnegoUserPrincipal implements Principal
|
|||
|
||||
public byte[] getToken()
|
||||
{
|
||||
if ( _token == null )
|
||||
{
|
||||
_token = B64Code.decode(_encodedToken);
|
||||
}
|
||||
if (_token == null)
|
||||
_token = Base64.getDecoder().decode(_encodedToken);
|
||||
return _token;
|
||||
}
|
||||
|
||||
public String getEncodedToken()
|
||||
{
|
||||
if ( _encodedToken == null )
|
||||
{
|
||||
_encodedToken = new String(B64Code.encode(_token,true));
|
||||
}
|
||||
if (_encodedToken == null)
|
||||
_encodedToken = new String(Base64.getEncoder().encode(_token));
|
||||
return _encodedToken;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.jetty.security.LoginService;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
|
||||
/**
|
||||
* <p>A service to query for user roles.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AuthorizationService
|
||||
{
|
||||
/**
|
||||
* @param request the current HTTP request
|
||||
* @param name the user name
|
||||
* @return a {@link UserIdentity} to query for roles of the given user
|
||||
*/
|
||||
UserIdentity getUserIdentity(HttpServletRequest request, String name);
|
||||
|
||||
/**
|
||||
* <p>Wraps a {@link LoginService} as an AuthorizationService</p>
|
||||
*
|
||||
* @param loginService the {@link LoginService} to wrap
|
||||
* @param credentials
|
||||
* @return an AuthorizationService that delegates the query for roles to the given {@link LoginService}
|
||||
*/
|
||||
public static AuthorizationService from(LoginService loginService, Object credentials)
|
||||
{
|
||||
return (request, name) -> loginService.login(name, credentials, request);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.security.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.security.ServerAuthException;
|
||||
import org.eclipse.jetty.security.SpnegoUserIdentity;
|
||||
import org.eclipse.jetty.security.SpnegoUserPrincipal;
|
||||
import org.eclipse.jetty.security.UserAuthentication;
|
||||
import org.eclipse.jetty.server.Authentication;
|
||||
import org.eclipse.jetty.server.Authentication.User;
|
||||
import org.eclipse.jetty.server.UserIdentity;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
/**
|
||||
* <p>A LoginAuthenticator that uses SPNEGO and the GSS API to authenticate requests.</p>
|
||||
* <p>A successful authentication from a client is cached for a configurable
|
||||
* {@link #getAuthenticationDuration() duration} using the HTTP session; this avoids
|
||||
* that the client is asked to authenticate for every request.</p>
|
||||
*
|
||||
* @see org.eclipse.jetty.security.ConfigurableSpnegoLoginService
|
||||
*/
|
||||
public class ConfigurableSpnegoAuthenticator extends LoginAuthenticator
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ConfigurableSpnegoAuthenticator.class);
|
||||
|
||||
private final String _authMethod;
|
||||
private Duration _authenticationDuration = Duration.ofNanos(-1);
|
||||
|
||||
public ConfigurableSpnegoAuthenticator()
|
||||
{
|
||||
this(Constraint.__SPNEGO_AUTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow for a custom authMethod value to be set for instances where SPNEGO may not be appropriate
|
||||
*
|
||||
* @param authMethod the auth method
|
||||
*/
|
||||
public ConfigurableSpnegoAuthenticator(String authMethod)
|
||||
{
|
||||
_authMethod = authMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthMethod()
|
||||
{
|
||||
return _authMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the authentication duration
|
||||
*/
|
||||
public Duration getAuthenticationDuration()
|
||||
{
|
||||
return _authenticationDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the duration of the authentication.</p>
|
||||
* <p>A negative duration means that the authentication is only valid for the current request.</p>
|
||||
* <p>A zero duration means that the authentication is valid forever.</p>
|
||||
* <p>A positive value means that the authentication is valid for the specified duration.</p>
|
||||
*
|
||||
* @param authenticationDuration the authentication duration
|
||||
*/
|
||||
public void setAuthenticationDuration(Duration authenticationDuration)
|
||||
{
|
||||
_authenticationDuration = authenticationDuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
|
||||
{
|
||||
if (!mandatory)
|
||||
return new DeferredAuthentication(this);
|
||||
|
||||
HttpServletRequest request = (HttpServletRequest)req;
|
||||
HttpServletResponse response = (HttpServletResponse)res;
|
||||
|
||||
String header = request.getHeader(HttpHeader.AUTHORIZATION.asString());
|
||||
String spnegoToken = getSpnegoToken(header);
|
||||
HttpSession httpSession = request.getSession(false);
|
||||
|
||||
// We have a token from the client, so run the login.
|
||||
if (header != null && spnegoToken != null)
|
||||
{
|
||||
SpnegoUserIdentity identity = (SpnegoUserIdentity)login(null, spnegoToken, request);
|
||||
if (identity.isEstablished())
|
||||
{
|
||||
if (!DeferredAuthentication.isDeferred(response))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Sending final token");
|
||||
// Send to the client the final token so that the
|
||||
// client can establish the GSS context with the server.
|
||||
SpnegoUserPrincipal principal = (SpnegoUserPrincipal)identity.getUserPrincipal();
|
||||
setSpnegoToken(response, principal.getEncodedToken());
|
||||
}
|
||||
|
||||
Duration authnDuration = getAuthenticationDuration();
|
||||
if (!authnDuration.isNegative())
|
||||
{
|
||||
if (httpSession == null)
|
||||
httpSession = request.getSession(true);
|
||||
httpSession.setAttribute(UserIdentityHolder.ATTRIBUTE, new UserIdentityHolder(identity));
|
||||
}
|
||||
return new UserAuthentication(getAuthMethod(), identity);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DeferredAuthentication.isDeferred(response))
|
||||
return Authentication.UNAUTHENTICATED;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Sending intermediate challenge");
|
||||
SpnegoUserPrincipal principal = (SpnegoUserPrincipal)identity.getUserPrincipal();
|
||||
sendChallenge(response, principal.getEncodedToken());
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
}
|
||||
// No token from the client; check if the client has logged in
|
||||
// successfully before and the authentication has not expired.
|
||||
else if (httpSession != null)
|
||||
{
|
||||
UserIdentityHolder holder = (UserIdentityHolder)httpSession.getAttribute(UserIdentityHolder.ATTRIBUTE);
|
||||
if (holder != null)
|
||||
{
|
||||
UserIdentity identity = holder._userIdentity;
|
||||
if (identity != null)
|
||||
{
|
||||
Duration authnDuration = getAuthenticationDuration();
|
||||
if (!authnDuration.isNegative())
|
||||
{
|
||||
boolean expired = !authnDuration.isZero() && Instant.now().isAfter(holder._validFrom.plus(authnDuration));
|
||||
// Allow non-GET requests even if they're expired, so that
|
||||
// the client does not need to send the request content again.
|
||||
if (!expired || !HttpMethod.GET.is(request.getMethod()))
|
||||
return new UserAuthentication(getAuthMethod(), identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DeferredAuthentication.isDeferred(response))
|
||||
return Authentication.UNAUTHENTICATED;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Sending initial challenge");
|
||||
sendChallenge(response, null);
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
|
||||
private void sendChallenge(HttpServletResponse response, String token) throws ServerAuthException
|
||||
{
|
||||
try
|
||||
{
|
||||
setSpnegoToken(response, token);
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
throw new ServerAuthException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSpnegoToken(HttpServletResponse response, String token)
|
||||
{
|
||||
String value = HttpHeader.NEGOTIATE.asString();
|
||||
if (token != null)
|
||||
value += " " + token;
|
||||
response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), value);
|
||||
}
|
||||
|
||||
private String getSpnegoToken(String header)
|
||||
{
|
||||
if (header == null)
|
||||
return null;
|
||||
String scheme = HttpHeader.NEGOTIATE.asString() + " ";
|
||||
if (header.regionMatches(true, 0, scheme, 0, scheme.length()))
|
||||
return header.substring(scheme.length()).trim();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class UserIdentityHolder implements Serializable
|
||||
{
|
||||
private static final String ATTRIBUTE = UserIdentityHolder.class.getName();
|
||||
|
||||
private transient final Instant _validFrom = Instant.now();
|
||||
private transient final UserIdentity _userIdentity;
|
||||
|
||||
private UserIdentityHolder(UserIdentity userIdentity)
|
||||
{
|
||||
_userIdentity = userIdentity;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,6 +35,10 @@ import org.eclipse.jetty.util.log.Log;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link ConfigurableSpnegoAuthenticator} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class SpnegoAuthenticator extends LoginAuthenticator
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
|
||||
|
|
|
@ -226,7 +226,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
|
|||
}
|
||||
|
||||
@Override
|
||||
@ManagedAttribute("Idle timeout")
|
||||
@ManagedAttribute("The connection idle timeout in milliseconds")
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return _idleTimeout;
|
||||
|
|
|
@ -31,6 +31,7 @@ import javax.servlet.http.HttpServlet;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.server.Dispatcher;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
|
@ -63,11 +64,13 @@ public class ErrorPageTest
|
|||
context.addServlet(FailServlet.class, "/fail/*");
|
||||
context.addServlet(FailClosedServlet.class, "/fail-closed/*");
|
||||
context.addServlet(ErrorServlet.class, "/error/*");
|
||||
context.addServlet(AppServlet.class, "/app/*");
|
||||
|
||||
ErrorPageErrorHandler error = new ErrorPageErrorHandler();
|
||||
context.setErrorHandler(error);
|
||||
error.addErrorPage(599,"/error/599");
|
||||
error.addErrorPage(IllegalStateException.class.getCanonicalName(),"/error/TestException");
|
||||
error.addErrorPage(BadMessageException.class,"/error/TestException");
|
||||
error.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE,"/error/GlobalErrorPage");
|
||||
|
||||
_server.start();
|
||||
|
@ -157,6 +160,33 @@ public class ErrorPageTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadMessage() throws Exception
|
||||
{
|
||||
String response = _connector.getResponse("GET /app?baa=%88%A4 HTTP/1.0\r\n\r\n");
|
||||
assertThat(response, Matchers.containsString("HTTP/1.1 400 Unable to parse URI query"));
|
||||
assertThat(response, Matchers.containsString("ERROR_PAGE: /TestException"));
|
||||
assertThat(response, Matchers.containsString("ERROR_MESSAGE: Unable to parse URI query"));
|
||||
assertThat(response, Matchers.containsString("ERROR_CODE: 400"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION: org.eclipse.jetty.http.BadMessageException: 400: Unable to parse URI query"));
|
||||
assertThat(response, Matchers.containsString("ERROR_EXCEPTION_TYPE: class org.eclipse.jetty.http.BadMessageException"));
|
||||
assertThat(response, Matchers.containsString("ERROR_SERVLET: org.eclipse.jetty.servlet.ErrorPageTest$AppServlet-"));
|
||||
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /app"));
|
||||
assertThat(response, Matchers.containsString("getParameterMap()= {}"));
|
||||
}
|
||||
|
||||
|
||||
public static class AppServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println(request.getRequestURI());
|
||||
writer.println(request.getParameterMap().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class FailServlet extends HttpServlet implements Servlet
|
||||
{
|
||||
@Override
|
||||
|
@ -202,6 +232,7 @@ public class ErrorPageTest
|
|||
writer.println("ERROR_EXCEPTION_TYPE: " + request.getAttribute(Dispatcher.ERROR_EXCEPTION_TYPE));
|
||||
writer.println("ERROR_SERVLET: " + request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
|
||||
writer.println("ERROR_REQUEST_URI: " + request.getAttribute(Dispatcher.ERROR_REQUEST_URI));
|
||||
writer.println("getParameterMap()= " + request.getParameterMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.util;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
@ -31,9 +27,13 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|||
import org.eclipse.jetty.util.MultiReleaseJarFile.VersionedJarEntry;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnJre;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class MultiReleaseJarFileTest
|
||||
{
|
||||
private File example = MavenTestingUtils.getTestResourceFile("example.jar");
|
||||
|
@ -124,7 +124,7 @@ public class MultiReleaseJarFileTest
|
|||
|
||||
|
||||
@Test
|
||||
@EnabledOnJre({JRE.JAVA_9, JRE.JAVA_10, JRE.JAVA_11})
|
||||
@DisabledOnJre(JRE.JAVA_8)
|
||||
public void testClassLoaderJava9() throws Exception
|
||||
{
|
||||
try(URLClassLoader loader = new URLClassLoader(new URL[]{example.toURI().toURL()}))
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -469,13 +469,6 @@
|
|||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${maven.surefire.version}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
|
Loading…
Reference in New Issue