Merge branch 'jetty-9.4.x' into jetty-9.4.x

This commit is contained in:
Greg Wilkins 2018-10-18 17:07:24 +11:00 committed by GitHub
commit d048dd321e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
135 changed files with 4317 additions and 2042 deletions

58
Jenkinsfile vendored
View File

@ -22,58 +22,11 @@ def getFullBuild(jdk, os) {
def settingsName = 'oss-settings.xml'
def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true'
try {
stage("Checkout - ${jdk}") {
checkout scm
}
} catch (Exception e) {
notifyBuild("Checkout Failure", jdk)
throw e
}
try {
stage("Compile - ${jdk}") {
timeout(time: 15, unit: 'MINUTES') {
withMaven(
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
globalMavenSettingsConfig: settingsName,
mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) {
sh "mvn -V -B clean install -DskipTests -T6 -e"
}
}
}
} catch(Exception e) {
notifyBuild("Compile Failure", jdk)
throw e
}
try {
stage("Javadoc - ${jdk}") {
timeout(time: 20, unit: 'MINUTES') {
withMaven(
maven: mvnName,
jdk: "$jdk",
publisherStrategy: 'EXPLICIT',
globalMavenSettingsConfig: settingsName,
mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) {
sh "mvn -V -B javadoc:javadoc -T6 -e"
}
}
}
} catch(Exception e) {
notifyBuild("Javadoc Failure", jdk)
throw e
}
try {
stage("Test - ${jdk}") {
stage("Build ${jdk}/${os}") {
timeout(time: 90, unit: 'MINUTES') {
// Run test phase / ignore test failures
checkout scm
withMaven(
maven: mvnName,
jdk: "$jdk",
@ -82,7 +35,8 @@ def getFullBuild(jdk, os) {
//options: [invokerPublisher(disabled: false)],
mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) {
sh "mvn -V -B install -Dmaven.test.failure.ignore=true -e -Pmongodb -T3 -Dunix.socket.tmp="+env.JENKINS_HOME
sh "mvn -V -B install -Dmaven.test.failure.ignore=true -e -Pmongodb -T3 -Djetty.testtracker.log=true -Dunix.socket.tmp="+env.JENKINS_HOME
sh "mvn -V -B javadoc:javadoc -T6 -e"
}
// withMaven doesn't label..
// Report failures in the jenkins UI
@ -112,6 +66,8 @@ def getFullBuild(jdk, os) {
consoleParsers = [[parserName: 'Maven'],
[parserName: 'JavaDoc'],
[parserName: 'JavaC']];
step([$class: 'MavenInvokerRecorder', reportsFilenamePattern: "**/target/invoker-reports/BUILD*.xml",
invokerBuildDir: "**/target/its"])
}
// Report on Maven and Javadoc warnings
@ -137,7 +93,7 @@ def getFullBuild(jdk, os) {
globalMavenSettingsConfig: settingsName,
mavenOpts: mavenOpts,
mavenLocalRepo: localRepo) {
sh "mvn -f aggregates/jetty-all-compact3 -V -B -Pcompact3 clean install -T5"
sh "mvn -f aggregates/jetty-all-compact3 -V -B -Pcompact3 clean install -T6"
}
}
} catch(Exception e) {

View File

@ -99,6 +99,17 @@ jetty-9.3.25.v20180904 - 04 September 2018
+ 2860 Leakage of HttpDestinations in HttpClient
+ 2871 Server reads -1 after client resets HTTP/2 stream
jetty-9.2.26.v20180806 - 06 August 2018
+ 2777 Workaround for Conscrypt's ssl == null
jetty-9.2.25.v20180606 - 06 June 2018
+ 2114 Fix NPE in JettyHttpServerProvider
+ 2135 Android 8.1 needs direct buffers for SSL/TLS to work
+ 2529 HttpParser cleanup
+ 2603 WebSocket ByteAccumulator initialized with wrong maximum
+ 2604 WebSocket ByteAccumulator should report sizes in
MessageTooLargeException
jetty-9.4.11.v20180605 - 05 June 2018
+ 1785 Support for vhost@connectorname syntax of virtual hosts
+ 2346 Revert stack trace logging for HTTPChannel.onException

View File

@ -10,7 +10,8 @@
<url>http://www.eclipse.org/jetty</url>
<packaging>jar</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.${project.artifactId}</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.apache-jsp</bundle-symbolic-name>
<jpms-module-name>${project.groupId}.apache.jsp</jpms-module-name>
</properties>
<build>
<plugins>

View File

@ -20,8 +20,23 @@ package org.eclipse.jetty.embedded;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.EnumSet;
public class OneServletContext
{
public static void main( String[] args ) throws Exception
@ -35,11 +50,72 @@ public class OneServletContext
server.setHandler(context);
// Add dump servlet
context.addServlet(DumpServlet.class, "/dump/*");
// Add default servlet
context.addServlet(
context.addServlet(DumpServlet.class, "/dump/*"),
"*.dump");
context.addServlet(HelloServlet.class, "/hello/*");
context.addServlet(DefaultServlet.class, "/");
context.addFilter(TestFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST));
context.addFilter(TestFilter.class,"/test", EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
context.addFilter(TestFilter.class,"*.test", EnumSet.of(DispatcherType.REQUEST,DispatcherType.INCLUDE,DispatcherType.FORWARD));
context.getServletHandler().addListener(new ListenerHolder(InitListener.class));
context.getServletHandler().addListener(new ListenerHolder(RequestListener.class));
server.start();
server.dumpStdErr();
server.join();
}
public static class TestFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
chain.doFilter(request, response);
}
@Override
public void destroy()
{
}
}
public static class InitListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
}
public static class RequestListener implements ServletRequestListener
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
}
@Override
public void requestInitialized(ServletRequestEvent sre)
{
}
}
}

View File

@ -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

View File

@ -0,0 +1,7 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[files]
maven://org.mortbay.jetty.alpn/alpn-boot/8.1.13.v20181017|lib/alpn/alpn-boot-8.1.13.v20181017.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.13.v20181017.jar

View File

@ -0,0 +1,7 @@
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
[files]
maven://org.mortbay.jetty.alpn/alpn-boot/8.1.13.v20181017|lib/alpn/alpn-boot-8.1.13.v20181017.jar
[exec]
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.13.v20181017.jar

View File

@ -13,3 +13,6 @@ lib/annotations/*.jar
[xml]
# Enable annotation scanning webapp configurations
etc/jetty-annotations.xml
[jpms]
add-modules:org.objectweb.asm

View File

@ -24,7 +24,6 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -36,6 +35,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.util.JavaVersion;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.ManifestUtils;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiReleaseJarFile;
import org.eclipse.jetty.util.log.Log;
@ -69,9 +69,9 @@ import org.objectweb.asm.Opcodes;
public class AnnotationParser
{
private static final Logger LOG = Log.getLogger(AnnotationParser.class);
protected static int ASM_OPCODE_VERSION = Opcodes.ASM6; //compatibility of api
protected static String ASM_OPCODE_VERSION_STR = "ASM6";
private static final int ASM_OPCODE_VERSION = Opcodes.ASM6; //compatibility of api
private static final String ASM_OPCODE_VERSION_STR = "ASM6";
/**
* Map of classnames scanned and the first location from which scan occurred
*/
@ -86,48 +86,44 @@ public class AnnotationParser
public static int asmVersion ()
{
int asmVersion = ASM_OPCODE_VERSION;
Package asm = Opcodes.class.getPackage();
if (asm == null)
LOG.warn("Unknown asm runtime version, assuming version {}", ASM_OPCODE_VERSION_STR);
String version = ManifestUtils.getVersion(Opcodes.class).orElse(null);
if (version == null)
{
LOG.warn("Unknown ASM version, assuming {}", ASM_OPCODE_VERSION_STR);
}
else
{
String s = asm.getImplementationVersion();
if (s==null)
LOG.info("Unknown asm implementation version, assuming version {}", ASM_OPCODE_VERSION_STR);
else
int dot = version.indexOf('.');
version = version.substring(0, (dot < 0 ? version.length() : dot)).trim();
try
{
int dot = s.indexOf('.');
s = s.substring(0, (dot < 0 ? s.length() : dot)).trim();
try
int v = Integer.parseInt(version);
switch (v)
{
int v = Integer.parseInt(s);
switch (v)
case 4:
{
case 4:
{
asmVersion = Opcodes.ASM4;
break;
}
case 5:
{
asmVersion = Opcodes.ASM5;
break;
}
case 6:
{
asmVersion = Opcodes.ASM6;
break;
}
default:
{
LOG.warn("Unrecognized runtime asm version, assuming {}", ASM_OPCODE_VERSION_STR);
}
asmVersion = Opcodes.ASM4;
break;
}
case 5:
{
asmVersion = Opcodes.ASM5;
break;
}
case 6:
{
asmVersion = Opcodes.ASM6;
break;
}
default:
{
LOG.warn("Unrecognized ASM version, assuming {}", ASM_OPCODE_VERSION_STR);
}
}
catch (NumberFormatException e)
{
LOG.warn("Unable to parse runtime asm version, assuming version {}", ASM_OPCODE_VERSION_STR);
}
}
catch (NumberFormatException e)
{
LOG.warn("Unable to parse ASM version, assuming {}", ASM_OPCODE_VERSION_STR);
}
}
return asmVersion;

View File

@ -81,12 +81,10 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
{
MetaData metaData = _context.getMetaData();
if (metaData.getOrigin(clazz.getName()+".listener") == Origin.NotSet)
{
java.util.EventListener listener = (java.util.EventListener)_context.getServletContext().createInstance(clazz);
{
ListenerHolder h = _context.getServletHandler().newListenerHolder(new Source(Source.Origin.ANNOTATION, clazz.getName()));
h.setListener(listener);
h.setHeldClass(clazz);
_context.getServletHandler().addListener(h);
_context.addEventListener(listener);
}
}
else

View File

@ -10,7 +10,7 @@
<name>Jetty :: Ant Plugin</name>
<properties>
<bundle-symbolic-name>org.eclipse.jetty.ant</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.ant</bundle-symbolic-name>
</properties>
<build>
<plugins>

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -108,7 +108,7 @@ public class HttpRequest implements Request
headers.put(userAgentField);
}
protected HttpConversation getConversation()
public HttpConversation getConversation()
{
return conversation;
}

View File

@ -26,7 +26,12 @@ public class HttpResponseException extends RuntimeException
public HttpResponseException(String message, Response response)
{
super(message);
this(message, response, null);
}
public HttpResponseException(String message, Response response, Throwable cause)
{
super(message, cause);
this.response = response;
}

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@ -338,18 +340,31 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
}
}
protected boolean anyToFailure(Throwable failure)
private void anyToFailure(Throwable failure)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return false;
return;
// Mark atomically the request as completed, with respect
// to concurrency between request success and request failure.
if (exchange.requestComplete(failure))
return abort(exchange, failure);
executeAbort(exchange, failure);
}
return false;
private void executeAbort(HttpExchange exchange, Throwable failure)
{
try
{
Executor executor = getHttpChannel().getHttpDestination().getHttpClient().getExecutor();
executor.execute(() -> abort(exchange, failure));
}
catch (RejectedExecutionException x)
{
if (LOG.isDebugEnabled())
LOG.debug(x);
abort(exchange, failure);
}
}
private void terminateRequest(HttpExchange exchange)

