Issue #3428 - Initial refactor to support javax websocket decoderLists

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2020-05-19 22:45:37 +10:00
parent aa52d67dbf
commit 4b19c19815
26 changed files with 964 additions and 677 deletions

View File

@ -49,14 +49,10 @@ public class JavaxWebSocketClientFrameHandlerFactory extends JavaxWebSocketFrame
public JavaxWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig) public JavaxWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
{ {
if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass)) if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass))
{
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig); return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
}
if (endpointClass.getAnnotation(ClientEndpoint.class) == null) if (endpointClass.getAnnotation(ClientEndpoint.class) == null)
{
return null; return null;
}
JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig);
return discoverJavaxFrameHandlerMetadata(endpointClass, metadata); return discoverJavaxFrameHandlerMetadata(endpointClass, metadata);

View File

@ -0,0 +1,79 @@
//
// ========================================================================
// 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.javax.common;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.util.InvokerUtils;
// The different kind of @OnMessage method parameter signatures expected.
public class JavaxWebSocketCallingArgs
{
static final InvokerUtils.Arg[] textCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(String.class).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[] binaryBufferCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(ByteBuffer.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[] binaryArrayCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(byte[].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[] inputStreamCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(InputStream.class).required()
};
static final InvokerUtils.Arg[] readerCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(Reader.class).required()
};
static final InvokerUtils.Arg[] pongCallingArgs = new InvokerUtils.Arg[]{
new InvokerUtils.Arg(Session.class),
new InvokerUtils.Arg(PongMessage.class).required()
};
}

View File

