Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.1.x

This commit is contained in:
gregw 2024-08-28 20:29:02 +10:00
commit 1ea02673ac
22 changed files with 817 additions and 216 deletions

View File

@ -47,6 +47,7 @@ import org.eclipse.jetty.http.MultiPartCompliance;
import org.eclipse.jetty.http.MultiPartConfig; import org.eclipse.jetty.http.MultiPartConfig;
import org.eclipse.jetty.http.Trailers; import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.internal.CompletionStreamWrapper; import org.eclipse.jetty.server.internal.CompletionStreamWrapper;
import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Attributes;
@ -716,6 +717,7 @@ public interface Request extends Attributes, Content.Source
* is returned, then this method must not generate a response, nor complete the callback. * is returned, then this method must not generate a response, nor complete the callback.
* @throws Exception if there is a failure during the handling. Catchers cannot assume that the callback will be * @throws Exception if there is a failure during the handling. Catchers cannot assume that the callback will be
* called and thus should attempt to complete the request as if a false had been returned. * called and thus should attempt to complete the request as if a false had been returned.
* @see AbortException
*/ */
boolean handle(Request request, Response response, Callback callback) throws Exception; boolean handle(Request request, Response response, Callback callback) throws Exception;
@ -725,6 +727,34 @@ public interface Request extends Attributes, Content.Source
{ {
return InvocationType.BLOCKING; return InvocationType.BLOCKING;
} }
/**
* A marker {@link Exception} that can be passed the {@link Callback#failed(Throwable)} of the {@link Callback}
* passed in {@link #handle(Request, Response, Callback)}, to cause request handling to be aborted. For HTTP/1
* an abort is handled with a {@link EndPoint#close()}, for later versions of HTTP, a reset message will be sent.
*/
class AbortException extends Exception
{
public AbortException()
{
super();
}
public AbortException(String message)
{
super(message);
}
public AbortException(String message, Throwable cause)
{
super(message, cause);
}
public AbortException(Throwable cause)
{
super(cause);
}
}
} }
/** /**

View File

@ -1590,18 +1590,18 @@ public class HttpChannelState implements HttpChannel, Components
httpChannelState._callbackFailure = failure; httpChannelState._callbackFailure = failure;
// Consume any input. if (!stream.isCommitted() && !(failure instanceof Request.Handler.AbortException))
Throwable unconsumed = stream.consumeAvailable(); {
ExceptionUtil.addSuppressedIfNotAssociated(failure, unconsumed); // Consume any input.
Throwable unconsumed = stream.consumeAvailable();
ExceptionUtil.addSuppressedIfNotAssociated(failure, unconsumed);
ChannelResponse response = httpChannelState._response; ChannelResponse response = httpChannelState._response;
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("failed stream.isCommitted={}, response.isCommitted={} {}", stream.isCommitted(), response.isCommitted(), this); LOG.debug("failed stream.isCommitted={}, response.isCommitted={} {}", stream.isCommitted(), response.isCommitted(), this);
// There may have been an attempt to write an error response that failed.
// Do not try to write again an error response if already committed.
if (!stream.isCommitted())
errorResponse = new ErrorResponse(request); errorResponse = new ErrorResponse(request);
}
} }
if (errorResponse != null) if (errorResponse != null)

View File

@ -816,4 +816,25 @@ public class TypeUtil
int result = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(value - 1)); int result = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(value - 1));
return result > 0 ? result : Integer.MAX_VALUE; return result > 0 ? result : Integer.MAX_VALUE;
} }
/**
* Test is a method has been declared on the class of an instance
* @param object The object to check
* @param methodName The method name
* @param args The arguments to the method
* @return {@code true} iff {@link Class#getDeclaredMethod(String, Class[])} can be called on the
* {@link Class} of the object, without throwing {@link NoSuchMethodException}.
*/
public static boolean isDeclaredMethodOn(Object object, String methodName, Class<?>... args)
{
try
{
object.getClass().getDeclaredMethod(methodName, args);
return true;
}
catch (NoSuchMethodException e)
{
return false;
}
}
} }

View File