View File

@ -110,16 +110,16 @@ public class HttpChannelOverHTTP extends HttpChannel
// Upgrade Response
HttpRequest request = exchange.getRequest();
if (request instanceof HttpConnectionUpgrader)
HttpConnectionUpgrader upgrader = (HttpConnectionUpgrader) request.getConversation().getAttribute(HttpConnectionUpgrader.class.getName());
if (upgrader != null)
{
HttpConnectionUpgrader listener = (HttpConnectionUpgrader)request;
try
{
listener.upgrade(response,getHttpConnection());
upgrader.upgrade(response, getHttpConnection());
}
catch (Throwable x)
{
return new Result(result,x);
return new Result(result, x);
}
}
}

View File

@ -346,7 +346,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
{
HttpResponse response = exchange.getResponse();
response.status(failure.getCode()).reason(failure.getReason());
failAndClose(new HttpResponseException("HTTP protocol violation: bad response on " + getHttpConnection(), response));
failAndClose(new HttpResponseException("HTTP protocol violation: bad response on " + getHttpConnection(), response, failure));
}
}

View File

@ -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};
}
}
}

View File

@ -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);
}
}

View File

@ -43,6 +43,7 @@ import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
@ -209,6 +210,8 @@ public class HttpReceiverOverHTTPTest
ExecutionException e = assertThrows(ExecutionException.class, ()->listener.get(5, TimeUnit.SECONDS));
assertThat(e.getCause(), instanceOf(HttpResponseException.class));
assertThat(e.getCause().getCause(),instanceOf(BadMessageException.class));
assertThat(e.getCause().getCause().getCause(),instanceOf(NumberFormatException.class));
}
@ParameterizedTest

View File

@ -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
{

View File

@ -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
{

View File

@ -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());
}
}

View File

@ -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

View File

@ -1,3 +1,4 @@
# Format is <user>:<password>,<roles>
basic:basic
digest:digest
spnego_client:,admin

View File

@ -47,6 +47,7 @@
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!--
@ -73,73 +74,6 @@
<excludes>META-INF/**</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>populate distribution from home</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${assembly-directory}">
<fileset dir="${home-directory}/jetty-home-${project.version}/" />
</copy>
</tasks>
</configuration>
</execution>
<execution>
<id>set jetty.sh</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<chmod dir="${assembly-directory}/bin" perm="755" includes="**/*.sh" />
</tasks>
</configuration>
</execution>
<execution>
<id>removeKeystore</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<delete file="${assembly-directory}/etc/keystore" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<resourceBundles>
<resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.2</resourceBundle>
</resourceBundles>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
@ -222,7 +156,7 @@
<artifactItems>
<artifactItem>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-webapp</artifactId>
<artifactId>test-jetty-webapp</artifactId>
<version>${project.version}</version>
<classifier>config</classifier>
<type>jar</type>
@ -321,6 +255,68 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>populate distribution from home</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${assembly-directory}">
<fileset dir="${home-directory}/jetty-home-${project.version}/" />
</copy>
</tasks>
</configuration>
</execution>
<execution>
<id>set jetty.sh</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<chmod dir="${assembly-directory}/bin" perm="755" includes="**/*.sh" />
</tasks>
</configuration>
</execution>
<execution>
<id>removeKeystore</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<delete file="${assembly-directory}/etc/keystore" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<executions>
<execution>
<phase>prepare-package</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<resourceBundles>
<resourceBundle>org.eclipse.jetty.toolchain:jetty-distribution-remote-resources:1.2</resourceBundle>
</resourceBundles>
<outputDirectory>${assembly-directory}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>

View File

@ -347,6 +347,8 @@ The ALPN implementation, relying on modifications of OpenJDK classes, updates ev
|1.8.0u171 |8.1.12.v20180117
|1.8.0u172 |8.1.12.v20180117
|1.8.0u181 |8.1.12.v20180117
|1.8.0u191 |8.1.13.v20181017
|1.8.0u192 |8.1.13.v20181017
|=============================
[[alpn-build]]

View File

@ -28,3 +28,4 @@ include::custom-modules.adoc[]
include::startup-xml-config.adoc[]
include::startup-unix-service.adoc[]
include::startup-windows-service.adoc[]
include::startup-jpms.adoc[]

View File

@ -79,6 +79,8 @@ If a user does not accept the license agreement, the module will not be activate
Additional Startup Commands - `[exec]`::
The `[exec]` section is used to define additional parameters specific to the module.
These commands are added to the server startup.
JPMS Module-Path Definitions - `[jpms]`::
The `[jpms]` section is used to add link:#startup-jpms[JPMS modules] to the module-path for startup when using the `--jpms` command.
[[custom-module-properties]]
==== Module Properties

View File

