Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-11.0.x

This commit is contained in:
Lachlan Roberts 2020-06-30 15:47:49 +10:00
commit 50654b0dde
42 changed files with 1680 additions and 1279 deletions

View File

@ -135,7 +135,7 @@
<!-- ensure we don't have legacy comments that confuse javadoc tooling -->
<module name="Regexp">
<property name="id" value="LegacyMethodSeparators"/>
<property name="format" value="/\*\s*[=*-]*\s*\*/"/>
<property name="format" value="/\*\s*[=*-]+\s*\*/" />
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="false"/>
<property name="message" value="Legacy Method Separators"/>

View File

@ -54,23 +54,28 @@ public class TestSecurityAnnotationConversions
{
}
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
"tom", "dick", "harry"
}))
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
{
"tom", "dick", "harry"
}))
public static class RolesServlet extends HttpServlet
{
}
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
"tom", "dick", "harry"
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")})
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
{
"tom", "dick", "harry"
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET")})
public static class Method1Servlet extends HttpServlet
{
}
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {
"tom", "dick", "harry"
}), httpMethodConstraints = {@HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL)})
@ServletSecurity(value = @HttpConstraint(value = EmptyRoleSemantic.PERMIT, transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed =
{
"tom", "dick", "harry"
}), httpMethodConstraints = {
@HttpMethodConstraint(value = "GET", transportGuarantee = TransportGuarantee.CONFIDENTIAL)
})
public static class Method2Servlet extends HttpServlet
{
}

View File

@ -37,11 +37,11 @@ public class HttpCookie
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
/**
*If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
* If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
**/
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
/**
*These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
* These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
**/
private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
@ -474,10 +474,10 @@ public class HttpCookie
LOG.debug("No default value for SameSite");
return null;
}
if (o instanceof SameSite)
return (SameSite)o;
try
{
SameSite samesite = Enum.valueOf(SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH));

View File

@ -53,7 +53,7 @@ public class TestJettyOSGiBootWithJsp
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());

View File

@ -39,12 +39,12 @@ public class LifeCycleCallbackCollectionTest
{
public static int postConstructCount = 0;
public static int preDestroyCount = 0;
public void postconstruct()
{
++postConstructCount;
}
public void predestroy()
{
++preDestroyCount;
@ -53,7 +53,6 @@ public class LifeCycleCallbackCollectionTest
/**
* An unsupported lifecycle callback type
*
*/
public class TestLifeCycleCallback extends LifeCycleCallback
{
@ -77,7 +76,6 @@ public class LifeCycleCallbackCollectionTest
/**
* A class that we can use to simulate having PostConstruct and
* PreDestroy annotations on.
*
*/
public class SomeTestClass
{
@ -175,7 +173,7 @@ public class LifeCycleCallbackCollectionTest
//expected
}
}
@Test
public void testServletPostConstructPreDestroy() throws Exception
{
@ -184,7 +182,7 @@ public class LifeCycleCallbackCollectionTest
context.setResourceBase(MavenTestingUtils.getTargetTestingDir("predestroy-test").toURI().toURL().toString());
context.setContextPath("/");
server.setHandler(context);
//add a non-async servlet
ServletHolder notAsync = new ServletHolder();
notAsync.setHeldClass(TestServlet.class);
@ -192,7 +190,7 @@ public class LifeCycleCallbackCollectionTest
notAsync.setAsyncSupported(false);
notAsync.setInitOrder(1);
context.getServletHandler().addServletWithMapping(notAsync, "/notasync/*");
//add an async servlet
ServletHolder async = new ServletHolder();
async.setHeldClass(TestServlet.class);
@ -200,7 +198,7 @@ public class LifeCycleCallbackCollectionTest
async.setAsyncSupported(true);
async.setInitOrder(1);
context.getServletHandler().addServletWithMapping(async, "/async/*");
//add a run-as servlet
ServletHolder runas = new ServletHolder();
runas.setHeldClass(TestServlet.class);
@ -208,7 +206,7 @@ public class LifeCycleCallbackCollectionTest
runas.setRunAsRole("admin");
runas.setInitOrder(1);
context.getServletHandler().addServletWithMapping(runas, "/runas/*");
//add both run-as and non async servlet
ServletHolder both = new ServletHolder();
both.setHeldClass(TestServlet.class);
@ -217,7 +215,7 @@ public class LifeCycleCallbackCollectionTest
both.setAsyncSupported(false);
both.setInitOrder(1);
context.getServletHandler().addServletWithMapping(both, "/both/*");
//Make fake lifecycle callbacks for all servlets
LifeCycleCallbackCollection collection = new LifeCycleCallbackCollection();
context.setAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, collection);
@ -225,28 +223,28 @@ public class LifeCycleCallbackCollectionTest
collection.add(pcNotAsync);
PreDestroyCallback pdNotAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdNotAsync);
PostConstructCallback pcAsync = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcAsync);
PreDestroyCallback pdAsync = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdAsync);
PostConstructCallback pcRunAs = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcRunAs);
PreDestroyCallback pdRunAs = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdRunAs);
PostConstructCallback pcBoth = new PostConstructCallback(TestServlet.class, "postconstruct");
collection.add(pcBoth);
PreDestroyCallback pdBoth = new PreDestroyCallback(TestServlet.class, "predestroy");
collection.add(pdBoth);
server.start();
assertEquals(4, TestServlet.postConstructCount);
server.stop();
assertEquals(4, TestServlet.preDestroyCount);
}

View File

@ -44,7 +44,6 @@ import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

View File