@ -77,6 +77,7 @@ import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -86,6 +87,7 @@ import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources; import org.eclipse.jetty.util.resource.Resources;
import org.eclipse.jetty.util.security.CertificateUtils; import org.eclipse.jetty.util.security.CertificateUtils;
import org.eclipse.jetty.util.security.CertificateValidator; import org.eclipse.jetty.util.security.CertificateValidator;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.util.security.Password;
import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -157,9 +159,9 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
private Resource _trustStoreResource; private Resource _trustStoreResource;
private String _trustStoreProvider; private String _trustStoreProvider;
private String _trustStoreType; private String _trustStoreType;
private Password _keyStorePassword; private Credential _keyStoreCredential;
private Password _keyManagerPassword; private Credential _keyManagerCredential;
private Password _trustStorePassword; private Credential _trustStoreCredential;
private String _sslProvider; private String _sslProvider;
private String _sslProtocol = "TLS"; private String _sslProtocol = "TLS";
private String _secureRandomAlgorithm; private String _secureRandomAlgorithm;
@ -811,46 +813,42 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
public String getKeyStorePassword() public String getKeyStorePassword()
{ {
return _keyStorePassword == null ? null : _keyStorePassword.toString(); return _keyStoreCredential == null ? null : _keyStoreCredential.toString();
} }
/** /**
* @param password The password for the key store. If null is passed and * @param password The password for the key store. If null is passed then
* a keystore is set, then * {@link #getCredential(String)} is used to obtain a password from
* the {@link #getPassword(String)} is used to * the {@value #PASSWORD_PROPERTY} system property.
* obtain a password either from the {@value #PASSWORD_PROPERTY}
* system property.
*/ */
public void setKeyStorePassword(String password) public void setKeyStorePassword(String password)
{ {
_keyStorePassword = password == null ? getPassword(PASSWORD_PROPERTY) : newPassword(password); _keyStoreCredential = password == null ? getCredential(PASSWORD_PROPERTY) : newCredential(password);
} }
public String getKeyManagerPassword() public String getKeyManagerPassword()
{ {
return _keyManagerPassword == null ? null : _keyManagerPassword.toString(); return _keyManagerCredential == null ? null : _keyManagerCredential.toString();
} }
/** /**
* @param password The password (if any) for the specific key within the key store. * @param password The password (if any) for the specific key within the key store.
* If null is passed and the {@value #KEYPASSWORD_PROPERTY} system property is set, * If null is passed then {@link #getCredential(String)} is used to
* then the {@link #getPassword(String)} is used to
* obtain a password from the {@value #KEYPASSWORD_PROPERTY} system property. * obtain a password from the {@value #KEYPASSWORD_PROPERTY} system property.
*/ */
public void setKeyManagerPassword(String password) public void setKeyManagerPassword(String password)
{ {
_keyManagerPassword = password == null ? getPassword(KEYPASSWORD_PROPERTY) : newPassword(password); _keyManagerCredential = password == null ? getCredential(KEYPASSWORD_PROPERTY) : newCredential(password);
} }
/** /**
* @param password The password for the truststore. If null is passed then * @param password The password for the truststore. If null is passed then
* the {@link #getPassword(String)} is used to * {@link #getCredential(String)} is used to obtain a password from the {@value #PASSWORD_PROPERTY}
* obtain a password from the {@value #PASSWORD_PROPERTY}
* system property. * system property.
*/ */
public void setTrustStorePassword(String password) public void setTrustStorePassword(String password)
{ {
_trustStorePassword = password == null ? getPassword(PASSWORD_PROPERTY) : newPassword(password); _trustStoreCredential = password == null ? getCredential(PASSWORD_PROPERTY) : newCredential(password);
} }
/** /**
@ -1133,7 +1131,7 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
*/ */
protected KeyStore loadKeyStore(Resource resource) throws Exception protected KeyStore loadKeyStore(Resource resource) throws Exception
{ {
String storePassword = Objects.toString(_keyStorePassword, null); String storePassword = Objects.toString(_keyStoreCredential, null);
return CertificateUtils.getKeyStore(resource, getKeyStoreType(), getKeyStoreProvider(), storePassword); return CertificateUtils.getKeyStore(resource, getKeyStoreType(), getKeyStoreProvider(), storePassword);
} }
@ -1148,12 +1146,12 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
{ {
String type = Objects.toString(getTrustStoreType(), getKeyStoreType()); String type = Objects.toString(getTrustStoreType(), getKeyStoreType());
String provider = Objects.toString(getTrustStoreProvider(), getKeyStoreProvider()); String provider = Objects.toString(getTrustStoreProvider(), getKeyStoreProvider());
Password passwd = _trustStorePassword; Credential passwd = _trustStoreCredential;
if (resource == null || resource.equals(_keyStoreResource)) if (resource == null || resource.equals(_keyStoreResource))
{ {
resource = _keyStoreResource; resource = _keyStoreResource;
if (passwd == null) if (passwd == null)
passwd = _keyStorePassword; passwd = _keyStoreCredential;
} }
return CertificateUtils.getKeyStore(resource, type, provider, Objects.toString(passwd, null)); return CertificateUtils.getKeyStore(resource, type, provider, Objects.toString(passwd, null));
} }
@ -1180,7 +1178,7 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
if (keyStore != null) if (keyStore != null)
{ {
KeyManagerFactory keyManagerFactory = getKeyManagerFactoryInstance(); KeyManagerFactory keyManagerFactory = getKeyManagerFactoryInstance();
keyManagerFactory.init(keyStore, _keyManagerPassword == null ? (_keyStorePassword == null ? null : _keyStorePassword.toString().toCharArray()) : _keyManagerPassword.toString().toCharArray()); keyManagerFactory.init(keyStore, _keyManagerCredential == null ? (_keyStoreCredential == null ? null : _keyStoreCredential.toString().toCharArray()) : _keyManagerCredential.toString().toCharArray());
managers = keyManagerFactory.getKeyManagers(); managers = keyManagerFactory.getKeyManagers();
if (managers != null) if (managers != null)
@ -1615,7 +1613,9 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
* *
* @param realm the realm * @param realm the realm
* @return the Password object * @return the Password object
* @deprecated use {#link getCredential} instead.
*/ */
@Deprecated(since = "12.0.13", forRemoval = true)
protected Password getPassword(String realm) protected Password getPassword(String realm)
{ {
String password = System.getProperty(realm); String password = System.getProperty(realm);
@ -1627,12 +1627,43 @@ public abstract class SslContextFactory extends ContainerLifeCycle implements Du
* *
* @param password the password string * @param password the password string
* @return the new Password object * @return the new Password object
* @deprecated use {#link newCredential} instead.
*/ */
@Deprecated(since = "12.0.13", forRemoval = true)
public Password newPassword(String password) public Password newPassword(String password)
{ {
return new Password(password); return new Password(password);
} }
/**
* Returns the credential object for the given realm.
*
* @param realm the realm
* @return the Credential object
*/
protected Credential getCredential(String realm)
{
if (TypeUtil.isDeclaredMethodOn(this, "getPassword", String.class))
return getPassword(realm);
String password = System.getProperty(realm);
return password == null ? null : newCredential(password);
}
/**
* Creates a new Credential object.
*
* @param password the password string
* @return the new Credential object
*/
public Credential newCredential(String password)
{
if (TypeUtil.isDeclaredMethodOn(this, "newPassword", String.class))
return newPassword(password);
return Credential.getCredential(password);
}
public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException public SSLServerSocket newSslServerSocket(String host, int port, int backlog) throws IOException
{ {
checkIsStarted(); checkIsStarted();

View File

@ -267,4 +267,34 @@ public class TypeUtilTest
assertThat(TypeUtil.ceilToNextPowerOfTwo(5), is(8)); assertThat(TypeUtil.ceilToNextPowerOfTwo(5), is(8));
assertThat(TypeUtil.ceilToNextPowerOfTwo(Integer.MAX_VALUE - 1), is(Integer.MAX_VALUE)); assertThat(TypeUtil.ceilToNextPowerOfTwo(Integer.MAX_VALUE - 1), is(Integer.MAX_VALUE));
} }
public static class Base
{
protected String methodA(String arg)
{
return "a" + arg.length();
}
protected String methodB(String arg)
{
return "b" + arg.length();
}
}
public static class Example extends Base
{
@Override
protected String methodB(String arg)
{
return "B" + arg;
}
}
@Test
public void testIsMethodDeclaredOn()
{
Example example = new Example();
assertFalse(TypeUtil.isDeclaredMethodOn(example, "methodA", String.class));
assertTrue(TypeUtil.isDeclaredMethodOn(example, "methodB", String.class));
}
} }

View File

@ -140,7 +140,7 @@ public class ServletApiResponse implements HttpServletResponse
{ {
switch (sc) switch (sc)
{ {
case -1 -> getServletChannel().abort(new IOException(msg)); case -1 -> getServletChannel().abort(new Request.Handler.AbortException(msg));
case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINTS_103 -> case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINTS_103 ->
{ {
if (!isCommitted()) if (!isCommitted())

View File

@ -13,8 +13,12 @@
package org.eclipse.jetty.ee10.servlet; package org.eclipse.jetty.ee10.servlet;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -50,6 +54,7 @@ import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
@ -70,6 +75,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertFalse; 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -780,6 +786,220 @@ public class ErrorPageTest
assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599")); assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599"));
} }
@Test
public void testAbortWithSendError() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorChunked() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.getOutputStream().write("test".getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertTrue(chunked);
line = in.readLine();
assertThat(line, is("4"));
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorContent() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.setContentLength(10);
response.getOutputStream().write("test\r\n".getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertFalse(chunked);
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorComplete() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.setContentLength(6);
response.getOutputStream().write("test\r\n".getBytes(StandardCharsets.UTF_8));
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertFalse(chunked);
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test @Test
public void testErrorCodeNoDefaultServletNonExistentErrorLocation() throws Exception public void testErrorCodeNoDefaultServletNonExistentErrorLocation() throws Exception
{ {

View File

@ -143,7 +143,7 @@ public class ServletApiResponse implements HttpServletResponse
{ {
switch (sc) switch (sc)
{ {
case -1 -> getServletChannel().abort(new IOException(msg)); case -1 -> getServletChannel().abort(new Request.Handler.AbortException(msg));
case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINTS_103 -> case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINTS_103 ->
{ {
if (!isCommitted()) if (!isCommitted())

View File

@ -13,8 +13,12 @@
package org.eclipse.jetty.ee11.servlet; package org.eclipse.jetty.ee11.servlet;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -50,6 +54,7 @@ import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
@ -70,6 +75,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertFalse; 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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -836,6 +842,220 @@ public class ErrorPageTest
assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599")); assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599"));
} }
@Test
public void testAbortWithSendError() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorChunked() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.getOutputStream().write("test".getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertTrue(chunked);
line = in.readLine();
assertThat(line, is("4"));
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorContent() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.setContentLength(10);
response.getOutputStream().write("test\r\n".getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertFalse(chunked);
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test
public void testAbortWithSendErrorComplete() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
HttpServlet failServlet = new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.setContentLength(6);
response.getOutputStream().write("test\r\n".getBytes(StandardCharsets.UTF_8));
response.sendError(-1);
}
};
contextHandler.addServlet(failServlet, "/abort");
startServer(contextHandler);
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertThat(line, is("HTTP/1.1 200 OK"));
boolean chunked = false;
while (!line.isEmpty())
{
line = in.readLine();
assertNotNull(line);
chunked |= line.equals("Transfer-Encoding: chunked");
}
assertFalse(chunked);
line = in.readLine();
assertThat(line, is("test"));
line = in.readLine();
assertNull(line);
}
}
@Test @Test
public void testErrorCodeNoDefaultServletNonExistentErrorLocation() throws Exception public void testErrorCodeNoDefaultServletNonExistentErrorLocation() throws Exception
{ {

View File

@ -488,7 +488,7 @@ public class Response implements HttpServletResponse
switch (code) switch (code)
{ {
case -1 -> _channel.abort(new IOException(message)); case -1 -> _channel.abort(new org.eclipse.jetty.server.Request.Handler.AbortException(message));
case HttpStatus.PROCESSING_102 -> sendProcessing(); case HttpStatus.PROCESSING_102 -> sendProcessing();
case HttpStatus.EARLY_HINTS_103 -> sendEarlyHint(); case HttpStatus.EARLY_HINTS_103 -> sendEarlyHint();
default -> _channel.getState().sendError(code, message); default -> _channel.getState().sendError(code, message);

View File

@ -13,8 +13,12 @@
package org.eclipse.jetty.ee9.servlet; package org.eclipse.jetty.ee9.servlet;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -29,7 +33,6 @@ import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig; import jakarta.servlet.FilterConfig;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.Servlet; import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
@ -49,10 +52,10 @@ import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -62,6 +65,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
//@Disabled // TODO //@Disabled // TODO
@ -105,6 +109,7 @@ public class ErrorPageTest
_context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*"); _context.addServlet(ErrorAndStatusServlet.class, "/error-and-status/*");
_context.addServlet(ErrorContentTypeCharsetWriterInitializedServlet.class, "/error-mime-charset-writer/*"); _context.addServlet(ErrorContentTypeCharsetWriterInitializedServlet.class, "/error-mime-charset-writer/*");
_context.addServlet(ExceptionServlet.class, "/exception-servlet"); _context.addServlet(ExceptionServlet.class, "/exception-servlet");
_context.addServlet(AbortServlet.class, "/abort");
HandlerWrapper noopHandler = new HandlerWrapper() HandlerWrapper noopHandler = new HandlerWrapper()
{ {
@ -300,6 +305,34 @@ public class ErrorPageTest
assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /fail/code")); assertThat(response, Matchers.containsString("ERROR_REQUEST_URI: /fail/code"));
} }
@Test
public void testAbortWithSendError() throws Exception
{
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
contextHandler.setContextPath("/");
ServerConnector connector = new ServerConnector(_server);
connector.setPort(0);
_server.addConnector(connector);
connector.start();
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
OutputStream output = socket.getOutputStream();
String request = """
GET /abort HTTP/1.1\r
Host: test\r
\r
""";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = in.readLine();
assertNull(line);
}
}
@Test @Test
public void testErrorException() throws Exception public void testErrorException() throws Exception
{ {
@ -871,4 +904,13 @@ public class ErrorPageTest
super(rootCause); super(rootCause);
} }
} }
public static class AbortServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
{
response.sendError(-1);
}
}
} }

View File

@ -23,10 +23,21 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId> <artifactId>bson</artifactId>
<version>${mongodb.version}</version> <version>${mongodb.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>${mongodb.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

View File

@ -14,11 +14,15 @@ sessions
sessions/mongo/${connection-type} sessions/mongo/${connection-type}
[files] [files]
maven://org.mongodb/mongo-java-driver/${mongodb.version}|lib/nosql/mongo-java-driver-${mongodb.version}.jar maven://org.mongodb/mongodb-driver-sync/${mongodb.version}|lib/nosql/mongodb-driver-sync-${mongodb.version}.jar
maven://org.mongodb/mongodb-driver-core/${mongodb.version}|lib/nosql/mongodb-driver-core-${mongodb.version}.jar
maven://org.mongodb/bson/${mongodb.version}|lib/nosql/bson-${mongodb.version}.jar
[lib] [lib]
lib/jetty-nosql-${jetty.version}.jar lib/jetty-nosql-${jetty.version}.jar
lib/nosql/mongo-java-driver-${mongodb.version}.jar lib/nosql/mongodb-driver-sync-${mongodb.version}.jar
lib/nosql/mongodb-driver-core-${mongodb.version}.jar
lib/nosql/bson-${mongodb.version}.jar
[license] [license]
The java driver for the MongoDB document-based database system is hosted on GitHub and released under the Apache 2.0 license. The java driver for the MongoDB document-based database system is hosted on GitHub and released under the Apache 2.0 license.

View File

@ -13,7 +13,9 @@
module org.eclipse.jetty.nosql module org.eclipse.jetty.nosql
{ {
requires transitive mongo.java.driver; requires transitive org.mongodb.driver.core;
requires transitive org.mongodb.driver.sync.client;
requires transitive org.mongodb.bson;
requires transitive org.eclipse.jetty.session; requires transitive org.eclipse.jetty.session;
exports org.eclipse.jetty.nosql; exports org.eclipse.jetty.nosql;

View File

@ -62,7 +62,7 @@ public abstract class NoSqlSessionDataStore extends ObjectStreamSessionDataStore
public Set<String> getAllAttributeNames() public Set<String> getAllAttributeNames()
{ {
return new HashSet<String>(_attributes.keySet()); return new HashSet<>(_attributes.keySet());
} }
} }

View File

@ -15,22 +15,26 @@ package org.eclipse.jetty.nosql.mongodb;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.MongoException; import com.mongodb.MongoException;
import com.mongodb.WriteConcern; import com.mongodb.client.FindIterable;
import com.mongodb.WriteResult; import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.Binary;
import org.eclipse.jetty.nosql.NoSqlSessionDataStore; import org.eclipse.jetty.nosql.NoSqlSessionDataStore;
import org.eclipse.jetty.session.SessionContext; import org.eclipse.jetty.session.SessionContext;
import org.eclipse.jetty.session.SessionData; import org.eclipse.jetty.session.SessionData;
@ -155,15 +159,15 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
/** /**
* Access to MongoDB * Access to MongoDB
*/ */
private DBCollection _dbSessions; private MongoCollection<Document> _dbSessions;
public void setDBCollection(DBCollection collection) public void setDBCollection(MongoCollection<Document> collection)
{ {
_dbSessions = collection; _dbSessions = collection;
} }
@ManagedAttribute(value = "DBCollection", readonly = true) @ManagedAttribute(value = "DBCollection", readonly = true)
public DBCollection getDBCollection() public MongoCollection<Document> getDBCollection()
{ {
return _dbSessions; return _dbSessions;
} }
@ -171,7 +175,7 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
@Override @Override
public SessionData doLoad(String id) throws Exception public SessionData doLoad(String id) throws Exception
{ {
DBObject sessionDocument = _dbSessions.findOne(new BasicDBObject(__ID, id)); Document sessionDocument = _dbSessions.find(Filters.eq(__ID, id)).first();
try try
{ {
@ -191,7 +195,8 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
Object version = MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__VERSION)); Object version = MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__VERSION));
Long lastSaved = (Long)MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__LASTSAVED)); Long lastSaved = (Long)MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__LASTSAVED));
String lastNode = (String)MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__LASTNODE)); String lastNode = (String)MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__LASTNODE));
byte[] attributes = (byte[])MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__ATTRIBUTES)); Binary binary = ((Binary)MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__ATTRIBUTES)));
byte[] attributes = binary == null ? null : binary.getData();
Long created = (Long)sessionDocument.get(__CREATED); Long created = (Long)sessionDocument.get(__CREATED);
Long accessed = (Long)sessionDocument.get(__ACCESSED); Long accessed = (Long)sessionDocument.get(__ACCESSED);
@ -202,7 +207,7 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
NoSqlSessionData data = null; NoSqlSessionData data = null;
// get the session for the context // get the session for the context
DBObject sessionSubDocumentForContext = (DBObject)MongoUtils.getNestedValue(sessionDocument, getContextField()); Document sessionSubDocumentForContext = (Document)MongoUtils.getNestedValue(sessionDocument, getContextField());
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("attrs {}", sessionSubDocumentForContext); LOG.debug("attrs {}", sessionSubDocumentForContext);
@ -239,9 +244,9 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
else else
{ {
//attributes have special serialized format //attributes have special serialized format
try (ByteArrayInputStream bais = new ByteArrayInputStream(attributes);) try (ByteArrayInputStream bais = new ByteArrayInputStream(attributes))
{ {
deserializeAttributes(data, bais); deserializeAttributes(data, bais);
} }
} }
} }
@ -269,17 +274,17 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
* Check if the session exists and if it does remove the context * Check if the session exists and if it does remove the context
* associated with this session * associated with this session
*/ */
BasicDBObject mongoKey = new BasicDBObject(__ID, id); Bson filterId = Filters.eq(__ID, id);
DBObject sessionDocument = _dbSessions.findOne(new BasicDBObject(__ID, id)); Document sessionDocument = _dbSessions.find(filterId).first();
if (sessionDocument != null) if (sessionDocument != null)
{ {
DBObject c = (DBObject)MongoUtils.getNestedValue(sessionDocument, __CONTEXT); Document c = (Document)MongoUtils.getNestedValue(sessionDocument, __CONTEXT);
if (c == null) if (c == null)
{ {
//delete whole doc //delete whole doc
_dbSessions.remove(mongoKey, WriteConcern.SAFE); _dbSessions.deleteOne(filterId);
return false; return false;
} }
@ -287,14 +292,14 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
if (contexts.isEmpty()) if (contexts.isEmpty())
{ {
//delete whole doc //delete whole doc
_dbSessions.remove(mongoKey, WriteConcern.SAFE); _dbSessions.deleteOne(filterId);
return false; return false;
} }
if (contexts.size() == 1 && contexts.iterator().next().equals(getCanonicalContextId())) if (contexts.size() == 1 && contexts.iterator().next().equals(getCanonicalContextId()))
{ {
//delete whole doc //delete whole doc
_dbSessions.remove(new BasicDBObject(__ID, id), WriteConcern.SAFE); _dbSessions.deleteOne(filterId);
return true; return true;
} }
@ -303,7 +308,7 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
BasicDBObject unsets = new BasicDBObject(); BasicDBObject unsets = new BasicDBObject();
unsets.put(getContextField(), 1); unsets.put(getContextField(), 1);
remove.put("$unset", unsets); remove.put("$unset", unsets);
_dbSessions.update(mongoKey, remove, false, false, WriteConcern.SAFE); _dbSessions.updateOne(filterId, remove);
return true; return true;
} }
else else
@ -315,12 +320,9 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
@Override @Override
public boolean doExists(String id) throws Exception public boolean doExists(String id) throws Exception
{ {
DBObject fields = new BasicDBObject(); Bson projection = Projections.fields(Projections.include(__ID, __VALID, __EXPIRY, __VERSION, getContextField()), Projections.excludeId());
fields.put(__EXPIRY, 1); Bson filterId = Filters.eq(__ID, id);
fields.put(__VALID, 1); Document sessionDocument = _dbSessions.find(filterId).projection(projection).first();
fields.put(getContextSubfield(__VERSION), 1);
DBObject sessionDocument = _dbSessions.findOne(new BasicDBObject(__ID, id), fields);
if (sessionDocument == null) if (sessionDocument == null)
return false; //doesn't exist return false; //doesn't exist
@ -332,48 +334,33 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
Long expiry = (Long)sessionDocument.get(__EXPIRY); Long expiry = (Long)sessionDocument.get(__EXPIRY);
//expired? //expired?
if (expiry.longValue() > 0 && expiry.longValue() < System.currentTimeMillis()) if (expiry != null && expiry > 0 && expiry < System.currentTimeMillis())
return false; //it's expired return false; //it's expired
//does it exist for this context? //does it exist for this context?
Object version = MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__VERSION)); Object version = MongoUtils.getNestedValue(sessionDocument, getContextSubfield(__VERSION));
if (version == null) return version != null;
return false;
return true;
} }
@Override @Override
public Set<String> doCheckExpired(Set<String> candidates, long time) public Set<String> doCheckExpired(Set<String> candidates, long time)
{ {
Set<String> expiredSessions = new HashSet<>();
//firstly ask mongo to verify if these candidate ids have expired - all of //firstly ask mongo to verify if these candidate ids have expired - all of
//these candidates will be for our node //these candidates will be for our node
BasicDBObject query = new BasicDBObject(); Bson query = Filters.and(
query.append(__ID, new BasicDBObject("$in", candidates)); Filters.in(__ID, candidates),
query.append(__EXPIRY, new BasicDBObject("$gt", 0).append("$lte", time)); Filters.gt(__EXPIRY, 0),
Filters.lte(__EXPIRY, time));
DBCursor verifiedExpiredSessions = null;
try
{
verifiedExpiredSessions = _dbSessions.find(query, new BasicDBObject(__ID, 1));
for (DBObject session : verifiedExpiredSessions)
{
String id = (String)session.get(__ID);
if (LOG.isDebugEnabled())
LOG.debug("{} Mongo confirmed expired session {}", _context, id);
expiredSessions.add(id);
}
}
finally
{
if (verifiedExpiredSessions != null)
verifiedExpiredSessions.close();
}
//check through sessions that were candidates, but not found as expired.
FindIterable<Document> verifiedExpiredSessions = _dbSessions.find(query); // , new BasicDBObject(__ID, 1)
Set<String> expiredSessions =
StreamSupport.stream(verifiedExpiredSessions.spliterator(), false)
.map(document -> document.getString(__ID))
.collect(Collectors.toSet());
//check through sessions that were candidates, but not found as expired.
//they may no longer be persisted, in which case they are treated as expired. //they may no longer be persisted, in which case they are treated as expired.
for (String c:candidates) for (String c:candidates)
{ {
@ -398,37 +385,17 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
{ {
// now ask mongo to find sessions for this context, last managed by any // now ask mongo to find sessions for this context, last managed by any
// node, that expired before timeLimit // node, that expired before timeLimit
Set<String> expiredSessions = new HashSet<>(); Bson query = Filters.and(
Filters.gt(__EXPIRY, 0),
Filters.lte(__EXPIRY, timeLimit)
);
BasicDBObject query = new BasicDBObject(); //TODO we should verify if there is a session for my context, not any context
BasicDBObject gt = new BasicDBObject(__EXPIRY, new BasicDBObject("$gt", 0));
BasicDBObject lt = new BasicDBObject(__EXPIRY, new BasicDBObject("$lte", timeLimit));
BasicDBList list = new BasicDBList();
list.add(gt);
list.add(lt);
query.append("$and", list);
DBCursor oldExpiredSessions = null;
try
{
BasicDBObject bo = new BasicDBObject(__ID, 1);
bo.append(__EXPIRY, 1);
oldExpiredSessions = _dbSessions.find(query, bo);
for (DBObject session : oldExpiredSessions)
{
String id = (String)session.get(__ID);
//TODO we should verify if there is a session for my context, not any context
expiredSessions.add(id);
}
}
finally
{
if (oldExpiredSessions != null)
oldExpiredSessions.close();
}
FindIterable<Document> documents = _dbSessions.find(query);
Set<String> expiredSessions = StreamSupport.stream(documents.spliterator(), false)
.map(document -> document.getString(__ID))
.collect(Collectors.toSet());
return expiredSessions; return expiredSessions;
} }
@ -438,9 +405,11 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
//Delete all session documents where the expiry time (which is always the most //Delete all session documents where the expiry time (which is always the most
//up-to-date expiry of all contexts sharing that session id) has already past as //up-to-date expiry of all contexts sharing that session id) has already past as
//at the timeLimit. //at the timeLimit.
BasicDBObject query = new BasicDBObject(); Bson query = Filters.and(
query.append(__EXPIRY, new BasicDBObject("$gt", 0).append("$lte", timeLimit)); Filters.gt(__EXPIRY, 0),
_dbSessions.remove(query, WriteConcern.SAFE); Filters.lte(__EXPIRY, timeLimit)
);
_dbSessions.deleteMany(query);
} }
/** /**
@ -458,8 +427,7 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
public void doStore(String id, SessionData data, long lastSaveTime) throws Exception public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
{ {
// Form query for upsert // Form query for upsert
final BasicDBObject key = new BasicDBObject(__ID, id); Bson key = Filters.eq(__ID, id);;
// Form updates // Form updates
BasicDBObject update = new BasicDBObject(); BasicDBObject update = new BasicDBObject();
boolean upsert = false; boolean upsert = false;
@ -487,12 +455,13 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
sets.put(getContextSubfield(__LASTNODE), data.getLastNode()); sets.put(getContextSubfield(__LASTNODE), data.getLastNode());
version = ((Number)version).longValue() + 1L; version = ((Number)version).longValue() + 1L;
((NoSqlSessionData)data).setVersion(version); ((NoSqlSessionData)data).setVersion(version);
update.put("$inc", _version1); // what is this?? this field is used no where...
//sets.put("$inc", _version1);
//if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc //if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc
BasicDBObject fields = new BasicDBObject(); BasicDBObject fields = new BasicDBObject();
fields.append(__MAX_IDLE, true); fields.append(__MAX_IDLE, true);
fields.append(__EXPIRY, true); fields.append(__EXPIRY, true);
DBObject o = _dbSessions.findOne(new BasicDBObject("id", id), fields); Document o = _dbSessions.find(key).first();
if (o != null) if (o != null)
{ {
Long tmpLong = (Long)o.get(__MAX_IDLE); Long tmpLong = (Long)o.get(__MAX_IDLE);
@ -516,37 +485,39 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();) try (ByteArrayOutputStream baos = new ByteArrayOutputStream();)
{ {
serializeAttributes(data, baos); serializeAttributes(data, baos);
sets.put(getContextSubfield(__ATTRIBUTES), baos.toByteArray()); Binary binary = new Binary(baos.toByteArray());
sets.put(getContextSubfield(__ATTRIBUTES), binary);
} }
// Do the upsert // Do the upsert
if (!sets.isEmpty()) if (!sets.isEmpty())
update.put("$set", sets); update.put("$set", sets);
WriteResult res = _dbSessions.update(key, update, upsert, false, WriteConcern.SAFE); UpdateResult res = _dbSessions.updateOne(key, update, new UpdateOptions().upsert(upsert));
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Save:db.sessions.update( {}, {},{} )", key, update, res); LOG.debug("Save:db.sessions.update( {}, {},{} )", key, update, res);
} }
protected void ensureIndexes() throws MongoException protected void ensureIndexes() throws MongoException
{ {
_version1 = new BasicDBObject(getContextSubfield(__VERSION), 1); var indexes =
DBObject idKey = BasicDBObjectBuilder.start().add("id", 1).get(); StreamSupport.stream(_dbSessions.listIndexes().spliterator(), false)
_dbSessions.createIndex(idKey, .toList();
BasicDBObjectBuilder.start() var indexesNames = indexes.stream().map(document -> document.getString("name")).toList();
.add("name", "id_1") if (!indexesNames.contains("id_1"))
.add("ns", _dbSessions.getFullName()) {
.add("sparse", false) String createResult = _dbSessions.createIndex(Indexes.text("id"),
.add("unique", true) new IndexOptions().unique(true).name("id_1").sparse(false));
.get()); LOG.info("create index {}, result: {}", "id_1", createResult);
}
DBObject versionKey = BasicDBObjectBuilder.start().add("id", 1).add("version", 1).get(); if (!indexesNames.contains("id_1_version_1"))
_dbSessions.createIndex(versionKey, BasicDBObjectBuilder.start() {
.add("name", "id_1_version_1") // Command failed with error 67 (CannotCreateIndex): 'only one text index per collection allowed, found existing text index "id_1"'
.add("ns", _dbSessions.getFullName()) String createResult = _dbSessions.createIndex(
.add("sparse", false) Indexes.compoundIndex(Indexes.descending("id"), Indexes.descending("version")),
.add("unique", true) new IndexOptions().unique(false).name("id_1_version_1").sparse(false));
.get()); LOG.info("create index {}, result: {}", "id_1_version_1", createResult);
}
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Done ensure Mongodb indexes existing"); LOG.debug("Done ensure Mongodb indexes existing");
//TODO perhaps index on expiry time? //TODO perhaps index on expiry time?
@ -587,4 +558,4 @@ public class MongoSessionDataStore extends NoSqlSessionDataStore
{ {
return String.format("%s[collection=%s]", super.toString(), getDBCollection()); return String.format("%s[collection=%s]", super.toString(), getDBCollection());
} }
} }

View File

@ -15,8 +15,8 @@ package org.eclipse.jetty.nosql.mongodb;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import com.mongodb.MongoClient; import com.mongodb.client.MongoClient;
import com.mongodb.MongoClientURI; import com.mongodb.client.MongoClients;
import org.eclipse.jetty.session.AbstractSessionDataStoreFactory; import org.eclipse.jetty.session.AbstractSessionDataStoreFactory;
import org.eclipse.jetty.session.SessionDataStore; import org.eclipse.jetty.session.SessionDataStore;
import org.eclipse.jetty.session.SessionManager; import org.eclipse.jetty.session.SessionManager;
@ -136,14 +136,14 @@ public class MongoSessionDataStoreFactory extends AbstractSessionDataStoreFactor
MongoClient mongo; MongoClient mongo;
if (!StringUtil.isBlank(getConnectionString())) if (!StringUtil.isBlank(getConnectionString()))
mongo = new MongoClient(new MongoClientURI(getConnectionString())); mongo = MongoClients.create(getConnectionString());
else if (!StringUtil.isBlank(getHost()) && getPort() != -1) else if (!StringUtil.isBlank(getHost()) && getPort() != -1)
mongo = new MongoClient(getHost(), getPort()); mongo = MongoClients.create("mongodb://" + getHost() + ":" + getPort());
else if (!StringUtil.isBlank(getHost())) else if (!StringUtil.isBlank(getHost()))
mongo = new MongoClient(getHost()); mongo = MongoClients.create("mongodb://" + getHost());
else else
mongo = new MongoClient(); mongo = MongoClients.create();
store.setDBCollection(mongo.getDB(getDbName()).getCollection(getCollectionName())); store.setDBCollection(mongo.getDatabase(getDbName()).getCollection(getCollectionName()));
return store; return store;
} }
} }

View File

@ -22,7 +22,8 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import org.bson.Document;
import org.bson.types.Binary;
import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
@ -40,6 +41,13 @@ public class MongoUtils
{ {
return valueToDecode; return valueToDecode;
} }
else if (valueToDecode instanceof Binary)
{
final byte[] decodeObject = ((Binary)valueToDecode).getData();
final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject);
final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
return objectInputStream.readUnshared();
}
else if (valueToDecode instanceof byte[]) else if (valueToDecode instanceof byte[])
{ {
final byte[] decodeObject = (byte[])valueToDecode; final byte[] decodeObject = (byte[])valueToDecode;
@ -47,13 +55,13 @@ public class MongoUtils
final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais); final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
return objectInputStream.readUnshared(); return objectInputStream.readUnshared();
} }
else if (valueToDecode instanceof DBObject) else if (valueToDecode instanceof Document)
{ {
Map<String, Object> map = new HashMap<String, Object>(); Map<String, Object> map = new HashMap<>();
for (String name : ((DBObject)valueToDecode).keySet()) for (String name : ((Document)valueToDecode).keySet())
{ {
String attr = decodeName(name); String attr = decodeName(name);
map.put(attr, decodeValue(((DBObject)valueToDecode).get(name))); map.put(attr, decodeValue(((Document)valueToDecode).get(name)));
} }
return map; return map;
} }
@ -107,19 +115,19 @@ public class MongoUtils
/** /**
* Dig through a given dbObject for the nested value * Dig through a given dbObject for the nested value
* *
* @param dbObject the mongo object to search * @param sessionDocument the mongo document to search
* @param nestedKey the field key to find * @param nestedKey the field key to find
* @return the value of the field key * @return the value of the field key
*/ */
public static Object getNestedValue(DBObject dbObject, String nestedKey) public static Object getNestedValue(Document sessionDocument, String nestedKey)
{ {
String[] keyChain = nestedKey.split("\\."); String[] keyChain = nestedKey.split("\\.");
DBObject temp = dbObject; Document temp = sessionDocument;
for (int i = 0; i < keyChain.length - 1; ++i) for (int i = 0; i < keyChain.length - 1; ++i)
{ {
temp = (DBObject)temp.get(keyChain[i]); temp = (Document)temp.get(keyChain[i]);
if (temp == null) if (temp == null)
{ {

View File

@ -373,8 +373,8 @@
<maven.version>3.9.0</maven.version> <maven.version>3.9.0</maven.version>
<maven.war.plugin.version>3.4.0</maven.war.plugin.version> <maven.war.plugin.version>3.4.0</maven.war.plugin.version>
<mina.core.version>2.2.3</mina.core.version> <mina.core.version>2.2.3</mina.core.version>
<mongo.docker.version>3.2.20</mongo.docker.version> <mongo.docker.version>5.0.26</mongo.docker.version>
<mongodb.version>3.12.14</mongodb.version> <mongodb.version>5.1.3</mongodb.version>
<netty.version>4.1.109.Final</netty.version> <netty.version>4.1.109.Final</netty.version>
<openpojo.version>0.9.1</openpojo.version> <openpojo.version>0.9.1</openpojo.version>
<org.osgi.annotation.version>8.1.0</org.osgi.annotation.version> <org.osgi.annotation.version>8.1.0</org.osgi.annotation.version>
@ -1191,6 +1191,11 @@
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
<version>${mariadb.version}</version> <version>${mariadb.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>${mongodb.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.mortbay.jetty.quiche</groupId> <groupId>org.mortbay.jetty.quiche</groupId>
<artifactId>jetty-quiche-native</artifactId> <artifactId>jetty-quiche-native</artifactId>

View File

@ -101,7 +101,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mongodb</groupId> <groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>${mongodb.version}</version> <version>${mongodb.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>

View File

@ -18,13 +18,20 @@ import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Map; import java.util.Map;
import java.util.stream.StreamSupport;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoException; import com.mongodb.MongoException;
import com.mongodb.WriteConcern; import com.mongodb.WriteConcern;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOptions;
import org.bson.Document;
import org.bson.types.Binary;
import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStore; import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStore;
import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory; import org.eclipse.jetty.nosql.mongodb.MongoSessionDataStoreFactory;
import org.eclipse.jetty.nosql.mongodb.MongoUtils; import org.eclipse.jetty.nosql.mongodb.MongoUtils;
@ -57,7 +64,7 @@ public class MongoTestHelper
static static
{ {
mongo = new MongoDBContainer(DockerImageName.parse("mongo:" + System.getProperty("mongo.docker.version", "3.2.20"))) mongo = new MongoDBContainer(DockerImageName.parse("mongo:" + System.getProperty("mongo.docker.version", "5.0.26")))
.withLogConsumer(new Slf4jLogConsumer(MONGO_LOG)); .withLogConsumer(new Slf4jLogConsumer(MONGO_LOG));
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
mongo.start(); mongo.start();
@ -65,21 +72,21 @@ public class MongoTestHelper
mongoPort = mongo.getMappedPort(MONGO_PORT); mongoPort = mongo.getMappedPort(MONGO_PORT);
LOG.info("Mongo container started for {}:{} - {}ms", mongoHost, mongoPort, LOG.info("Mongo container started for {}:{} - {}ms", mongoHost, mongoPort,
System.currentTimeMillis() - start); System.currentTimeMillis() - start);
mongoClient = new MongoClient(mongoHost, mongoPort); mongoClient = MongoClients.create(mongo.getConnectionString());
} }
public static MongoClient getMongoClient() throws UnknownHostException public static MongoClient getMongoClient() throws UnknownHostException
{ {
if (mongoClient == null) if (mongoClient == null)
{ {
mongoClient = new MongoClient(mongoHost, mongoPort); mongoClient = MongoClients.create(mongo.getConnectionString());
} }
return mongoClient; return mongoClient;
} }
public static void dropCollection(String dbName, String collectionName) throws Exception public static void dropCollection(String dbName, String collectionName) throws Exception
{ {
getMongoClient().getDB(dbName).getCollection(collectionName).drop(); getMongoClient().getDatabase(dbName).getCollection(collectionName).withWriteConcern(WriteConcern.JOURNALED).drop();
} }
public static void shutdown() throws Exception public static void shutdown() throws Exception
@ -89,12 +96,14 @@ public class MongoTestHelper
public static void createCollection(String dbName, String collectionName) throws UnknownHostException, MongoException public static void createCollection(String dbName, String collectionName) throws UnknownHostException, MongoException
{ {
getMongoClient().getDB(dbName).createCollection(collectionName, null); if (StreamSupport.stream(getMongoClient().getDatabase(dbName).listCollectionNames().spliterator(), false)
.filter(collectionName::equals).findAny().isEmpty())
getMongoClient().getDatabase(dbName).withWriteConcern(WriteConcern.JOURNALED).createCollection(collectionName, new CreateCollectionOptions());
} }
public static DBCollection getCollection(String dbName, String collectionName) throws UnknownHostException, MongoException public static MongoCollection<Document> getCollection(String dbName, String collectionName) throws UnknownHostException, MongoException
{ {
return getMongoClient().getDB(dbName).getCollection(collectionName); return getMongoClient().getDatabase(dbName).getCollection(collectionName);
} }
public static MongoSessionDataStoreFactory newSessionDataStoreFactory(String dbName, String collectionName) public static MongoSessionDataStoreFactory newSessionDataStoreFactory(String dbName, String collectionName)
@ -108,15 +117,15 @@ public class MongoTestHelper
} }
public static boolean checkSessionExists(String id, String dbName, String collectionName) public static boolean checkSessionExists(String id, String dbName, String collectionName)
throws Exception throws Exception
{ {
DBCollection collection = getMongoClient().getDB(dbName).getCollection(collectionName); MongoCollection<Document> collection = getMongoClient().getDatabase(dbName).getCollection(collectionName);
DBObject fields = new BasicDBObject(); DBObject fields = new BasicDBObject();
fields.put(MongoSessionDataStore.__EXPIRY, 1); fields.put(MongoSessionDataStore.__EXPIRY, 1);
fields.put(MongoSessionDataStore.__VALID, 1); fields.put(MongoSessionDataStore.__VALID, 1);
DBObject sessionDocument = collection.findOne(new BasicDBObject(MongoSessionDataStore.__ID, id), fields); Document sessionDocument = collection.find(Filters.eq(MongoSessionDataStore.__ID, id)).first();
if (sessionDocument == null) if (sessionDocument == null)
return false; //doesn't exist return false; //doesn't exist
@ -125,21 +134,21 @@ public class MongoTestHelper
} }
public static boolean checkSessionPersisted(SessionData data, String dbName, String collectionName) public static boolean checkSessionPersisted(SessionData data, String dbName, String collectionName)
throws Exception throws Exception
{ {
DBCollection collection = getMongoClient().getDB(dbName).getCollection(collectionName); MongoCollection<Document> collection = getMongoClient().getDatabase(dbName).getCollection(collectionName);
DBObject fields = new BasicDBObject(); DBObject fields = new BasicDBObject();
DBObject sessionDocument = collection.findOne(new BasicDBObject(MongoSessionDataStore.__ID, data.getId()), fields); Document sessionDocument = collection.find(Filters.eq(MongoSessionDataStore.__ID, data.getId())).first();
if (sessionDocument == null) if (sessionDocument == null)
return false; //doesn't exist return false; //doesn't exist
LOG.debug("{}", sessionDocument); LOG.debug("{}", sessionDocument);
Boolean valid = (Boolean)sessionDocument.get(MongoSessionDataStore.__VALID); boolean valid = (Boolean)sessionDocument.get(MongoSessionDataStore.__VALID);
if (valid == null || !valid) if (!valid)
return false; return false;
Long created = (Long)sessionDocument.get(MongoSessionDataStore.__CREATED); Long created = (Long)sessionDocument.get(MongoSessionDataStore.__CREATED);
@ -149,13 +158,13 @@ public class MongoTestHelper
Long expiry = (Long)sessionDocument.get(MongoSessionDataStore.__EXPIRY); Long expiry = (Long)sessionDocument.get(MongoSessionDataStore.__EXPIRY);
Object version = MongoUtils.getNestedValue(sessionDocument, Object version = MongoUtils.getNestedValue(sessionDocument,
MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__VERSION); MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__VERSION);
Long lastSaved = (Long)MongoUtils.getNestedValue(sessionDocument, Long lastSaved = (Long)MongoUtils.getNestedValue(sessionDocument,
MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__LASTSAVED); MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__LASTSAVED);
String lastNode = (String)MongoUtils.getNestedValue(sessionDocument, String lastNode = (String)MongoUtils.getNestedValue(sessionDocument,
MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__LASTNODE); MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__LASTNODE);
byte[] attributes = (byte[])MongoUtils.getNestedValue(sessionDocument, byte[] attributes = ((Binary)MongoUtils.getNestedValue(sessionDocument,
MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__ATTRIBUTES); MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath() + "." + MongoSessionDataStore.__ATTRIBUTES)).getData();
assertEquals(data.getCreated(), created.longValue()); assertEquals(data.getCreated(), created.longValue());
assertEquals(data.getAccessed(), accessed.longValue()); assertEquals(data.getAccessed(), accessed.longValue());
@ -167,9 +176,9 @@ public class MongoTestHelper
assertNotNull(lastSaved); assertNotNull(lastSaved);
// get the session for the context // get the session for the context
DBObject sessionSubDocumentForContext = Document sessionSubDocumentForContext =
(DBObject)MongoUtils.getNestedValue(sessionDocument, (Document)MongoUtils.getNestedValue(sessionDocument,
MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath()); MongoSessionDataStore.__CONTEXT + "." + data.getVhost().replace('.', '_') + ":" + data.getContextPath());
assertNotNull(sessionSubDocumentForContext); assertNotNull(sessionSubDocumentForContext);
@ -200,12 +209,9 @@ public class MongoTestHelper
long lastAccessed, long maxIdle, long expiry, long lastAccessed, long maxIdle, long expiry,
Map<String, Object> attributes, String dbName, Map<String, Object> attributes, String dbName,
String collectionName) String collectionName)
throws Exception throws Exception
{ {
DBCollection collection = getMongoClient().getDB(dbName).getCollection(collectionName); MongoCollection<Document> collection = getMongoClient().getDatabase(dbName).getCollection(collectionName);
// Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
// Form updates // Form updates
BasicDBObject update = new BasicDBObject(); BasicDBObject update = new BasicDBObject();
@ -236,12 +242,12 @@ public class MongoTestHelper
ObjectOutputStream oos = new ObjectOutputStream(baos)) ObjectOutputStream oos = new ObjectOutputStream(baos))
{ {
SessionData.serializeAttributes(tmp, oos); SessionData.serializeAttributes(tmp, oos);
sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoSessionDataStore.__ATTRIBUTES, baos.toByteArray()); sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoSessionDataStore.__ATTRIBUTES, new Binary(baos.toByteArray()));
} }
} }
update.put("$set", sets); update.put("$set", sets);
collection.update(key, update, upsert, false, WriteConcern.SAFE); collection.updateOne(Filters.eq(MongoSessionDataStore.__ID, id), update, new UpdateOptions().upsert(true));
} }
public static void createSession(String id, String contextPath, String vhost, public static void createSession(String id, String contextPath, String vhost,
@ -249,10 +255,10 @@ public class MongoTestHelper
long lastAccessed, long maxIdle, long expiry, long lastAccessed, long maxIdle, long expiry,
Map<String, Object> attributes, String dbName, Map<String, Object> attributes, String dbName,
String collectionName) String collectionName)
throws Exception throws Exception
{ {
DBCollection collection = getMongoClient().getDB(dbName).getCollection(collectionName); MongoCollection<Document> collection = getMongoClient().getDatabase(dbName).getCollection(collectionName);
// Form query for upsert // Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id); BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
@ -283,12 +289,12 @@ public class MongoTestHelper
ObjectOutputStream oos = new ObjectOutputStream(baos)) ObjectOutputStream oos = new ObjectOutputStream(baos))
{ {
SessionData.serializeAttributes(tmp, oos); SessionData.serializeAttributes(tmp, oos);
sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoSessionDataStore.__ATTRIBUTES, baos.toByteArray()); sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoSessionDataStore.__ATTRIBUTES, new Binary(baos.toByteArray()));
} }
} }
update.put("$set", sets); update.put("$set", sets);
collection.update(key, update, upsert, false, WriteConcern.SAFE); collection.updateOne(key, update, new UpdateOptions().upsert(true));
} }
public static void createLegacySession(String id, String contextPath, String vhost, public static void createLegacySession(String id, String contextPath, String vhost,
@ -296,10 +302,10 @@ public class MongoTestHelper
long lastAccessed, long maxIdle, long expiry, long lastAccessed, long maxIdle, long expiry,
Map<String, Object> attributes, String dbName, Map<String, Object> attributes, String dbName,
String collectionName) String collectionName)
throws Exception throws Exception
{ {
//make old-style session to test if we can retrieve it //make old-style session to test if we can retrieve it
DBCollection collection = getMongoClient().getDB(dbName).getCollection(collectionName); MongoCollection<Document> collection = getMongoClient().getDatabase(dbName).getCollection(collectionName);
// Form query for upsert // Form query for upsert
BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id); BasicDBObject key = new BasicDBObject(MongoSessionDataStore.__ID, id);
@ -329,10 +335,10 @@ public class MongoTestHelper
{ {
Object value = attributes.get(name); Object value = attributes.get(name);
sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoUtils.encodeName(name), sets.put(MongoSessionDataStore.__CONTEXT + "." + vhost.replace('.', '_') + ":" + contextPath + "." + MongoUtils.encodeName(name),
MongoUtils.encodeName(value)); MongoUtils.encodeName(value));
} }
} }
update.put("$set", sets); update.put("$set", sets);
collection.update(key, update, upsert, false, WriteConcern.SAFE); collection.updateOne(key, update, new UpdateOptions().upsert(true));
} }
} }

View File

@ -35,7 +35,7 @@ public class MongodbSessionDistributionTests extends AbstractSessionDistribution
private static final int MONGO_PORT = 27017; private static final int MONGO_PORT = 27017;
final String imageName = "mongo:" + System.getProperty("mongo.docker.version", "3.2.20"); final String imageName = "mongo:" + System.getProperty("mongo.docker.version", "5.0.26");
final MongoDBContainer mongoDBContainer = final MongoDBContainer mongoDBContainer =
new MongoDBContainer(DockerImageName.parse(imageName)) new MongoDBContainer(DockerImageName.parse(imageName))