@ -170,7 +170,7 @@ Note: order presented here is how they would appear on the classpath.
13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar
14: 1.1.0.v201105071233 | ${jetty.home}/lib/jndi/javax.activation-1.1.0.v201105071233.jar
15: 1.4.1.v201005082020 | ${jetty.home}/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
16: 1.2 | ${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
16: 1.3 | ${jetty.home}/lib/jndi/javax.transaction-api-1.3.jar
17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar
18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar
19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar

View File

@ -80,7 +80,7 @@ Note: order presented here is how they would appear on the classpath.
13: {VERSION} | ${jetty.home}/lib/jetty-jndi-{VERSION}.jar
14: 1.1.0.v201105071233 | ${jetty.home}/lib/jndi/javax.activation-1.1.0.v201105071233.jar
15: 1.4.1.v201005082020 | ${jetty.home}/lib/jndi/javax.mail.glassfish-1.4.1.v201005082020.jar
16: 1.2 | ${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar
16: 1.3 | ${jetty.home}/lib/jndi/javax.transaction-api-1.3.jar
17: {VERSION} | ${jetty.home}/lib/jetty-rewrite-{VERSION}.jar
18: {VERSION} | ${jetty.home}/lib/jetty-security-{VERSION}.jar
19: {VERSION} | ${jetty.home}/lib/jetty-servlet-{VERSION}.jar

View File

@ -0,0 +1,158 @@
//
// ========================================================================
// 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.
// ========================================================================
//
[[startup-jpms]]
=== Startup using the Java Platform Module System (JPMS)
Jetty modules also act ass automatic https://en.wikipedia.org/wiki/Java_Platform_Module_System[JPMS] modules via the `Automatic-Module-Name` attribute in the jar's `MANIFEST.MF` file.
This makes possible to run Jetty from the module-path, rather than the class-path.
We recommend using JDK 11 or greater due to the fact that JDK 11 removed all the "enterprise" modules from the JDK.
The classes in these "enterprise" modules were bundled with JDK 8, and present in "enterprise" modules in JDK 9 and JDK 10.
With JDK 11, these "enterprise" classes are either not available in the JDK (because their corresponding module was removed), or they are present in a different module.
Because some of these "enterprise" classes are required by Jetty or by applications running in Jetty, it is better to use a stable source for those classes - in this case by using JDK 11
or greater.
[[jpms-module-path]]
==== Starting Jetty on the module-path
To start Jetty on the module-path rather than the class-path, it is enough to add the `--jpms` option to the command line, for example:
[source, screen, subs="{sub-order}"]
....
$ mkdir my-jetty-base
$ cd my-jetty-base
$ java -jar $JETTY_HOME/start.jar --add-to-start=http
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : http initialized in ${jetty.base}/start.ini
INFO : threadpool transitively enabled, ini template available with --add-to-start=threadpool
INFO : Base directory was modified
$ java -jar $JETTY_HOME/start.jar --jpms
....
The example above creates a link:#startup-base-and-home[Jetty base directory] and enables the `http` module using the `--add-to-start` command.
The server then starts Jetty on the module-path using the `--jpms` option.
----
[NOTE]
When running on the module-path using the `--jpms` option, the Jetty start mechanism will fork a second JVM passing it the right JVM options to run on the module-path.
You will have two JVMs running: one that runs `start.jar` and one that runs Jetty on the module-path.
----
If you are interested in the details of how the command line to run Jetty on the module-path looks like, you can add the `--dry-run` option:
[source, screen, subs="{sub-order}"]
....
$ java -jar $JETTY_HOME/start.jar --jpms --dry-run
....
This will give an out put looking something like this (broken in sections for clarity):
[source, screen, subs="{sub-order}"]
....
/opt/openjdk-11+28/bin/java
--module-path /opt/jetty/lib/servlet-api-3.1.jar:/opt/jetty/lib/jetty-schemas-3.1.jar:/opt/jetty/lib/jetty-http-9.4.13-SNAPSHOT.jar:...
--patch-module servlet.api=/opt/jetty/lib/jetty-schemas-3.1.jar
--module org.eclipse.jetty.xml/org.eclipse.jetty.xml.XmlConfiguration /opt/jetty/etc/jetty-threadpool.xml /opt/jetty/etc/jetty.xml ...
....
The `--module-path` option specifies the list of Jetty jars.
This list depends on the Jetty modules that have been enabled via the link:#startup-modules[`--add-to-start`] command.
The `--patch-module` option is necessary for Servlet and JSP Containers to find XML DTDs and XML Schemas required to validate the various XML files present in web applications (such as `web.xml` and others).
The `--module` option tells the JVM to run main class `XmlConfiguration` from the `org.eclipse.jetty.xml` module, with the given XML files as program arguments.
When the JVM starts, module `org.eclipse.jetty.xml` is added to the set of JPMS _root modules_; all other Jetty modules, being automatic, will be resolved and added to the module graph.
JAR files that are not modules, such as `servlet-api-3.1.jar`, are on the module-path and therefore will be made automatic modules by the JVM (hence the derived module name `servlet.api` for this jar, referenced by the `--patch-module` command line option above).
[[jpms-advanced-config]]
==== Advanced JPMS Configuration
Web applications may need additional services from the Servlet Container, such as JDBC `DataSource` references or JTA `UserTransaction` references.
For example, for JDBC it is typical to store, in JNDI, a reference to the connection pool's `DataSource` (such as `com.zaxxer.hikari.HikariDataSource`) or a reference directly to the JDBC driver's `DataSource` (`com.mysql.jdbc.jdbc2.optional.MysqlDataSource`).
Jetty needs to be able to instantiate those classes and therefore needs to be able to load those classes and all their super-classes, among which includes `javax.sql.DataSource`.
When Jetty runs on the class-path, this is easily achieved by using a link:#custom-modules[custom module]:
[source, screen, subs="{sub-order}"]
.mysql.mod
....
[description]
MySQL module
[lib]
lib/mysql/mysql-connector-java-*.jar
....
However, when running on the module-path, things are quite different.
Class `javax.sql.DataSource` is in a JDK bundled module named `java.sql`, which is not automatic (it's a proper JPMS module) and it is not in the _root modules_ set.
Because it is not an automatic module, it is not added to the module graph, and therefore needs to be added explicitly using the JVM command line `--add-modules`.
To add the JPMS module `java.sql` to the module graph, you need to modify your custom module in the following way, using our `mysql.mod` as an example:
[source, screen, subs="{sub-order}"]
.mysql.mod
....
[description]
MySQL module
[lib]
lib/mysql/mysql-connector-java-*.jar
[jpms]
add-modules: java.sql
....
The new `[jpms]` section is only used when Jetty is started on the module-path via the `--jpms` command line option.
Assuming that `mysql-connector-java-*.jar` is a non JPMS modular jar, or an automatic JPMS modular jar, the Jetty start mechanism will add `mysql-connector-java-*.jar` to the module-path, and will add the JVM command line option `--add-modules java.sql`.
If `mysql-connector-java-*.jar` were a proper JPMS modular jar with name (for example) `com.mysql.jdbc`, then it would need to be explicitly added to the module graph, in this way:
[source, screen, subs="{sub-order}"]
.mysql.mod
....
[description]
MySQL module
[lib]
lib/mysql/mysql-connector-java-*.jar
[jpms]
add-modules: com.mysql.jdbc
....
The JPMS module `java.sql` does not need to be explicitly added because it would be a dependency of the `com.mysql.jdbc` module and therefore automatically added to the module graph.
The `[jpms]` section has the following format:
[source, screen, subs="{sub-order}"]
....
[jpms]
add-modules: <module name>(,<module name>)*
patch-module: <module>=<file>(:<file>)*
add-opens: <module>/<package>=<target-module>(,<target-module>)*
add-exports: <module>/<package>=<target-module>(,<target-module>)*
add-reads: <module>=<target-module>(,<target-module>)*
....

View File

@ -19,14 +19,11 @@
[[websocket-intro]]
== WebSocket Introduction
WebSocket is a new protocol for bidirectional communications over HTTP.
It is based on a low level framing protocol that delivers messages in either UTF-8 TEXT or BINARY format.
A single message in WebSocket can be of any size (the underlying framing however does have a single frame limit of http://en.wikipedia.org/wiki/9223372036854775807[63-bits])
WebSocket is a new protocol for bidirectional communications initiated via HTTP/1.1 upgrade and providing basic message framing, layered over TCP.
It is based on a low-level framing protocol that delivers messages in either UTF-8 TEXT or BINARY format.
A single message in WebSocket can be of any size (the underlying framing however does have a single frame limit of http://en.wikipedia.org/wiki/9223372036854775807[63-bits]).
There can be an unlimited number of messages sent.
Messages are sent sequentially, the base protocol does not support interleaved messages.
A WebSocket connection goes through some basic state changes:
@ -78,11 +75,9 @@ https://datatracker.ietf.org/doc/draft-ietf-hybi-websocket-perframe-compression/
Per Frame Compression Extension.
+
An early extension draft from the Google/Chromium team that would provide WebSocket frame compression.
+
perframe-compression using deflate algorithm is present on many versions of Chrome/Chromium.
+
Jetty's support for perframe-compression is based on the draft-04 spec.
+
This standard is being replaced with permessage-compression.
https://datatracker.ietf.org/doc/draft-tyoshino-hybi-permessage-compression/[permessage-compression]::
@ -108,12 +103,11 @@ Java WebSocket Server API::
=== Enabling WebSocket
To enable websocket, you need to link:#enabling-modules[enable] the `websocket` link:#enabling-modules[module].
To enable Websocket, you need to enable the `websocket` link:#enabling-modules[module].
Once this module is enabled for your jetty base, it will apply to all webapps deployed to that base.
If you want to be more selective about which webapps use websocket, then you can:
Once this module is enabled for your Jetty base, it will apply to all webapps deployed to that base. If you want to be more selective about which webapps use Websocket, then you can:
Disable jsr-356 for a particular webapp:::
Disable JSR-356 for a particular webapp:::
You can disable jsr-356 for a particular webapp by setting the link:#context_attributes[context attribute] `org.eclipse.jetty.websocket.jsr356` to `false`.
This will mean that websockets are not available to your webapp, however deployment time scanning for websocket-related classes such as endpoints will still occur.
This can be a significant impost if your webapp contains a lot of classes and/or jar files.

View File

@ -88,7 +88,7 @@ Instead, you could redefine the DefaultServlet in your web.xml file, making sure
<web-app ...>
...
<servlet>
<servlet-name>Default</servlet-name>
<servlet-name>default</servlet-name>
<servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
<init-param>
<param-name>useFileMappedBuffer</param-name>
@ -97,9 +97,7 @@ Instead, you could redefine the DefaultServlet in your web.xml file, making sure
<load-on-startup>0</load-on-startup>
</servlet>
...
</web-app>
</web-app>
----
==== Alternate Remedy
@ -117,7 +115,6 @@ Configure this in an xml file like so:
.
.
</New>
----
____

View File

@ -324,7 +324,7 @@
</goals>
<configuration>
<includeGroupIds>javax.annotation,org.eclipse.jetty.orbit,org.ow2.asm</includeGroupIds>
<includeArtifactIds>javax.annotation-api,asm,asm-commons</includeArtifactIds>
<includeArtifactIds>javax.annotation-api,asm,asm-commons,asm-tree,asm-analysis</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/annotations</outputDirectory>
</configuration>
@ -337,7 +337,7 @@
</goals>
<configuration>
<includeGroupIds>javax.annotation,org.eclipse.jetty.orbit,org.ow2.asm</includeGroupIds>
<includeArtifactIds>javax.annotation-api,asm,asm-commons</includeArtifactIds>
<includeArtifactIds>javax.annotation-api,asm,asm-commons,asm-tree,asm-analysis</includeArtifactIds>
<includeTypes>jar</includeTypes>
<classifier>sources</classifier>
<outputDirectory>${source-assembly-directory}/lib/annotations</outputDirectory>
@ -547,6 +547,14 @@
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
</dependency>
<!-- jetty deps -->
<dependency>

View File

@ -25,19 +25,18 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.util.thread.TryExecutor;
public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPool
{
private static final Logger LOG = Log.getLogger(DelegatingThreadPool.class);
public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPool, TryExecutor
{
private Executor _executor; // memory barrier provided by start/stop semantics
private TryExecutor _tryExecutor;
public DelegatingThreadPool(Executor executor)
{
_executor=executor;
_tryExecutor=TryExecutor.asTryExecutor(executor);
addBean(_executor);
}
@ -54,6 +53,7 @@ public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPo
throw new IllegalStateException(getState());
updateBean(_executor,executor);
_executor=executor;
_tryExecutor=TryExecutor.asTryExecutor(executor);
}
/* ------------------------------------------------------------ */
@ -63,6 +63,13 @@ public class DelegatingThreadPool extends ContainerLifeCycle implements ThreadPo
_executor.execute(job);
}
/* ------------------------------------------------------------ */
@Override
public boolean tryExecute(Runnable task)
{
return _tryExecutor.tryExecute(task);
}
/* ------------------------------------------------------------ */
@Override
public int getIdleThreads()

View File

@ -22,9 +22,9 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import javax.jws.WebMethod;
import javax.jws.WebService;
@ -35,13 +35,13 @@ public class TestEndpointMultiplePublishProblem
private static String default_impl = System.getProperty("com.sun.net.httpserver.HttpServerProvider");
@BeforeClass
@BeforeAll
public static void change_Impl()
{
System.setProperty("com.sun.net.httpserver.HttpServerProvider", JettyHttpServerProvider.class.getName());
}
@AfterClass
@AfterAll
public static void restore_Impl()
{
if(default_impl != null)

View File

@ -52,9 +52,7 @@ public class BadMessageException extends RuntimeException
public BadMessageException(int code, String reason)
{
super(code+": "+reason);
_code=code;
_reason=reason;
this(code, reason, null);
}
public BadMessageException(int code, String reason, Throwable cause)

View File

@ -1092,7 +1092,7 @@ public class HttpParser
catch(NumberFormatException e)
{
LOG.ignore(e);
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value");
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value",e);
}
}

View File

@ -333,15 +333,16 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
}
case SettingsFrame.ENABLE_PUSH:
{
boolean enabled = value == 1;
if (LOG.isDebugEnabled())
LOG.debug("{} push for {}", pushEnabled ? "Enabling" : "Disabling", this);
pushEnabled = value == 1;
LOG.debug("{} push for {}", enabled ? "Enabling" : "Disabling", this);
pushEnabled = enabled;
break;
}
case SettingsFrame.MAX_CONCURRENT_STREAMS:
{
if (LOG.isDebugEnabled())
LOG.debug("Updating max local concurrent streams to {} for {}", maxLocalStreams, this);
LOG.debug("Updating max local concurrent streams to {} for {}", value, this);
maxLocalStreams = value;
break;
}

View File

@ -176,7 +176,8 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
@Override
public boolean isRemotelyClosed()
{
return closeState.get() == CloseState.REMOTELY_CLOSED;
CloseState state = closeState.get();
return state == CloseState.REMOTELY_CLOSED || state == CloseState.CLOSING;
}
public boolean isLocallyClosed()
@ -627,13 +628,14 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
@Override
public String toString()
{
return String.format("%s@%x#%d{sendWindow=%s,recvWindow=%s,reset=%b,%s,age=%d,attachment=%s}",
return String.format("%s@%x#%d{sendWindow=%s,recvWindow=%s,reset=%b/%b,%s,age=%d,attachment=%s}",
getClass().getSimpleName(),
hashCode(),
getId(),
sendWindow,
recvWindow,
isReset(),
localReset,
remoteReset,
closeState,
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - timeStamp),
attachment);

View File

@ -33,7 +33,7 @@ public class MetaDataBuilder
{
private final int _maxSize;
private int _size;
private int _status=-1;
private Integer _status;
private String _method;
private HttpScheme _scheme;
private HostPortHttpField _authority;
@ -47,7 +47,7 @@ public class MetaDataBuilder
/**
* @param maxHeadersSize The maximum size of the headers, expressed as total name and value characters.
*/
MetaDataBuilder(int maxHeadersSize)
protected MetaDataBuilder(int maxHeadersSize)
{
_maxSize=maxHeadersSize;
}
@ -84,7 +84,7 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
if(checkHeader(header, _status))
if(checkPseudoHeader(header, _status))
_status = (Integer)staticField.getStaticValue();
_response = true;
break;
@ -110,8 +110,8 @@ public class MetaDataBuilder
switch(header)
{
case C_STATUS:
if(checkHeader(header, _status))
_status = field.getIntValue();
if(checkPseudoHeader(header, _status))
_status = Integer.valueOf(field.getIntValue());
_response = true;
break;
@ -197,7 +197,7 @@ public class MetaDataBuilder
}
}
void streamException(String messageFormat, Object... args)
protected void streamException(String messageFormat, Object... args)
{
HpackException.StreamException stream = new HpackException.StreamException(messageFormat, args);
if (_streamException==null)
@ -206,20 +206,7 @@ public class MetaDataBuilder
_streamException.addSuppressed(stream);
}
private boolean checkHeader(HttpHeader header, int value)
{
if (_fields.size()>0)
{
streamException("Pseudo header %s after fields", header.asString());
return false;
}
if (value==-1)
return true;
streamException("Duplicate pseudo header %s", header.asString());
return false;
}
private boolean checkPseudoHeader(HttpHeader header, Object value)
protected boolean checkPseudoHeader(HttpHeader header, Object value)
{
if (_fields.size()>0)
{
@ -258,22 +245,26 @@ public class MetaDataBuilder
return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
}
if (_response)
return new MetaData.Response(HttpVersion.HTTP_2,_status,fields,_contentLength);
{
if (_status==null)
throw new HpackException.StreamException("No Status");
return new MetaData.Response(HttpVersion.HTTP_2, _status, fields, _contentLength);
}
return new MetaData(HttpVersion.HTTP_2,fields,_contentLength);
}
finally
{
_fields = new HttpFields(Math.max(10,fields.size()+5));
_request=false;
_response=false;
_status=-1;
_method=null;
_scheme=null;
_authority=null;
_path=null;
_size=0;
_contentLength=Long.MIN_VALUE;
_fields = new HttpFields(Math.max(10, fields.size() + 5));
_request = false;
_response = false;
_status = null;
_method = null;
_scheme = null;
_authority = null;
_path = null;
_size = 0;
_contentLength = Long.MIN_VALUE;
}
}

View File

@ -451,12 +451,15 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
catch (Throwable x)
{
closeNoExceptions(_selector);
_selector = null;
if (isRunning())
LOG.warn(x);
else
{
LOG.warn(x.toString());
LOG.debug(x);
}
closeNoExceptions(_selector);
}
return false;
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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")

View File

@ -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

View File

@ -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"));
}
}

View File

@ -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"));
}
}