@ -23,6 +23,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -46,6 +47,7 @@ import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.exception.ProtocolException; import org.eclipse.jetty.websocket.core.exception.ProtocolException;
import org.eclipse.jetty.websocket.core.exception.WebSocketException; import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryStreamMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryStreamMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextMessageSink;
@ -95,9 +97,9 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
private MethodHandle openHandle; private MethodHandle openHandle;
private MethodHandle closeHandle; private MethodHandle closeHandle;
private MethodHandle errorHandle; private MethodHandle errorHandle;
private JavaxWebSocketFrameHandlerMetadata.MessageMetadata textMetadata;
private JavaxWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata;
private MethodHandle pongHandle; private MethodHandle pongHandle;
private JavaxWebSocketMessageMetadata textMetadata;
private JavaxWebSocketMessageMetadata binaryMetadata;
private UpgradeRequest upgradeRequest; private UpgradeRequest upgradeRequest;
@ -114,8 +116,8 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
public JavaxWebSocketFrameHandler(JavaxWebSocketContainer container, public JavaxWebSocketFrameHandler(JavaxWebSocketContainer container,
Object endpointInstance, Object endpointInstance,
MethodHandle openHandle, MethodHandle closeHandle, MethodHandle errorHandle, MethodHandle openHandle, MethodHandle closeHandle, MethodHandle errorHandle,
JavaxWebSocketFrameHandlerMetadata.MessageMetadata textMetadata, JavaxWebSocketMessageMetadata textMetadata,
JavaxWebSocketFrameHandlerMetadata.MessageMetadata binaryMetadata, JavaxWebSocketMessageMetadata binaryMetadata,
MethodHandle pongHandle, MethodHandle pongHandle,
EndpointConfig endpointConfig) EndpointConfig endpointConfig)
{ {
@ -170,26 +172,32 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
errorHandle = InvokerUtils.bindTo(errorHandle, session); errorHandle = InvokerUtils.bindTo(errorHandle, session);
pongHandle = InvokerUtils.bindTo(pongHandle, session); pongHandle = InvokerUtils.bindTo(pongHandle, session);
JavaxWebSocketFrameHandlerMetadata.MessageMetadata actualTextMetadata = JavaxWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(textMetadata); JavaxWebSocketMessageMetadata actualTextMetadata = JavaxWebSocketMessageMetadata.copyOf(textMetadata);
if (actualTextMetadata != null) if (actualTextMetadata != null)
{ {
if (actualTextMetadata.isMaxMessageSizeSet()) if (actualTextMetadata.isMaxMessageSizeSet())
session.setMaxTextMessageBufferSize(actualTextMetadata.maxMessageSize); session.setMaxTextMessageBufferSize(actualTextMetadata.getMaxMessageSize());
MethodHandle methodHandle = actualTextMetadata.getMethodHandle();
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
methodHandle = JavaxWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
actualTextMetadata.setMethodHandle(methodHandle);
actualTextMetadata.handle = InvokerUtils.bindTo(actualTextMetadata.handle, endpointInstance, endpointConfig, session);
actualTextMetadata.handle = JavaxWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualTextMetadata.handle, session);
textSink = JavaxWebSocketFrameHandlerFactory.createMessageSink(session, actualTextMetadata); textSink = JavaxWebSocketFrameHandlerFactory.createMessageSink(session, actualTextMetadata);
textMetadata = actualTextMetadata; textMetadata = actualTextMetadata;
} }
JavaxWebSocketFrameHandlerMetadata.MessageMetadata actualBinaryMetadata = JavaxWebSocketFrameHandlerMetadata.MessageMetadata.copyOf(binaryMetadata); JavaxWebSocketMessageMetadata actualBinaryMetadata = JavaxWebSocketMessageMetadata.copyOf(binaryMetadata);
if (actualBinaryMetadata != null) if (actualBinaryMetadata != null)
{ {
if (actualBinaryMetadata.isMaxMessageSizeSet()) if (actualBinaryMetadata.isMaxMessageSizeSet())
session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.maxMessageSize); session.setMaxBinaryMessageBufferSize(actualBinaryMetadata.getMaxMessageSize());
MethodHandle methodHandle = actualBinaryMetadata.getMethodHandle();
methodHandle = InvokerUtils.bindTo(methodHandle, endpointInstance, endpointConfig, session);
methodHandle = JavaxWebSocketFrameHandlerFactory.wrapNonVoidReturnType(methodHandle, session);
actualBinaryMetadata.setMethodHandle(methodHandle);
actualBinaryMetadata.handle = InvokerUtils.bindTo(actualBinaryMetadata.handle, endpointInstance, endpointConfig, session);
actualBinaryMetadata.handle = JavaxWebSocketFrameHandlerFactory.wrapNonVoidReturnType(actualBinaryMetadata.handle, session);
binarySink = JavaxWebSocketFrameHandlerFactory.createMessageSink(session, actualBinaryMetadata); binarySink = JavaxWebSocketFrameHandlerFactory.createMessageSink(session, actualBinaryMetadata);
binaryMetadata = actualBinaryMetadata; binaryMetadata = actualBinaryMetadata;
} }
@ -350,12 +358,12 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
return messageHandlerMap; return messageHandlerMap;
} }
public JavaxWebSocketFrameHandlerMetadata.MessageMetadata getBinaryMetadata() public JavaxWebSocketMessageMetadata getBinaryMetadata()
{ {
return binaryMetadata; return binaryMetadata;
} }
public JavaxWebSocketFrameHandlerMetadata.MessageMetadata getTextMetadata() public JavaxWebSocketMessageMetadata getTextMetadata()
{ {
return textMetadata; return textMetadata;
} }
@ -369,7 +377,7 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
} }
} }
public <T> void addMessageHandler(JavaxWebSocketSession session, Class<T> clazz, MessageHandler.Partial<T> handler) public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Partial<T> handler)
{ {
try try
{ {
@ -384,9 +392,9 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialByteArrayMessageSink(coreSession, partialMessageHandler); MessageSink messageSink = new PartialByteArrayMessageSink(coreSession, partialMessageHandler);
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink); this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
JavaxWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JavaxWebSocketFrameHandlerMetadata.MessageMetadata(); JavaxWebSocketMessageMetadata metadata = new JavaxWebSocketMessageMetadata();
metadata.handle = partialMessageHandler; metadata.setMethodHandle(partialMessageHandler);
metadata.sinkClass = PartialByteArrayMessageSink.class; metadata.setSinkClass(PartialByteArrayMessageSink.class);
this.binaryMetadata = metadata; this.binaryMetadata = metadata;
} }
else if (ByteBuffer.class.isAssignableFrom(clazz)) else if (ByteBuffer.class.isAssignableFrom(clazz))
@ -394,9 +402,9 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialByteBufferMessageSink(coreSession, partialMessageHandler); MessageSink messageSink = new PartialByteBufferMessageSink(coreSession, partialMessageHandler);
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink); this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
JavaxWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JavaxWebSocketFrameHandlerMetadata.MessageMetadata(); JavaxWebSocketMessageMetadata metadata = new JavaxWebSocketMessageMetadata();
metadata.handle = partialMessageHandler; metadata.setMethodHandle(partialMessageHandler);
metadata.sinkClass = PartialByteBufferMessageSink.class; metadata.setSinkClass(PartialByteBufferMessageSink.class);
this.binaryMetadata = metadata; this.binaryMetadata = metadata;
} }
else if (String.class.isAssignableFrom(clazz)) else if (String.class.isAssignableFrom(clazz))
@ -404,9 +412,9 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
MessageSink messageSink = new PartialStringMessageSink(coreSession, partialMessageHandler); MessageSink messageSink = new PartialStringMessageSink(coreSession, partialMessageHandler);
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink); this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
JavaxWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JavaxWebSocketFrameHandlerMetadata.MessageMetadata(); JavaxWebSocketMessageMetadata metadata = new JavaxWebSocketMessageMetadata();
metadata.handle = partialMessageHandler; metadata.setMethodHandle(partialMessageHandler);
metadata.sinkClass = PartialStringMessageSink.class; metadata.setSinkClass(PartialStringMessageSink.class);
this.textMetadata = metadata; this.textMetadata = metadata;
} }
else else
@ -426,67 +434,67 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
} }
} }
public <T> void addMessageHandler(JavaxWebSocketSession session, Class<T> clazz, MessageHandler.Whole<T> handler) public <T> void addMessageHandler(Class<T> clazz, MessageHandler.Whole<T> handler)
{ {
try try
{ {
MethodHandles.Lookup lookup = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup(); MethodHandle methodHandle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
MethodHandle wholeMsgMethodHandle = lookup.findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class)); .findVirtual(MessageHandler.Whole.class, "onMessage", MethodType.methodType(void.class, Object.class))
wholeMsgMethodHandle = wholeMsgMethodHandle.bindTo(handler); .bindTo(handler);
if (PongMessage.class.isAssignableFrom(clazz)) if (PongMessage.class.isAssignableFrom(clazz))
{ {
assertBasicTypeNotRegistered(OpCode.PONG, this.pongHandle, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.PONG, this.pongHandle, handler.getClass().getName());
this.pongHandle = wholeMsgMethodHandle; this.pongHandle = methodHandle;
registerMessageHandler(OpCode.PONG, clazz, handler, null); registerMessageHandler(OpCode.PONG, clazz, handler, null);
} }
else else
{ {
AvailableDecoders availableDecoders = session.getDecoders(); AvailableDecoders availableDecoders = session.getDecoders();
AvailableDecoders.RegisteredDecoder registeredDecoder = availableDecoders.getRegisteredDecoderFor(clazz); RegisteredDecoder registeredDecoder = availableDecoders.getFirstRegisteredDecoder(clazz);
if (registeredDecoder == null) if (registeredDecoder == null)
{ {
throw new IllegalStateException("Unable to find Decoder for type: " + clazz); throw new IllegalStateException("Unable to find Decoder for type: " + clazz);
} }
JavaxWebSocketFrameHandlerMetadata.MessageMetadata metadata = new JavaxWebSocketFrameHandlerMetadata.MessageMetadata(); JavaxWebSocketMessageMetadata metadata = new JavaxWebSocketMessageMetadata();
metadata.handle = wholeMsgMethodHandle; metadata.setMethodHandle(methodHandle);
metadata.registeredDecoder = registeredDecoder; metadata.setRegisteredDecoder(registeredDecoder);
if (registeredDecoder.implementsInterface(Decoder.Binary.class)) if (registeredDecoder.implementsInterface(Decoder.Binary.class))
{ {
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
Decoder.Binary<T> decoder = availableDecoders.getInstanceOf(registeredDecoder); List<RegisteredDecoder> binaryDecoders = availableDecoders.getBinaryDecoders(clazz);
MessageSink messageSink = new DecodedBinaryMessageSink(coreSession, decoder, wholeMsgMethodHandle); MessageSink messageSink = new DecodedBinaryMessageSink<T>(coreSession, methodHandle, binaryDecoders);
metadata.sinkClass = messageSink.getClass(); metadata.setSinkClass(messageSink.getClass());
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink); this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
this.binaryMetadata = metadata; this.binaryMetadata = metadata;
} }
else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class)) else if (registeredDecoder.implementsInterface(Decoder.BinaryStream.class))
{ {
assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.BINARY, this.binaryMetadata, handler.getClass().getName());
Decoder.BinaryStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder); List<RegisteredDecoder> binaryStreamDecoders = availableDecoders.getBinaryStreamDecoders(clazz);
MessageSink messageSink = new DecodedBinaryStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle); MessageSink messageSink = new DecodedBinaryStreamMessageSink<T>(coreSession, methodHandle, binaryStreamDecoders);
metadata.sinkClass = messageSink.getClass(); metadata.setSinkClass(messageSink.getClass());
this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink); this.binarySink = registerMessageHandler(OpCode.BINARY, clazz, handler, messageSink);
this.binaryMetadata = metadata; this.binaryMetadata = metadata;
} }
else if (registeredDecoder.implementsInterface(Decoder.Text.class)) else if (registeredDecoder.implementsInterface(Decoder.Text.class))
{ {
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
Decoder.Text<T> decoder = availableDecoders.getInstanceOf(registeredDecoder); List<RegisteredDecoder> textDecoders = availableDecoders.getTextDecoders(clazz);
MessageSink messageSink = new DecodedTextMessageSink(coreSession, decoder, wholeMsgMethodHandle); MessageSink messageSink = new DecodedTextMessageSink<T>(coreSession, methodHandle, textDecoders);
metadata.sinkClass = messageSink.getClass(); metadata.setSinkClass(messageSink.getClass());
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink); this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
this.textMetadata = metadata; this.textMetadata = metadata;
} }
else if (registeredDecoder.implementsInterface(Decoder.TextStream.class)) else if (registeredDecoder.implementsInterface(Decoder.TextStream.class))
{ {
assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName()); assertBasicTypeNotRegistered(OpCode.TEXT, this.textMetadata, handler.getClass().getName());
Decoder.TextStream<T> decoder = availableDecoders.getInstanceOf(registeredDecoder); List<RegisteredDecoder> textStreamDecoders = availableDecoders.getTextStreamDecoders(clazz);
MessageSink messageSink = new DecodedTextStreamMessageSink(coreSession, decoder, wholeMsgMethodHandle); MessageSink messageSink = new DecodedTextStreamMessageSink<T>(coreSession, methodHandle, textStreamDecoders);
metadata.sinkClass = messageSink.getClass(); metadata.setSinkClass(messageSink.getClass());
this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink); this.textSink = registerMessageHandler(OpCode.TEXT, clazz, handler, messageSink);
this.textMetadata = metadata; this.textMetadata = metadata;
} }

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.websocket.javax.common; package org.eclipse.jetty.websocket.javax.common;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
@ -27,11 +25,11 @@ import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.Decoder; import javax.websocket.Decoder;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
@ -39,15 +37,14 @@ import javax.websocket.OnClose;
import javax.websocket.OnError; import javax.websocket.OnError;
import javax.websocket.OnMessage; import javax.websocket.OnMessage;
import javax.websocket.OnOpen; import javax.websocket.OnOpen;
import javax.websocket.PongMessage;
import javax.websocket.Session; import javax.websocket.Session;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.javax.common.messages.AbstractDecodedMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryStreamMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedBinaryStreamMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextMessageSink;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextStreamMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextStreamMessageSink;
import org.eclipse.jetty.websocket.util.InvalidSignatureException; import org.eclipse.jetty.websocket.util.InvalidSignatureException;
@ -65,61 +62,11 @@ import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
import org.eclipse.jetty.websocket.util.messages.StringMessageSink; import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata.MessageMetadata;
public abstract class JavaxWebSocketFrameHandlerFactory public abstract class JavaxWebSocketFrameHandlerFactory
{ {
private static final MethodHandle FILTER_RETURN_TYPE_METHOD; 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 static
{ {
try try
@ -172,8 +119,8 @@ public abstract class JavaxWebSocketFrameHandlerFactory
MethodHandle errorHandle = metadata.getErrorHandle(); MethodHandle errorHandle = metadata.getErrorHandle();
MethodHandle pongHandle = metadata.getPongHandle(); MethodHandle pongHandle = metadata.getPongHandle();
MessageMetadata textMetadata = MessageMetadata.copyOf(metadata.getTextMetadata()); JavaxWebSocketMessageMetadata textMetadata = JavaxWebSocketMessageMetadata.copyOf(metadata.getTextMetadata());
MessageMetadata binaryMetadata = MessageMetadata.copyOf(metadata.getBinaryMetadata()); JavaxWebSocketMessageMetadata binaryMetadata = JavaxWebSocketMessageMetadata.copyOf(metadata.getBinaryMetadata());
UriTemplatePathSpec templatePathSpec = metadata.getUriTemplatePathSpec(); UriTemplatePathSpec templatePathSpec = metadata.getUriTemplatePathSpec();
if (templatePathSpec != null) if (templatePathSpec != null)
@ -188,9 +135,9 @@ public abstract class JavaxWebSocketFrameHandlerFactory
pongHandle = bindTemplateVariables(pongHandle, namedVariables, pathParams); pongHandle = bindTemplateVariables(pongHandle, namedVariables, pathParams);
if (textMetadata != null) if (textMetadata != null)
textMetadata.handle = bindTemplateVariables(textMetadata.handle, namedVariables, pathParams); textMetadata.setMethodHandle(bindTemplateVariables(textMetadata.getMethodHandle(), namedVariables, pathParams));
if (binaryMetadata != null) if (binaryMetadata != null)
binaryMetadata.handle = bindTemplateVariables(binaryMetadata.handle, namedVariables, pathParams); binaryMetadata.setMethodHandle(bindTemplateVariables(binaryMetadata.getMethodHandle(), namedVariables, pathParams));
} }
openHandle = InvokerUtils.bindTo(openHandle, endpoint); openHandle = InvokerUtils.bindTo(openHandle, endpoint);
@ -313,8 +260,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory
return retHandle; return retHandle;
} }
@SuppressWarnings("Duplicates") public static MessageSink createMessageSink(JavaxWebSocketSession session, JavaxWebSocketMessageMetadata msgMetadata)
public static MessageSink createMessageSink(JavaxWebSocketSession session, MessageMetadata msgMetadata)
{ {
if (msgMetadata == null) if (msgMetadata == null)
return null; return null;
@ -322,27 +268,27 @@ public abstract class JavaxWebSocketFrameHandlerFactory
try try
{ {
MethodHandles.Lookup lookup = getServerMethodHandleLookup(); MethodHandles.Lookup lookup = getServerMethodHandleLookup();
if (DecodedMessageSink.class.isAssignableFrom(msgMetadata.sinkClass)) if (AbstractDecodedMessageSink.class.isAssignableFrom(msgMetadata.getSinkClass()))
{ {
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass, MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
MethodType.methodType(void.class, CoreSession.class, msgMetadata.registeredDecoder.interfaceType, MethodHandle.class)); MethodType.methodType(void.class, CoreSession.class, MethodHandle.class, List.class));
Decoder decoder = session.getDecoders().getInstanceOf(msgMetadata.registeredDecoder); List<RegisteredDecoder> registeredDecoders = msgMetadata.getRegisteredDecoders();
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), decoder, msgMetadata.handle); return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle(), registeredDecoders);
} }
else else
{ {
MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.sinkClass, MethodHandle ctorHandle = lookup.findConstructor(msgMetadata.getSinkClass(),
MethodType.methodType(void.class, CoreSession.class, MethodHandle.class)); MethodType.methodType(void.class, CoreSession.class, MethodHandle.class));
return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.handle); return (MessageSink)ctorHandle.invoke(session.getCoreSession(), msgMetadata.getMethodHandle());
} }
} }
catch (NoSuchMethodException e) catch (NoSuchMethodException e)
{ {
throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.sinkClass.getName(), e); throw new RuntimeException("Missing expected MessageSink constructor found at: " + msgMetadata.getSinkClass().getName(), e);
} }
catch (IllegalAccessException | InstantiationException | InvocationTargetException e) catch (IllegalAccessException | InstantiationException | InvocationTargetException e)
{ {
throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.sinkClass.getName(), e); throw new RuntimeException("Unable to create MessageSink: " + msgMetadata.getSinkClass().getName(), e);
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
@ -456,15 +402,19 @@ public abstract class JavaxWebSocketFrameHandlerFactory
for (Method onMsg : onMessages) for (Method onMsg : onMessages)
{ {
assertSignatureValid(endpointClass, onMsg, OnMessage.class); assertSignatureValid(endpointClass, onMsg, OnMessage.class);
MessageMetadata msgMetadata = new MessageMetadata();
OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class); OnMessage onMessageAnno = onMsg.getAnnotation(OnMessage.class);
if (onMessageAnno.maxMessageSize() > Integer.MAX_VALUE)
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", throw new InvalidWebSocketException(String.format("Value too large: %s#%s - @OnMessage.maxMessageSize=%,d > Integer.MAX_VALUE",
endpointClass.getName(), onMsg.getName(), onMessageAnno.maxMessageSize())); endpointClass.getName(), onMsg.getName(), annotationMaxMessageSize));
} }
msgMetadata.maxMessageSize = (int)onMessageAnno.maxMessageSize();
// Create MessageMetadata and set annotated maxMessageSize if it is not the default value.
JavaxWebSocketMessageMetadata msgMetadata = new JavaxWebSocketMessageMetadata();
if (annotationMaxMessageSize != -1)
msgMetadata.setMaxMessageSize((int)annotationMaxMessageSize);
// Function to search for matching MethodHandle for the endpointClass given a signature. // Function to search for matching MethodHandle for the endpointClass given a signature.
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) -> Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle = (signature) ->
@ -486,91 +436,91 @@ public abstract class JavaxWebSocketFrameHandlerFactory
return metadata; return metadata;
} }
private boolean matchOnMessage(Method onMsg, JavaxWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata, private boolean matchOnMessage(Method onMsg, JavaxWebSocketFrameHandlerMetadata metadata, JavaxWebSocketMessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle) Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{ {
// Whole Text Message. // Whole Text Message.
MethodHandle methodHandle = getMethodHandle.apply(textCallingArgs); MethodHandle methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.textCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = StringMessageSink.class; msgMetadata.setSinkClass(StringMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setTextMetadata(msgMetadata, onMsg); metadata.setTextMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Partial Text Message. // Partial Text Message.
methodHandle = getMethodHandle.apply(textPartialCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.textPartialCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = PartialStringMessageSink.class; msgMetadata.setSinkClass(PartialStringMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setTextMetadata(msgMetadata, onMsg); metadata.setTextMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Whole ByteBuffer Binary Message. // Whole ByteBuffer Binary Message.
methodHandle = getMethodHandle.apply(binaryBufferCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.binaryBufferCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = ByteBufferMessageSink.class; msgMetadata.setSinkClass(ByteBufferMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg); metadata.setBinaryMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Partial ByteBuffer Binary Message. // Partial ByteBuffer Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialBufferCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.binaryPartialBufferCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = PartialByteBufferMessageSink.class; msgMetadata.setSinkClass(PartialByteBufferMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg); metadata.setBinaryMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Whole byte[] Binary Message. // Whole byte[] Binary Message.
methodHandle = getMethodHandle.apply(binaryArrayCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.binaryArrayCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = ByteArrayMessageSink.class; msgMetadata.setSinkClass(ByteArrayMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg); metadata.setBinaryMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Partial byte[] Binary Message. // Partial byte[] Binary Message.
methodHandle = getMethodHandle.apply(binaryPartialArrayCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.binaryPartialArrayCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = PartialByteArrayMessageSink.class; msgMetadata.setSinkClass(PartialByteArrayMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg); metadata.setBinaryMetadata(msgMetadata, onMsg);
return true; return true;
} }
// InputStream Binary Message. // InputStream Binary Message.
methodHandle = getMethodHandle.apply(inputStreamCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.inputStreamCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = InputStreamMessageSink.class; msgMetadata.setSinkClass(InputStreamMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setBinaryMetadata(msgMetadata, onMsg); metadata.setBinaryMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Reader Text Message. // Reader Text Message.
methodHandle = getMethodHandle.apply(readerCallingArgs); methodHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.readerCallingArgs);
if (methodHandle != null) if (methodHandle != null)
{ {
msgMetadata.sinkClass = ReaderMessageSink.class; msgMetadata.setSinkClass(ReaderMessageSink.class);
msgMetadata.handle = methodHandle; msgMetadata.setMethodHandle(methodHandle);
metadata.setTextMetadata(msgMetadata, onMsg); metadata.setTextMetadata(msgMetadata, onMsg);
return true; return true;
} }
// Pong Message. // Pong Message.
MethodHandle pongHandle = getMethodHandle.apply(pongCallingArgs); MethodHandle pongHandle = getMethodHandle.apply(JavaxWebSocketCallingArgs.pongCallingArgs);
if (pongHandle != null) if (pongHandle != null)
{ {
metadata.setPongHandle(pongHandle, onMsg); metadata.setPongHandle(pongHandle, onMsg);
@ -580,88 +530,70 @@ public abstract class JavaxWebSocketFrameHandlerFactory
return false; return false;
} }
private boolean matchDecoders(Method onMsg, JavaxWebSocketFrameHandlerMetadata metadata, MessageMetadata msgMetadata, private boolean matchDecoders(Method onMsg, JavaxWebSocketFrameHandlerMetadata metadata, JavaxWebSocketMessageMetadata msgMetadata,
Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle) Function<InvokerUtils.Arg[], MethodHandle> getMethodHandle)
{ {
// TODO: we should be able to get this information directly from the AvailableDecoders in the metadata. // We need to get all the decoders which match not just the first.
List<DecodedArgs> decodedTextCallingArgs = new ArrayList<>(); Stream<RegisteredDecoder> matchedDecodersStream = metadata.getAvailableDecoders().stream().filter(registeredDecoder ->
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()}; InvokerUtils.Arg[] args = {new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(registeredDecoder.objectType).required()};
DecodedArgs decodedArgs = new DecodedArgs(decoder, args); return getMethodHandle.apply(args) != null;
});
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;
}
}
// Use the interface type of the first matched decoder.
RegisteredDecoder firstDecoder = matchedDecodersStream.findFirst().orElse(null);
if (firstDecoder == null)
return false; return false;
// TODO: COMMENT
List<RegisteredDecoder> decoders = new ArrayList<>();
Class<? extends Decoder> interfaceType = firstDecoder.interfaceType;
metadata.getAvailableDecoders().stream()
.filter(registeredDecoder -> registeredDecoder.interfaceType.equals(interfaceType))
.forEach(decoders::add);
// Get the original argument type.
Class<?> type = firstDecoder.objectType;
for (Class<?> clazz : onMsg.getParameterTypes())
{
if (clazz.isAssignableFrom(firstDecoder.objectType))
type = clazz;
}
InvokerUtils.Arg[] generalArgs = {new InvokerUtils.Arg(Session.class), new InvokerUtils.Arg(type).required()};
MethodHandle generalMethodHandle = getMethodHandle.apply(generalArgs);
if (generalMethodHandle == null)
{
// TODO: warn or throw
return false;
}
msgMetadata.setRegisteredDecoders(decoders);
msgMetadata.setMethodHandle(generalMethodHandle);
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) private void assertSignatureValid(Class<?> endpointClass, Method method, Class<? extends Annotation> annotationClass)
@ -752,16 +684,4 @@ public abstract class JavaxWebSocketFrameHandlerFactory
{ {
return MethodHandles.publicLookup().in(lookupClass); 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

@ -19,7 +19,6 @@
package org.eclipse.jetty.websocket.javax.common; package org.eclipse.jetty.websocket.javax.common;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import javax.websocket.Decoder;
import javax.websocket.Encoder; import javax.websocket.Encoder;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
@ -27,25 +26,21 @@ import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException; import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class JavaxWebSocketFrameHandlerMetadata public class JavaxWebSocketFrameHandlerMetadata
{ {
private static final String[] NO_VARIABLES = new String[0]; private static final String[] NO_VARIABLES = new String[0];
// EndpointConfig entries // EndpointConfig entries
private final EndpointConfig endpointConfig;
private final AvailableDecoders availableDecoders; private final AvailableDecoders availableDecoders;
private final AvailableEncoders availableEncoders; private final AvailableEncoders availableEncoders;
private MethodHandle openHandle; private MethodHandle openHandle;
private MethodHandle closeHandle; private MethodHandle closeHandle;
private MethodHandle errorHandle; private MethodHandle errorHandle;
private MessageMetadata textMetadata;
private MessageMetadata binaryMetadata;
private MethodHandle pongHandle; private MethodHandle pongHandle;
private JavaxWebSocketMessageMetadata textMetadata;
private JavaxWebSocketMessageMetadata binaryMetadata;
/** /**
* For {@code @ServerEndpoint} or {@code ServerEndpointConfig} based endpoints, this * For {@code @ServerEndpoint} or {@code ServerEndpointConfig} based endpoints, this
@ -76,7 +71,6 @@ public class JavaxWebSocketFrameHandlerMetadata
public JavaxWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig) public JavaxWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig)
{ {
this.endpointConfig = endpointConfig;
this.availableDecoders = new AvailableDecoders(endpointConfig); this.availableDecoders = new AvailableDecoders(endpointConfig);
this.availableEncoders = new AvailableEncoders(endpointConfig); this.availableEncoders = new AvailableEncoders(endpointConfig);
} }
@ -91,7 +85,7 @@ public class JavaxWebSocketFrameHandlerMetadata
return availableEncoders; return availableEncoders;
} }
public MessageMetadata getBinaryMetadata() public JavaxWebSocketMessageMetadata getBinaryMetadata()
{ {
return binaryMetadata; return binaryMetadata;
} }
@ -133,7 +127,7 @@ public class JavaxWebSocketFrameHandlerMetadata
return pongHandle; return pongHandle;
} }
public MessageMetadata getTextMetadata() public JavaxWebSocketMessageMetadata getTextMetadata()
{ {
return textMetadata; return textMetadata;
} }
@ -148,7 +142,7 @@ public class JavaxWebSocketFrameHandlerMetadata
return (textMetadata != null); return (textMetadata != null);
} }
public void setBinaryMetadata(MessageMetadata metadata, Object origin) public void setBinaryMetadata(JavaxWebSocketMessageMetadata metadata, Object origin)
{ {
assertNotSet(this.binaryMetadata, "BINARY Message Metadata", origin); assertNotSet(this.binaryMetadata, "BINARY Message Metadata", origin);
this.binaryMetadata = metadata; this.binaryMetadata = metadata;
@ -160,11 +154,6 @@ public class JavaxWebSocketFrameHandlerMetadata
this.closeHandle = close; this.closeHandle = close;
} }
public void setDecoders(Class<? extends Decoder>[] decoders)
{
this.availableDecoders.registerAll(decoders);
}
public void setEncoders(Class<? extends Encoder>[] encoders) public void setEncoders(Class<? extends Encoder>[] encoders)
{ {
this.availableEncoders.registerAll(encoders); this.availableEncoders.registerAll(encoders);
@ -188,7 +177,7 @@ public class JavaxWebSocketFrameHandlerMetadata
this.pongHandle = pong; this.pongHandle = pong;
} }
public void setTextMetadata(MessageMetadata metadata, Object origin) public void setTextMetadata(JavaxWebSocketMessageMetadata metadata, Object origin)
{ {
assertNotSet(this.textMetadata, "TEXT Messsage Metadata", origin); assertNotSet(this.textMetadata, "TEXT Messsage Metadata", origin);
this.textMetadata = metadata; this.textMetadata = metadata;
@ -219,33 +208,4 @@ public class JavaxWebSocketFrameHandlerMetadata
return obj.toString(); 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,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.javax.common;
import java.lang.invoke.MethodHandle;
import java.util.List;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class JavaxWebSocketMessageMetadata
{
public static enum Type
{
TEXT,
BINARY,
TEXT_STREAM,
BINARY_STREAM
}
private MethodHandle methodHandle;
private Class<? extends MessageSink> sinkClass;
private List<RegisteredDecoder> registeredDecoders;
private int maxMessageSize = -1;
private boolean maxMessageSizeSet = false;
public static JavaxWebSocketMessageMetadata copyOf(JavaxWebSocketMessageMetadata metadata)
{
if (metadata == null)
return null;
JavaxWebSocketMessageMetadata copy = new JavaxWebSocketMessageMetadata();
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 RegisteredDecoder getRegisteredDecoder()
{
if (registeredDecoders == null || registeredDecoders.isEmpty())
return null;
return registeredDecoders.get(0);
}
public void setRegisteredDecoder(RegisteredDecoder registeredDecoder)
{
this.registeredDecoders = List.of(registeredDecoder);
}
public List<RegisteredDecoder> getRegisteredDecoders()
{
return registeredDecoders;
}
public void setRegisteredDecoders(List<RegisteredDecoder> decoders)
{
this.registeredDecoders = decoders;
}
}

View File

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

View File

@ -20,82 +20,28 @@ package org.eclipse.jetty.websocket.javax.common.decoders;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.websocket.Decoder; import javax.websocket.Decoder;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.javax.common.InitException;
import org.eclipse.jetty.websocket.util.InvalidSignatureException; import org.eclipse.jetty.websocket.util.InvalidSignatureException;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException; import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.eclipse.jetty.websocket.util.ReflectUtils; import org.eclipse.jetty.websocket.util.ReflectUtils;
public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredDecoder> public class AvailableDecoders implements Iterable<RegisteredDecoder>
{ {
public static class RegisteredDecoder private final List<RegisteredDecoder> registeredDecoders = new LinkedList<>();
{
// The user supplied Decoder class
public final Class<? extends Decoder> decoder;
// The javax.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 EndpointConfig config; private final EndpointConfig config;
private LinkedList<RegisteredDecoder> registeredDecoders;
public AvailableDecoders(EndpointConfig config) public AvailableDecoders(EndpointConfig config)
{ {
Objects.requireNonNull(config); this.config = Objects.requireNonNull(config);
this.config = config;
registeredDecoders = new LinkedList<>();
// TEXT based [via Class reference] // TEXT based [via Class reference]
registerPrimitive(BooleanDecoder.class, Decoder.Text.class, Boolean.class); registerPrimitive(BooleanDecoder.class, Decoder.Text.class, Boolean.class);
@ -132,7 +78,7 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
private void registerPrimitive(Class<? extends Decoder> decoderClass, Class<? extends Decoder> interfaceType, Class<?> type) 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) public void register(Class<? extends Decoder> decoder)
@ -175,23 +121,10 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
} }
} }
// TODO: consider removing (if not used)
public void registerAll(Class<? extends Decoder>[] decoders)
{
if (decoders == null)
return;
for (Class<? extends Decoder> decoder : decoders)
{
register(decoder);
}
}
public void registerAll(List<Class<? extends Decoder>> decoders) public void registerAll(List<Class<? extends Decoder>> decoders)
{ {
if (decoders == null) if (decoders == null)
return; return;
decoders.forEach(this::register); decoders.forEach(this::register);
} }
@ -200,52 +133,20 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
Class<?> objectType = ReflectUtils.findGenericClassFor(decoder, interfaceClass); Class<?> objectType = ReflectUtils.findGenericClassFor(decoder, interfaceClass);
if (objectType == null) if (objectType == null)
{ {
StringBuilder err = new StringBuilder(); String err = "Unknown Decoder Object type declared for interface " +
err.append("Unknown Decoder Object type declared for interface "); interfaceClass.getName() + " on class " + decoder;
err.append(interfaceClass.getName()); throw new InvalidWebSocketException(err);
err.append(" on class ");
err.append(decoder);
throw new InvalidWebSocketException(err.toString());
} }
try boolean alreadyRegistered = registeredDecoders.stream().anyMatch(registered ->
{ registered.decoder.equals(decoder) && registered.interfaceType.equals(interfaceClass));
RegisteredDecoder conflicts = registeredDecoders.stream()
.filter(registered -> registered.isType(objectType))
.filter(registered -> !registered.primitive)
.findFirst()
.get();
if (conflicts.decoder.equals(decoder) && conflicts.implementsInterface(interfaceClass)) // If decoder is already registered for this interfaceType, don't bother adding it again.
{ if (!alreadyRegistered)
// Same decoder as what is there already, don't bother adding it again. registeredDecoders.add(0, new RegisteredDecoder(decoder, interfaceClass, objectType, config));
return;
} }
StringBuilder err = new StringBuilder(); public RegisteredDecoder getFirstRegisteredDecoder(Class<?> type)
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));
}
}
// 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)
{ {
return registeredDecoders.stream() return registeredDecoders.stream()
.filter(registered -> registered.isType(type)) .filter(registered -> registered.isType(type))
@ -253,49 +154,38 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
.orElse(null); .orElse(null);
} }
// TODO: consider removing (if not used) public List<RegisteredDecoder> getRegisteredDecoders(Class<?> returnType)
public Class<? extends Decoder> getDecoderFor(Class<?> type)
{ {
try return registeredDecoders.stream()
{ .filter(registered -> registered.isType(returnType))
return getRegisteredDecoderFor(type).decoder; .collect(Collectors.toList());
}
catch (NoSuchElementException e)
{
throw new InvalidWebSocketException("No Decoder found for type " + type);
}
} }
public <T extends Decoder> T getInstanceOf(RegisteredDecoder registeredDecoder) public List<RegisteredDecoder> getRegisteredDecoders(Class<? extends Decoder> interfaceType, Class<?> returnType)
{ {
if (registeredDecoder.instance != null) return registeredDecoders.stream()
{ .filter(registered -> registered.interfaceType.equals(interfaceType) && registered.isType(returnType))
return (T)registeredDecoder.instance; .collect(Collectors.toList());
} }
try public List<RegisteredDecoder> getTextDecoders(Class<?> returnType)
{ {
registeredDecoder.instance = registeredDecoder.decoder.getConstructor().newInstance(); return getRegisteredDecoders(Decoder.Text.class, returnType);
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);
}
} }
public <T extends Decoder> T getInstanceFor(Class<?> type) public List<RegisteredDecoder> getBinaryDecoders(Class<?> returnType)
{ {
try return getRegisteredDecoders(Decoder.Binary.class, returnType);
{
RegisteredDecoder registeredDecoder = getRegisteredDecoderFor(type);
return getInstanceOf(registeredDecoder);
} }
catch (NoSuchElementException e)
public List<RegisteredDecoder> getTextStreamDecoders(Class<?> returnType)
{ {
throw new InvalidWebSocketException("No Decoder found for type " + type); return getRegisteredDecoders(Decoder.TextStream.class, returnType);
} }
public List<RegisteredDecoder> getBinaryStreamDecoders(Class<?> returnType)
{
return getRegisteredDecoders(Decoder.BinaryStream.class, returnType);
} }
@Override @Override
@ -303,4 +193,9 @@ public class AvailableDecoders implements Iterable<AvailableDecoders.RegisteredD
{ {
return registeredDecoders.iterator(); return registeredDecoders.iterator();
} }
public Stream<RegisteredDecoder> stream()
{
return registeredDecoders.stream();
}
} }

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.javax.common.decoders;
import java.lang.reflect.InvocationTargetException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.javax.common.InitException;
public class RegisteredDecoder
{
// The user supplied Decoder class
public final Class<? extends Decoder> decoder;
// The javax.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,75 @@
//
// ========================================================================
// 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.javax.common.messages;
import java.lang.invoke.MethodHandle;
import java.util.List;
import java.util.stream.Collectors;
import javax.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.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractDecodedMessageSink<T extends Decoder> implements MessageSink
{
protected final Logger _logger;
protected final CoreSession _coreSession;
protected final MethodHandle _methodHandle;
protected final MessageSink _messageSink;
protected final List<T> _decoders;
public AbstractDecodedMessageSink(CoreSession coreSession, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
{
_logger = LoggerFactory.getLogger(getClass());
_coreSession = coreSession;
_methodHandle = methodHandle;
_decoders = decoders.stream()
.map(RegisteredDecoder::<T>getInstance)
.collect(Collectors.toList());
try
{
_messageSink = getMessageSink();
}
catch (Exception e)
{
// Throwing from here is an error implementation of the DecodedMessageSink.
throw new RuntimeException(e);
}
}
/**
* @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 getMessageSink() throws Exception;
@Override
public void accept(Frame frame, Callback callback)
{
if (_logger.isDebugEnabled())
_logger.debug("accepting frame {} for {}", frame, _messageSink);
_messageSink.accept(frame, callback);
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.javax.common.messages;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
import javax.websocket.Decoder; import javax.websocket.Decoder;
@ -28,46 +29,38 @@ import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException; import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink; import org.eclipse.jetty.websocket.util.messages.ByteBufferMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink; import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class DecodedBinaryMessageSink<T> extends DecodedMessageSink<Decoder.Binary<T>> public class DecodedBinaryMessageSink<T> extends AbstractDecodedMessageSink<Decoder.Binary<T>>
{ {
public DecodedBinaryMessageSink(CoreSession session, public DecodedBinaryMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders)
Decoder.Binary<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
{ {
super(session, decoder, methodHandle); super(session, methodHandle, decoders);
} }
@Override @Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException MessageSink getMessageSink() throws Exception
{ {
return JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryMessageSink.class, MethodHandle methodHandle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryMessageSink.class,
"onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class)) "onWholeMessage", MethodType.methodType(void.class, ByteBuffer.class))
.bindTo(this); .bindTo(this);
} return new ByteBufferMessageSink(_coreSession, methodHandle);
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{
return new ByteBufferMessageSink(session, rawMethodHandle);
} }
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public void onWholeMessage(ByteBuffer wholeMessage) public void onWholeMessage(ByteBuffer wholeMessage)
{ {
if (!getDecoder().willDecode(wholeMessage)) for (Decoder.Binary<T> decoder : _decoders)
{
if (decoder.willDecode(wholeMessage))
{ {
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
return;
}
try try
{ {
T obj = getDecoder().decode(wholeMessage); T obj = decoder.decode(wholeMessage);
methodHandle.invoke(obj); _methodHandle.invoke(obj);
return;
} }
catch (DecodeException e) catch (DecodeException e)
{ {
@ -79,3 +72,5 @@ public class DecodedBinaryMessageSink<T> extends DecodedMessageSink<Decoder.Bina
} }
} }
} }
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.javax.common.messages;
import java.io.InputStream; import java.io.InputStream;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.List;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
import javax.websocket.Decoder; import javax.websocket.Decoder;
@ -28,40 +29,36 @@ import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException; import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink; import org.eclipse.jetty.websocket.util.messages.InputStreamMessageSink;
import org.eclipse.jetty.websocket.util.messages.MessageSink; import org.eclipse.jetty.websocket.util.messages.MessageSink;
public class DecodedBinaryStreamMessageSink<T> extends DecodedMessageSink<Decoder.BinaryStream<T>> public class DecodedBinaryStreamMessageSink<T> extends AbstractDecodedMessageSink<Decoder.BinaryStream<T>>
{ {
public DecodedBinaryStreamMessageSink(CoreSession session, public DecodedBinaryStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders) throws NoSuchMethodException, IllegalAccessException
Decoder.BinaryStream<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
{ {
super(session, decoder, methodHandle); super(session, methodHandle, decoders);
} }
@Override @Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException MessageSink getMessageSink() throws Exception
{ {
return JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryStreamMessageSink.class, MethodHandle methodHandle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedBinaryStreamMessageSink.class,
"onStreamStart", MethodType.methodType(void.class, InputStream.class)) "onStreamStart", MethodType.methodType(void.class, InputStream.class))
.bindTo(this); .bindTo(this);
} return new InputStreamMessageSink(_coreSession, methodHandle);
@Override
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{
return new InputStreamMessageSink(session, rawMethodHandle);
} }
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public void onStreamStart(InputStream stream) public void onStreamStart(InputStream stream)
{
for (Decoder.BinaryStream<T> decoder : _decoders)
{ {
try try
{ {
T obj = getDecoder().decode(stream); T obj = decoder.decode(stream);
methodHandle.invoke(obj); _methodHandle.invoke(obj);
return;
} }
catch (DecodeException e) catch (DecodeException e)
{ {
@ -73,3 +70,4 @@ public class DecodedBinaryStreamMessageSink<T> extends DecodedMessageSink<Decode
} }
} }
} }
}

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.javax.common.messages;
import java.lang.invoke.MethodHandle;
import javax.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.javax.common.messages;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.List;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
import javax.websocket.Decoder; import javax.websocket.Decoder;
@ -27,46 +28,37 @@ import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException; import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink; import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.eclipse.jetty.websocket.util.messages.StringMessageSink; import org.eclipse.jetty.websocket.util.messages.StringMessageSink;
public class DecodedTextMessageSink<T> extends DecodedMessageSink<Decoder.Text<T>> public class DecodedTextMessageSink<T> extends AbstractDecodedMessageSink<Decoder.Text<T>>
{ {
public DecodedTextMessageSink(CoreSession session, public DecodedTextMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders) throws NoSuchMethodException, IllegalAccessException
Decoder.Text<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
{ {
super(session, decoder, methodHandle); super(session, methodHandle, decoders);
} }
@Override @Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException MessageSink getMessageSink() throws NoSuchMethodException, IllegalAccessException
{ {
return JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextMessageSink.class, MethodHandle methodHandle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup()
"onWholeMessage", MethodType.methodType(void.class, String.class)) .findVirtual(getClass(), "onMessage", MethodType.methodType(void.class, String.class))
.bindTo(this); .bindTo(this);
return new StringMessageSink(_coreSession, methodHandle);
} }
@Override public void onMessage(String wholeMessage)
protected MessageSink newRawMessageSink(CoreSession session, MethodHandle rawMethodHandle)
{ {
return new StringMessageSink(session, rawMethodHandle); for (Decoder.Text<T> decoder : _decoders)
}
@SuppressWarnings("Duplicates")
public void onWholeMessage(String wholeMessage)
{ {
if (!getDecoder().willDecode(wholeMessage)) if (decoder.willDecode(wholeMessage))
{ {
logger.warn("Message lost, decoder " + getDecoder().getClass().getName() + "#willDecode() has rejected it.");
return;
}
try try
{ {
T obj = getDecoder().decode(wholeMessage); T obj = decoder.decode(wholeMessage);
methodHandle.invoke(obj); _methodHandle.invoke(obj);
return;
} }
catch (DecodeException e) catch (DecodeException e)
{ {
@ -78,3 +70,5 @@ public class DecodedTextMessageSink<T> extends DecodedMessageSink<Decoder.Text<T
} }
} }
} }
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.javax.common.messages;
import java.io.Reader; import java.io.Reader;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.List;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
import javax.websocket.Decoder; import javax.websocket.Decoder;
@ -28,40 +29,35 @@ import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.exception.CloseException; import org.eclipse.jetty.websocket.core.exception.CloseException;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.messages.MessageSink; import org.eclipse.jetty.websocket.util.messages.MessageSink;
import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink; import org.eclipse.jetty.websocket.util.messages.ReaderMessageSink;
public class DecodedTextStreamMessageSink<T> extends DecodedMessageSink<Decoder.TextStream<T>> public class DecodedTextStreamMessageSink<T> extends AbstractDecodedMessageSink<Decoder.TextStream<T>>
{ {
public DecodedTextStreamMessageSink(CoreSession session, public DecodedTextStreamMessageSink(CoreSession session, MethodHandle methodHandle, List<RegisteredDecoder> decoders) throws NoSuchMethodException, IllegalAccessException
Decoder.TextStream<T> decoder,
MethodHandle methodHandle)
throws NoSuchMethodException, IllegalAccessException
{ {
super(session, decoder, methodHandle); super(session, methodHandle, decoders);
} }
@Override @Override
protected MethodHandle newRawMethodHandle() throws NoSuchMethodException, IllegalAccessException MessageSink getMessageSink() throws Exception
{ {
return JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextStreamMessageSink.class, MethodHandle methodHandle = JavaxWebSocketFrameHandlerFactory.getServerMethodHandleLookup().findVirtual(DecodedTextStreamMessageSink.class,
"onStreamStart", MethodType.methodType(void.class, Reader.class)) "onStreamStart", MethodType.methodType(void.class, Reader.class))
.bindTo(this); .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) public void onStreamStart(Reader reader)
{
for (Decoder.TextStream<T> decoder : _decoders)
{ {
try try
{ {
T obj = getDecoder().decode(reader); T obj = decoder.decode(reader);
methodHandle.invoke(obj); _methodHandle.invoke(obj);
return;
} }
catch (DecodeException e) catch (DecodeException e)
{ {
@ -73,3 +69,4 @@ public class DecodedTextStreamMessageSink<T> extends DecodedMessageSink<Decoder.
} }
} }
} }
}

View File

@ -20,13 +20,34 @@ package org.eclipse.jetty.websocket.javax.common.messages;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.Decoder;
import org.eclipse.jetty.websocket.javax.common.AbstractSessionTest; import org.eclipse.jetty.websocket.javax.common.AbstractSessionTest;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
public abstract class AbstractMessageSinkTest extends AbstractSessionTest 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) public <T> MethodHandle getAcceptHandle(Consumer<T> copy, Class<T> type)
{ {
try try

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,162 @@
//
// ========================================================================
// 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.javax.tests;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer;
import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.is;
public class DecoderListTest
{
private Server server;
private URI serverUri;
private JavaxWebSocketClientContainer client;
@BeforeEach
public void before() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/");
server.setHandler(contextHandler);
JavaxWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
container.addEndpoint(DecoderListEndpoint.class));
server.start();
serverUri = WSURI.toWebsocket(server.getURI());
client = new JavaxWebSocketClientContainer();
client.start();
}
@AfterEach
public void after() throws Exception
{
server.stop();
client.stop();
}
public static Stream<Arguments> getArguments()
{
return Stream.of(
Arguments.of("=DecodeEquals", "DecodeEquals="),
Arguments.of("+DecodePlus", "DecodePlus+"),
Arguments.of("-DecodeMinus", "DecodeMinus-"),
Arguments.of("DecodeNoMatch", null)
);
}
@ParameterizedTest
@MethodSource("getArguments")
public void testDecoderList(String request, String expected) throws Exception
{
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));
}
@ServerEndpoint(value = "/", decoders = {EqualsDecoder.class, PlusDecoder.class, MinusDecoder.class})
public static class DecoderListEndpoint
{
@OnMessage
public String echo(String message)
{
return message;
}
}
public static class EqualsDecoder extends PrefixStringDecoder
{
public EqualsDecoder()
{
super("=");
}
}
public static class PlusDecoder extends PrefixStringDecoder
{
public PlusDecoder()
{
super("+");
}
}
public static class MinusDecoder extends PrefixStringDecoder
{
public MinusDecoder()
{
super("-");
}
}
public static class PrefixStringDecoder 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);
}
@Override
public void init(EndpointConfig config)
{
}
@Override
public void destroy()
{
}
}
}

View File

@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
import java.time.Instant; import java.time.Instant;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.NoSuchElementException;
import java.util.TimeZone; import java.util.TimeZone;
import javax.websocket.DecodeException; import javax.websocket.DecodeException;
import javax.websocket.Decoder; import javax.websocket.Decoder;
@ -31,6 +32,7 @@ import org.eclipse.jetty.toolchain.test.Hex;
import org.eclipse.jetty.websocket.javax.client.internal.BasicClientEndpointConfig; import org.eclipse.jetty.websocket.javax.client.internal.BasicClientEndpointConfig;
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.javax.common.decoders.IntegerDecoder; import org.eclipse.jetty.websocket.javax.common.decoders.IntegerDecoder;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.util.InvalidWebSocketException; import org.eclipse.jetty.websocket.util.InvalidWebSocketException;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -52,132 +54,145 @@ public class AvailableDecodersTest
testConfig = new BasicClientEndpointConfig(); testConfig = new BasicClientEndpointConfig();
} }
private AvailableDecoders decoders = new AvailableDecoders(testConfig); private final 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 = decoders.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()); assertThat("Decoder instance", decoder, notNullValue());
T decoded = decoder.decode(value); T decoded = decoder.decode(value);
assertThat("Decoded", decoded, is(expectedDecoded)); assertThat("Decoded", decoded, is(expectedDecoded));
} }
private <T> void assertBinaryDecoder(Class<T> type, ByteBuffer value, T 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()); assertThat("Decoder Class", decoder, notNullValue());
T decoded = decoder.decode(value); T decoded = decoder.decode(value);
assertThat("Decoded", decoded, equalTo(expectedDecoded)); assertThat("Decoded", decoded, equalTo(expectedDecoded));
} }
@Test @Test
public void testCoreDecodeBoolean() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeBoolean() throws DecodeException
{ {
Boolean expected = Boolean.TRUE; Boolean expected = Boolean.TRUE;
assertTextDecoder(Boolean.class, "true", expected); assertTextDecoder(Boolean.class, "true", expected);
} }
@Test @Test
public void testCoreDecodeboolean() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeboolean() throws DecodeException
{ {
boolean expected = false; boolean expected = false;
assertTextDecoder(Boolean.TYPE, "false", expected); assertTextDecoder(Boolean.TYPE, "false", expected);
} }
@Test @Test
public void testCoreDecodeByte() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeByte() throws DecodeException
{ {
Byte expected = (byte)0x21; Byte expected = (byte)0x21;
assertTextDecoder(Byte.class, "33", expected); assertTextDecoder(Byte.class, "33", expected);
} }
@Test @Test
public void testCoreDecodebyte() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodebyte() throws DecodeException
{ {
byte expected = 0x21; byte expected = 0x21;
assertTextDecoder(Byte.TYPE, "33", expected); assertTextDecoder(Byte.TYPE, "33", expected);
} }
@Test @Test
public void testCoreDecodeCharacter() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeCharacter() throws DecodeException
{ {
Character expected = '!'; Character expected = '!';
assertTextDecoder(Character.class, "!", expected); assertTextDecoder(Character.class, "!", expected);
} }
@Test @Test
public void testCoreDecodechar() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodechar() throws DecodeException
{ {
char expected = '!'; char expected = '!';
assertTextDecoder(Character.TYPE, "!", expected); assertTextDecoder(Character.TYPE, "!", expected);
} }
@Test @Test
public void testCoreDecodeDouble() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeDouble() throws DecodeException
{ {
Double expected = 123.45D; Double expected = 123.45D;
assertTextDecoder(Double.class, "123.45", expected); assertTextDecoder(Double.class, "123.45", expected);
} }
@Test @Test
public void testCoreDecodedouble() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodedouble() throws DecodeException
{ {
double expected = 123.45D; double expected = 123.45D;
assertTextDecoder(Double.TYPE, "123.45", expected); assertTextDecoder(Double.TYPE, "123.45", expected);
} }
@Test @Test
public void testCoreDecodeFloat() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeFloat() throws DecodeException
{ {
Float expected = 123.4567F; Float expected = 123.4567F;
assertTextDecoder(Float.class, "123.4567", expected); assertTextDecoder(Float.class, "123.4567", expected);
} }
@Test @Test
public void testCoreDecodefloat() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodefloat() throws DecodeException
{ {
float expected = 123.4567F; float expected = 123.4567F;
assertTextDecoder(Float.TYPE, "123.4567", expected); assertTextDecoder(Float.TYPE, "123.4567", expected);
} }
@Test @Test
public void testCoreDecodeInteger() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeInteger() throws DecodeException
{ {
Integer expected = 1234; Integer expected = 1234;
assertTextDecoder(Integer.class, "1234", expected); assertTextDecoder(Integer.class, "1234", expected);
} }
@Test @Test
public void testCoreDecodeint() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeint() throws DecodeException
{ {
int expected = 1234; int expected = 1234;
assertTextDecoder(Integer.TYPE, "1234", expected); assertTextDecoder(Integer.TYPE, "1234", expected);
} }
@Test @Test
public void testCoreDecodeLong() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeLong() throws DecodeException
{ {
Long expected = 123_456_789L; Long expected = 123_456_789L;
assertTextDecoder(Long.class, "123456789", expected); assertTextDecoder(Long.class, "123456789", expected);
} }
@Test @Test
public void testCoreDecodelong() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodelong() throws DecodeException
{ {
long expected = 123_456_789L; long expected = 123_456_789L;
assertTextDecoder(Long.TYPE, "123456789", expected); assertTextDecoder(Long.TYPE, "123456789", expected);
} }
@Test @Test
public void testCoreDecodeString() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeString() throws DecodeException
{ {
String expected = "Hello World"; String expected = "Hello World";
assertTextDecoder(String.class, "Hello World", expected); assertTextDecoder(String.class, "Hello World", expected);
} }
@Test @Test
public void testCoreDecodeByteBuffer() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeByteBuffer() throws DecodeException
{ {
ByteBuffer val = Hex.asByteBuffer("112233445566778899"); ByteBuffer val = Hex.asByteBuffer("112233445566778899");
ByteBuffer expected = Hex.asByteBuffer("112233445566778899"); ByteBuffer expected = Hex.asByteBuffer("112233445566778899");
@ -185,7 +200,7 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCoreDecodeByteArray() throws IllegalAccessException, InstantiationException, DecodeException public void testCoreDecodeByteArray() throws DecodeException
{ {
ByteBuffer val = Hex.asByteBuffer("112233445566778899"); ByteBuffer val = Hex.asByteBuffer("112233445566778899");
byte[] expected = Hex.asByteArray("112233445566778899"); byte[] expected = Hex.asByteArray("112233445566778899");
@ -193,7 +208,7 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderInteger() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderInteger() throws DecodeException
{ {
decoders.register(IntegerDecoder.class); decoders.register(IntegerDecoder.class);
@ -203,7 +218,7 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderTime() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderTime() throws DecodeException
{ {
decoders.register(TimeDecoder.class); decoders.register(TimeDecoder.class);
@ -222,7 +237,7 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderDate() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderDate() throws DecodeException
{ {
decoders.register(DateDecoder.class); decoders.register(DateDecoder.class);
@ -241,7 +256,7 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderDateTime() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderDateTime() throws DecodeException
{ {
decoders.register(DateTimeDecoder.class); decoders.register(DateTimeDecoder.class);
@ -264,11 +279,11 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderValidDualText() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderValidDualText() throws DecodeException
{ {
decoders.register(ValidDualDecoder.class); decoders.register(ValidDualDecoder.class);
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Integer.class); RegisteredDecoder registered = decoders.getFirstRegisteredDecoder(Integer.class);
assertThat("Registered Decoder for Integer", registered.decoder.getName(), is(ValidDualDecoder.class.getName())); assertThat("Registered Decoder for Integer", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
String val = "[1,234,567]"; String val = "[1,234,567]";
@ -278,11 +293,11 @@ public class AvailableDecodersTest
} }
@Test @Test
public void testCustomDecoderValidDualBinary() throws IllegalAccessException, InstantiationException, DecodeException public void testCustomDecoderValidDualBinary() throws DecodeException
{ {
decoders.register(ValidDualDecoder.class); decoders.register(ValidDualDecoder.class);
AvailableDecoders.RegisteredDecoder registered = decoders.getRegisteredDecoderFor(Long.class); RegisteredDecoder registered = decoders.getFirstRegisteredDecoder(Long.class);
assertThat("Registered Decoder for Long", registered.decoder.getName(), is(ValidDualDecoder.class.getName())); assertThat("Registered Decoder for Long", registered.decoder.getName(), is(ValidDualDecoder.class.getName()));
ByteBuffer val = ByteBuffer.allocate(16); ByteBuffer val = ByteBuffer.allocate(16);

View File

@ -27,11 +27,13 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.Decoder; import javax.websocket.Decoder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder;
import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextStreamMessageSink; import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextStreamMessageSink;
import org.eclipse.jetty.websocket.javax.tests.FunctionMethod; import org.eclipse.jetty.websocket.javax.tests.FunctionMethod;
import org.eclipse.jetty.websocket.javax.tests.client.AbstractClientSessionTest; import org.eclipse.jetty.websocket.javax.tests.client.AbstractClientSessionTest;
@ -64,7 +66,6 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
@Test @Test
public void testQuotesDecodedReaderMessageSink() throws Exception public void testQuotesDecodedReaderMessageSink() throws Exception
{ {
Decoder.TextStream<Quotes> decoder = new QuotesDecoder();
CompletableFuture<Quotes> futureQuotes = new CompletableFuture<>(); CompletableFuture<Quotes> futureQuotes = new CompletableFuture<>();
MethodHandle functionHandle = FunctionMethod.getFunctionApplyMethodHandle(); MethodHandle functionHandle = FunctionMethod.getFunctionApplyMethodHandle();
MethodHandle quoteHandle = functionHandle.bindTo((Function<Quotes, Void>)(quotes) -> MethodHandle quoteHandle = functionHandle.bindTo((Function<Quotes, Void>)(quotes) ->
@ -80,7 +81,8 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
return null; 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<>(); List<FutureCallback> callbacks = new ArrayList<>();
FutureCallback finCallback = null; FutureCallback finCallback = null;
@ -107,4 +109,21 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest
assertThat("Quotes.author", quotes.getAuthor(), is("Benjamin Franklin")); assertThat("Quotes.author", quotes.getAuthor(), is("Benjamin Franklin"));
assertThat("Quotes.count", quotes.getQuotes().size(), is(3)); 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()));
}
} }