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