View File

@ -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));

View File

@ -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

View File

@ -185,6 +185,26 @@
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId>

View File

@ -10,6 +10,7 @@
<properties>
<jetty.version>@project.version@</jetty.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
@ -38,7 +39,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
<version>2.9.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -57,6 +57,7 @@
<systemPropertyVariables>
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -43,6 +43,7 @@
<configuration>
<systemPropertyVariables>
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
<contentCheck>Bean Validation Webapp example</contentCheck>
</systemPropertyVariables>
<dependenciesToScan>

View File

@ -0,0 +1,12 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>test.jetty-maven-plugin-provided-module-dep</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>api</artifactId>
</project>

View File

@ -0,0 +1,28 @@
//
// ========================================================================
// 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 test;
public class Api
{
public void noOp()
{
}
}

View File

@ -0,0 +1,2 @@
invoker.goals = verify -V -e
#test-compile failsafe:integration-test

View File

@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.its</groupId>
<artifactId>it-parent-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>test.jetty-maven-plugin-provided-module-dep</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>api</module>
<module>web</module>
</modules>
</project>

View File

@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
File buildLog = new File( basedir, 'build.log' )
assert buildLog.text.contains( 'Started Jetty Server' )
assert buildLog.text.contains( 'ClassNotFoundException')

View File