@ -414,7 +414,7 @@ public class ResponseTest
@Test
public void testResponseCharacterEncoding() throws Exception
{
{
_server.stop();
ContextHandler handler = new CharEncodingContextHandler();
_server.setHandler(handler);
@ -429,7 +429,7 @@ public class ResponseTest
_channel.getRequest().setContext(null, "/");
response.recycle();
//test that explicit overrides default
response = getResponse();
_channel.getRequest().setContext(handler.getServletContext(), "/");
@ -438,10 +438,10 @@ public class ResponseTest
//getWriter should not change explicit character encoding
response.getWriter();
assertThat("ascii", Matchers.equalTo(response.getCharacterEncoding()));
_channel.getRequest().setContext(null, "/");
response.recycle();
//test that assumed overrides default
response = getResponse();
_channel.getRequest().setContext(handler.getServletContext(), "/");
@ -450,10 +450,10 @@ public class ResponseTest
response.getWriter();
//getWriter should not have modified character encoding
assertThat("utf-8", Matchers.equalTo(response.getCharacterEncoding()));
_channel.getRequest().setContext(null, "/");
response.recycle();
//test that inferred overrides default
response = getResponse();
_channel.getRequest().setContext(handler.getServletContext(), "/");
@ -462,10 +462,10 @@ public class ResponseTest
//getWriter should not have modified character encoding
response.getWriter();
assertThat("utf-8", Matchers.equalTo(response.getCharacterEncoding()));
_channel.getRequest().setContext(null, "/");
response.recycle();
//test that without a default or any content type, use iso-8859-1
response = getResponse();
assertThat("iso-8859-1", Matchers.equalTo(response.getCharacterEncoding()));
@ -473,7 +473,7 @@ public class ResponseTest
response.getWriter();
assertThat("iso-8859-1", Matchers.equalTo(response.getCharacterEncoding()));
}
@Test
public void testLocaleAndContentTypeEncoding() throws Exception
{
@ -488,7 +488,7 @@ public class ResponseTest
Response response = getResponse();
response.getHttpChannel().getRequest().setContext(handler.getServletContext(), "/");
response.setContentType("text/html");
assertEquals("iso-8859-1", response.getCharacterEncoding());
@ -562,14 +562,14 @@ public class ResponseTest
response.setCharacterEncoding("ISO-8859-1");
assertEquals("text/xml;charset=utf-8", response.getContentType());
}
@Test
public void testContentEncodingViaContentTypeChange() throws Exception
{
Response response = getResponse();
response.setContentType("text/html;charset=Shift_Jis");
assertEquals("Shift_Jis", response.getCharacterEncoding());
response.setContentType("text/xml");
assertEquals("Shift_Jis", response.getCharacterEncoding());
}
@ -1111,7 +1111,7 @@ public class ResponseTest
assertEquals("name=value; Path=/path; Domain=domain; Secure; HttpOnly", set);
}
@Test
public void testAddCookieInInclude() throws Exception
{
@ -1128,7 +1128,7 @@ public class ResponseTest
assertNull(response.getHttpFields().get("Set-Cookie"));
}
@Test
public void testAddCookieSameSiteDefault() throws Exception
{
@ -1145,12 +1145,12 @@ public class ResponseTest
response.addCookie(cookie);
String set = response.getHttpFields().get("Set-Cookie");
assertEquals("name=value; Path=/path; Domain=domain; Secure; HttpOnly; SameSite=Strict", set);
response.getHttpFields().remove("Set-Cookie");
//test bad default samesite value
context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "FooBar");
assertThrows(IllegalStateException.class,
() -> response.addCookie(cookie));
}
@ -1225,7 +1225,7 @@ public class ResponseTest
response.setContentType("some/type");
response.setContentLength(3);
response.setHeader(HttpHeader.EXPIRES,"never");
response.setHeader(HttpHeader.EXPIRES, "never");
response.setHeader("SomeHeader", "SomeValue");
@ -1293,7 +1293,7 @@ public class ResponseTest
List<String> actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
assertThat("HttpCookie order", actual, hasItems(expected));
}
@Test
public void testReplaceHttpCookieSameSite()
{
@ -1335,7 +1335,7 @@ public class ResponseTest
actual = Collections.list(response.getHttpFields().getValues("Set-Cookie"));
assertThat(actual, hasItems(new String[]{"Foo=replaced; Path=/path; Domain=Bah"}));
}
@Test
public void testReplaceParsedHttpCookieSiteDefault()
{
@ -1378,7 +1378,7 @@ public class ResponseTest
super(handler, new SessionData(id, "", "0.0.0.0", 0, 0, 0, 300));
}
}
private static class TestServletContextHandler extends ContextHandler
{
private class Context extends ContextHandler.Context
@ -1400,7 +1400,7 @@ public class ResponseTest
@Override
public void setAttribute(String name, Object object)
{
_attributes.put(name,object);
_attributes.put(name, object);
}
@Override

View File

@ -447,7 +447,7 @@ public class ContextHandlerTest
assertThat(connector.getResponse("GET /foo/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo'"));
assertThat(connector.getResponse("GET /foo/bar/xxx HTTP/1.0\n\n"), Matchers.containsString("ctx='/foo/bar'"));
}
@Test
public void testContextInitializationDestruction() throws Exception
{
@ -858,12 +858,12 @@ public class ContextHandlerTest
writer.println("ctx='" + request.getContextPath() + "'");
}
}
private static class TestServletContextListener implements ServletContextListener
{
public int initialized = 0;
public int destroyed = 0;
@Override
public void contextInitialized(ServletContextEvent sce)
{

View File

@ -38,7 +38,6 @@ import java.util.stream.Stream;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;

View File

@ -110,12 +110,12 @@ public class ServletContextHandlerTest
private static final AtomicInteger __testServlets = new AtomicInteger();
private static int __initIndex = 0;
private static int __destroyIndex = 0;
public class StopTestFilter implements Filter
{
int _initIndex;
int _destroyIndex;
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
@ -136,7 +136,7 @@ public class ServletContextHandlerTest
}
public class StopTestServlet extends GenericServlet
{
{
int _initIndex;
int _destroyIndex;
@ -182,18 +182,18 @@ public class ServletContextHandlerTest
{
boolean callSessionTimeouts;
int timeout;
public MySCI(boolean callSessionTimeouts, int timeout)
{
this.callSessionTimeouts = callSessionTimeouts;
this.timeout = timeout;
}
public MySCI()
{
this(false, -1);
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
{
@ -274,24 +274,24 @@ public class ServletContextHandlerTest
{
boolean callSessionTimeouts;
int timeout;
public MyContextListener(boolean callSessionTimeouts, int timeout)
{
this.callSessionTimeouts = callSessionTimeouts;
this.timeout = timeout;
}
public MyContextListener()
{
this(false, -1);
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
assertNull(sce.getServletContext().getAttribute("MyContextListener.contextInitialized"));
sce.getServletContext().setAttribute("MyContextListener.contextInitialized", Boolean.TRUE);
assertNull(sce.getServletContext().getAttribute("MyContextListener.defaultSessionTrackingModes"));
try
{
@ -301,9 +301,9 @@ public class ServletContextHandlerTest
catch (UnsupportedOperationException e)
{
//Should NOT be able to call getDefaultSessionTrackingModes from programmatic SCL
sce.getServletContext().setAttribute("MyContextListener.defaultSessionTrackingModes", Boolean.TRUE);
sce.getServletContext().setAttribute("MyContextListener.defaultSessionTrackingModes", Boolean.TRUE);
}
assertNull(sce.getServletContext().getAttribute("MyContextListener.effectiveSessionTrackingModes"));
try
{
@ -313,9 +313,9 @@ public class ServletContextHandlerTest
catch (UnsupportedOperationException e)
{
//Should NOT be able to call getEffectiveSessionTrackingModes from programmatic SCL
sce.getServletContext().setAttribute("MyContextListener.effectiveSessionTrackingModes", Boolean.TRUE);
sce.getServletContext().setAttribute("MyContextListener.effectiveSessionTrackingModes", Boolean.TRUE);
}
assertNull(sce.getServletContext().getAttribute("MyContextListener.setSessionTrackingModes"));
try
{
@ -325,9 +325,9 @@ public class ServletContextHandlerTest
catch (UnsupportedOperationException e)
{
//Should NOT be able to call setSessionTrackingModes from programmatic SCL
sce.getServletContext().setAttribute("MyContextListener.setSessionTrackingModes", Boolean.TRUE);
sce.getServletContext().setAttribute("MyContextListener.setSessionTrackingModes", Boolean.TRUE);
}
if (callSessionTimeouts)
{
try
@ -338,9 +338,9 @@ public class ServletContextHandlerTest
catch (UnsupportedOperationException e)
{
//Should NOT be able to call setSessionTimeout from this SCL
sce.getServletContext().setAttribute("MyContextListener.setSessionTimeout", Boolean.TRUE);
sce.getServletContext().setAttribute("MyContextListener.setSessionTimeout", Boolean.TRUE);
}
try
{
sce.getServletContext().getSessionTimeout();
@ -349,7 +349,7 @@ public class ServletContextHandlerTest
catch (UnsupportedOperationException e)
{
//Should NOT be able to call getSessionTimeout from this SCL
sce.getServletContext().setAttribute("MyContextListener.getSessionTimeout", Boolean.TRUE);
sce.getServletContext().setAttribute("MyContextListener.getSessionTimeout", Boolean.TRUE);
}
}
}
@ -384,7 +384,7 @@ public class ServletContextHandlerTest
}
public static class MyTestSessionListener implements HttpSessionAttributeListener, HttpSessionListener
{
{
@Override
public void sessionCreated(HttpSessionEvent se)
{
@ -410,13 +410,13 @@ public class ServletContextHandlerTest
{
}
}
public static class MySCAListener implements ServletContextAttributeListener
{
public static int adds = 0;
public static int removes = 0;
public static int replaces = 0;
@Override
public void attributeAdded(ServletContextAttributeEvent event)
{
@ -443,7 +443,7 @@ public class ServletContextHandlerTest
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
{
++destroys;
}
@ -453,13 +453,13 @@ public class ServletContextHandlerTest
++inits;
}
}
public static class MyRAListener implements ServletRequestAttributeListener
{
public static int adds = 0;
public static int removes = 0;
public static int replaces = 0;
@Override
public void attributeAdded(ServletRequestAttributeEvent srae)
{
@ -478,7 +478,7 @@ public class ServletContextHandlerTest
++replaces;
}
}
public static class MySListener implements HttpSessionListener
{
public static int creates = 0;
@ -495,15 +495,14 @@ public class ServletContextHandlerTest
{
++destroys;
}
}
public static class MySAListener implements HttpSessionAttributeListener
{
public static int adds = 0;
public static int removes = 0;
public static int replaces = 0;
@Override
public void attributeAdded(HttpSessionBindingEvent event)
{
@ -520,20 +519,20 @@ public class ServletContextHandlerTest
public void attributeReplaced(HttpSessionBindingEvent event)
{
++replaces;
}
}
}
public static class MySIListener implements HttpSessionIdListener
{
public static int changes = 0;
@Override
public void sessionIdChanged(HttpSessionEvent event, String oldSessionId)
{
++changes;
}
}
/**
* ServletContextListener that is designed to be added programmatically,
* which should make all of the createListener, createServlet, createFilter
@ -558,7 +557,7 @@ public class ServletContextHandlerTest
{
fail(e);
}
try
{
sce.getServletContext().createServlet(HelloServlet.class);
@ -572,7 +571,7 @@ public class ServletContextHandlerTest
{
fail(e);
}
try
{
sce.getServletContext().createListener(MyContextListener.class);
@ -587,13 +586,13 @@ public class ServletContextHandlerTest
fail(e);
}
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
{
}
}
public class InitialListener implements ServletContextListener
{
@Override
@ -639,16 +638,15 @@ public class ServletContextHandlerTest
{
fail(x);
}
sce.getServletContext().setAttribute("foo", "bar");
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
}
@BeforeEach
@ -667,7 +665,7 @@ public class ServletContextHandlerTest
_server.stop();
_server.join();
}
@Test
public void testInitParams() throws Exception
{
@ -681,7 +679,7 @@ public class ServletContextHandlerTest
{
sce.getServletContext().setInitParameter("foo", "bar");
assertEquals("bar", sce.getServletContext().getInitParameter("foo"));
assertThrows(NullPointerException.class,
assertThrows(NullPointerException.class,
() -> sce.getServletContext().setInitParameter(null, "bad")
);
assertThrows(NullPointerException.class,
@ -689,11 +687,11 @@ public class ServletContextHandlerTest
);
}
});
root.getServletHandler().addListener(initialListener);
_server.start();
}
@Test
public void testGetSetSessionTimeout() throws Exception
{
@ -706,7 +704,7 @@ public class ServletContextHandlerTest
root.getSessionHandler().setMaxInactiveInterval((int)TimeUnit.MINUTES.toSeconds(startMin));
root.addBean(new MySCIStarter(root.getServletContext(), new MySCI(true, timeout.intValue())), true);
_server.start();
//test starting value of setSessionTimeout
assertEquals(startMin, (Integer)root.getServletContext().getAttribute("MYSCI.startSessionTimeout"));
//test can set session timeout from ServletContainerInitializer
@ -741,7 +739,7 @@ public class ServletContextHandlerTest
root.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
_server.start();
_server.stop();
assertEquals(0, stopTestListener._initIndex); //listeners contextInitialized called first
assertEquals(1, stopTestFilter._initIndex); //filters init
assertEquals(2, stopTestServlet._initIndex); //servlets init
@ -750,7 +748,7 @@ public class ServletContextHandlerTest
assertEquals(1, stopTestServlet._destroyIndex); //servlets destroyed next
assertEquals(2, stopTestListener._destroyIndex); //listener contextDestroyed last
}
@Test
public void testAddSessionListener() throws Exception
{
@ -787,7 +785,7 @@ public class ServletContextHandlerTest
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.effectiveSessionTrackingModes"));
assertTrue((Boolean)root.getServletContext().getAttribute("MyContextListener.setSessionTrackingModes"));
}
@Test
public void testContextInitializationDestruction() throws Exception
{
@ -800,7 +798,7 @@ public class ServletContextHandlerTest
{
public int initialized = 0;
public int destroyed = 0;
@Override
public void contextInitialized(ServletContextEvent sce)
{
@ -813,7 +811,7 @@ public class ServletContextHandlerTest
destroyed++;
}
}
TestServletContextListener listener = new TestServletContextListener();
root.addEventListener(listener);
server.start();
@ -822,7 +820,7 @@ public class ServletContextHandlerTest
server.stop();
assertEquals(1, listener.destroyed);
}
@Test
public void testListenersFromContextListener() throws Exception
{
@ -835,7 +833,7 @@ public class ServletContextHandlerTest
root.getServletHandler().addListener(initialListener);
ServletHolder holder0 = root.addServlet(TestServlet.class, "/test");
_server.start();
ListenerHolder[] listenerHolders = root.getServletHandler().getListeners();
assertNotNull(listenerHolders);
for (ListenerHolder l : listenerHolders)
@ -852,22 +850,24 @@ public class ServletContextHandlerTest
List<String> listenerClassNames = new ArrayList<>();
for (EventListener l : root.getEventListeners())
{
listenerClassNames.add(l.getClass().getName());
}
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySCAListener"));
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MyRequestListener"));
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MyRAListener"));
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySListener"));
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySAListener"));
assertTrue(listenerClassNames.contains("org.eclipse.jetty.servlet.ServletContextHandlerTest$MySIListener"));
//test ServletRequestAttributeListener
String response = _connector.getResponse("GET /test?req=all HTTP/1.0\r\n\r\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals(1, MyRAListener.adds);
assertEquals(1, MyRAListener.replaces);
assertEquals(1, MyRAListener.removes);
//test HttpSessionAttributeListener
response = _connector.getResponse("GET /test?session=create HTTP/1.0\r\n\r\n");
String sessionid = response.substring(response.indexOf("JSESSIONID"), response.indexOf(";"));
@ -898,7 +898,7 @@ public class ServletContextHandlerTest
assertEquals(1, MySAListener.adds);
assertEquals(1, MySAListener.replaces);
assertEquals(1, MySAListener.removes);
//test HttpSessionIdListener.sessionIdChanged
request = new StringBuffer();
request.append("GET /test?session=change HTTP/1.0\n");
@ -909,7 +909,7 @@ public class ServletContextHandlerTest
assertThat(response, Matchers.containsString("200 OK"));
assertEquals(1, MySIListener.changes);
sessionid = response.substring(response.indexOf("JSESSIONID"), response.indexOf(";"));
//test HttpServletListener.sessionDestroyed
request = new StringBuffer();
request.append("GET /test?session=delete HTTP/1.0\n");
@ -919,7 +919,7 @@ public class ServletContextHandlerTest
response = _connector.getResponse(request.toString());
assertThat(response, Matchers.containsString("200 OK"));
assertEquals(1, MySListener.destroys);
//test ServletContextAttributeListener
//attribute was set when context listener registered
assertEquals(1, MySCAListener.adds);
@ -1018,10 +1018,10 @@ public class ServletContextHandlerTest
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
fail(e);
}
}
@Test
public void testCreateMethodsFromSCI() throws Exception
{
@ -1063,11 +1063,11 @@ public class ServletContextHandlerTest
}
}
}
root.addBean(new MySCIStarter(root.getServletContext(), new FilterCreatingSCI()), true);
_server.start();
_server.start();
}
@Test
public void testCreateMethodsFromSCL() throws Exception
{
@ -1084,7 +1084,7 @@ public class ServletContextHandlerTest
ctx.addListener(new CreatingSCL());
}
}
root.addBean(new MySCIStarter(root.getServletContext(), new ListenerCreatingSCI()), true);
_server.start();
assertTrue((Boolean)root.getServletContext().getAttribute("CreatingSCL.filter"));
@ -1148,9 +1148,8 @@ public class ServletContextHandlerTest
@Override
public void destroy()
{
{
}
});
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
context.getServletHandler().setStartWithUnavailable(false);
@ -1200,9 +1199,8 @@ public class ServletContextHandlerTest
@Override
public void destroy()
{
{
}
});
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
context.getServletHandler().setStartWithUnavailable(false);
@ -1252,9 +1250,8 @@ public class ServletContextHandlerTest
@Override
public void destroy()
{
{
}
});
context.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));
context.getServletHandler().setStartWithUnavailable(false);
@ -1275,7 +1272,7 @@ public class ServletContextHandlerTest
fail(e);
}
}
}
}
@Test
public void testAddServletFromSCL() throws Exception
@ -1296,9 +1293,8 @@ public class ServletContextHandlerTest
@Override
public void contextDestroyed(ServletContextEvent sce)
{
{
}
});
_server.setHandler(context);
_server.start();
@ -1329,10 +1325,10 @@ public class ServletContextHandlerTest
rego.addMapping("/hello/*");
}
}
root.addBean(new MySCIStarter(root.getServletContext(), new ServletAddingSCI()), true);
_server.start();
StringBuffer request = new StringBuffer();
request.append("GET /hello HTTP/1.0\n");
request.append("Host: localhost\n");
@ -1455,7 +1451,7 @@ public class ServletContextHandlerTest
assertNotNull(mappedServlet.getServletHolder());
assertEquals("some.jsp", mappedServlet.getServletHolder().getName());
}
@Test
public void testAddServletAfterStart() throws Exception
{
@ -1483,7 +1479,7 @@ public class ServletContextHandlerTest
response = _connector.getResponse(request.toString());
assertThat("Response", response, containsString("Hello World"));
}
@Test
public void testServletRegistrationByClass() throws Exception
{
@ -1523,7 +1519,7 @@ public class ServletContextHandlerTest
String response = _connector.getResponse(request.toString());
assertThat("Response", response, containsString("Test"));
}
@Test
public void testPartialServletRegistrationByName() throws Exception
{
@ -1532,7 +1528,7 @@ public class ServletContextHandlerTest
ServletHolder partial = new ServletHolder();
partial.setName("test");
context.addServlet(partial, "/test");
//complete partial servlet registration by providing name of the servlet class
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class.getName());
assertNotNull(reg);
@ -1549,7 +1545,7 @@ public class ServletContextHandlerTest
String response = _connector.getResponse(request.toString());
assertThat("Response", response, containsString("Test"));
}
@Test
public void testPartialServletRegistrationByClass() throws Exception
{
@ -1558,7 +1554,7 @@ public class ServletContextHandlerTest
ServletHolder partial = new ServletHolder();
partial.setName("test");
context.addServlet(partial, "/test");
//complete partial servlet registration by providing the servlet class
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class);
assertNotNull(reg);
@ -1576,7 +1572,7 @@ public class ServletContextHandlerTest
String response = _connector.getResponse(request.toString());
assertThat("Response", response, containsString("Test"));
}
@Test
public void testNullServletRegistration() throws Exception
{
@ -1586,7 +1582,7 @@ public class ServletContextHandlerTest
full.setName("test");
full.setHeldClass(TestServlet.class);
context.addServlet(full, "/test");
//Must return null if the servlet has been fully defined previously
ServletRegistration reg = context.getServletContext().addServlet("test", TestServlet.class);
assertNull(reg);
@ -1602,7 +1598,7 @@ public class ServletContextHandlerTest
String response = _connector.getResponse(request.toString());
assertThat("Response", response, containsString("Test"));
}
@Test
public void testHandlerBeforeServletHandler() throws Exception
{
@ -1701,23 +1697,23 @@ public class ServletContextHandlerTest
@Override
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response,
Object constraintInfo, UserIdentity userIdentity)
throws IOException
throws IOException
{
return false;
}
};
//check the linking order
context.setSecurityHandler(myHandler);
assertSame(myHandler, context.getSecurityHandler());
h = (HandlerWrapper)context.getHandler();
assertSame(h, sessionHandler);
h = (HandlerWrapper)h.getHandler();
assertSame(h, myHandler);
}
@Test
public void testReplaceServletHandlerWithoutServlet() throws Exception
{
@ -1857,13 +1853,13 @@ public class ServletContextHandlerTest
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
{
resp.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = resp.getWriter();
writer.write("Hello World");
}
}
public static class MyFilter implements Filter
{
@ -1931,10 +1927,10 @@ public class ServletContextHandlerTest
}
}
}
public static class FakeJspServlet extends HttpServlet
{
}
public static class ServletAddingServlet extends HttpServlet
@ -1953,7 +1949,7 @@ public class ServletContextHandlerTest
dynamic.addMapping("/added/*");
}
}
public static class FilterAddingServlet extends HttpServlet
{
@Override
@ -2006,7 +2002,7 @@ public class ServletContextHandlerTest
resp.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = resp.getWriter();
writer.write("Test");
String action = req.getParameter("session");
if (!Objects.isNull(action))
{
@ -2038,7 +2034,7 @@ public class ServletContextHandlerTest
}
else
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
@ -2049,10 +2045,10 @@ public class ServletContextHandlerTest
req.setAttribute("some", "value");
req.setAttribute("some", "other");
req.removeAttribute("some");
return;
}
action = req.getParameter("ctx");
if (!Objects.isNull(action))
{

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.server.internal.HandshakerSelector;

View File

@ -48,14 +48,10 @@ public class JakartaWebSocketClientFrameHandlerFactory extends JakartaWebSocketF
public JakartaWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
{
if (jakarta.websocket.Endpoint.class.isAssignableFrom(endpointClass))
{
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
}
if (endpointClass.getAnnotation(ClientEndpoint.class) == null)
{
return null;
}
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
return discoverJakartaFrameHandlerMetadata(endpointClass, metadata);

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.websocket.jakarta.common;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.ByteBuffer;
import java.util.HashMap;
@ -46,6 +45,7 @@ import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.exception.ProtocolException;
import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryStreamMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextMessageSink;
@ -95,9 +95,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
private MethodHandle openHandle;
private MethodHandle closeHandle;
private MethodHandle errorHandle;
private JakartaWebSocketFrameHandlerMetadata.MessageMetadata textMetadata;
private JakartaWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata;
private MethodHandle pongHandle;
private JakartaWebSocketMessageMetadata textMetadata;
private JakartaWebSocketMessageMetadata binaryMetadata;
private UpgradeRequest upgradeRequest;
@ -114,8 +114,8 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
public JakartaWebSocketFrameHandler(JakartaWebSocketContainer container,
Object endpointInstance,
MethodHandle openHandle, MethodHandle closeHandle, MethodHandle errorHandle,
JakartaWebSocketFrameHandlerMetadata.MessageMetadata textMetadata,
JakartaWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata,
JakartaWebSocketMessageMetadata textMetadata,
JakartaWebSocketMessageMetadata binaryMetadata,
MethodHandle pongHandle,
EndpointConfig endpointConfig)
{
@ -170,26 +170,32 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
errorHandle = InvokerUtils.bindTo(errorHandle, session);
pongHandle = InvokerUtils.bindTo(pongHandle, session);
JakartaWebSocketFrameHandlerMetadata.MessageMetadata actualTextMetadata = JakartaWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(textMetadata);
JakartaWebSocketMessageMetadata actualTextMetadata = JakartaWebSocketMessageMetadata.copyOf(textMetadata);
if (actualTextMetadata != null)
{
if (actualTextMetadata.isMaxMessageSizeSet())
session.setMaxTextMessageBufferSize(actualTextMetadata.maxMessageSize);
session.setMaxTextMessageBufferSize(actualTextMetadata.getMaxMessageSize());
MethodHandle methodHandle = actualTextMetadata.getMethodHandle();
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
methodHandle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
actualTextMetadata.setMethodHandle(methodHandle);
actualTextMetadata.handle = InvokerUtils.bindTo(actualTextMetadata.handle, endpointInstance, endpointConfig, session);
actualTextMetadata.handle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualTextMetadata.handle, session);
textSink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, actualTextMetadata);
textMetadata = actualTextMetadata;
}
JakartaWebSocketFrameHandlerMetadata.MessageMetadata actualBinaryMetadata = JakartaWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(binaryMetadata);
JakartaWebSocketMessageMetadata actualBinaryMetadata = JakartaWebSocketMessageMetadata.copyOf(binaryMetadata);
if (actualBinaryMetadata != null)
{
if (actualBinaryMetadata.isMaxMessageSizeSet())
session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.maxMessageSize);
session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.getMaxMessageSize());
MethodHandle methodHandle = actualBinaryMetadata.getMethodHandle();
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
methodHandle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
actualBinaryMetadata.setMethodHandle(methodHandle);
actualBinaryMetadata.handle = InvokerUtils.bindTo(actualBinaryMetadata.handle, endpointInstance, endpointConfig, session);
actualBinaryMetadata.handle = JakartaWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualBinaryMetadata.handle, session);
binarySink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, actualBinaryMetadata);
binaryMetadata = actualBinaryMetadata;
}
@ -350,64 +356,42 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
return messageHandlerMap;
}
public JakartaWebSocketFrameHandlerMetadata.MessageMetadata getBinaryMetadata()
public JakartaWebSocketMessageMetadata getBinaryMetadata()
{
return binaryMetadata;
}
public JakartaWebSocketFrameHandlerMetadata.MessageMetadata getTextMetadata()
public JakartaWebSocketMessageMetadata getTextMetadata()
{
return textMetadata;
}
private void assertBasicTypeNotRegistered(byte basicWebSocketType, Object messageImpl, String replacement)
{
if (messageImpl != null)
{
throw new IllegalStateException(
"Cannot register " + replacement + ": Basic WebSocket type " + OpCode.name(basicWebSocketType) + " is already registered");
}
}
public <T> void addMessageHandler(JakartaWebSocketSession session, Class<T> clazz, MessageHandler.Partial<T> handler)
public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Partial<T> handler)
{
try
{
MethodHandles.Lookup lookup = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
MethodHandle partialMessageHandler = lookup
.findVirtual(MessageHandler.Partial.class, "onMessage", MethodType.methodType(void.class, Object.class, boolean.class));
partialMessageHandler = partialMessageHandler.bindTo(handler);
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(MessageHandler.Partial.class, "onMessage", MethodType.methodType(void.class, Object.class, boolean.class))
.bindTo(handler);
JakartaWebSocketMessageMetadata metadata = new JakartaWebSocketMessageMetadata();
metadata.setMethodHandle(methodHandle);
byte basicType;
// MessageHandler.Partial has no decoder support!
if (byte[].class.isAssignableFrom(clazz))
{
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialByteArrayMessageSink(coreSession, partialMessageHandler);
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
metadata.handle = partialMessageHandler;
metadata.sinkClass = PartialByteArrayMessageSink.class;
this.binaryMetadata = metadata;
basicType = OpCode.BINARY;
metadata.setSinkClass(PartialByteArrayMessageSink.class);
}
else if (ByteBuffer.class.isAssignableFrom(clazz))
{
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialByteBufferMessageSink(coreSession, partialMessageHandler);
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
metadata.handle = partialMessageHandler;
metadata.sinkClass = PartialByteBufferMessageSink.class;
this.binaryMetadata = metadata;
basicType = OpCode.BINARY;
metadata.setSinkClass(PartialByteBufferMessageSink.class);
}
else if (String.class.isAssignableFrom(clazz))
{
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialStringMessageSink(coreSession, partialMessageHandler);
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
metadata.handle = partialMessageHandler;
metadata.sinkClass = PartialStringMessageSink.class;
this.textMetadata = metadata;
basicType = OpCode.TEXT;
metadata.setSinkClass(PartialStringMessageSink.class);
}
else
{
@ -415,6 +399,9 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
"Unable to add " + handler.getClass().getName() + " with type " + clazz + ": only supported types byte[], " + ByteBuffer.class.getName() +
", " + String.class.getName());
}
// Register the Metadata as a MessageHandler.
registerMessageHandler(clazz, handler, basicType, metadata);
}
catch (NoSuchMethodException e)
{
@ -426,75 +413,62 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
}
}
public <T> void addMessageHandler(JakartaWebSocketSession session, Class<T> clazz, MessageHandler.Whole<T> handler)
public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Whole<T> handler)
{
try
{
MethodHandles.Lookup lookup = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
MethodHandle wholeMsgMethodHandle = lookup.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class));
wholeMsgMethodHandle = wholeMsgMethodHandle.bindTo(handler);
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class))
.bindTo(handler);
if (PongMessage.class.isAssignableFrom(clazz))
{
assertBasicTypeNotRegistered(OpCode.PONG, this.pongHandle, handler.getClass().getName());
this.pongHandle = wholeMsgMethodHandle;
assertBasicTypeNotRegistered(OpCode.PONG, handler);
this.pongHandle = methodHandle;
registerMessageHandler(OpCode.PONG, clazz, handler, null);
return;
}
AvailableDecoders availableDecoders = session.getDecoders();
RegisteredDecoder registeredDecoder = availableDecoders.getFirstRegisteredDecoder(clazz);
if (registeredDecoder == null)
throw new IllegalStateException("Unable to find Decoder for type: " + clazz);
// Create the message metadata specific to the MessageHandler type.
JakartaWebSocketMessageMetadata metadata = new JakartaWebSocketMessageMetadata();
metadata.setMethodHandle(methodHandle);
byte basicType;
if (registeredDecoder.implementsInterface(Decoder.Binary.class))
{
basicType = OpCode.BINARY;
metadata.setRegisteredDecoders(availableDecoders.getBinaryDecoders(clazz));
metadata.setSinkClass(DecodedBinaryMessageSink.class);
}
else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class))
{
basicType = OpCode.BINARY;
metadata.setRegisteredDecoders(availableDecoders.getBinaryStreamDecoders(clazz));
metadata.setSinkClass(DecodedBinaryStreamMessageSink.class);
}
else if (registeredDecoder.implementsInterface(Decoder.Text.class))
{
basicType = OpCode.TEXT;
metadata.setRegisteredDecoders(availableDecoders.getTextDecoders(clazz));
metadata.setSinkClass(DecodedTextMessageSink.class);
}
else if (registeredDecoder.implementsInterface(Decoder.TextStream.class))
{
basicType = OpCode.TEXT;
metadata.setRegisteredDecoders(availableDecoders.getTextStreamDecoders(clazz));
metadata.setSinkClass(DecodedTextStreamMessageSink.class);
}
else
{
AvailableDecoders availableDecoders = session.getDecoders();
AvailableDecoders.RegisteredDecoder registeredDecoder = availableDecoders.getRegisteredDecoderFor(clazz);
if (registeredDecoder == null)
{
throw new IllegalStateException("Unable to find Decoder for type: " + clazz);
}
JakartaWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JakartaWebSocketFrameHandlerMetadata.MessageMetadata();
metadata.handle = wholeMsgMethodHandle;
metadata.registeredDecoder = registeredDecoder;
if (registeredDecoder.implementsInterface(Decoder.Binary.class))
{
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
Decoder.Binary<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
MessageSink messageSink = new DecodedBinaryMessageSink(coreSession, decoder, wholeMsgMethodHandle);
metadata.sinkClass = messageSink.getClass();
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
this.binaryMetadata = metadata;
}
else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class))
{
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
Decoder.BinaryStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
MessageSink messageSink = new DecodedBinaryStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle);
metadata.sinkClass = messageSink.getClass();
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
this.binaryMetadata = metadata;
}
else if (registeredDecoder.implementsInterface(Decoder.Text.class))
{
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
Decoder.Text<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
MessageSink messageSink = new DecodedTextMessageSink(coreSession, decoder, wholeMsgMethodHandle);
metadata.sinkClass = messageSink.getClass();
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
this.textMetadata = metadata;
}
else if (registeredDecoder.implementsInterface(Decoder.TextStream.class))
{
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
Decoder.TextStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder);
MessageSink messageSink = new DecodedTextStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle);
metadata.sinkClass = messageSink.getClass();
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
this.textMetadata = metadata;
}
else
{
throw new RuntimeException("Unable to add " + handler.getClass().getName() + ": type " + clazz + " is unrecognized by declared decoders");
}
throw new RuntimeException("Unable to add " + handler.getClass().getName() + ": type " + clazz + " is unrecognized by declared decoders");
}
// Register the Metadata as a MessageHandler.
registerMessageHandler(clazz, handler, basicType, metadata);
}
catch (NoSuchMethodException e)
{
@ -506,6 +480,50 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
}
}
private void assertBasicTypeNotRegistered(byte basicWebSocketType, MessageHandler replacement)
{
Object messageImpl;
switch (basicWebSocketType)
{
case OpCode.TEXT:
messageImpl = textSink;
break;
case OpCode.BINARY:
messageImpl = binarySink;
break;
case OpCode.PONG:
messageImpl = pongHandle;
break;
default:
throw new IllegalStateException();
}
if (messageImpl != null)
{
throw new IllegalStateException("Cannot register " + replacement.getClass().getName() +
": Basic WebSocket type " + OpCode.name(basicWebSocketType) + " is already registered");
}
}
private void registerMessageHandler(Class<?> clazz, MessageHandler handler, byte basicMessageType, JakartaWebSocketMessageMetadata metadata)
{
assertBasicTypeNotRegistered(basicMessageType, handler);
MessageSink messageSink = JakartaWebSocketFrameHandlerFactory.createMessageSink(session, metadata);
switch (basicMessageType)
{
case OpCode.TEXT:
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
this.textMetadata = metadata;
break;
case OpCode.BINARY:
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
this.binaryMetadata = metadata;
break;
default:
throw new IllegalStateException();
}
}
private <T> MessageSink registerMessageHandler(byte basicWebSocketMessageType, Class<T> handlerType, MessageHandler handler, MessageSink messageSink)
{
synchronized (messageHandlerMap)
@ -552,7 +570,7 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
this.binarySink = null;
break;
default:
break; // TODO ISE?
throw new IllegalStateException("Invalid MessageHandler type " + OpCode.name(key));
}
}
}
@ -610,7 +628,6 @@ public class JakartaWebSocketFrameHandler implements FrameHandler
// Use JSR356 PongMessage interface
JakartaWebSocketPongMessage pongMessage = new JakartaWebSocketPongMessage(payload);
pongHandle.invoke(pongMessage);
}
catch (Throwable cause)

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.jakarta.common;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@ -35,6 +33,7 @@ import java.util.function.Function;
import jakarta.websocket.CloseReason;
import jakarta.websocket.Decoder;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
@ -44,90 +43,33 @@ import jakarta.websocket.PongMessage;
import jakarta.websocket.Session;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.common.messages.AbstractDecodedMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedBinaryStreamMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextMessageSink;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextStreamMessageSink;
import org.eclipse.jetty.websocket.util.InvalidSignatureException;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.eclipse.jetty.websocket.util.InvokerUtils;
import org.eclipse.jetty.websocket.util.ReflectUtils;
import org.eclipse.jetty.websocket.util.messages.ByteArrayMessageSink;
import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink;
import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.eclipse.jetty.websocket.util.messages.PartialByteArrayMessageSink;
import org.eclipse.jetty.websocket.util.messages.PartialByteBufferMessageSink;
import org.eclipse.jetty.websocket.util.messages.PartialStringMessageSink;
import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerMetadata.MessageMetadata;
public abstract class JakartaWebSocketFrameHandlerFactory
{
private static final MethodHandle FILTER_RETURN_TYPE_METHOD;
// The different kind of @OnMessage method parameter signatures expected.
private static final InvokerUtils.Arg[] textCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(String.class).required()
};
private static final InvokerUtils.Arg[] textPartialCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(String.class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
private static final InvokerUtils.Arg[] binaryBufferCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(ByteBuffer.class).required()
};
private static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(ByteBuffer.class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
private static final InvokerUtils.Arg[] binaryArrayCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(byte[].class).required()
};
private static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(byte[].class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
private static final InvokerUtils.Arg[] inputStreamCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(InputStream.class).required()
};
private static final InvokerUtils.Arg[] readerCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(Reader.class).required()
};
private static final InvokerUtils.Arg[] pongCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(PongMessage.class).required()
};
static
{
try
{
FILTER_RETURN_TYPE_METHOD = getServerMethodHandleLookup()
.findVirtual(JakartaWebSocketSession.class, "filterReturnType", MethodType.methodType(void.class, Object.class));
}
catch (Throwable e)
{
@ -135,6 +77,34 @@ public abstract class JakartaWebSocketFrameHandlerFactory
}
}
static InvokerUtils.Arg[] getArgsFor(Class<?> objectType)
{
return new InvokerUtils.Arg[]{new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(objectType).required()};
}
static final InvokerUtils.Arg[] textPartialCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(String.class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
static final InvokerUtils.Arg[] binaryPartialBufferCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(ByteBuffer.class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
static final InvokerUtils.Arg[] binaryPartialArrayCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(byte[].class).required(),
new InvokerUtils.Arg(boolean.class).required()
};
static final InvokerUtils.Arg[] pongCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(PongMessage.class).required()
};
protected final JakartaWebSocketContainer container;
protected final InvokerUtils.ParamIdentifier paramIdentifier;
@ -174,8 +144,8 @@ public abstract class JakartaWebSocketFrameHandlerFactory
MethodHandle errorHandle = metadata.getErrorHandle();
MethodHandle pongHandle = metadata.getPongHandle();
MessageMetadata textMetadata = MessageMetadata.copyOf(metadata.getTextMetadata());
MessageMetadata binaryMetadata = MessageMetadata.copyOf(metadata.getBinaryMetadata());
JakartaWebSocketMessageMetadata textMetadata = JakartaWebSocketMessageMetadata.copyOf(metadata.getTextMetadata());
JakartaWebSocketMessageMetadata binaryMetadata = JakartaWebSocketMessageMetadata.copyOf(metadata.getBinaryMetadata());
UriTemplatePathSpec templatePathSpec = metadata.getUriTemplatePathSpec();
if (templatePathSpec != null)
@ -190,9 +160,9 @@ public abstract class JakartaWebSocketFrameHandlerFactory
pongHandle = bindTemplateVariables(pongHandle, namedVariables, pathParams);
if (textMetadata != null)
textMetadata.handle = bindTemplateVariables(textMetadata.handle, namedVariables, pathParams);
textMetadata.setMethodHandle(bindTemplateVariables(textMetadata.getMethodHandle(), namedVariables, pathParams));
if (binaryMetadata != null)
binaryMetadata.handle = bindTemplateVariables(binaryMetadata.handle, namedVariables, pathParams);
binaryMetadata.setMethodHandle(bindTemplateVariables(binaryMetadata.getMethodHandle(), namedVariables, pathParams));
}
openHandle = InvokerUtils.bindTo(openHandle, endpoint);
@ -209,6 +179,318 @@ public abstract class JakartaWebSocketFrameHandlerFactory
config);
}
public static MessageSink createMessageSink(JakartaWebSocketSession session, JakartaWebSocketMessageMetadata msgMetadata)
{
if (msgMetadata == null)
return null;
try
{
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
if (AbstractDecodedMessageSink.class.isAssignableFrom(msgMetadata.getSinkClass()))
{
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, List.class));
List<RegisteredDecoder> registeredDecoders = msgMetadata.getRegisteredDecoders();
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle(), registeredDecoders);
}
else
{
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class));
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle());
}
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.getSinkClass().getName(), e);
}
catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
{
throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.getSinkClass().getName(), e);
}
catch (RuntimeException e)
{
throw e;
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
public static MethodHandle wrapNonVoidReturnType(MethodHandle handle, JakartaWebSocketSession session)
{
if (handle == null)
return null;
if (handle.type().returnType() == Void.TYPE)
return handle;
// Technique from https://stackoverflow.com/questions/48505787/methodhandle-with-general-non-void-return-filter
// Change the return type of the to be Object so it will match exact with JakartaWebSocketSession.filterReturnType(Object)
handle = handle.asType(handle.type().changeReturnType(Object.class));
// Filter the method return type to a call to JakartaWebSocketSession.filterReturnType() bound to this session
handle = MethodHandles.filterReturnValue(handle, FILTER_RETURN_TYPE_METHOD.bindTo(session));
return handle;
}
private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method)
{
try
{
return lookup.unreflect(method);
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Unable to access method " + method, e);
}
}
protected JakartaWebSocketFrameHandlerMetadata createEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig endpointConfig)
{
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen", Session.class, EndpointConfig.class);
MethodHandle open = toMethodHandle(lookup, openMethod);
metadata.setOpenHandler(open, openMethod);
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onClose", Session.class, CloseReason.class);
MethodHandle close = toMethodHandle(lookup, closeMethod);
metadata.setCloseHandler(close, closeMethod);
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onError", Session.class, Throwable.class);
MethodHandle error = toMethodHandle(lookup, errorMethod);
metadata.setErrorHandler(error, errorMethod);
return metadata;
}
protected JakartaWebSocketFrameHandlerMetadata discoverJakartaFrameHandlerMetadata(Class<?> endpointClass, JakartaWebSocketFrameHandlerMetadata metadata)
{
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
Method onmethod;
// OnOpen [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnOpen.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnOpen.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg ENDPOINT_CONFIG = new InvokerUtils.Arg(EndpointConfig.class);
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, ENDPOINT_CONFIG);
metadata.setOpenHandler(methodHandle, onmethod);
}
// OnClose [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnClose.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnClose.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg CLOSE_REASON = new InvokerUtils.Arg(CloseReason.class);
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CLOSE_REASON);
metadata.setCloseHandler(methodHandle, onmethod);
}
// OnError [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnError.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnError.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required();
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CAUSE);
metadata.setErrorHandler(methodHandle, onmethod);
}
// OnMessage [0..2]
Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnMessage.class);
if (onMessages != null && onMessages.length > 0)
{
for (Method onMsg : onMessages)
{
assertSignatureValid(endpointClass, onMsg, OnMessage.class);
OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class);
long annotationMaxMessageSize = onMessageAnno.maxMessageSize();
if (annotationMaxMessageSize > Integer.MAX_VALUE)
{
throw new InvalidWebSocketException(String.format("Value too large: %s#%s - @OnMessage.maxMessageSize=%,d > Integer.MAX_VALUE",
endpointClass.getName(), onMsg.getName(), annotationMaxMessageSize));
}
// Create MessageMetadata and set annotated maxMessageSize if it is not the default value.
JakartaWebSocketMessageMetadata msgMetadata = new JakartaWebSocketMessageMetadata();
if (annotationMaxMessageSize != -1)
msgMetadata.setMaxMessageSize((int)annotationMaxMessageSize);
// Function to search for matching MethodHandle for the endpointClass given a signature.
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) ->
InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, paramIdentifier, metadata.getNamedTemplateVariables(), signature);
// Try to match from available decoders (includes primitive types).
if (matchDecoders(onMsg, metadata, msgMetadata, getMethodHandle))
continue;
// No decoders matched try partial signatures and pong signatures.
if (matchOnMessage(onMsg, metadata, msgMetadata, getMethodHandle))
continue;
// Not a valid @OnMessage declaration signature.
throw InvalidSignatureException.build(endpointClass, OnMessage.class, onMsg);
}
}
return metadata;
}
private boolean matchOnMessage(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, JakartaWebSocketMessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{
// Partial Text Message.
MethodHandle methodHandle = getMethodHandle.apply(textPartialCallingArgs);
if (methodHandle != null)
{
msgMetadata.setSinkClass(PartialStringMessageSink.class);
msgMetadata.setMethodHandle(methodHandle);
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
// Partial ByteBuffer Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialBufferCallingArgs);
if (methodHandle != null)
{
msgMetadata.setSinkClass(PartialByteBufferMessageSink.class);
msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Partial byte[] Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialArrayCallingArgs);
if (methodHandle != null)
{
msgMetadata.setSinkClass(PartialByteArrayMessageSink.class);
msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Pong Message.
MethodHandle pongHandle = getMethodHandle.apply(pongCallingArgs);
if (pongHandle != null)
{
metadata.setPongHandle(pongHandle, onMsg);
return true;
}
return false;
}
private boolean matchDecoders(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, JakartaWebSocketMessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{
// Get the first decoder match.
RegisteredDecoder firstDecoder = metadata.getAvailableDecoders().stream()
.filter(registeredDecoder -> getMethodHandle.apply(getArgsFor(registeredDecoder.objectType)) != null)
.findFirst()
.orElse(null);
if (firstDecoder == null)
return false;
// Assemble a list of matching decoders which implement the interface type of the first matching decoder found.
List<RegisteredDecoder> decoders = new ArrayList<>();
Class<? extends Decoder> interfaceType = firstDecoder.interfaceType;
metadata.getAvailableDecoders().stream().filter(decoder ->
decoder.interfaceType.equals(interfaceType) && (getMethodHandle.apply(getArgsFor(decoder.objectType)) != null))
.forEach(decoders::add);
msgMetadata.setRegisteredDecoders(decoders);
// Get the general methodHandle which applies to all the decoders in the list.
Class<?> objectType = firstDecoder.objectType;
for (RegisteredDecoder decoder : decoders)
{
if (decoder.objectType.isAssignableFrom(objectType))
objectType = decoder.objectType;
}
MethodHandle methodHandle = getMethodHandle.apply(getArgsFor(objectType));
msgMetadata.setMethodHandle(methodHandle);
// Set the sinkClass and then set the MessageMetadata on the FrameHandlerMetadata
if (interfaceType.equals(Decoder.Text.class))
{
msgMetadata.setSinkClass(DecodedTextMessageSink.class);
metadata.setTextMetadata(msgMetadata, onMsg);
}
else if (interfaceType.equals(Decoder.Binary.class))
{
msgMetadata.setSinkClass(DecodedBinaryMessageSink.class);
metadata.setBinaryMetadata(msgMetadata, onMsg);
}
else if (interfaceType.equals(Decoder.TextStream.class))
{
msgMetadata.setSinkClass(DecodedTextStreamMessageSink.class);
metadata.setTextMetadata(msgMetadata, onMsg);
}
else if (interfaceType.equals(Decoder.BinaryStream.class))
{
msgMetadata.setSinkClass(DecodedBinaryStreamMessageSink.class);
metadata.setBinaryMetadata(msgMetadata, onMsg);
}
return true;
}
private void assertSignatureValid(Class<?> endpointClass, Method method, Class<? extends Annotation> annotationClass)
{
// Test modifiers
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" method must be public: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
if (Modifier.isStatic(mods))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" method must not be static: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
// Test return type
Class<?> returnType = method.getReturnType();
if ((returnType == Void.TYPE) || (returnType == Void.class))
{
// Void is 100% valid, always
return;
}
if (!OnMessage.class.isAssignableFrom(annotationClass))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" return must be void: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
}
/**
* Bind the URI Template Variables to their provided values, converting to the type
* that the MethodHandle target has declared.
@ -315,398 +597,6 @@ public abstract class JakartaWebSocketFrameHandlerFactory
return retHandle;
}
@SuppressWarnings("Duplicates")
public static MessageSink createMessageSink(JakartaWebSocketSession session, MessageMetadata msgMetadata)
{
if (msgMetadata == null)
return null;
try
{
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
if (DecodedMessageSink.class.isAssignableFrom(msgMetadata.sinkClass))
{
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass,
MethodType.methodType(void.class, CoreSession.class, msgMetadata.registeredDecoder.interfaceType, MethodHandle.class));
Decoder decoder = session.getDecoders().getInstanceOf(msgMetadata.registeredDecoder);
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), decoder, msgMetadata.handle);
}
else
{
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass,
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class));
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.handle);
}
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.sinkClass.getName(), e);
}
catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
{
throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.sinkClass.getName(), e);
}
catch (RuntimeException e)
{
throw e;
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
public static MethodHandle wrapNonVoidReturnType(MethodHandle handle, JakartaWebSocketSession session)
{
if (handle == null)
return null;
if (handle.type().returnType() == Void.TYPE)
return handle;
// Technique from https://stackoverflow.com/questions/48505787/methodhandle-with-general-non-void-return-filter
// Change the return type of the to be Object so it will match exact with JakartaWebSocketSession.filterReturnType(Object)
handle = handle.asType(handle.type().changeReturnType(Object.class));
// Filter the method return type to a call to JakartaWebSocketSession.filterReturnType() bound to this session
handle = MethodHandles.filterReturnValue(handle, FILTER_RETURN_TYPE_METHOD.bindTo(session));
return handle;
}
private MethodHandle toMethodHandle(MethodHandles.Lookup lookup, Method method)
{
try
{
return lookup.unreflect(method);
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Unable to access method " + method, e);
}
}
protected JakartaWebSocketFrameHandlerMetadata createEndpointMetadata(Class<? extends jakarta.websocket.Endpoint> endpointClass, EndpointConfig endpointConfig)
{
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen",
jakarta.websocket.Session.class, jakarta.websocket.EndpointConfig.class);
MethodHandle open = toMethodHandle(lookup, openMethod);
metadata.setOpenHandler(open, openMethod);
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onClose",
jakarta.websocket.Session.class, jakarta.websocket.CloseReason.class);
MethodHandle close = toMethodHandle(lookup, closeMethod);
metadata.setCloseHandler(close, closeMethod);
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onError",
jakarta.websocket.Session.class, Throwable.class);
MethodHandle error = toMethodHandle(lookup, errorMethod);
metadata.setErrorHandler(error, errorMethod);
return metadata;
}
protected JakartaWebSocketFrameHandlerMetadata discoverJakartaFrameHandlerMetadata(Class<?> endpointClass, JakartaWebSocketFrameHandlerMetadata metadata)
{
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
Method onmethod;
// OnOpen [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnOpen.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnOpen.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg ENDPOINT_CONFIG = new InvokerUtils.Arg(EndpointConfig.class);
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, ENDPOINT_CONFIG);
metadata.setOpenHandler(methodHandle, onmethod);
}
// OnClose [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnClose.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnClose.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg CLOSE_REASON = new InvokerUtils.Arg(CloseReason.class);
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CLOSE_REASON);
metadata.setCloseHandler(methodHandle, onmethod);
}
// OnError [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnError.class);
if (onmethod != null)
{
assertSignatureValid(endpointClass, onmethod, OnError.class);
final InvokerUtils.Arg SESSION = new InvokerUtils.Arg(Session.class);
final InvokerUtils.Arg CAUSE = new InvokerUtils.Arg(Throwable.class).required();
MethodHandle methodHandle = InvokerUtils
.mutatedInvoker(lookup, endpointClass, onmethod, paramIdentifier, metadata.getNamedTemplateVariables(), SESSION, CAUSE);
metadata.setErrorHandler(methodHandle, onmethod);
}
// OnMessage [0..2]
Method[] onMessages = ReflectUtils.findAnnotatedMethods(endpointClass, OnMessage.class);
if (onMessages != null && onMessages.length > 0)
{
for (Method onMsg : onMessages)
{
assertSignatureValid(endpointClass, onMsg, OnMessage.class);
MessageMetadata msgMetadata = new MessageMetadata();
OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class);
if (onMessageAnno.maxMessageSize() > Integer.MAX_VALUE)
{
throw new InvalidWebSocketException(String.format("Value too large: %s#%s - @OnMessage.maxMessageSize=%,d > Integer.MAX_VALUE",
endpointClass.getName(), onMsg.getName(), onMessageAnno.maxMessageSize()));
}
msgMetadata.maxMessageSize = (int)onMessageAnno.maxMessageSize();
// Function to search for matching MethodHandle for the endpointClass given a signature.
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) ->
InvokerUtils.optionalMutatedInvoker(lookup, endpointClass, onMsg, paramIdentifier, metadata.getNamedTemplateVariables(), signature);
// Try to match from available decoders.
if (matchDecoders(onMsg, metadata, msgMetadata, getMethodHandle))
continue;
// No decoders matched try basic signatures to call onMessage directly.
if (matchOnMessage(onMsg, metadata, msgMetadata, getMethodHandle))
continue;
// Not a valid @OnMessage declaration signature.
throw InvalidSignatureException.build(endpointClass, OnMessage.class, onMsg);
}
}
return metadata;
}
private boolean matchOnMessage(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{
// Whole Text Message.
MethodHandle methodHandle = getMethodHandle.apply(textCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = StringMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
// Partial Text Message.
methodHandle = getMethodHandle.apply(textPartialCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = PartialStringMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
// Whole ByteBuffer Binary Message.
methodHandle = getMethodHandle.apply(binaryBufferCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = ByteBufferMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Partial ByteBuffer Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialBufferCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = PartialByteBufferMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Whole byte[] Binary Message.
methodHandle = getMethodHandle.apply(binaryArrayCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = ByteArrayMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Partial byte[] Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialArrayCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = PartialByteArrayMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// InputStream Binary Message.
methodHandle = getMethodHandle.apply(inputStreamCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = InputStreamMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
// Reader Text Message.
methodHandle = getMethodHandle.apply(readerCallingArgs);
if (methodHandle != null)
{
msgMetadata.sinkClass = ReaderMessageSink.class;
msgMetadata.handle = methodHandle;
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
// Pong Message.
MethodHandle pongHandle = getMethodHandle.apply(pongCallingArgs);
if (pongHandle != null)
{
metadata.setPongHandle(pongHandle, onMsg);
return true;
}
return false;
}
private boolean matchDecoders(Method onMsg, JakartaWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{
// TODO: we should be able to get this information directly from the AvailableDecoders in the metadata.
List<DecodedArgs> decodedTextCallingArgs = new ArrayList<>();
List<DecodedArgs> decodedTextStreamCallingArgs = new ArrayList<>();
List<DecodedArgs> decodedBinaryCallingArgs = new ArrayList<>();
List<DecodedArgs> decodedBinaryStreamCallingArgs = new ArrayList<>();
for (AvailableDecoders.RegisteredDecoder decoder : metadata.getAvailableDecoders())
{
InvokerUtils.Arg[] args = {new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(decoder.objectType).required()};
DecodedArgs decodedArgs = new DecodedArgs(decoder, args);
if (decoder.implementsInterface(Decoder.Text.class))
decodedTextCallingArgs.add(decodedArgs);
if (decoder.implementsInterface(Decoder.TextStream.class))
decodedTextStreamCallingArgs.add(decodedArgs);
if (decoder.implementsInterface(Decoder.Binary.class))
decodedBinaryCallingArgs.add(decodedArgs);
if (decoder.implementsInterface(Decoder.BinaryStream.class))
decodedBinaryStreamCallingArgs.add(decodedArgs);
}
MethodHandle methodHandle;
// Decoder.Text
for (DecodedArgs decodedArgs : decodedTextCallingArgs)
{
methodHandle = getMethodHandle.apply(decodedArgs.args);
if (methodHandle != null)
{
msgMetadata.sinkClass = DecodedTextMessageSink.class;
msgMetadata.handle = methodHandle;
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
}
// Decoder.Binary
for (DecodedArgs decodedArgs : decodedBinaryCallingArgs)
{
methodHandle = getMethodHandle.apply(decodedArgs.args);
if (methodHandle != null)
{
msgMetadata.sinkClass = DecodedBinaryMessageSink.class;
msgMetadata.handle = methodHandle;
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
}
// Try to match Text Stream decoders.
for (DecodedArgs decodedArgs : decodedTextStreamCallingArgs)
{
methodHandle = getMethodHandle.apply(decodedArgs.args);
if (methodHandle != null)
{
msgMetadata.sinkClass = DecodedTextStreamMessageSink.class;
msgMetadata.handle = methodHandle;
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
metadata.setTextMetadata(msgMetadata, onMsg);
return true;
}
}
// Decoder.BinaryStream
for (DecodedArgs decodedArgs : decodedBinaryStreamCallingArgs)
{
methodHandle = getMethodHandle.apply(decodedArgs.args);
if (methodHandle != null)
{
msgMetadata.sinkClass = DecodedBinaryStreamMessageSink.class;
msgMetadata.handle = methodHandle;
msgMetadata.registeredDecoder = decodedArgs.registeredDecoder;
metadata.setBinaryMetadata(msgMetadata, onMsg);
return true;
}
}
return false;
}
private void assertSignatureValid(Class<?> endpointClass, Method method, Class<? extends Annotation> annotationClass)
{
// Test modifiers
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" method must be public: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
if (Modifier.isStatic(mods))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" method must not be static: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
// Test return type
Class<?> returnType = method.getReturnType();
if ((returnType == Void.TYPE) || (returnType == Void.class))
{
// Void is 100% valid, always
return;
}
if (!OnMessage.class.isAssignableFrom(annotationClass))
{
StringBuilder err = new StringBuilder();
err.append("@").append(annotationClass.getSimpleName());
err.append(" return must be void: ");
ReflectUtils.append(err, endpointClass, method);
throw new InvalidSignatureException(err.toString());
}
}
/**
* <p>
* Gives a {@link MethodHandles.Lookup} instance to be used to find methods in server classes.
@ -755,16 +645,4 @@ public abstract class JakartaWebSocketFrameHandlerFactory
{
return MethodHandles.publicLookup().in(lookupClass);
}
private static class DecodedArgs
{
public final AvailableDecoders.RegisteredDecoder registeredDecoder;
public final InvokerUtils.Arg[] args;
public DecodedArgs(AvailableDecoders.RegisteredDecoder registeredDecoder, InvokerUtils.Arg... args)
{
this.registeredDecoder = registeredDecoder;
this.args = args;
}
}
}

View File

@ -20,32 +20,27 @@ package org.eclipse.jetty.websocket.jakarta.common;
import java.lang.invoke.MethodHandle;
import jakarta.websocket.Decoder;
import jakarta.websocket.Encoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class JakartaWebSocketFrameHandlerMetadata
{
private static final String[] NO_VARIABLES = new String[0];
// EndpointConfig entries
private final EndpointConfig endpointConfig;
private final AvailableDecoders availableDecoders;
private final AvailableEncoders availableEncoders;
private MethodHandle openHandle;
private MethodHandle closeHandle;
private MethodHandle errorHandle;
private MessageMetadata textMetadata;
private MessageMetadata binaryMetadata;
private MethodHandle pongHandle;
private JakartaWebSocketMessageMetadata textMetadata;
private JakartaWebSocketMessageMetadata binaryMetadata;
/**
* For {@code @ServerEndpoint} or {@code ServerEndpointConfig} based endpoints, this
@ -76,7 +71,6 @@ public class JakartaWebSocketFrameHandlerMetadata
public JakartaWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig)
{
this.endpointConfig = endpointConfig;
this.availableDecoders = new AvailableDecoders(endpointConfig);
this.availableEncoders = new AvailableEncoders(endpointConfig);
}
@ -91,7 +85,7 @@ public class JakartaWebSocketFrameHandlerMetadata
return availableEncoders;
}
public MessageMetadata getBinaryMetadata()
public JakartaWebSocketMessageMetadata getBinaryMetadata()
{
return binaryMetadata;
}
@ -133,7 +127,7 @@ public class JakartaWebSocketFrameHandlerMetadata
return pongHandle;
}
public MessageMetadata getTextMetadata()
public JakartaWebSocketMessageMetadata getTextMetadata()
{
return textMetadata;
}
@ -148,7 +142,7 @@ public class JakartaWebSocketFrameHandlerMetadata
return (textMetadata != null);
}
public void setBinaryMetadata(MessageMetadata metadata, Object origin)
public void setBinaryMetadata(JakartaWebSocketMessageMetadata metadata, Object origin)
{
assertNotSet(this.binaryMetadata, "BINARY Message Metadata", origin);
this.binaryMetadata = metadata;
@ -160,11 +154,6 @@ public class JakartaWebSocketFrameHandlerMetadata
this.closeHandle = close;
}
public void setDecoders(Class<? extends Decoder>[] decoders)
{
this.availableDecoders.registerAll(decoders);
}
public void setEncoders(Class<? extends Encoder>[] encoders)
{
this.availableEncoders.registerAll(encoders);
@ -188,7 +177,7 @@ public class JakartaWebSocketFrameHandlerMetadata
this.pongHandle = pong;
}
public void setTextMetadata(MessageMetadata metadata, Object origin)
public void setTextMetadata(JakartaWebSocketMessageMetadata metadata, Object origin)
{
assertNotSet(this.textMetadata, "TEXT Messsage Metadata", origin);
this.textMetadata = metadata;
@ -219,33 +208,4 @@ public class JakartaWebSocketFrameHandlerMetadata
return obj.toString();
}
public static class MessageMetadata
{
private static final int UNSET = -1;
public MethodHandle handle;
public Class<? extends MessageSink> sinkClass;
public AvailableDecoders.RegisteredDecoder registeredDecoder;
public int maxMessageSize = UNSET;
public static MessageMetadata copyOf(MessageMetadata metadata)
{
if (metadata == null)
return null;
MessageMetadata copy = new MessageMetadata();
copy.handle = metadata.handle;
copy.sinkClass = metadata.sinkClass;
copy.registeredDecoder = metadata.registeredDecoder;
copy.maxMessageSize = metadata.maxMessageSize;
return copy;
}
public boolean isMaxMessageSizeSet()
{
return maxMessageSize != UNSET;
}
}
}

View File

@ -0,0 +1,95 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common;
import java.lang.invoke.MethodHandle;
import java.util.List;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class JakartaWebSocketMessageMetadata
{
private MethodHandle methodHandle;
private Class<? extends MessageSink> sinkClass;
private List<RegisteredDecoder> registeredDecoders;
private int maxMessageSize = -1;
private boolean maxMessageSizeSet = false;
public static JakartaWebSocketMessageMetadata copyOf(JakartaWebSocketMessageMetadata metadata)
{
if (metadata == null)
return null;
JakartaWebSocketMessageMetadata copy = new JakartaWebSocketMessageMetadata();
copy.methodHandle = metadata.methodHandle;
copy.sinkClass = metadata.sinkClass;
copy.registeredDecoders = metadata.registeredDecoders;
copy.maxMessageSize = metadata.maxMessageSize;
copy.maxMessageSizeSet = metadata.maxMessageSizeSet;
return copy;
}
public boolean isMaxMessageSizeSet()
{
return maxMessageSizeSet;
}
public int getMaxMessageSize()
{
return maxMessageSize;
}
public void setMaxMessageSize(int maxMessageSize)
{
this.maxMessageSize = maxMessageSize;
this.maxMessageSizeSet = true;
}
public MethodHandle getMethodHandle()
{
return methodHandle;
}
public void setMethodHandle(MethodHandle methodHandle)
{
this.methodHandle = methodHandle;
}
public Class<? extends MessageSink> getSinkClass()
{
return sinkClass;
}
public void setSinkClass(Class<? extends MessageSink> sinkClass)
{
this.sinkClass = sinkClass;
}
public List<RegisteredDecoder> getRegisteredDecoders()
{
return registeredDecoders;
}
public void setRegisteredDecoders(List<RegisteredDecoder> decoders)
{
this.registeredDecoders = decoders;
}
}

View File

@ -59,12 +59,11 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
private final JakartaWebSocketContainer container;
private final CoreSession coreSession;
private final JakartaWebSocketFrameHandler frameHandler;
private final EndpointConfig config;
private final AvailableDecoders availableDecoders;
private final AvailableEncoders availableEncoders;
private final Map<String, String> pathParameters;
private final String sessionId;
private Map<String, Object> userProperties;
private final Map<String, Object> userProperties;
private List<Extension> negotiatedExtensions;
private JakartaWebSocketAsyncRemote asyncRemote;
@ -75,17 +74,17 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
JakartaWebSocketFrameHandler frameHandler,
EndpointConfig endpointConfig)
{
Objects.requireNonNull(endpointConfig);
this.container = container;
this.coreSession = coreSession;
this.frameHandler = frameHandler;
this.sessionId = UUID.randomUUID().toString();
this.config = Objects.requireNonNull(endpointConfig);
this.availableDecoders = new AvailableDecoders(this.config);
this.availableEncoders = new AvailableEncoders(this.config);
this.availableDecoders = new AvailableDecoders(endpointConfig);
this.availableEncoders = new AvailableEncoders(endpointConfig);
if (this.config instanceof PathParamProvider)
if (endpointConfig instanceof PathParamProvider)
{
PathParamProvider pathParamProvider = (PathParamProvider)this.config;
PathParamProvider pathParamProvider = (PathParamProvider)endpointConfig;
this.pathParameters = new HashMap<>(pathParamProvider.getPathParams());
}
else
@ -93,7 +92,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
this.pathParameters = Collections.emptyMap();
}
this.userProperties = this.config.getUserProperties();
this.userProperties = endpointConfig.getUserProperties();
}
public CoreSession getCoreSession()
@ -116,7 +115,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
LOG.debug("Add MessageHandler.Partial: {}", handler);
}
frameHandler.addMessageHandler(this, clazz, handler);
frameHandler.addMessageHandler(clazz, handler);
}
/**
@ -134,7 +133,7 @@ public class JakartaWebSocketSession implements jakarta.websocket.Session
LOG.debug("Add MessageHandler.Whole: {}", handler);
}
frameHandler.addMessageHandler(this, clazz, handler);
frameHandler.addMessageHandler(clazz, handler);
}
/**

View File

@ -33,7 +33,7 @@ public class SessionTracker extends AbstractLifeCycle implements JakartaWebSocke
{
private static final Logger LOG = LoggerFactory.getLogger(SessionTracker.class);
private CopyOnWriteArraySet<JakartaWebSocketSession> sessions = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<JakartaWebSocketSession> sessions = new CopyOnWriteArraySet<>();
public Set<Session> getSessions()
{

View File

@ -20,82 +20,30 @@ package org.eclipse.jetty.websocket.jakarta.common.decoders;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.jakarta.common.InitException;
import org.eclipse.jetty.websocket.util.InvalidSignatureException;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.eclipse.jetty.websocket.util.ReflectUtils;
public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredDecoder>
public class AvailableDecoders implements Iterable<RegisteredDecoder>
{
public static class RegisteredDecoder
{
// The user supplied Decoder class
public final Class<? extends Decoder> decoder;
// The jakarta.websocket.Decoder.* type (eg: Decoder.Binary, Decoder.BinaryStream, Decoder.Text, Decoder.TextStream)
public final Class<? extends Decoder> interfaceType;
public final Class<?> objectType;
public final boolean primitive;
public Decoder instance;
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType)
{
this(decoder, interfaceType, objectType, false);
}
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, boolean primitive)
{
this.decoder = decoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
}
public boolean implementsInterface(Class<? extends Decoder> type)
{
return interfaceType.isAssignableFrom(type);
}
public boolean isType(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(RegisteredDecoder.class.getSimpleName());
str.append('[').append(decoder.getName());
str.append(',').append(interfaceType.getName());
str.append(',').append(objectType.getName());
if (primitive)
{
str.append(",PRIMITIVE");
}
str.append(']');
return str.toString();
}
}
private final List<RegisteredDecoder> registeredDecoders = new ArrayList<>();
private final EndpointConfig config;
private LinkedList<RegisteredDecoder> registeredDecoders;
public AvailableDecoders(EndpointConfig config)
{
Objects.requireNonNull(config);
this.config = config;
registeredDecoders = new LinkedList<>();
// Register the Config Based Decoders.
this.config = Objects.requireNonNull(config);
registerAll(config.getDecoders());
// TEXT based [via Class reference]
registerPrimitive(BooleanDecoder.class, Decoder.Text.class, Boolean.class);
@ -125,17 +73,14 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
// STREAMING based
registerPrimitive(ReaderDecoder.class, Decoder.TextStream.class, Reader.class);
registerPrimitive(InputStreamDecoder.class, Decoder.BinaryStream.class, InputStream.class);
// Config Based
registerAll(config.getDecoders());
}
private void registerPrimitive(Class<? extends Decoder> decoderClass, Class<? extends Decoder> interfaceType, Class<?> type)
{
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, true));
registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, true));
}
public void register(Class<? extends Decoder> decoder)
private void register(Class<? extends Decoder> decoder)
{
if (!ReflectUtils.isDefaultConstructable(decoder))
{
@ -175,23 +120,10 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
}
}
// TODO: consider removing (if not used)
public void registerAll(Class<? extends Decoder>[] decoders)
private void registerAll(List<Class<? extends Decoder>> decoders)
{
if (decoders == null)
return;
for (Class<? extends Decoder> decoder : decoders)
{
register(decoder);
}
}
public void registerAll(List<Class<? extends Decoder>> decoders)
{
if (decoders == null)
return;
decoders.forEach(this::register);
}
@ -200,52 +132,34 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
Class<?> objectType = ReflectUtils.findGenericClassFor(decoder, interfaceClass);
if (objectType == null)
{
StringBuilder err = new StringBuilder();
err.append("Unknown Decoder Object type declared for interface ");
err.append(interfaceClass.getName());
err.append(" on class ");
err.append(decoder);
throw new InvalidWebSocketException(err.toString());
String err = "Unknown Decoder Object type declared for interface " +
interfaceClass.getName() + " on class " + decoder;
throw new InvalidWebSocketException(err);
}
try
// Validate the decoder to be added against the existing registered decoders.
for (RegisteredDecoder registered : registeredDecoders)
{
RegisteredDecoder conflicts = registeredDecoders.stream()
.filter(registered -> registered.isType(objectType))
.filter(registered -> !registered.primitive)
.findFirst()
.get();
if (conflicts.decoder.equals(decoder) && conflicts.implementsInterface(interfaceClass))
if (!registered.primitive && objectType.equals(registered.objectType))
{
// Same decoder as what is there already, don't bother adding it again.
return;
// Streaming decoders can only have one decoder per object type.
if (interfaceClass.equals(Decoder.TextStream.class) || interfaceClass.equals(Decoder.BinaryStream.class))
throw new InvalidWebSocketException("Multiple decoders for objectType" + objectType);
// If we have the same objectType, then the interfaceTypes must be the same to form a decoder list.
if (!registered.interfaceType.equals(interfaceClass))
throw new InvalidWebSocketException("Multiple decoders with different interface types for objectType " + objectType);
}
StringBuilder err = new StringBuilder();
err.append("Duplicate Decoder Object type ");
err.append(objectType.getName());
err.append(" in ");
err.append(decoder.getName());
err.append(", previously declared in ");
err.append(conflicts.decoder.getName());
throw new InvalidWebSocketException(err.toString());
}
catch (NoSuchElementException e)
{
registeredDecoders.addFirst(new RegisteredDecoder(decoder, interfaceClass, objectType));
// If this decoder is already registered for this interface type we can skip adding a duplicate.
if (registered.decoder.equals(decoder) && registered.interfaceType.equals(interfaceClass))
return;
}
registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config));
}
// TODO: consider removing (if not used)
public List<RegisteredDecoder> supporting(Class<? extends Decoder> interfaceType)
{
return registeredDecoders.stream()
.filter(registered -> registered.implementsInterface(interfaceType))
.collect(Collectors.toList());
}
public RegisteredDecoder getRegisteredDecoderFor(Class<?> type)
public RegisteredDecoder getFirstRegisteredDecoder(Class<?> type)
{
return registeredDecoders.stream()
.filter(registered -> registered.isType(type))
@ -253,49 +167,39 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
.orElse(null);
}
// TODO: consider removing (if not used)
public Class<? extends Decoder> getDecoderFor(Class<?> type)
public List<RegisteredDecoder> getRegisteredDecoders(Class<?> returnType)
{
try
{
return getRegisteredDecoderFor(type).decoder;
}
catch (NoSuchElementException e)
{
throw new InvalidWebSocketException("No Decoder found for type " + type);
}
return registeredDecoders.stream()
.filter(registered -> registered.isType(returnType))
.collect(Collectors.toList());
}
public <T extends Decoder> T getInstanceOf(RegisteredDecoder registeredDecoder)
public List<RegisteredDecoder> getRegisteredDecoders(Class<? extends Decoder> interfaceType, Class<?> returnType)
{
if (registeredDecoder.instance != null)
{
return (T)registeredDecoder.instance;
}
try
{
registeredDecoder.instance = registeredDecoder.decoder.getConstructor().newInstance();
registeredDecoder.instance.init(this.config);
return (T)registeredDecoder.instance;
}
catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
{
throw new InitException("Unable to init Decoder for type:" + registeredDecoder.decoder.getName(), e);
}
return registeredDecoders.stream()
.filter(registered -> registered.interfaceType.equals(interfaceType))
.filter(registered -> registered.isType(returnType))
.collect(Collectors.toList());
}
public <T extends Decoder> T getInstanceFor(Class<?> type)
public List<RegisteredDecoder> getTextDecoders(Class<?> returnType)
{
try
{
RegisteredDecoder registeredDecoder = getRegisteredDecoderFor(type);
return getInstanceOf(registeredDecoder);
}
catch (NoSuchElementException e)
{
throw new InvalidWebSocketException("No Decoder found for type " + type);
}
return getRegisteredDecoders(Decoder.Text.class, returnType);
}
public List<RegisteredDecoder> getBinaryDecoders(Class<?> returnType)
{
return getRegisteredDecoders(Decoder.Binary.class, returnType);
}
public List<RegisteredDecoder> getTextStreamDecoders(Class<?> returnType)
{
return getRegisteredDecoders(Decoder.TextStream.class, returnType);
}
public List<RegisteredDecoder> getBinaryStreamDecoders(Class<?> returnType)
{
return getRegisteredDecoders(Decoder.BinaryStream.class, returnType);
}
@Override
@ -303,4 +207,9 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
{
return registeredDecoders.iterator();
}
public Stream<RegisteredDecoder> stream()
{
return registeredDecoders.stream();
}
}

View File

@ -1,60 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common.decoders;
import java.nio.ByteBuffer;
import jakarta.websocket.DecodeException;
import jakarta.websocket.Decoder;
import jakarta.websocket.PongMessage;
import org.eclipse.jetty.util.BufferUtil;
public class PongMessageDecoder extends AbstractDecoder implements Decoder.Binary<PongMessage>
{
private static class PongMsg implements PongMessage
{
private final ByteBuffer bytes;
public PongMsg(ByteBuffer buf)
{
int len = buf.remaining();
this.bytes = ByteBuffer.allocate(len);
BufferUtil.put(buf, this.bytes);
BufferUtil.flipToFlush(this.bytes, 0);
}
@Override
public ByteBuffer getApplicationData()
{
return this.bytes;
}
}
@Override
public PongMessage decode(ByteBuffer bytes) throws DecodeException
{
return new PongMsg(bytes);
}
@Override
public boolean willDecode(ByteBuffer bytes)
{
return true;
}
}

View File

@ -0,0 +1,97 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common.decoders;
import java.lang.reflect.InvocationTargetException;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.jakarta.common.InitException;
public class RegisteredDecoder
{
// The user supplied Decoder class
public final Class<? extends Decoder> decoder;
// The jakarta.websocket.Decoder.* type (eg: Decoder.Binary, Decoder.BinaryStream, Decoder.Text, Decoder.TextStream)
public final Class<? extends Decoder> interfaceType;
public final Class<?> objectType;
public final boolean primitive;
public final EndpointConfig config;
private Decoder instance;
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig)
{
this(decoder, interfaceType, objectType, endpointConfig, false);
}
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<? extends Decoder> interfaceType, Class<?> objectType, EndpointConfig endpointConfig, boolean primitive)
{
this.decoder = decoder;
this.interfaceType = interfaceType;
this.objectType = objectType;
this.primitive = primitive;
this.config = endpointConfig;
}
public boolean implementsInterface(Class<? extends Decoder> type)
{
return interfaceType.isAssignableFrom(type);
}
public boolean isType(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
public <T extends Decoder> T getInstance()
{
if (instance == null)
{
try
{
instance = decoder.getConstructor().newInstance();
instance.init(config);
return (T)instance;
}
catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
{
throw new InitException("Unable to init Decoder for type:" + decoder.getName(), e);
}
}
return (T)instance;
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder();
str.append(RegisteredDecoder.class.getSimpleName());
str.append('[').append(decoder.getName());
str.append(',').append(interfaceType.getName());
str.append(',').append(objectType.getName());
if (primitive)
{
str.append(",PRIMITIVE");
}
str.append(']');
return str.toString();
}
}

View File

@ -0,0 +1,115 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import java.util.List;
import java.util.stream.Collectors;
import jakarta.websocket.CloseReason;
import jakarta.websocket.Decoder;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractDecodedMessageSink implements MessageSink
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractDecodedMessageSink.class);
private final MethodHandle _methodHandle;
private final MessageSink _messageSink;
public AbstractDecodedMessageSink(CoreSession coreSession, MethodHandle methodHandle)
{
_methodHandle = methodHandle;
try
{
_messageSink = newMessageSink(coreSession);
}
catch (Exception e)
{
// Throwing from here is an error implementation of the DecodedMessageSink.
throw new RuntimeException(e);
}
}
/**
* Invoke the MessageSink with the decoded message.
* @param message the decoded message.
*/
void invoke(Object message)
{
try
{
_methodHandle.invoke(message);
}
catch (Throwable t)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
}
}
/**
* @return a message sink which will first decode the message then pass it to {@link #_methodHandle}.
* @throws Exception for any error in creating the message sink.
*/
abstract MessageSink newMessageSink(CoreSession coreSession) throws Exception;
@Override
public void accept(Frame frame, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("accepting frame {} for {}", frame, _messageSink);
_messageSink.accept(frame, callback);
}
public abstract static class Basic<T extends Decoder> extends AbstractDecodedMessageSink
{
protected final List<T> _decoders;
public Basic(CoreSession coreSession, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(coreSession, methodHandle);
if (decoders.isEmpty())
throw new IllegalArgumentException("Require at least one decoder for " + this.getClass());
_decoders = decoders.stream()
.map(RegisteredDecoder::<T>getInstance)
.collect(Collectors.toList());
}
}
public abstract static class Stream<T extends Decoder> extends AbstractDecodedMessageSink
{
protected final T _decoder;
public Stream(CoreSession coreSession, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(coreSession, methodHandle);
if (decoders.size() != 1)
throw new IllegalArgumentException("Require exactly one decoder for " + this.getClass());
_decoder = decoders.get(0).getInstance();
}
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.ByteBuffer;
import java.util.List;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DecodeException;
@ -28,54 +29,49 @@ import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecodedBinaryMessageSink<T> extends DecodedMessageSink<Decoder.Binary<T>>
public class DecodedBinaryMessageSink<T> extends AbstractDecodedMessageSink.Basic<Decoder.Binary<T>>
{
public DecodedBinaryMessageSink(CoreSession session,
Decoder.Binary<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
private static final Logger LOG = LoggerFactory.getLogger(DecodedBinaryMessageSink.class);
public DecodedBinaryMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(session, decoder, methodHandle);
super(session, methodHandle, decoders);
}
@Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
MessageSink newMessageSink(CoreSession coreSession) throws Exception
{
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryMessageSink.class,
"onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class))
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(DecodedBinaryMessageSink.class, "onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class))
.bindTo(this);
return new ByteBufferMessageSink(coreSession, methodHandle);
}
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{
return new ByteBufferMessageSink(session, rawMethodHandle);
}
@SuppressWarnings("Duplicates")
public void onWholeMessage(ByteBuffer wholeMessage)
{
if (!getDecoder().willDecode(wholeMessage))
for (Decoder.Binary<T> decoder : _decoders)
{
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
return;
if (decoder.willDecode(wholeMessage))
{
try
{
T obj = decoder.decode(wholeMessage);
invoke(obj);
return;
}
catch (DecodeException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
}
}
try
{
T obj = getDecoder().decode(wholeMessage);
methodHandle.invoke(obj);
}
catch (DecodeException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
catch (Throwable t)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
}
LOG.warn("Message lost, willDecode() has returned false for all decoders in the decoder list.");
}
}

View File

@ -18,9 +18,11 @@
package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.List;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DecodeException;
@ -28,48 +30,36 @@ import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class DecodedBinaryStreamMessageSink<T> extends DecodedMessageSink<Decoder.BinaryStream<T>>
public class DecodedBinaryStreamMessageSink<T> extends AbstractDecodedMessageSink.Stream<Decoder.BinaryStream<T>>
{
public DecodedBinaryStreamMessageSink(CoreSession session,
Decoder.BinaryStream<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
public DecodedBinaryStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(session, decoder, methodHandle);
super(session, methodHandle, decoders);
}
@Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
MessageSink newMessageSink(CoreSession coreSession) throws Exception
{
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryStreamMessageSink.class,
"onStreamStart", MethodType.methodType(void.class, InputStream.class))
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(DecodedBinaryStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, InputStream.class))
.bindTo(this);
return new InputStreamMessageSink(coreSession, methodHandle);
}
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{
return new InputStreamMessageSink(session, rawMethodHandle);
}
@SuppressWarnings("Duplicates")
public void onStreamStart(InputStream stream)
{
try
{
T obj = getDecoder().decode(stream);
methodHandle.invoke(obj);
T obj = _decoder.decode(stream);
invoke(obj);
}
catch (DecodeException e)
catch (DecodeException | IOException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
catch (Throwable t)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
}
}
}

View File

@ -1,64 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import jakarta.websocket.Decoder;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.util.messages.AbstractMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class DecodedMessageSink<T extends Decoder> extends AbstractMessageSink
{
protected final Logger logger;
private final T decoder;
private final MethodHandle rawMethodHandle;
private final MessageSink rawMessageSink;
public DecodedMessageSink(CoreSession session, T decoder, MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
{
super(session, methodHandle);
this.logger = LoggerFactory.getLogger(this.getClass());
this.decoder = decoder;
this.rawMethodHandle = newRawMethodHandle();
this.rawMessageSink = newRawMessageSink(session, rawMethodHandle);
}
protected abstract MethodHandle newRawMethodHandle()
throws NoSuchMethodException, IllegalAccessException;
protected abstract MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle);
public T getDecoder()
{
return decoder;
}
@Override
public void accept(Frame frame, Callback callback)
{
this.rawMessageSink.accept(frame, callback);
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.List;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DecodeException;
@ -27,54 +28,49 @@ import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecodedTextMessageSink<T> extends DecodedMessageSink<Decoder.Text<T>>
public class DecodedTextMessageSink<T> extends AbstractDecodedMessageSink.Basic<Decoder.Text<T>>
{
public DecodedTextMessageSink(CoreSession session,
Decoder.Text<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
private static final Logger LOG = LoggerFactory.getLogger(DecodedTextMessageSink.class);
public DecodedTextMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(session, decoder, methodHandle);
super(session, methodHandle, decoders);
}
@Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
MessageSink newMessageSink(CoreSession coreSession) throws NoSuchMethodException, IllegalAccessException
{
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextMessageSink.class,
"onWholeMessage", MethodType.methodType(void.class, String.class))
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(getClass(), "onMessage", MethodType.methodType(void.class, String.class))
.bindTo(this);
return new StringMessageSink(coreSession, methodHandle);
}
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
public void onMessage(String wholeMessage)
{
return new StringMessageSink(session, rawMethodHandle);
}
@SuppressWarnings("Duplicates")
public void onWholeMessage(String wholeMessage)
{
if (!getDecoder().willDecode(wholeMessage))
for (Decoder.Text<T> decoder : _decoders)
{
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
return;
if (decoder.willDecode(wholeMessage))
{
try
{
T obj = decoder.decode(wholeMessage);
invoke(obj);
return;
}
catch (DecodeException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
}
}
try
{
T obj = getDecoder().decode(wholeMessage);
methodHandle.invoke(obj);
}
catch (DecodeException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
catch (Throwable t)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
}
LOG.warn("Message lost, willDecode() has returned false for all decoders in the decoder list.");
}
}

View File

@ -18,9 +18,11 @@
package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.io.IOException;
import java.io.Reader;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.List;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DecodeException;
@ -28,48 +30,36 @@ import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
public class DecodedTextStreamMessageSink<T> extends DecodedMessageSink<Decoder.TextStream<T>>
public class DecodedTextStreamMessageSink<T> extends AbstractDecodedMessageSink.Stream<Decoder.TextStream<T>>
{
public DecodedTextStreamMessageSink(CoreSession session,
Decoder.TextStream<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
public DecodedTextStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
super(session, decoder, methodHandle);
super(session, methodHandle, decoders);
}
@Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException
MessageSink newMessageSink(CoreSession coreSession) throws Exception
{
return JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextStreamMessageSink.class,
"onStreamStart", MethodType.methodType(void.class, Reader.class))
MethodHandle methodHandle = JakartaWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
.findVirtual(DecodedTextStreamMessageSink.class, "onStreamStart", MethodType.methodType(void.class, Reader.class))
.bindTo(this);
return new ReaderMessageSink(coreSession, methodHandle);
}
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{
return new ReaderMessageSink(session, rawMethodHandle);
}
@SuppressWarnings("Duplicates")
public void onStreamStart(Reader reader)
{
try
{
T obj = getDecoder().decode(reader);
methodHandle.invoke(obj);
T obj = _decoder.decode(reader);
invoke(obj);
}
catch (DecodeException e)
catch (DecodeException | IOException e)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Unable to decode", e);
}
catch (Throwable t)
{
throw new CloseException(CloseReason.CloseCodes.CANNOT_ACCEPT.getCode(), "Endpoint notification error", t);
}
}
}

View File

@ -20,13 +20,34 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.List;
import java.util.function.Consumer;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.Decoder;
import org.eclipse.jetty.websocket.jakarta.common.AbstractSessionTest;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
public abstract class AbstractMessageSinkTest extends AbstractSessionTest
{
public List<RegisteredDecoder> toRegisteredDecoderList(Class<? extends Decoder> clazz, Class<?> objectType)
{
Class<? extends Decoder> interfaceType;
if (Decoder.Text.class.isAssignableFrom(clazz))
interfaceType = Decoder.Text.class;
else if (Decoder.Binary.class.isAssignableFrom(clazz))
interfaceType = Decoder.Binary.class;
else if (Decoder.TextStream.class.isAssignableFrom(clazz))
interfaceType = Decoder.TextStream.class;
else if (Decoder.BinaryStream.class.isAssignableFrom(clazz))
interfaceType = Decoder.BinaryStream.class;
else
throw new IllegalStateException();
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
}
public <T> MethodHandle getAcceptHandle(Consumer<T> copy, Class<T> type)
{
try

View File

@ -22,6 +22,7 @@ import java.lang.invoke.MethodHandle;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -34,6 +35,7 @@ import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.jakarta.common.AbstractSessionTest;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -49,8 +51,8 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
Decoder.Binary<Calendar> decoder = new GmtDecoder();
DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink(AbstractSessionTest.session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedBinaryMessageSink<Calendar> sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders);
FutureCallback finCallback = new FutureCallback();
ByteBuffer data = ByteBuffer.allocate(16);
@ -72,8 +74,8 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
Decoder.Binary<Calendar> decoder = new GmtDecoder();
DecodedBinaryMessageSink sink = new DecodedBinaryMessageSink(AbstractSessionTest.session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedBinaryMessageSink<Calendar> sink = new DecodedBinaryMessageSink<>(AbstractSessionTest.session.getCoreSession(), copyHandle, decoders);
FutureCallback callback1 = new FutureCallback();
FutureCallback callback2 = new FutureCallback();
@ -130,7 +132,6 @@ public class DecodedBinaryMessageSinkTest extends AbstractMessageSinkTest
@SuppressWarnings("Duplicates")
public static class GmtDecoder implements Decoder.Binary<Calendar>
{
@Override
public Calendar decode(ByteBuffer buffer) throws DecodeException
{

View File

@ -24,6 +24,7 @@ import java.lang.invoke.MethodHandle;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -36,6 +37,7 @@ import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -51,8 +53,8 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
Decoder.BinaryStream<Calendar> decoder = new GmtDecoder();
DecodedBinaryStreamMessageSink sink = new DecodedBinaryStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedBinaryStreamMessageSink<Calendar> sink = new DecodedBinaryStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback finCallback = new FutureCallback();
ByteBuffer data = ByteBuffer.allocate(16);
@ -74,8 +76,8 @@ public class DecodedBinaryStreamMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Calendar> copyFuture = new CompletableFuture<>();
DecodedCalendarCopy copy = new DecodedCalendarCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Calendar.class);
Decoder.BinaryStream<Calendar> decoder = new GmtDecoder();
DecodedBinaryStreamMessageSink sink = new DecodedBinaryStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedBinaryStreamMessageSink<Calendar> sink = new DecodedBinaryStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback callback1 = new FutureCallback();
FutureCallback callback2 = new FutureCallback();

View File

@ -21,7 +21,9 @@ package org.eclipse.jetty.websocket.jakarta.common.messages;
import java.lang.invoke.MethodHandle;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -33,6 +35,7 @@ import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -48,8 +51,8 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
Decoder.Text<Date> decoder = new GmtDecoder();
DecodedTextMessageSink sink = new DecodedTextMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedTextMessageSink<Calendar> sink = new DecodedTextMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback finCallback = new FutureCallback();
sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback);
@ -66,8 +69,8 @@ public class DecodedTextMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
Decoder.Text<Date> decoder = new GmtDecoder();
DecodedTextMessageSink sink = new DecodedTextMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedTextMessageSink<Calendar> sink = new DecodedTextMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback callback1 = new FutureCallback();
FutureCallback callback2 = new FutureCallback();

View File

@ -23,7 +23,9 @@ import java.io.Reader;
import java.lang.invoke.MethodHandle;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@ -36,6 +38,7 @@ import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -51,8 +54,8 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
Decoder.TextStream<Date> decoder = new GmtDecoder();
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedTextStreamMessageSink<Calendar> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback finCallback = new FutureCallback();
sink.accept(new Frame(OpCode.TEXT).setPayload("2018.02.13").setFin(true), finCallback);
@ -69,8 +72,8 @@ public class DecodedTextStreamMessageSinkTest extends AbstractMessageSinkTest
CompletableFuture<Date> copyFuture = new CompletableFuture<>();
DecodedDateCopy copy = new DecodedDateCopy(copyFuture);
MethodHandle copyHandle = getAcceptHandle(copy, Date.class);
Decoder.TextStream<Date> decoder = new GmtDecoder();
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, copyHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(GmtDecoder.class, Calendar.class);
DecodedTextStreamMessageSink<Calendar> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), copyHandle, decoders);
FutureCallback callback1 = new FutureCallback();
FutureCallback callback2 = new FutureCallback();

View File

@ -41,15 +41,11 @@ public class JakartaWebSocketServerFrameHandlerFactory extends JakartaWebSocketC
public JakartaWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
{
if (jakarta.websocket.Endpoint.class.isAssignableFrom(endpointClass))
{
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
}
ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class);
if (anno == null)
{
return super.getMetadata(endpointClass, endpointConfig);
}
UriTemplatePathSpec templatePathSpec = new UriTemplatePathSpec(anno.value());
JakartaWebSocketFrameHandlerMetadata metadata = new JakartaWebSocketFrameHandlerMetadata(endpointConfig);

View File

@ -22,7 +22,7 @@ import jakarta.websocket.Decoder;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.PongMessage;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketSession;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.tests.MessageType;
import org.eclipse.jetty.websocket.util.ReflectUtils;
import org.hamcrest.Description;
@ -77,7 +77,7 @@ public class IsMessageHandlerType extends TypeSafeMatcher<MessageHandler>
return false;
}
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageClass);
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageClass);
if (registeredDecoder == null)
{
return false;

View File

@ -25,7 +25,7 @@ import jakarta.websocket.MessageHandler;
import jakarta.websocket.PongMessage;
import org.eclipse.jetty.websocket.jakarta.common.JakartaWebSocketSession;
import org.eclipse.jetty.websocket.jakarta.common.RegisteredMessageHandler;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.tests.MessageType;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
@ -60,7 +60,7 @@ public class IsMessageHandlerTypeRegistered extends TypeSafeMatcher<JakartaWebSo
{
Class<?> onMessageType = registeredMessageHandler.getHandlerType();
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageType);
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageType);
if (registeredDecoder == null)
{
continue;
@ -130,7 +130,7 @@ public class IsMessageHandlerTypeRegistered extends TypeSafeMatcher<JakartaWebSo
mismatchDescription.appendText("<" + onMessageType.getName() + ">");
AvailableDecoders.RegisteredDecoder registeredDecoder = session.getDecoders().getRegisteredDecoderFor(onMessageType);
RegisteredDecoder registeredDecoder = session.getDecoders().getFirstRegisteredDecoder(onMessageType);
if (registeredDecoder == null)
{
mismatchDescription.appendText("(!NO-DECODER!)");

View File

@ -20,19 +20,21 @@ package org.eclipse.jetty.websocket.jakarta.tests.coders;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.TimeZone;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.DecodeException;
import jakarta.websocket.Decoder;
import jakarta.websocket.EndpointConfig;
import org.eclipse.jetty.toolchain.test.Hex;
import org.eclipse.jetty.websocket.jakarta.client.BasicClientEndpointConfig;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jakarta.common.decoders.IntegerDecoder;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -44,169 +46,198 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class AvailableDecodersTest
{
private static EndpointConfig testConfig;
private AvailableDecoders availableDecoders;
@BeforeAll
public static void initConfig()
@SafeVarargs
public final void init(Class<? extends Decoder>... decoder)
{
testConfig = new BasicClientEndpointConfig();
EndpointConfig testConfig = ClientEndpointConfig.Builder.create()
.decoders(Arrays.asList(decoder))
.build();
this.availableDecoders = new AvailableDecoders(testConfig);
}
private AvailableDecoders decoders = new AvailableDecoders(testConfig);
private <T> void assertTextDecoder(Class<T> type, String value, T expectedDecoded) throws IllegalAccessException, InstantiationException, DecodeException
public <T extends Decoder> T getInstanceFor(Class<?> type)
{
Decoder.Text<T> decoder = (Decoder.Text<T>)decoders.getInstanceFor(type);
try
{
RegisteredDecoder registeredDecoder = availableDecoders.getFirstRegisteredDecoder(type);
return registeredDecoder.getInstance();
}
catch (NoSuchElementException e)
{
throw new InvalidWebSocketException("No Decoder found for type " + type);
}
}
private <T> void assertTextDecoder(Class<T> type, String value, T expectedDecoded) throws DecodeException
{
Decoder.Text<T> decoder = getInstanceFor(type);
assertThat("Decoder instance", decoder, notNullValue());
T decoded = decoder.decode(value);
assertThat("Decoded", decoded, is(expectedDecoded));
}
private <T> void assertBinaryDecoder(Class<T> type, ByteBuffer value, T expectedDecoded)
throws IllegalAccessException, InstantiationException, DecodeException
throws DecodeException
{
Decoder.Binary<T> decoder = (Decoder.Binary<T>)decoders.getInstanceFor(type);
Decoder.Binary<T> decoder = getInstanceFor(type);
assertThat("Decoder Class", decoder, notNullValue());
T decoded = decoder.decode(value);
assertThat("Decoded", decoded, equalTo(expectedDecoded));
}
@Test
public void testCoreDecodeBoolean() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeBoolean() throws DecodeException
{
init();
Boolean expected = Boolean.TRUE;
assertTextDecoder(Boolean.class, "true", expected);
}
@Test
public void testCoreDecodeboolean() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeboolean() throws DecodeException
{
init();
boolean expected = false;
assertTextDecoder(Boolean.TYPE, "false", expected);
}
@Test
public void testCoreDecodeByte() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeByte() throws DecodeException
{
init();
Byte expected = (byte)0x21;
assertTextDecoder(Byte.class, "33", expected);
}
@Test
public void testCoreDecodebyte() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodebyte() throws DecodeException
{
init();
byte expected = 0x21;
assertTextDecoder(Byte.TYPE, "33", expected);
}
@Test
public void testCoreDecodeCharacter() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeCharacter() throws DecodeException
{
init();
Character expected = '!';
assertTextDecoder(Character.class, "!", expected);
}
@Test
public void testCoreDecodechar() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodechar() throws DecodeException
{
init();
char expected = '!';
assertTextDecoder(Character.TYPE, "!", expected);
}
@Test
public void testCoreDecodeDouble() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeDouble() throws DecodeException
{
init();
Double expected = 123.45D;
assertTextDecoder(Double.class, "123.45", expected);
}
@Test
public void testCoreDecodedouble() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodedouble() throws DecodeException
{
init();
double expected = 123.45D;
assertTextDecoder(Double.TYPE, "123.45", expected);
}
@Test
public void testCoreDecodeFloat() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeFloat() throws DecodeException
{
init();
Float expected = 123.4567F;
assertTextDecoder(Float.class, "123.4567", expected);
}
@Test
public void testCoreDecodefloat() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodefloat() throws DecodeException
{
init();
float expected = 123.4567F;
assertTextDecoder(Float.TYPE, "123.4567", expected);
}
@Test
public void testCoreDecodeInteger() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeInteger() throws DecodeException
{
init();
Integer expected = 1234;
assertTextDecoder(Integer.class, "1234", expected);
}
@Test
public void testCoreDecodeint() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeint() throws DecodeException
{
init();
int expected = 1234;
assertTextDecoder(Integer.TYPE, "1234", expected);
}
@Test
public void testCoreDecodeLong() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeLong() throws DecodeException
{
init();
Long expected = 123_456_789L;
assertTextDecoder(Long.class, "123456789", expected);
}
@Test
public void testCoreDecodelong() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodelong() throws DecodeException
{
init();
long expected = 123_456_789L;
assertTextDecoder(Long.TYPE, "123456789", expected);
}
@Test
public void testCoreDecodeString() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeString() throws DecodeException
{
init();
String expected = "Hello World";
assertTextDecoder(String.class, "Hello World", expected);
}
@Test
public void testCoreDecodeByteBuffer() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeByteBuffer() throws DecodeException
{
init();
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
ByteBuffer expected = Hex.asByteBuffer("112233445566778899");
assertBinaryDecoder(ByteBuffer.class, val, expected);
}
@Test
public void testCoreDecodeByteArray() throws IllegalAccessException, InstantiationException, DecodeException
public void testCoreDecodeByteArray() throws DecodeException
{
init();
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
byte[] expected = Hex.asByteArray("112233445566778899");
assertBinaryDecoder(byte[].class, val, expected);
}
@Test
public void testCustomDecoderInteger() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderInteger() throws DecodeException
{
decoders.register(IntegerDecoder.class);
init(IntegerDecoder.class);
String val = "11223344";
int expected = 11223344;
assertTextDecoder(Integer.class, val, expected);
}
@Test
public void testCustomDecoderTime() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderTime() throws DecodeException
{
decoders.register(TimeDecoder.class);
init(TimeDecoder.class);
String val = "12:34:56 GMT";
Date epoch = Date.from(Instant.EPOCH);
@ -222,10 +253,9 @@ public class AvailableDecodersTest
}
@Test
public void testCustomDecoderDate() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderDate() throws DecodeException
{
decoders.register(DateDecoder.class);
init(DateDecoder.class);
String val = "2016.08.22";
Date epoch = Date.from(Instant.EPOCH);
@ -241,10 +271,9 @@ public class AvailableDecodersTest
}
@Test
public void testCustomDecoderDateTime() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderDateTime() throws DecodeException
{
decoders.register(DateTimeDecoder.class);
init(DateTimeDecoder.class);
String val = "2016.08.22 AD at 12:34:56 GMT";
Date epoch = Date.from(Instant.EPOCH);
@ -264,11 +293,10 @@ public class AvailableDecodersTest
}
@Test
public void testCustomDecoderValidDualText() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderValidDualText() throws DecodeException
{
decoders.register(ValidDualDecoder.class);
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Integer.class);
init(ValidDualDecoder.class);
RegisteredDecoder registered = availableDecoders.getFirstRegisteredDecoder(Integer.class);
assertThat("Registered Decoder for Integer", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
String val = "[1,234,567]";
@ -278,11 +306,10 @@ public class AvailableDecodersTest
}
@Test
public void testCustomDecoderValidDualBinary() throws IllegalAccessException, InstantiationException, DecodeException
public void testCustomDecoderValidDualBinary() throws DecodeException
{
decoders.register(ValidDualDecoder.class);
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Long.class);
init(ValidDualDecoder.class);
RegisteredDecoder registered = availableDecoders.getFirstRegisteredDecoder(Long.class);
assertThat("Registered Decoder for Long", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
ByteBuffer val = ByteBuffer.allocate(16);
@ -299,18 +326,16 @@ public class AvailableDecodersTest
public void testCustomDecoderRegisterDuplicate()
{
// has duplicated support for the same target Type
Exception e = assertThrows(InvalidWebSocketException.class, () -> decoders.register(BadDualDecoder.class));
assertThat(e.getMessage(), containsString("Duplicate"));
Exception e = assertThrows(InvalidWebSocketException.class, () -> init(BadDualDecoder.class));
assertThat(e.getMessage(), containsString("Multiple decoders with different interface types"));
}
@Test
public void testCustomDecoderRegisterOtherDuplicate()
{
// This duplicate of decoders is of the same interface type so will form a decoder list.
// Register DateDecoder (decodes java.util.Date)
decoders.register(DateDecoder.class);
// Register TimeDecoder (which also wants to decode java.util.Date)
Exception e = assertThrows(InvalidWebSocketException.class, () -> decoders.register(TimeDecoder.class));
assertThat(e.getMessage(), containsString("Duplicate"));
init(DateDecoder.class, TimeDecoder.class);
}
}

View File

@ -0,0 +1,422 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.jakarta.tests.coders;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import jakarta.websocket.Decoder;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerContainer;
import jakarta.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.websocket.jakarta.client.JakartaWebSocketClientContainer;
import org.eclipse.jetty.websocket.jakarta.common.decoders.AbstractDecoder;
import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.jakarta.tests.EventSocket;
import org.eclipse.jetty.websocket.jakarta.tests.WSURI;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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 static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DecoderListTest
{
private Server server;
private ServletContextHandler contextHandler;
private URI serverUri;
private JakartaWebSocketClientContainer client;
@BeforeEach
public void before() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/");
server.setHandler(contextHandler);
client = new JakartaWebSocketClientContainer();
client.start();
}
@AfterEach
public void after() throws Exception
{
server.stop();
client.stop();
}
public interface CheckedConsumer<T>
{
void accept(T t) throws DeploymentException;
}
public void start(CheckedConsumer<ServerContainer> containerConsumer) throws Exception
{
JakartaWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
containerConsumer.accept(container));
server.start();
serverUri = WSURI.toWebsocket(server.getURI());
}
public static Stream<Arguments> getTextArguments()
{
return Stream.of(
Arguments.of("=DecodeEquals", "DecodeEquals="),
Arguments.of("+DecodePlus", "DecodePlus+"),
Arguments.of("-DecodeMinus", "DecodeMinus-"),
Arguments.of("DecodeNoMatch", "DecodeNoMatch")
);
}
public static Stream<Arguments> getBinaryArguments()
{
return Stream.of(
Arguments.of("=DecodeEquals", "DecodeEquals="),
Arguments.of("+DecodePlus", "DecodePlus+"),
Arguments.of("-DecodeMinus", "DecodeMinus-"),
// No decoder accepts this message and we have no default decoder for this type, so we get no response.
Arguments.of("DecodeNoMatch", null)
);
}
@ParameterizedTest
@MethodSource("getTextArguments")
public void testTextDecoderList(String request, String expected) throws Exception
{
start(container ->
{
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
.decoders(List.of(EqualsTextDecoder.class, PlusTextDecoder.class, MinusTextDecoder.class))
.build();
container.addEndpoint(endpointConfig);
});
EventSocket clientEndpoint = new EventSocket();
Session session = client.connectToServer(clientEndpoint, serverUri);
session.getBasicRemote().sendText(request);
String response = clientEndpoint.textMessages.poll(3, TimeUnit.SECONDS);
assertThat(response, is(expected));
}
@ParameterizedTest
@MethodSource("getBinaryArguments")
public void testBinaryDecoderList(String request, String expected) throws Exception
{
start(container ->
{
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(BinaryDecoderListEndpoint.class, "/")
.decoders(List.of(EqualsBinaryDecoder.class, PlusBinaryDecoder.class, MinusBinaryDecoder.class))
.build();
container.addEndpoint(endpointConfig);
});
EventSocket clientEndpoint = new EventSocket();
Session session = client.connectToServer(clientEndpoint, serverUri);
session.getBasicRemote().sendBinary(BufferUtil.toBuffer(request));
ByteBuffer response = clientEndpoint.binaryMessages.poll(3, TimeUnit.SECONDS);
assertThat(BufferUtil.toString(response), is(expected));
}
@Test
public void testDecoderOrder() throws Exception
{
start(container ->
{
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
.decoders(List.of(AppendingPlusDecoder.class, AppendingMinusDecoder.class))
.build();
container.addEndpoint(endpointConfig);
});
// The AppendingPlusDecoder should be the one used as it was first in the list.
EventSocket clientEndpoint = new EventSocket();
Session session = client.connectToServer(clientEndpoint, serverUri);
session.getBasicRemote().sendText("");
String response = clientEndpoint.textMessages.poll(3, TimeUnit.SECONDS);
assertThat(response, is("+"));
}
@Test
public void testStreamDecoders()
{
// Stream decoders will not be able to form a decoder list as they don't implement willDecode().
Throwable error = assertThrows(Throwable.class, () ->
start(container ->
{
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(TextDecoderListEndpoint.class, "/")
.decoders(List.of(TextStreamDecoder1.class, TextStreamDecoder2.class))
.build();
container.addEndpoint(endpointConfig);
})
);
assertThat(error, instanceOf(RuntimeException.class));
Throwable cause = error.getCause();
assertThat(cause, instanceOf(DeploymentException.class));
Throwable invalidWebSocketException = cause.getCause();
assertThat(invalidWebSocketException, instanceOf(InvalidWebSocketException.class));
assertThat(invalidWebSocketException.getMessage(), containsString("Multiple decoders for objectTypeclass java.lang.String"));
}
public static class TextDecoderListEndpoint extends Endpoint
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(new PartialTextHandler(session));
}
}
public static class BinaryDecoderListEndpoint extends Endpoint
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
session.addMessageHandler(new PartialBinaryHandler(session));
}
@Override
public void onError(Session session, Throwable t)
{
t.printStackTrace();
}
}
private static class PartialTextHandler implements MessageHandler.Whole<String>
{
private final Session _session;
public PartialTextHandler(Session session)
{
_session = session;
}
@Override
public void onMessage(String message)
{
_session.getAsyncRemote().sendText(message);
}
}
private static class PartialBinaryHandler implements MessageHandler.Whole<ByteBufferWrapper>
{
private final Session _session;
public PartialBinaryHandler(Session session)
{
_session = session;
}
@Override
public void onMessage(ByteBufferWrapper message)
{
_session.getAsyncRemote().sendBinary(message.getByteBuffer());
}
}
public static class ByteBufferWrapper
{
private final ByteBuffer byteBuffer;
public ByteBufferWrapper(ByteBuffer byteBuffer)
{
this.byteBuffer = byteBuffer;
}
public ByteBuffer getByteBuffer()
{
return byteBuffer;
}
}
public static class EqualsBinaryDecoder extends ByteBufferWrapperDecoder
{
public EqualsBinaryDecoder()
{
super("=");
}
}
public static class PlusBinaryDecoder extends ByteBufferWrapperDecoder
{
public PlusBinaryDecoder()
{
super("+");
}
}
public static class MinusBinaryDecoder extends ByteBufferWrapperDecoder
{
public MinusBinaryDecoder()
{
super("-");
}
}
public static class EqualsTextDecoder extends PrefixStringDecoder
{
public EqualsTextDecoder()
{
super("=");
}
}
public static class PlusTextDecoder extends PrefixStringDecoder
{
public PlusTextDecoder()
{
super("+");
}
}
public static class MinusTextDecoder extends PrefixStringDecoder
{
public MinusTextDecoder()
{
super("-");
}
}
public static class AppendingPlusDecoder extends AppendingStringDecoder
{
public AppendingPlusDecoder()
{
super("+");
}
}
public static class AppendingMinusDecoder extends AppendingStringDecoder
{
public AppendingMinusDecoder()
{
super("-");
}
}
public static class TextStreamDecoder1 extends AbstractDecoder implements Decoder.TextStream<String>
{
@Override
public String decode(Reader reader) throws IOException
{
return "Decoder1: " + IO.toString(reader);
}
}
public static class TextStreamDecoder2 extends AbstractDecoder implements Decoder.TextStream<String>
{
@Override
public String decode(Reader reader) throws IOException
{
return "Decoder2: " + IO.toString(reader);
}
}
public static class ByteBufferWrapperDecoder extends AbstractDecoder implements Decoder.Binary<ByteBufferWrapper>
{
private final String prefix;
public ByteBufferWrapperDecoder(String prefix)
{
this.prefix = prefix;
}
@Override
public ByteBufferWrapper decode(ByteBuffer bytes)
{
String s = BufferUtil.toString(bytes).substring(prefix.length()) + prefix;
return new ByteBufferWrapper(BufferUtil.toBuffer(s));
}
@Override
public boolean willDecode(ByteBuffer bytes)
{
return BufferUtil.toString(bytes).startsWith(prefix);
}
}
public static class PrefixStringDecoder extends AbstractDecoder implements Decoder.Text<String>
{
private final String prefix;
public PrefixStringDecoder(String prefix)
{
this.prefix = prefix;
}
@Override
public String decode(String s)
{
return s.substring(prefix.length()) + prefix;
}
@Override
public boolean willDecode(String s)
{
return s.startsWith(prefix);
}
}
public static class AppendingStringDecoder extends AbstractDecoder implements Decoder.Text<String>
{
private final String s;
public AppendingStringDecoder(String prefix)
{
this.s = prefix;
}
@Override
public String decode(String message)
{
return message + s;
}
@Override
public boolean willDecode(String message)
{
return true;
}
}
}

View File

@ -28,10 +28,12 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.Decoder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.jakarta.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.jakarta.common.messages.DecodedTextStreamMessageSink;
import org.eclipse.jetty.websocket.jakarta.tests.FunctionMethod;
import org.eclipse.jetty.websocket.jakarta.tests.client.AbstractClientSessionTest;
@ -64,7 +66,6 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
@Test
public void testQuotesDecodedReaderMessageSink() throws Exception
{
Decoder.TextStream<Quotes> decoder = new QuotesDecoder();
CompletableFuture<Quotes> futureQuotes = new CompletableFuture<>();
MethodHandle functionHandle = FunctionMethod.getFunctionApplyMethodHandle();
MethodHandle quoteHandle = functionHandle.bindTo((Function<Quotes, Void>)(quotes) ->
@ -80,7 +81,8 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
return null;
});
DecodedTextStreamMessageSink sink = new DecodedTextStreamMessageSink(session.getCoreSession(), decoder, quoteHandle);
List<RegisteredDecoder> decoders = toRegisteredDecoderList(QuotesDecoder.class, Quotes.class);
DecodedTextStreamMessageSink<Quotes> sink = new DecodedTextStreamMessageSink<>(session.getCoreSession(), quoteHandle, decoders);
List<FutureCallback> callbacks = new ArrayList<>();
FutureCallback finCallback = null;
@ -107,4 +109,21 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
assertThat("Quotes.author", quotes.getAuthor(), is("Benjamin Franklin"));
assertThat("Quotes.count", quotes.getQuotes().size(), is(3));
}
public List<RegisteredDecoder> toRegisteredDecoderList(Class<? extends Decoder> clazz, Class<?> objectType)
{
Class<? extends Decoder> interfaceType;
if (Decoder.Text.class.isAssignableFrom(clazz))
interfaceType = Decoder.Text.class;
else if (Decoder.Binary.class.isAssignableFrom(clazz))
interfaceType = Decoder.Binary.class;
else if (Decoder.TextStream.class.isAssignableFrom(clazz))
interfaceType = Decoder.TextStream.class;
else if (Decoder.BinaryStream.class.isAssignableFrom(clazz))
interfaceType = Decoder.BinaryStream.class;
else
throw new IllegalStateException();
return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build()));
}
}

View File

@ -18,7 +18,7 @@
<jetty.url>http://www.eclipse.org/jetty</jetty.url>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<build-support.version>1.4</build-support.version>
<checkstyle.version>8.20</checkstyle.version>
<checkstyle.version>8.29</checkstyle.version>
<slf4j.version>2.0.0-alpha1</slf4j.version>
<log4j2.version>2.13.0</log4j2.version>
<logback.version>1.3.0-alpha5</logback.version>

View File

@ -64,10 +64,10 @@ public class MongoTestHelper
{
long start = System.currentTimeMillis();
mongo.start();
String containerIpAddress = mongo.getContainerIpAddress();
String containerIpAddress = mongo.getContainerIpAddress();
int mongoPort = mongo.getMappedPort(27017);
LOG.info("Mongo container started for {}:{} - {}ms", containerIpAddress, mongoPort,
System.currentTimeMillis() - start);
System.currentTimeMillis() - start);
System.setProperty("embedmongoHost", containerIpAddress);
System.setProperty("embedmongoPort", Integer.toString(mongoPort));
}

View File

@ -85,14 +85,14 @@ public class DuplicateCookieTest
client.stop();
}
}
@Test
public void testMultipleSessionCookiesOnlyOneValid() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
HttpClient client = null;
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
@ -114,7 +114,7 @@ public class DuplicateCookieTest
createInvalidSession(contextHandler.getSessionHandler().getSessionCache(),
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
"2233");
client = new HttpClient();
client.start();
@ -132,7 +132,7 @@ public class DuplicateCookieTest
client.stop();
}
}
@Test
public void testMultipleSessionCookiesMultipleExists() throws Exception
{
@ -163,7 +163,7 @@ public class DuplicateCookieTest
createUnExpiredSession(contextHandler.getSessionHandler().getSessionCache(),
contextHandler.getSessionHandler().getSessionCache().getSessionDataStore(),
"9111");
client = new HttpClient();
client.start();
@ -190,14 +190,14 @@ public class DuplicateCookieTest
cache.add(id, s);
return s;
}
public Session createInvalidSession(SessionCache cache, SessionDataStore store, String id) throws Exception
{
Session session = createUnExpiredSession(cache, store, id);
session._state = Session.State.INVALID;
return session;
}
public static class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;

View File

@ -95,7 +95,7 @@ public class SessionInvalidationTest
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@ -117,7 +117,7 @@ public class SessionInvalidationTest
//invalidate existing session
session.invalidate();
assertThrows(IllegalStateException.class, () -> session.invalidate());
assertThrows(IllegalStateException.class, () -> session.getLastAccessedTime());
assertThrows(IllegalStateException.class, () -> session.getCreationTime());
@ -130,7 +130,6 @@ public class SessionInvalidationTest
assertThrows(IllegalStateException.class, () -> session.removeValue("foo"));
assertThrows(IllegalStateException.class, () -> session.setAttribute("a", "b"));
assertDoesNotThrow(() -> session.getId());
}
}
}