@ -0,0 +1,58 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>test.jetty-maven-plugin-provided-module-dep</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>web</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<executions>
<execution>
<id>start-jetty</id>
<phase>test-compile</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<nonBlocking>true</nonBlocking>
<jettyXml>${basedir}/src/config/jetty.xml</jettyXml>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,48 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Set name="secureScheme">https</Set>
<Set name="securePort">
<Property name="jetty.secure.port" default="8443"/>
</Set>
<Set name="outputBufferSize">32768</Set>
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
<Set name="headerCacheSize">4096</Set>
</New>
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref refid="Server"/>
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref refid="httpConfig"/>
</Arg>
</New>
</Item>
</Array>
</Arg>
<Call name="addLifeCycleListener">
<Arg>
<New class="org.eclipse.jetty.maven.plugin.ServerConnectorListener">
</New>
</Arg>
</Call>
<Set name="host">
<Property name="jetty.host"/>
</Set>
<Set name="port"><Property name="jetty.port" default="0"/>0
</Set>
<Set name="idleTimeout">30000</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -16,40 +16,34 @@
// ========================================================================
//
package org.eclipse.jetty.gcloud.session;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
package test;
/**
* GCloudTestSuite
*
* Sets up the gcloud emulator once before running all tests.
*
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
GCloudSessionDataStoreTest.class,
InvalidationSessionTest.class,
ClusteredSessionScavengingTest.class,
ClusteredOrphanedSessionTest.class
})
public class GCloudTestSuite
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ClassLoadingTestingServletContextListener
implements ServletContextListener
{
public static GCloudSessionTestSupport __testSupport;
@BeforeAll
public static void setUp () throws Exception
@Override
public void contextInitialized( ServletContextEvent sce )
{
__testSupport = new GCloudSessionTestSupport();
__testSupport.setUp();
try
{
Api api = new Api();
}
catch ( java.lang.Exception exception )
{
exception.printStackTrace();
}
//System.out.println("Class " + api.getClass().getName() + " is available and loaded by classloader " + api.getClass().getClassLoader().toString() + ". Expected ClassNotFoundException.");
}
@AfterAll
public static void tearDown () throws Exception
@Override
public void contextDestroyed( ServletContextEvent sce )
{
__testSupport.tearDown();
}
}
}

View File

@ -70,6 +70,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -70,6 +70,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -73,6 +73,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -71,6 +71,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -71,6 +71,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -69,6 +69,7 @@
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<pingServlet>true</pingServlet>
<helloServlet>true</helloServlet>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -74,6 +74,7 @@
<systemPropertyVariables>
<jetty.port.file>${jetty.port.file}</jetty.port.file>
<contentCheck>Please enter your name</contentCheck>
<maven.it.name>${project.groupId}:${project.artifactId}</maven.it.name>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.eclipse.jetty:jetty-maven-plugin</dependency>

View File

@ -532,9 +532,14 @@ public class JettyRunMojo extends AbstractJettyMojo
{
// Include runtime and compile time libraries, and possibly test libs too
if(artifact.getType().equals("war"))
{
continue;
}
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
continue; //never add dependencies of scope=provided to the webapp's classpath (see also <useProvidedScope> param)
if (Artifact.SCOPE_TEST.equals(artifact.getScope()) && !useTestScope)
continue; //only add dependencies of scope=test if explicitly required
MavenProject mavenProject = getProjectReference( artifact, project );
if (mavenProject != null)
{
@ -544,12 +549,6 @@ public class JettyRunMojo extends AbstractJettyMojo
continue;
}
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
continue; //never add dependencies of scope=provided to the webapp's classpath (see also <useProvidedScope> param)
if (Artifact.SCOPE_TEST.equals(artifact.getScope()) && !useTestScope)
continue; //only add dependencies of scope=test if explicitly required
dependencyFiles.add(artifact.getFile());
getLog().debug( "Adding artifact " + artifact.getFile().getName() + " with scope "+artifact.getScope()+" for WEB-INF/lib " );
}

View File

@ -51,23 +51,24 @@ public class TestGetContent
if (Boolean.getBoolean( "helloServlet" ))
{
String response = httpClient.GET( "http://localhost:" + port + "/hello?name=beer" ).getContentAsString();
assertEquals( "Hello beer", response.trim() );
assertEquals( "Hello beer", response.trim(), "it test " + System.getProperty( "maven.it.name" ) );
response = httpClient.GET( "http://localhost:" + port + "/hello?name=foo" ).getContentAsString();
assertEquals( "Hello foo", response.trim() );
assertEquals( "Hello foo", response.trim(), "it test " + System.getProperty( "maven.it.name" ) );
System.out.println( "helloServlet" );
}
if (Boolean.getBoolean( "pingServlet" ))
{
System.out.println( "pingServlet ok" );
String response = httpClient.GET( "http://localhost:" + port + "/ping?name=beer" ).getContentAsString();
assertEquals( "pong beer", response.trim() );
System.out.println( "pingServlet" );
String response = httpClient.GET( "http://localhost:" + port + "/ping?name=beer" ).getContentAsString();
assertEquals( "pong beer", response.trim(), "it test " + System.getProperty( "maven.it.name" ) );
System.out.println( "pingServlet ok" );
}
String contentCheck = System.getProperty( "contentCheck" );
if(StringUtils.isNotBlank( contentCheck ) )
{
String response = httpClient.GET( "http://localhost:" + port ).getContentAsString();
assertTrue(response.contains(contentCheck), "response not contentCheck: " + contentCheck + ", response:" + response);
assertTrue(response.contains(contentCheck), "it test " + System.getProperty( "maven.it.name" )
+ ", response not contentCheck: " + contentCheck + ", response:" + response);
System.out.println( "contentCheck" );
}
}

View File

@ -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,12 +45,6 @@
<artifactId>pax-exam-junit4</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
@ -402,13 +396,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>
@ -435,6 +427,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>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>

View File

@ -43,7 +43,7 @@
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>javax.transaction*;version="[1.1,1.3)",*</Import-Package>
<Import-Package>javax.transaction.*;version="1.1",*</Import-Package>
</instructions>
</configuration>
</plugin>

View File

@ -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

View File

@ -37,7 +37,6 @@
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -49,7 +48,6 @@
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.mail.glassfish</artifactId>
<version>1.4.1.v201005082020</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -51,6 +51,7 @@ import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -176,10 +177,10 @@ public class QuickStartDescriptorGenerator
.tag("param-value",_webApp.getInitParameter(p))
.closeTag();
if (_webApp.getEventListeners() != null)
for (EventListener e : _webApp.getEventListeners())
out.openTag("listener",origin(md,e.getClass().getCanonicalName() + ".listener"))
.tag("listener-class",e.getClass().getCanonicalName())
if (_webApp.getServletHandler().getListeners() != null)
for (ListenerHolder e : _webApp.getServletHandler().getListeners())
out.openTag("listener",origin(md,e.getClassName() + ".listener"))
.tag("listener-class",e.getClassName())
.closeTag();
ServletHandler servlets = _webApp.getServletHandler();

View File

@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
import java.io.File;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@ -69,8 +70,10 @@ public class TestQuickStart
ServletHolder fooHolder = new ServletHolder();
fooHolder.setServlet(new FooServlet());
fooHolder.setName("foo");
quickstart.getServletHandler().addServlet(fooHolder);
quickstart.addEventListener(new FooContextListener());
quickstart.getServletHandler().addServlet(fooHolder);
ListenerHolder lholder = new ListenerHolder();
lholder.setListener(new FooContextListener());
quickstart.getServletHandler().addListener(lholder);
server.setHandler(quickstart);
server.start();
server.stop();

View File

@ -42,7 +42,7 @@ import java.io.StringReader;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.fail;
public class CookiePatternRuleTest

View File

@ -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;
}
}
}

View File

@ -26,7 +26,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@ -44,33 +43,33 @@ import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.Credential;
/**
* PropertyUserStore
* <p>
* This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
* <p>This class monitors a property file of the format mentioned below
* and notifies registered listeners of the changes to the the given file.</p>
*
* <pre>
* username: password [,rolename ...]
* </pre>
*
* Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
* checksums.
* <p>Passwords may be clear text, obfuscated or checksummed.
* The class {@link org.eclipse.jetty.util.security.Password} should be used
* to generate obfuscated passwords or password checksums.</p>
*
* If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
* <p>If DIGEST Authentication is used, the password must be in a recoverable
* format, either plain text or obfuscated.</p>
*/
public class PropertyUserStore extends UserStore implements PathWatcher.Listener
{
private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
protected Path _configPath;
protected PathWatcher _pathWatcher;
protected boolean _hotReload = false; // default is not to reload
protected boolean _firstLoad = true; // true if first load, false from that point on
protected List<UserListener> _listeners;
/**
* Get the config (as a string)
*
* @return the config path as a string
*/
public String getConfig()
@ -82,6 +81,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* Set the Config Path from a String reference to a file
*
* @param config the config file
*/
public void setConfig(String config)
@ -91,11 +91,11 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
_configPath = null;
return;
}
try
{
Resource configResource = Resource.newResource(config);
if (configResource instanceof JarFileResource)
_configPath = extractPackedFile((JarFileResource)configResource);
else if (configResource instanceof PathResource)
@ -110,11 +110,11 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
_configPath = null;
throw new IllegalStateException(e);
}
}
/**
* Get the Config {@link Path} reference.
*
* @return the config path
*/
public Path getConfigPath()
@ -124,7 +124,8 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* Set the Config Path from a String reference to a file
* @param configFile the config file can a be a file path or a reference to a file within a jar file <code>jar:file:</code>
*
* @param configFile the config file can a be a file path or a reference to a file within a jar file {@code jar:file:}
*/
@Deprecated
public void setConfigPath(String configFile)
@ -132,25 +133,24 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
setConfig(configFile);
}
private Path extractPackedFile( JarFileResource configResource )
throws IOException
private Path extractPackedFile(JarFileResource configResource) throws IOException
{
String uri = configResource.getURI().toASCIIString();
int colon = uri.lastIndexOf(":");
int bang_slash = uri.indexOf("!/");
if (colon<0 || bang_slash<0 || colon>bang_slash)
throw new IllegalArgumentException("Not resolved JarFile resource: "+uri);
String entry_path = uri.substring(colon+2).replace("!/","__").replace('/','_').replace('.','_');
if (colon < 0 || bang_slash < 0 || colon > bang_slash)
throw new IllegalArgumentException("Not resolved JarFile resource: " + uri);
String entry_path = uri.substring(colon + 2).replace("!/", "__").replace('/', '_').replace('.', '_');
Path tmpDirectory = Files.createTempDirectory( "users_store" );
Path tmpDirectory = Files.createTempDirectory("users_store");
tmpDirectory.toFile().deleteOnExit();
Path extractedPath = Paths.get(tmpDirectory.toString(), entry_path);
Files.deleteIfExists( extractedPath );
Files.deleteIfExists(extractedPath);
extractedPath.toFile().deleteOnExit();
IO.copy(configResource.getInputStream(),new FileOutputStream(extractedPath.toFile()));
IO.copy(configResource.getInputStream(), new FileOutputStream(extractedPath.toFile()));
if (isHotReload())
{
LOG.warn("Cannot hot reload from packed configuration: {}",configResource);
LOG.warn("Cannot hot reload from packed configuration: {}", configResource);
setHotReload(false);
}
return extractedPath;
@ -158,6 +158,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* Set the Config Path from a {@link File} reference
*
* @param configFile the config file
*/
@Deprecated
@ -165,24 +166,23 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
{
setConfigFile(configFile);
}
/**
* Set the Config Path from a {@link File} reference
*
* @param configFile the config file
*/
public void setConfigFile(File configFile)
{
if(configFile == null)
{
if (configFile == null)
_configPath = null;
return;
}
_configPath = configFile.toPath();
else
_configPath = configFile.toPath();
}
/**
* Set the Config Path
*
* @param configPath the config path
*/
public void setConfigPath(Path configPath)
@ -192,18 +192,17 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* @return the resource associated with the configured properties file, creating it if necessary
* @throws IOException if unable to get the resource
*/
public Resource getConfigResource() throws IOException
public Resource getConfigResource()
{
if (_configPath==null)
if (_configPath == null)
return null;
return new PathResource(_configPath);
}
/**
* Is hot reload enabled on this user store
*
*
* @return true if hot reload was enabled before startup
*/
public boolean isHotReload()
@ -213,7 +212,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* Enable Hot Reload of the Property File
*
*
* @param enable true to enable, false to disable
*/
public void setHotReload(boolean enable)
@ -225,33 +224,27 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
this._hotReload = enable;
}
@Override
public String toString()
{
StringBuilder s = new StringBuilder();
s.append(this.getClass().getName());
s.append("[");
s.append("users.count=").append(this.getKnownUserIdentities().size());
s.append("identityService=").append(this.getIdentityService());
s.append("]");
return s.toString();
return String.format("%s@%x[users.count=%d,identityService=%s]", getClass().getSimpleName(), hashCode(), getKnownUserIdentities().size(), getIdentityService());
}
/* ------------------------------------------------------------ */
protected void loadUsers() throws IOException
{
if (_configPath == null)
throw new IllegalStateException ("No config path set");
throw new IllegalStateException("No config path set");
if (LOG.isDebugEnabled())
LOG.debug("Loading {} from {}", this, _configPath);
if (LOG.isDebugEnabled()) LOG.debug("Loading {} from {}",this, _configPath);
Resource config = getConfigResource();
if (!config.exists())
throw new IllegalStateException ("Config does not exist: "+ config);
throw new IllegalStateException("Config does not exist: " + config);
Properties properties = new Properties();
properties.load(config.getInputStream());
@ -263,63 +256,50 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
String credentials = ((String)entry.getValue()).trim();
String roles = null;
int c = credentials.indexOf(',');
if (c > 0)
if (c >= 0)
{
roles = credentials.substring(c + 1).trim();
credentials = credentials.substring(0,c).trim();
credentials = credentials.substring(0, c).trim();
}
if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
if (username.length() > 0)
{
String[] roleArray = IdentityService.NO_ROLES;
if (roles != null && roles.length() > 0)
{
roleArray = StringUtil.csvSplit(roles);
}
known.add(username);
Credential credential = Credential.getCredential(credentials);
addUser( username, credential, roleArray );
notifyUpdate(username,credential,roleArray);
addUser(username, credential, roleArray);
notifyUpdate(username, credential, roleArray);
}
}
final List<String> currentlyKnownUsers = new ArrayList<String>(getKnownUserIdentities().keySet());
/*
*
* if its not the initial load then we want to process removed users
*/
List<String> currentlyKnownUsers = new ArrayList<>(getKnownUserIdentities().keySet());
// if its not the initial load then we want to process removed users
if (!_firstLoad)
{
Iterator<String> users = currentlyKnownUsers.iterator();
while (users.hasNext())
for (String user : currentlyKnownUsers)
{
String user = users.next();
if (!known.contains(user))
{
removeUser( user );
removeUser(user);
notifyRemove(user);
}
}
}
/*
* set initial load to false as there should be no more initial loads
*/
// set initial load to false as there should be no more initial loads
_firstLoad = false;
if (LOG.isDebugEnabled())
{
LOG.debug("Loaded " + this + " from " + _configPath);
}
}
/* ------------------------------------------------------------ */
/**
* Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
* it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
*
*
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
* Depending on the value of the refresh interval, this method will either start
* up a scanner thread that will monitor the properties file for changes after
* it has initially loaded it. Otherwise the users will be loaded and there will
* be no active monitoring thread so changes will not be detected.
*/
@Override
protected void doStart() throws Exception
@ -327,7 +307,7 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
super.doStart();
loadUsers();
if ( isHotReload() && (_configPath != null) )
if (isHotReload() && (_configPath != null))
{
this._pathWatcher = new PathWatcher();
this._pathWatcher.watch(_configPath);
@ -335,18 +315,15 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
this._pathWatcher.setNotifyExistingOnStart(false);
this._pathWatcher.start();
}
}
@Override
public void onPathWatchEvent(PathWatchEvent event)
{
try
{
if (LOG.isDebugEnabled())
{
LOG.debug( "PATH WATCH EVENT: {}", event.getType() );
}
LOG.debug("Path watch event: {}", event.getType());
loadUsers();
}
catch (IOException e)
@ -355,10 +332,6 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
}
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
*/
@Override
protected void doStop() throws Exception
{
@ -371,53 +344,45 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
/**
* Notifies the registered listeners of potential updates to a user
*
* @param username
* @param credential
* @param roleArray
* @param username the user that was updated
* @param credential the updated credentials
* @param roleArray the updated roles
*/
private void notifyUpdate(String username, Credential credential, String[] roleArray)
{
if (_listeners != null)
{
for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
{
i.next().update(username,credential,roleArray);
}
for (UserListener _listener : _listeners)
_listener.update(username, credential, roleArray);
}
}
/**
* notifies the registered listeners that a user has been removed.
* Notifies the registered listeners that a user has been removed.
*
* @param username
* @param username the user that was removed
*/
private void notifyRemove(String username)
{
if (_listeners != null)
{
for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
{
i.next().remove(username);
}
for (UserListener _listener : _listeners)
_listener.remove(username);
}
}
/**
* registers a listener to be notified of the contents of the property file
* Registers a listener to be notified of the contents of the property file
*
* @param listener the user listener
*/
public void registerUserListener(UserListener listener)
{
if (_listeners == null)
{
_listeners = new ArrayList<UserListener>();
}
_listeners = new ArrayList<>();
_listeners.add(listener);
}
/**
* UserListener
*/
public interface UserListener
{
public void update(String username, Credential credential, String[] roleArray);

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -40,62 +40,55 @@ public abstract class LoginAuthenticator implements Authenticator
protected LoginService _loginService;
protected IdentityService _identityService;
private boolean _renewSession;
/* ------------------------------------------------------------ */
protected LoginAuthenticator()
{
}
/* ------------------------------------------------------------ */
@Override
public void prepareRequest(ServletRequest request)
{
//empty implementation as the default
}
/* ------------------------------------------------------------ */
public UserIdentity login(String username, Object password, ServletRequest request)
public UserIdentity login(String username, Object password, ServletRequest servletRequest)
{
UserIdentity user = _loginService.login(username,password, request);
if (user!=null)
UserIdentity user = _loginService.login(username, password, servletRequest);
if (user != null)
{
renewSession((HttpServletRequest)request, (request instanceof Request? ((Request)request).getResponse() : null));
Request request = Request.getBaseRequest(servletRequest);
renewSession(request, request == null ? null : request.getResponse());
return user;
}
return null;
}
/* ------------------------------------------------------------ */
@Override
public void setConfiguration(AuthConfiguration configuration)
{
_loginService=configuration.getLoginService();
if (_loginService==null)
throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
_identityService=configuration.getIdentityService();
if (_identityService==null)
throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
_renewSession=configuration.isSessionRenewedOnAuthentication();
_loginService = configuration.getLoginService();
if (_loginService == null)
throw new IllegalStateException("No LoginService for " + this + " in " + configuration);
_identityService = configuration.getIdentityService();
if (_identityService == null)
throw new IllegalStateException("No IdentityService for " + this + " in " + configuration);
_renewSession = configuration.isSessionRenewedOnAuthentication();
}
/* ------------------------------------------------------------ */
public LoginService getLoginService()
{
return _loginService;
}
/* ------------------------------------------------------------ */
/** Change the session id.
/**
* Change the session id.
* The session is changed to a new instance with a new ID if and only if:<ul>
* <li>A session exists.
* <li>The {@link org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
* <li>The session ID has been given to unauthenticated responses
* </ul>
* @param request the request
*
* @param request the request
* @param response the response
* @return The new session.
*/
@ -103,13 +96,13 @@ public abstract class LoginAuthenticator implements Authenticator
{
HttpSession httpSession = request.getSession(false);
if (_renewSession && httpSession!=null)
if (_renewSession && httpSession != null)
{
synchronized (httpSession)
{
//if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
//(indicated by SESSION_SECURED not being set on the session) then we should change id
if (httpSession.getAttribute(Session.SESSION_CREATED_SECURE)!=Boolean.TRUE)
if (httpSession.getAttribute(Session.SESSION_CREATED_SECURE) != Boolean.TRUE)
{
if (httpSession instanceof Session)
{
@ -117,13 +110,15 @@ public abstract class LoginAuthenticator implements Authenticator
String oldId = s.getId();
s.renewId(request);
s.setAttribute(Session.SESSION_CREATED_SECURE, Boolean.TRUE);
if (s.isIdChanged() && response != null && (response instanceof Response))
if (s.isIdChanged() && (response instanceof Response))
((Response)response).addCookie(s.getSessionHandler().getSessionCookie(s, request.getContextPath(), request.isSecure()));
LOG.debug("renew {}->{}",oldId,s.getId());
if (LOG.isDebugEnabled())
LOG.debug("renew {}->{}", oldId, s.getId());
}
else
LOG.warn("Unable to renew session "+httpSession);
{
LOG.warn("Unable to renew session " + httpSession);
}
return httpSession;
}
}

View File

@ -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);

View File

@ -24,6 +24,9 @@ lib/jetty-io-${jetty.version}.jar
[xml]
etc/jetty.xml
[jpms]
patch-module: servlet.api=lib/jetty-schemas-3.1.jar
[ini-template]
### Common HTTP configuration
## Scheme to use to build URIs for secure redirects

View File

@ -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;

View File

@ -31,13 +31,18 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class Dispatcher implements RequestDispatcher
{
private static final Logger LOG = Log.getLogger(Dispatcher.class);
public final static String __ERROR_DISPATCH="org.eclipse.jetty.server.Dispatcher.ERROR";
/** Dispatch include attribute names */
@ -195,8 +200,27 @@ public class Dispatcher implements RequestDispatcher
baseRequest.setContextPath(_contextHandler.getContextPath());
baseRequest.setServletPath(null);
baseRequest.setPathInfo(_pathInContext);
if (_uri.getQuery()!=null || old_uri.getQuery()!=null)
baseRequest.mergeQueryParameters(old_uri.getQuery(),_uri.getQuery(), true);
if (_uri.getQuery() != null || old_uri.getQuery() != null)
{
try
{
baseRequest.mergeQueryParameters(old_uri.getQuery(), _uri.getQuery(), true);
}
catch (BadMessageException e)
{
// Only throw BME if not in Error Dispatch Mode
// This allows application ErrorPageErrorHandler to handle BME messages
if (dispatch != DispatcherType.ERROR)
{
throw e;
}
else
{
LOG.warn("Ignoring Original Bad Request Query String: " + old_uri, e);
}
}
}
baseRequest.setAttributes(attr);

View File

@ -21,12 +21,12 @@ package org.eclipse.jetty.server.handler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -201,7 +201,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private final List<ServletRequestAttributeListener> _servletRequestAttributeListeners = new CopyOnWriteArrayList<>();
private final List<ContextScopeListener> _contextListeners = new CopyOnWriteArrayList<>();
private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
private Map<String, Object> _managedAttributes;
private String[] _protectedTargets;
private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
@ -258,9 +257,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public void dump(Appendable out, String indent) throws IOException
{
dumpBeans(out,indent,Collections.singletonList(new ClassLoaderDump(getClassLoader())),
Collections.singletonList(new DumpableCollection("Handler attributes " + this,((AttributesMap)getAttributes()).getAttributeEntrySet())),
Collections.singletonList(new DumpableCollection("Context attributes " + this,((Context)getServletContext()).getAttributeEntrySet())),
Collections.singletonList(new DumpableCollection("Initparams " + this,getInitParams().entrySet())));
Collections.singletonList(new DumpableCollection("eventListeners "+this,_eventListeners)),
Collections.singletonList(new DumpableCollection("handler attributes " + this,((AttributesMap)getAttributes()).getAttributeEntrySet())),
Collections.singletonList(new DumpableCollection("context attributes " + this,((Context)getServletContext()).getAttributeEntrySet())),
Collections.singletonList(new DumpableCollection("initparams " + this,getInitParams().entrySet())));
}
/* ------------------------------------------------------------ */
@ -1553,10 +1553,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
@Deprecated
public void setManagedAttribute(String name, Object value)
{
Object old = _managedAttributes.put(name,value);
updateBean(old,value);
}
/* ------------------------------------------------------------ */
@ -2585,35 +2584,25 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// no security manager just return the classloader
if (!_usingSecurityManager)
{
return _classLoader;
}
else
{
// check to see if the classloader of the caller is the same as the context
// classloader, or a parent of it
try
// classloader, or a parent of it, as required by the javadoc specification.
// Wrap in a PrivilegedAction so that only Jetty code will require the
// "createSecurityManager" permission, not also application code that calls this method.
Caller caller = AccessController.doPrivileged((PrivilegedAction<Caller>)Caller::new);
ClassLoader callerLoader = caller.getCallerClassLoader(2);
while (callerLoader != null)
{
Class<?> reflect = Loader.loadClass("sun.reflect.Reflection");
Method getCallerClass = reflect.getMethod("getCallerClass",Integer.TYPE);
Class<?> caller = (Class<?>)getCallerClass.invoke(null,2);
boolean ok = false;
ClassLoader callerLoader = caller.getClassLoader();
while (!ok && callerLoader != null)
{
if (callerLoader == _classLoader)
ok = true;
else
callerLoader = callerLoader.getParent();
}
if (ok)
if (callerLoader == _classLoader)
return _classLoader;
else
callerLoader = callerLoader.getParent();
}
catch (Exception e)
{
LOG.warn("Unable to check classloader of caller",e);
}
AccessController.checkPermission(new RuntimePermission("getClassLoader"));
return _classLoader;
}
@ -3083,4 +3072,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
void exitScope(Context context, Request request);
}
private static class Caller extends SecurityManager
{
public ClassLoader getCallerClassLoader(int depth)
{
if (depth < 0)
return null;
Class<?>[] classContext = getClassContext();
if (classContext.length <= depth)
return null;
return classContext[depth].getClassLoader();
}
}
}

View File

@ -32,12 +32,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.*;
import org.eclipse.jetty.http.pathmap.PathSpecSet;
import org.eclipse.jetty.server.DeflaterPool;
import org.eclipse.jetty.server.HttpOutput;
@ -156,6 +151,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
public static final int DEFAULT_MIN_GZIP_SIZE=16;
private static final Logger LOG = Log.getLogger(GzipHandler.class);
private static final HttpField X_CE_GZIP = new PreEncodedHttpField("X-Content-Encoding","gzip");
private static final HttpField TE_CHUNKED = new PreEncodedHttpField(HttpHeader.TRANSFER_ENCODING, HttpHeaderValue.CHUNKED.asString());
private static final Pattern COMMA_GZIP = Pattern.compile(".*, *gzip");
private int POOL_CAPACITY = -1;
@ -622,29 +618,46 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
// Handle request inflation
if (_inflateBufferSize>0)
{
boolean inflate = false;
for (ListIterator<HttpField> i = baseRequest.getHttpFields().listIterator(); i.hasNext();)
{
HttpField field = i.next();
if (field.getHeader()!=HttpHeader.CONTENT_ENCODING)
continue;
if (field.getValue().equalsIgnoreCase("gzip"))
if (field.getHeader()==HttpHeader.CONTENT_ENCODING)
{
i.set(X_CE_GZIP);
baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(),_inflateBufferSize));
break;
}
if (field.getValue().equalsIgnoreCase("gzip"))
{
i.set(X_CE_GZIP);
inflate = true;
break;
}
if (COMMA_GZIP.matcher(field.getValue()).matches())
{
String v = field.getValue();
v = v.substring(0,v.lastIndexOf(','));
i.set(new HttpField(HttpHeader.CONTENT_ENCODING,v));
i.add(X_CE_GZIP);
baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(),_inflateBufferSize));
break;
if (COMMA_GZIP.matcher(field.getValue()).matches())
{
String v = field.getValue();
v = v.substring(0, v.lastIndexOf(','));
i.set(new HttpField(HttpHeader.CONTENT_ENCODING, v));
i.add(X_CE_GZIP);
inflate = true;
break;
}
}
}
}
if (inflate)
{
baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(), _inflateBufferSize));
for (ListIterator<HttpField> i = baseRequest.getHttpFields().listIterator(); i.hasNext();)
{
HttpField field = i.next();
if (field.getHeader()==HttpHeader.CONTENT_LENGTH)
{
i.set(new HttpField("X-Content-Length", field.getValue()));
break;
}
}
}
}
// Are we already being gzipped?

View File

@ -27,7 +27,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
@ -38,7 +37,8 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class CustomResourcesMonitorTest
{
@ -111,28 +111,14 @@ public class CustomResourcesMonitorTest
InputStream input1 = socket1.getInputStream();
assertTrue(_fileOnDirectoryMonitor.isLowOnResources());
try
{
input1.read();
fail();
}
catch (SocketTimeoutException expected)
{
}
assertThrows(SocketTimeoutException.class, () -> input1.read());
// Wait a couple of lowResources idleTimeouts.
Thread.sleep(2 * lowResourcesIdleTimeout);
// Verify the new socket is still open.
assertTrue(_fileOnDirectoryMonitor.isLowOnResources());
try
{
input1.read();
fail();
}
catch (SocketTimeoutException expected)
{
}
assertThrows(SocketTimeoutException.class, () -> input1.read());
Files.delete( tmpFile );

View File

@ -23,6 +23,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -38,7 +39,6 @@ import java.util.concurrent.CountDownLatch;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.*;
public class LowResourcesMonitorTest
@ -248,28 +248,15 @@ public class LowResourcesMonitorTest
InputStream input1 = socket1.getInputStream();
assertTrue(_lowResourcesMonitor.isLowOnResources());
try
{
input1.read();
fail();
}
catch (SocketTimeoutException expected)
{
}
assertThrows( SocketTimeoutException.class, () -> input1.read());
// Wait a couple of lowResources idleTimeouts.
Thread.sleep(2 * lowResourcesIdleTimeout);
// Verify the new socket is still open.
assertTrue(_lowResourcesMonitor.isLowOnResources());
try
{
input1.read();
fail();
}
catch (SocketTimeoutException expected)
{
}
assertThrows( SocketTimeoutException.class, () -> input1.read());
// Let the maxLowResourcesTime elapse.
Thread.sleep(maxLowResourcesTime);

View File

@ -19,7 +19,9 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.stream.Collectors;
import javax.servlet.DispatcherType;
@ -86,11 +88,31 @@ public class FilterMapping implements Dumpable
throw new IllegalArgumentException(type.toString());
}
/* ------------------------------------------------------------ */
/** Dispatch type from name
* @param type the dispatcher type
* @return the type constant ({@link #REQUEST}, {@link #ASYNC}, {@link #FORWARD}, {@link #INCLUDE}, or {@link #ERROR})
*/
public static DispatcherType dispatch(int type)
{
switch(type)
{
case REQUEST:
return DispatcherType.REQUEST;
case ASYNC:
return DispatcherType.ASYNC;
case FORWARD:
return DispatcherType.FORWARD;
case INCLUDE:
return DispatcherType.INCLUDE;
case ERROR:
return DispatcherType.ERROR;
}
throw new IllegalArgumentException(Integer.toString(type));
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private int _dispatches=DEFAULT;
private String _filterName;
private transient FilterHolder _holder;
@ -122,7 +144,7 @@ public class FilterMapping implements Dumpable
/* ------------------------------------------------------------ */
/** Check if this filter applies to a particular dispatch type.
* @param type The type of request:
* {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}.
* {@link #REQUEST}, {@link #FORWARD}, {@link #INCLUDE} or {@link #ERROR}.
* @return <code>true</code> if this filter applies
*/
boolean appliesTo(int type)
@ -295,9 +317,9 @@ public class FilterMapping implements Dumpable
public String toString()
{
return
TypeUtil.asList(_pathSpecs)+"/"+
TypeUtil.asList(_servletNames)+"=="+
_dispatches+"=>"+
TypeUtil.asList(_pathSpecs)+"/"+
TypeUtil.asList(_servletNames)+"/"+
Arrays.stream(DispatcherType.values()).filter(this::appliesTo).collect(Collectors.toSet())+"=>"+
_filterName;
}

View File

@ -21,54 +21,117 @@ package org.eclipse.jetty.servlet;
import java.util.EventListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* ListenerHolder
*
* Specialization of AbstractHolder for servlet listeners. This
* Specialization of BaseHolder for servlet listeners. This
* allows us to record where the listener originated - web.xml,
* annotation, api etc.
*/
public class ListenerHolder extends BaseHolder<EventListener>
{
private EventListener _listener;
private boolean _initialized = false;
public ListenerHolder ()
{
this (Source.EMBEDDED);
}
public ListenerHolder(Source source)
{
super(source);
}
public void setListener(EventListener listener)
{
_listener = listener;
setClassName(listener.getClass().getName());
setHeldClass(listener.getClass());
_extInstance=true;
}
public ListenerHolder(Class<? extends EventListener> listenerClass)
{
super(Source.EMBEDDED);
setHeldClass(listenerClass);
}
public EventListener getListener()
{
return _listener;
}
/**
* Set an explicit instance. In this case,
* just like ServletHolder and FilterHolder,
* the listener will not be introspected for
* annotations like Resource etc.
*
* @param listener
*/
public void setListener (EventListener listener)
{
_listener = listener;
_extInstance=true;
setHeldClass(_listener.getClass());
setClassName(_listener.getClass().getName());
}
public void initialize (ServletContext context) throws Exception
{
if (!_initialized)
{
initialize();
if (_listener == null)
{
//create an instance of the listener and decorate it
try
{
_listener = (context instanceof ServletContextHandler.Context)
?((ServletContextHandler.Context)context).createListener(getHeldClass())
:getHeldClass().getDeclaredConstructor().newInstance();
}
catch (ServletException se)
{
Throwable cause = se.getRootCause();
if (cause instanceof InstantiationException)
throw (InstantiationException)cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException)cause;
throw se;
}
}
_initialized = true;
}
}
@Override
public void doStart() throws Exception
{
//Listeners always have an instance eagerly created, it cannot be deferred to the doStart method
if (_listener == null)
throw new IllegalStateException("No listener instance");
super.doStart();
if (!java.util.EventListener.class.isAssignableFrom(_class))
{
String msg = _class+" is not a java.util.EventListener";
super.stop();
throw new IllegalStateException(msg);
}
}
@Override
public void doStop() throws Exception
{
super.doStop();
if (!_extInstance)
_listener = null;
_initialized = false;
}
@Override
public String toString()
{
return super.toString()+(_listener == null?"":": "+getClassName());
}
return super.toString()+": "+getClassName();
}
}

View File

@ -153,7 +153,7 @@ public class ServletContextHandler extends ContextHandler
/* ------------------------------------------------------------ */
public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler,int options)
{
super((ContextHandler.Context)null);
super(parent, contextPath);
_options=options;
_scontext = new Context();
_sessionHandler = sessionHandler;
@ -163,15 +163,6 @@ public class ServletContextHandler extends ContextHandler
_objFactory = new DecoratedObjectFactory();
_objFactory.addDecorator(new DeprecationWarning());
if (contextPath!=null)
setContextPath(contextPath);
if (parent instanceof HandlerWrapper)
((HandlerWrapper)parent).setHandler(this);
else if (parent instanceof HandlerCollection)
((HandlerCollection)parent).addHandler(this);
// Link the handlers
relinkHandlers();
@ -356,13 +347,16 @@ public class ServletContextHandler extends ContextHandler
if (_servletHandler != null)
{
// Call decorators on all holders, and also on any EventListeners before
// decorators are called on any other classes (like servlets and filters)
//Ensure listener instances are created, added to ContextHandler
if(_servletHandler.getListeners() != null)
{
for (ListenerHolder holder:_servletHandler.getListeners())
{
_objFactory.decorate(holder.getListener());
holder.start();
//we need to pass in the context because the ServletHandler has not
//yet got a reference to the ServletContext (happens in super.startContext)
holder.initialize(_scontext);
addEventListener(holder.getListener());
}
}
}

Some files were not shown because too many files have changed in this diff Show More