Issue #207 - Support javax.websocket version 1.1

WIP
This commit is contained in:
Joakim Erdfelt 2016-05-06 12:20:21 -07:00
parent 3198d12127
commit 57bbf67735
83 changed files with 4394 additions and 1693 deletions

View File

@ -20,10 +20,12 @@ package org.eclipse.jetty.websocket.jsr356;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.common.ManagedEndpoint;
/**
* Associate a JSR Endpoint with its optional {@link EndpointConfig}
*/
public class ConfiguredEndpoint
public class ConfiguredEndpoint implements ManagedEndpoint
{
/** The instance of the Endpoint */
private final Object endpoint;
@ -45,4 +47,10 @@ public class ConfiguredEndpoint
{
return endpoint;
}
@Override
public Object getRawEndpoint()
{
return endpoint;
}
}

View File

@ -42,6 +42,7 @@ import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet;
* <li>Container declared DecoderMetadataSet (primitives)</li>
* </ul>
*/
@Deprecated
public class DecoderFactory implements Configurable
{
public static class Wrapper implements Configurable

View File

@ -35,6 +35,7 @@ import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
/**
* Represents all of the declared {@link Encoder}s that the Container is aware of.
*/
@Deprecated
public class EncoderFactory implements Configurable
{
public static class Wrapper implements Configurable

View File

@ -1,342 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.function.Predicate;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* Used to identify Endpoint Functions
*/
public class EndpointFunctions
{
private static final Logger LOG = Log.getLogger(EndpointFunctions.class);
public enum Role
{
OPEN,
CLOSE,
ERROR,
TEXT,
TEXT_STREAM,
BINARY,
BINARY_STREAM,
PONG
}
public class ArgRole
{
public Role role;
public DynamicArgs.Builder argBuilder;
public Predicate<Method> predicate;
}
private final Map<String, String> uriParams;
private final ArgRole[] argRoles;
public EndpointFunctions()
{
this(null);
}
public EndpointFunctions(Map<String, String> uriParams)
{
this.uriParams = uriParams;
argRoles = new ArgRole[Role.values().length + 1];
int argRoleIdx = 0;
// ------------------------------------------------
// @OnOpen
ArgRole onOpen = new ArgRole();
onOpen.role = Role.OPEN;
onOpen.argBuilder = newDynamicBuilder(
new Arg(Session.class)
);
onOpen.predicate = (method) ->
hasAnnotation(method, OnOpen.class) &&
isPublicNonStatic(method) &&
isReturnType(method, Void.TYPE) &&
onOpen.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onOpen;
// ------------------------------------------------
// @OnClose
ArgRole onClose = new ArgRole();
onClose.role = Role.CLOSE;
onClose.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(CloseReason.class) // close
);
onClose.predicate = (method) ->
hasAnnotation(method, OnClose.class) &&
isPublicNonStatic(method) &&
isReturnType(method, Void.TYPE) &&
onClose.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onClose;
// ------------------------------------------------
// @OnError
ArgRole onError = new ArgRole();
onError.role = Role.ERROR;
onError.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(Throwable.class) // cause
);
onError.predicate = (method) ->
hasAnnotation(method, OnError.class) &&
isPublicNonStatic(method) &&
isReturnType(method, Void.TYPE) &&
onError.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onError;
// ------------------------------------------------
// @OnMessage / Text (whole message)
ArgRole onText = new ArgRole();
onText.role = Role.TEXT;
onText.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(String.class) // message
);
onText.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
hasSupportedReturnType(method) &&
onText.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onText;
// ------------------------------------------------
// @OnMessage / Binary (whole message, byte array)
ArgRole onBinaryArray = new ArgRole();
onBinaryArray.role = Role.BINARY;
onBinaryArray.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(byte[].class), // buffer
new Arg(int.class), // length
new Arg(int.class) // offset
);
onBinaryArray.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
hasSupportedReturnType(method) &&
onBinaryArray.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onBinaryArray;
// ------------------------------------------------
// @OnMessage / Binary (whole message, ByteBuffer)
ArgRole onBinaryBuffer = new ArgRole();
onBinaryBuffer.role = Role.BINARY;
onBinaryBuffer.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(ByteBuffer.class) // buffer
);
onBinaryBuffer.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
hasSupportedReturnType(method) &&
onBinaryBuffer.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onBinaryBuffer;
// ------------------------------------------------
// @OnMessage / Text (streamed)
ArgRole onTextStream = new ArgRole();
onTextStream.role = Role.TEXT_STREAM;
onTextStream.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(Reader.class) // stream
);
onTextStream.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
hasSupportedReturnType(method) &&
onTextStream.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onTextStream;
// ------------------------------------------------
// @OnMessage / Binary (streamed)
ArgRole onBinaryStream = new ArgRole();
onBinaryStream.role = Role.BINARY_STREAM;
onBinaryStream.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(InputStream.class) // stream
);
onBinaryStream.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
hasSupportedReturnType(method) &&
onBinaryStream.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onBinaryStream;
// ------------------------------------------------
// @OnMessage / Pong
ArgRole onPong = new ArgRole();
onPong.role = Role.PONG;
onPong.argBuilder = newDynamicBuilder(
new Arg(Session.class),
new Arg(PongMessage.class) // payload
);
onPong.predicate = (method) ->
hasAnnotation(method, OnMessage.class) &&
isPublicNonStatic(method) &&
isReturnType(method, Void.TYPE) &&
onPong.argBuilder.hasMatchingSignature(method);
argRoles[argRoleIdx++] = onPong;
}
private DynamicArgs.Builder newDynamicBuilder(Arg... args)
{
DynamicArgs.Builder argBuilder = new DynamicArgs.Builder();
int argCount = args.length;
if (this.uriParams != null)
argCount += uriParams.size();
Arg[] callArgs = new Arg[argCount];
int idx = 0;
for (Arg arg : args)
{
callArgs[idx++] = arg;
}
if (this.uriParams != null)
{
for (Map.Entry<String, String> uriParam : uriParams.entrySet())
{
// TODO: translate from UriParam String to method param type?
// TODO: use decoder?
callArgs[idx++] = new Arg(uriParam.getValue().getClass()).setTag(uriParam.getKey());
}
}
argBuilder.addSignature(new UnorderedSignature(callArgs));
return argBuilder;
}
private static boolean hasAnnotation(Method method, Class<? extends Annotation> annoClass)
{
return (method.getAnnotation(annoClass) != null);
}
private static boolean isReturnType(Method method, Class<?> type)
{
if (!type.equals(method.getReturnType()))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(System.lineSeparator());
err.append("Return type must be ").append(type);
LOG.warn(err.toString());
return false;
}
return true;
}
private static boolean hasSupportedReturnType(Method method)
{
// TODO: check Encoder list
return true;
}
public static boolean isPublicNonStatic(Method method)
{
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(System.lineSeparator());
err.append("Method modifier must be public");
LOG.warn(err.toString());
return false;
}
if (Modifier.isStatic(mods))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(System.lineSeparator());
err.append("Method modifier must not be static");
LOG.warn(err.toString());
return false;
}
return true;
}
public ArgRole getArgRole(Method method, Class<? extends Annotation> annoClass, Role role)
{
ArgRole ret = null;
for (ArgRole argRole : this.argRoles)
{
if ((argRole.role == role) && (argRole.predicate.test(method)))
{
ret = argRole;
}
}
if (ret == null)
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of @");
err.append(annoClass.getSimpleName());
err.append(" method ");
err.append(method);
throw new InvalidSignatureException(err.toString());
}
return ret;
}
public ArgRole findArgRole(Method method)
{
for (ArgRole argRole : this.argRoles)
{
if (argRole.predicate.test(method))
{
return argRole;
}
}
return null;
}
}

View File

@ -85,7 +85,7 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
this.container = container;
ConfiguredEndpoint cendpoint = (ConfiguredEndpoint)websocket;
ConfiguredEndpoint cendpoint = (ConfiguredEndpoint) websocket;
this.config = cendpoint.getConfig();
DecoderMetadataSet decoderSet = new DecoderMetadataSet();
@ -93,14 +93,14 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
// TODO: figure out how to populate the decoderSet / encoderSet
this.id = id;
this.decoderFactory = new DecoderFactory(this,decoderSet,container.getDecoderFactory());
this.encoderFactory = new EncoderFactory(this,encoderSet,container.getEncoderFactory());
this.decoderFactory = new DecoderFactory(this, decoderSet, container.getDecoderFactory());
this.encoderFactory = new EncoderFactory(this, encoderSet, container.getEncoderFactory());
}
@Override
protected void discoverEndpointFunctions(Object obj)
{
if(obj instanceof ConfiguredEndpoint)
if (!(obj instanceof ConfiguredEndpoint))
{
throw new IllegalArgumentException("JSR356 Implementation expects a " + ConfiguredEndpoint.class.getName() + " but got: " + obj.getClass().getName());
}
@ -110,21 +110,21 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
// Endpoint
Object websocket = cendpoint.getEndpoint();
if(websocket instanceof Endpoint)
if (websocket instanceof Endpoint)
{
Endpoint endpoint = (Endpoint)websocket;
Endpoint endpoint = (Endpoint) websocket;
onOpenFunction = (sess) -> {
endpoint.onOpen(this,config);
endpoint.onOpen(this, config);
return null;
};
onCloseFunction = (closeinfo) -> {
CloseCode closeCode = CloseCodes.getCloseCode(closeinfo.getStatusCode());
CloseReason closeReason = new CloseReason(closeCode,closeinfo.getReason());
endpoint.onClose(this,closeReason);
CloseReason closeReason = new CloseReason(closeCode, closeinfo.getReason());
endpoint.onClose(this, closeReason);
return null;
};
onErrorFunction = (cause) -> {
endpoint.onError(this,cause);
endpoint.onError(this, cause);
return null;
};
}
@ -133,7 +133,7 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
Class<?> websocketClass = websocket.getClass();
ClientEndpoint clientEndpoint = websocketClass.getAnnotation(ClientEndpoint.class);
if(clientEndpoint != null)
if (clientEndpoint != null)
{
/*Method onmethod = null;
@ -180,27 +180,27 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
Objects.requireNonNull(handler, "MessageHandler.Partial cannot be null");
if (LOG.isDebugEnabled())
{
LOG.debug("MessageHandler.Partial class: {}",handler.getClass());
LOG.debug("MessageHandler.Partial class: {}", handler.getClass());
}
// No decoders for Partial messages per JSR-356 (PFD1 spec)
if(String.class.isAssignableFrom(clazz))
if (String.class.isAssignableFrom(clazz))
{
@SuppressWarnings("unchecked")
Partial<String> strhandler = (Partial<String>)handler;
Partial<String> strhandler = (Partial<String>) handler;
setMessageAppender(MessageType.TEXT, new TextPartialMessage(strhandler));
}
else if(ByteBuffer.class.isAssignableFrom(clazz))
else if (ByteBuffer.class.isAssignableFrom(clazz))
{
@SuppressWarnings("unchecked")
Partial<ByteBuffer> bufhandler = (Partial<ByteBuffer>)handler;
Partial<ByteBuffer> bufhandler = (Partial<ByteBuffer>) handler;
// setMessageAppender(MessageType.BINARY, new BinaryBufferPartialMessage(bufhandler));
}
else if(byte[].class.isAssignableFrom(clazz))
else if (byte[].class.isAssignableFrom(clazz))
{
@SuppressWarnings("unchecked")
Partial<byte[]> arrhandler = (Partial<byte[]>)handler;
Partial<byte[]> arrhandler = (Partial<byte[]>) handler;
// setMessageAppender(MessageType.BINARY, new BinaryArrayPartialMessage(arrhandler));
}
else
@ -218,7 +218,7 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
Objects.requireNonNull(handler, "MessageHandler.Whole cannot be null");
if (LOG.isDebugEnabled())
{
LOG.debug("MessageHandler.Whole class: {}",handler.getClass());
LOG.debug("MessageHandler.Whole class: {}", handler.getClass());
}
// Determine Decoder
@ -234,45 +234,45 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
throw new IllegalStateException(err.toString());
}
if(decoderWrapper.getMetadata().isStreamed())
if (decoderWrapper.getMetadata().isStreamed())
{
// Streaming
if(InputStream.class.isAssignableFrom(clazz))
if (InputStream.class.isAssignableFrom(clazz))
{
// Whole Text Streaming
@SuppressWarnings("unchecked")
Whole<Object> streamhandler = (Whole<Object>)handler;
Decoder.BinaryStream<?> streamdecoder = (Decoder.BinaryStream<?>)decoderWrapper.getDecoder();
Whole<Object> streamhandler = (Whole<Object>) handler;
Decoder.BinaryStream<?> streamdecoder = (Decoder.BinaryStream<?>) decoderWrapper.getDecoder();
// setMessageAppender(MessageType.TEXT,new JsrInputStreamMessage(streamhandler, streamdecoder, websocket, getExecutor()));
}
else if(Reader.class.isAssignableFrom(clazz))
}
else if (Reader.class.isAssignableFrom(clazz))
{
// Whole Reader Streaming
@SuppressWarnings("unchecked")
Whole<Object> streamhandler = (Whole<Object>)handler;
Decoder.TextStream<?> streamdecoder = (Decoder.TextStream<?>)decoderWrapper.getDecoder();
Whole<Object> streamhandler = (Whole<Object>) handler;
Decoder.TextStream<?> streamdecoder = (Decoder.TextStream<?>) decoderWrapper.getDecoder();
// setMessageAppender(MessageType.BINARY,new JsrReaderMessage(streamhandler, streamdecoder, websocket, getExecutor()));
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public void addMessageHandler(MessageHandler handler) throws IllegalStateException
{
Objects.requireNonNull(handler, "MessageHandler cannot be null");
Class<? extends MessageHandler> handlerClass = handler.getClass();
Class<? extends MessageHandler> handlerClass = handler.getClass();
if (MessageHandler.Whole.class.isAssignableFrom(handlerClass))
{
Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handlerClass,MessageHandler.Whole.class);
addMessageHandler(onMessageClass,(Whole)handler);
Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handlerClass, MessageHandler.Whole.class);
addMessageHandler(onMessageClass, (Whole) handler);
}
if (MessageHandler.Partial.class.isAssignableFrom(handlerClass))
{
Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handlerClass,MessageHandler.Partial.class);
addMessageHandler(onMessageClass,(Partial)handler);
Class<?> onMessageClass = ReflectUtils.findGenericClassFor(handlerClass, MessageHandler.Partial.class);
addMessageHandler(onMessageClass, (Partial) handler);
}
}
@ -361,7 +361,7 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
@Override
public void close(CloseReason closeReason) throws IOException
{
close(closeReason.getCloseCode().getCode(),closeReason.getReasonPhrase());
close(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase());
}
@Override
@ -589,5 +589,4 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess
// JSR 356 specification mandates default batch mode to be off.
return BatchMode.OFF;
}
}

View File

@ -27,5 +27,5 @@ public enum MessageType
{
TEXT,
BINARY,
PONG;
PONG
}

View File

@ -0,0 +1,318 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.decoders;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.PongMessage;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
public class AvailableDecoders implements Predicate<Class<?>>
{
private static class RegisteredDecoder implements Predicate<Class<?>>
{
public final Class<? extends Decoder> decoder;
public final Class<?> objectType;
public RegisteredDecoder(Class<? extends Decoder> decoder, Class<?> objectType)
{
this.decoder = decoder;
this.objectType = objectType;
}
@Override
public boolean test(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
}
private List<RegisteredDecoder> registeredDecoders;
public void register(Class<? extends Decoder> decoder)
{
if (!ReflectUtils.isDefaultConstructable(decoder))
{
throw new InvalidSignatureException("Decoder must have public, no-args constructor: " + decoder.getName());
}
boolean foundDecoder = false;
if (Decoder.Binary.class.isAssignableFrom(decoder))
{
add(decoder, Decoder.Binary.class);
foundDecoder = true;
}
if (Decoder.BinaryStream.class.isAssignableFrom(decoder))
{
add(decoder, Decoder.BinaryStream.class);
foundDecoder = true;
}
if (Decoder.Text.class.isAssignableFrom(decoder))
{
add(decoder, Decoder.Text.class);
foundDecoder = true;
}
if (Decoder.TextStream.class.isAssignableFrom(decoder))
{
add(decoder, Decoder.TextStream.class);
foundDecoder = true;
}
if (!foundDecoder)
{
throw new InvalidSignatureException("Not a valid Decoder class: " + decoder.getName() + " implements no " + Decoder.class.getName() + " interfaces");
}
}
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)
{
if (decoders == null)
return;
decoders.forEach(this::register);
}
private void add(Class<? extends Decoder> decoder, Class<?> interfaceClass)
{
Class<?> objectType = ReflectUtils.findGenericClassFor(decoder, interfaceClass);
if (objectType == null)
{
StringBuilder err = new StringBuilder();
err.append("Invalid Decoder Object type declared for interface ");
err.append(interfaceClass.getName());
err.append(" on class ");
err.append(decoder);
throw new InvalidWebSocketException(err.toString());
}
if (registeredDecoders == null)
registeredDecoders = new ArrayList<>();
registeredDecoders.add(new RegisteredDecoder(decoder, objectType));
}
public Class<? extends Decoder> getDecoderFor(Class<?> type)
{
// Check registered decoders first
if (registeredDecoders != null)
{
for (RegisteredDecoder registered : registeredDecoders)
{
if (registered.objectType.isAssignableFrom(type))
return registered.decoder;
}
}
// Check default decoders next
// TEXT based [via Class reference]
if (Boolean.class.isAssignableFrom(type)) return BooleanDecoder.class;
if (Byte.class.isAssignableFrom(type)) return ByteDecoder.class;
if (Character.class.isAssignableFrom(type)) return CharacterDecoder.class;
if (Double.class.isAssignableFrom(type)) return DoubleDecoder.class;
if (Float.class.isAssignableFrom(type)) return FloatDecoder.class;
if (Integer.class.isAssignableFrom(type)) return IntegerDecoder.class;
if (Long.class.isAssignableFrom(type)) return LongDecoder.class;
if (String.class.isAssignableFrom(type)) return StringDecoder.class;
// TEXT based [via Primitive reference]
if (Boolean.TYPE.isAssignableFrom(type)) return BooleanDecoder.class;
if (Byte.TYPE.isAssignableFrom(type)) return ByteDecoder.class;
if (Character.TYPE.isAssignableFrom(type)) return CharacterDecoder.class;
if (Double.TYPE.isAssignableFrom(type)) return DoubleDecoder.class;
if (Float.TYPE.isAssignableFrom(type)) return FloatDecoder.class;
if (Integer.TYPE.isAssignableFrom(type)) return IntegerDecoder.class;
if (Long.TYPE.isAssignableFrom(type)) return LongDecoder.class;
// BINARY based
if (ByteBuffer.class.isAssignableFrom(type)) return ByteBufferDecoder.class;
if (byte[].class.isAssignableFrom(type)) return ByteArrayDecoder.class;
// PONG based
if (PongMessage.class.isAssignableFrom(type)) return PongMessageDecoder.class;
// STREAMING based
if (Reader.class.isAssignableFrom(type)) return ReaderDecoder.class;
if (InputStream.class.isAssignableFrom(type)) return InputStreamDecoder.class;
throw new InvalidWebSocketException("No Decoder found for type " + type);
}
public static Object decodePrimitive(String value, Class<?> type) throws DecodeException
{
if (value == null)
return null;
// Simplest (and most common) form of @PathParam
if (String.class.isAssignableFrom(type))
return value;
try
{
// Per JSR356 spec, just the java primitives
if (Boolean.class.isAssignableFrom(type))
{
return new Boolean(value);
}
if (Boolean.TYPE.isAssignableFrom(type))
{
return Boolean.parseBoolean(value);
}
if (Byte.class.isAssignableFrom(type))
{
return new Byte(value);
}
if (Byte.TYPE.isAssignableFrom(type))
{
return Byte.parseByte(value);
}
if (Character.class.isAssignableFrom(type))
{
if (value.length() != 1)
throw new DecodeException(value, "Invalid Size: Cannot decode as type " + Character.class.getName());
return new Character(value.charAt(0));
}
if (Character.TYPE.isAssignableFrom(type))
{
if (value.length() != 1)
throw new DecodeException(value, "Invalid Size: Cannot decode as type " + Character.class.getName());
return value.charAt(0);
}
if (Double.class.isAssignableFrom(type))
{
return new Double(value);
}
if (Double.TYPE.isAssignableFrom(type))
{
return Double.parseDouble(value);
}
if (Float.class.isAssignableFrom(type))
{
return new Float(value);
}
if (Float.TYPE.isAssignableFrom(type))
{
return Float.parseFloat(value);
}
if (Integer.class.isAssignableFrom(type))
{
return new Integer(value);
}
if (Integer.TYPE.isAssignableFrom(type))
{
return Integer.parseInt(value);
}
if (Long.class.isAssignableFrom(type))
{
return new Long(value);
}
if (Long.TYPE.isAssignableFrom(type))
{
return Long.parseLong(value);
}
// Not a primitive!
throw new DecodeException(value, "Not a recognized primitive type: " + type);
}
catch (NumberFormatException e)
{
throw new DecodeException(value, "Unable to decode as type " + type.getName());
}
}
@Override
public boolean test(Class<?> type)
{
if (registeredDecoders != null)
{
for (RegisteredDecoder registered : registeredDecoders)
{
if (registered.test(type))
return true;
}
}
// TEXT based [via Class references]
if (Boolean.class.isAssignableFrom(type) ||
Byte.class.isAssignableFrom(type) ||
Character.class.isAssignableFrom(type) ||
Double.class.isAssignableFrom(type) ||
Float.class.isAssignableFrom(type) ||
Integer.class.isAssignableFrom(type) ||
Long.class.isAssignableFrom(type) ||
String.class.isAssignableFrom(type) ||
Reader.class.isAssignableFrom(type))
{
return true;
}
// TEXT based [via Primitive reference]
if (Boolean.TYPE.isAssignableFrom(type) ||
Byte.TYPE.isAssignableFrom(type) ||
Character.TYPE.isAssignableFrom(type) ||
Double.TYPE.isAssignableFrom(type) ||
Float.TYPE.isAssignableFrom(type) ||
Integer.TYPE.isAssignableFrom(type) ||
Long.TYPE.isAssignableFrom(type))
{
return true;
}
// BINARY based
if (ByteBuffer.class.isAssignableFrom(type) ||
byte[].class.isAssignableFrom(type) ||
InputStream.class.isAssignableFrom(type))
{
return true;
}
// PONG based
if (PongMessage.class.isAssignableFrom(type))
{
return true;
}
return false;
}
}

View File

@ -0,0 +1,221 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import javax.websocket.Encoder;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
public class AvailableEncoders implements Predicate<Class<?>>
{
private static class RegisteredEncoder implements Predicate<Class<?>>
{
public final Class<? extends Encoder> encoder;
public final Class<?> objectType;
public RegisteredEncoder(Class<? extends Encoder> encoder, Class<?> objectType)
{
this.encoder = encoder;
this.objectType = objectType;
}
@Override
public boolean test(Class<?> type)
{
return objectType.isAssignableFrom(type);
}
}
private List<RegisteredEncoder> registeredEncoders;
public void register(Class<? extends Encoder> encoder)
{
if (!ReflectUtils.isDefaultConstructable(encoder))
{
throw new InvalidSignatureException("Encoder must have public, no-args constructor: " + encoder.getName());
}
boolean foundEncoder = false;
if (Encoder.Binary.class.isAssignableFrom(encoder))
{
add(encoder, Encoder.Binary.class);
foundEncoder = true;
}
if (Encoder.BinaryStream.class.isAssignableFrom(encoder))
{
add(encoder, Encoder.BinaryStream.class);
foundEncoder = true;
}
if (Encoder.Text.class.isAssignableFrom(encoder))
{
add(encoder, Encoder.Text.class);
foundEncoder = true;
}
if (Encoder.TextStream.class.isAssignableFrom(encoder))
{
add(encoder, Encoder.TextStream.class);
foundEncoder = true;
}
if (!foundEncoder)
{
throw new InvalidSignatureException("Not a valid Encoder class: " + encoder.getName() + " implements no " + Encoder.class.getName() + " interfaces");
}
}
public void registerAll(Class<? extends Encoder>[] encoders)
{
if (encoders == null)
return;
for (Class<? extends Encoder> encoder : encoders)
{
register(encoder);
}
}
public void registerAll(List<Class<? extends Encoder>> encoders)
{
if (encoders == null)
return;
encoders.forEach(this::register);
}
private void add(Class<? extends Encoder> encoder, Class<?> interfaceClass)
{
Class<?> objectType = ReflectUtils.findGenericClassFor(encoder, interfaceClass);
if (objectType == null)
{
StringBuilder err = new StringBuilder();
err.append("Invalid Encoder Object type declared for interface ");
err.append(interfaceClass.getName());
err.append(" on class ");
err.append(encoder);
throw new InvalidWebSocketException(err.toString());
}
if (registeredEncoders == null)
registeredEncoders = new ArrayList<>();
registeredEncoders.add(new RegisteredEncoder(encoder, objectType));
}
public Class<? extends Encoder> getEncoderFor(Class<?> type)
{
// Check registered encoders first
if (registeredEncoders != null)
{
for (RegisteredEncoder registered : registeredEncoders)
{
if (registered.objectType.isAssignableFrom(type))
return registered.encoder;
}
}
// Check default encoders next
// TEXT based [via Class reference]
if (Boolean.class.isAssignableFrom(type)) return BooleanEncoder.class;
if (Byte.class.isAssignableFrom(type)) return ByteEncoder.class;
if (Character.class.isAssignableFrom(type)) return CharacterEncoder.class;
if (Double.class.isAssignableFrom(type)) return DoubleEncoder.class;
if (Float.class.isAssignableFrom(type)) return FloatEncoder.class;
if (Integer.class.isAssignableFrom(type)) return IntegerEncoder.class;
if (Long.class.isAssignableFrom(type)) return LongEncoder.class;
if (String.class.isAssignableFrom(type)) return StringEncoder.class;
// TEXT based [via Primitive reference]
if (Boolean.TYPE.isAssignableFrom(type)) return BooleanEncoder.class;
if (Byte.TYPE.isAssignableFrom(type)) return ByteEncoder.class;
if (Character.TYPE.isAssignableFrom(type)) return CharacterEncoder.class;
if (Double.TYPE.isAssignableFrom(type)) return DoubleEncoder.class;
if (Float.TYPE.isAssignableFrom(type)) return FloatEncoder.class;
if (Integer.TYPE.isAssignableFrom(type)) return IntegerEncoder.class;
if (Long.TYPE.isAssignableFrom(type)) return LongEncoder.class;
// BINARY based
if (ByteBuffer.class.isAssignableFrom(type)) return ByteBufferEncoder.class;
if (byte[].class.isAssignableFrom(type)) return ByteArrayEncoder.class;
// Note: Streams (Writer / OutputStream) are not present here
// as you don't write with a Stream via an encoder, you tell the
// encoder to write an object to a Stream
throw new InvalidWebSocketException("No Encoder found for type " + type);
}
@Override
public boolean test(Class<?> type)
{
if (registeredEncoders != null)
{
for (RegisteredEncoder registered : registeredEncoders)
{
if (registered.test(type))
return true;
}
}
// TEXT based [via Class references]
if (Boolean.class.isAssignableFrom(type) ||
Byte.class.isAssignableFrom(type) ||
Character.class.isAssignableFrom(type) ||
Double.class.isAssignableFrom(type) ||
Float.class.isAssignableFrom(type) ||
Integer.class.isAssignableFrom(type) ||
Long.class.isAssignableFrom(type) ||
String.class.isAssignableFrom(type))
{
return true;
}
// TEXT based [via Primitive reference]
if (Boolean.TYPE.isAssignableFrom(type) ||
Byte.TYPE.isAssignableFrom(type) ||
Character.TYPE.isAssignableFrom(type) ||
Double.TYPE.isAssignableFrom(type) ||
Float.TYPE.isAssignableFrom(type) ||
Integer.TYPE.isAssignableFrom(type) ||
Long.TYPE.isAssignableFrom(type))
{
return true;
}
// BINARY based
if (ByteBuffer.class.isAssignableFrom(type) ||
byte[].class.isAssignableFrom(type))
{
return true;
}
return false;
}
}

View File

@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.jsr356.MessageType;
import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet;
@Deprecated
public class PrimitiveEncoderMetadataSet extends EncoderMetadataSet
{
public static final EncoderMetadataSet INSTANCE = new PrimitiveEncoderMetadataSet();

View File

@ -0,0 +1,427 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.DecodeException;
import javax.websocket.Encoder;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.function.CommonEndpointFunctions;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.UnorderedSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
/**
* Endpoint Functions used as interface between from the parsed websocket frames
* and the user provided endpoint methods.
*/
public class JsrEndpointFunctions extends CommonEndpointFunctions<JsrSession>
{
private static final Logger LOG = Log.getLogger(JsrEndpointFunctions.class);
/**
* Represents a static value (as seen from a URI PathParam)
* <p>
* The decoding of the raw String to a object occurs later,
* when the callable/sink/function is created for a method
* that needs it converted to an object.
* </p>
*/
protected static class StaticArg implements Comparator<StaticArg>
{
public final String name;
public final String value;
public StaticArg(String name, String value)
{
this.name = name;
this.value = value;
}
@Override
public int compare(StaticArg o1, StaticArg o2)
{
return o1.name.compareTo(o2.name);
}
}
private final AvailableEncoders encoders;
private final AvailableDecoders decoders;
private final EndpointConfig endpointConfig;
private List<StaticArg> staticArgs;
public JsrEndpointFunctions(Object endpoint, WebSocketPolicy policy, Executor executor,
AvailableEncoders encoders, AvailableDecoders decoders,
Map<String, String> uriParams, EndpointConfig endpointConfig)
{
super(endpoint, policy, executor);
this.encoders = encoders;
this.decoders = decoders;
this.endpointConfig = endpointConfig;
if (uriParams != null)
{
this.staticArgs = new ArrayList<>();
this.staticArgs.addAll(uriParams.entrySet().stream()
.map(entry -> new StaticArg(entry.getKey(), entry.getValue()))
.sorted()
.collect(Collectors.toList()));
}
}
@Override
protected void discoverEndpointFunctions(Object endpoint)
{
if (endpoint instanceof Endpoint)
{
Endpoint jsrEndpoint = (Endpoint) endpoint;
setOnOpen((session) -> {
jsrEndpoint.onOpen(session, endpointConfig);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onOpen", Session.class, EndpointConfig.class)
);
setOnClose((close) -> {
CloseReason closeReason = new CloseReason(
CloseReason.CloseCodes.getCloseCode(close.getStatusCode())
, close.getReason());
jsrEndpoint.onClose(getSession(), closeReason);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onClose", Session.class, EndpointConfig.class)
);
setOnError((cause) -> {
jsrEndpoint.onError(getSession(), cause);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onError", Session.class, EndpointConfig.class)
);
// If using an Endpoint, there's nothing else left to map at this point.
// Eventually, the endpoint should call .addMessageHandler() to declare
// the various TEXT / BINARY / PONG message functions
return;
}
discoverAnnotatedEndpointFunctions(endpoint);
}
/**
* Generic discovery of annotated endpoint functions.
*
* @param endpoint the endpoint object
*/
protected void discoverAnnotatedEndpointFunctions(Object endpoint)
{
Class<?> endpointClass = endpoint.getClass();
// Use the JSR/Client annotation
ClientEndpoint websocket = endpointClass.getAnnotation(ClientEndpoint.class);
if (websocket != null)
{
encoders.registerAll(websocket.encoders());
decoders.registerAll(websocket.decoders());
// From here, the discovery of endpoint method is standard across
// both JSR356/Client and JSR356/Server endpoints
try
{
discoverJsrAnnotatedEndpointFunctions(endpoint);
}
catch (DecodeException e)
{
throw new InvalidWebSocketException("Cannot instantiate WebSocket", e);
}
}
}
/**
* JSR356 Specific discovery of Annotated Endpoint Methods
*
* @param endpoint the endpoint
*/
protected void discoverJsrAnnotatedEndpointFunctions(Object endpoint) throws DecodeException
{
Class<?> endpointClass = endpoint.getClass();
Method method = null;
// OnOpen [0..1]
method = ReflectUtils.findAnnotatedMethod(endpointClass, OnOpen.class);
if (method != null)
{
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Analyze @OnOpen method declaration techniques
DynamicArgs.Builder builder = createDynamicArgs(
new Arg(Session.class),
new Arg(EndpointConfig.class));
DynamicArgs.Signature sig = builder.getMatchingSignature(method);
assertSignatureValid(sig, OnOpen.class, method);
final Object[] args = newCallArgs(sig.getCallArgs());
DynamicArgs invoker = builder.build(method, sig);
setOnOpen((jsrSession) ->
{
args[0] = jsrSession;
args[1] = endpointConfig;
invoker.invoke(endpoint, args);
return null;
}, method);
}
// OnClose [0..1]
method = ReflectUtils.findAnnotatedMethod(endpointClass, OnClose.class);
if (method != null)
{
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Analyze @OnClose method declaration techniques
DynamicArgs.Builder builder = createDynamicArgs(
new Arg(Session.class),
new Arg(CloseReason.class));
DynamicArgs.Signature sig = builder.getMatchingSignature(method);
assertSignatureValid(sig, OnClose.class, method);
final Object[] args = newCallArgs(sig.getCallArgs());
DynamicArgs invoker = builder.build(method, sig);
setOnClose((closeInfo) ->
{
// Convert Jetty CloseInfo to JSR CloseReason
CloseReason.CloseCode closeCode = CloseReason.CloseCodes.getCloseCode(closeInfo.getStatusCode());
CloseReason closeReason = new CloseReason(closeCode, closeInfo.getReason());
args[0] = getSession();
args[1] = closeReason;
invoker.invoke(endpoint, args);
return null;
}, method);
}
// OnError [0..1]
method = ReflectUtils.findAnnotatedMethod(endpointClass, OnError.class);
if (method != null)
{
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Analyze @OnError method declaration techniques
DynamicArgs.Builder builder = createDynamicArgs(
new Arg(Session.class),
new Arg(Throwable.class));
DynamicArgs.Signature sig = builder.getMatchingSignature(method);
assertSignatureValid(sig, OnError.class, method);
final Object[] args = newCallArgs(sig.getCallArgs());
DynamicArgs invoker = builder.build(method, sig);
setOnError((cause) ->
{
args[0] = getSession();
args[1] = cause;
invoker.invoke(endpoint, args);
return null;
}, method);
}
// OnMessage [0..3] (TEXT / BINARY / PONG)
Method messageMethods[] = ReflectUtils.findAnnotatedMethods(endpointClass, OnMessage.class);
if (messageMethods != null && messageMethods.length > 0)
{
for (Method messageMethod : messageMethods)
{
// Analyze @OnMessage method declaration
// Must be a public, non-static method
ReflectUtils.assertIsPublicNonStatic(method);
// If a return type is declared, it must be capable
// of being encoded with an available Encoder
Class<?> returnType = messageMethod.getReturnType();
Encoder returnEncoder = newEncoderFor(returnType);
// Try to determine Message type (BINARY / TEXT / PONG) from signature
// Test for Whole TEXT
DynamicArgs.Builder builder = createDynamicArgs(
new Arg(Session.class),
new Arg(CloseReason.class));
DynamicArgs.Signature sig = builder.getMatchingSignature(method);
if(sig != null)
{
}
// Test for Whole BINARY
// Test for Partial TEXT
// Test for Partial BINARY
// Test for Streaming TEXT
// Test for Streaming BINARY
// Test for PONG
// TODO: super.setOnText()
// TODO: super.setOnBinary()
// TODO: super.setOnPong()
/*
else
{
// Not a valid @OnMessage declaration signature
throw InvalidSignatureException.build(onmsg, OnMessage.class,
OnTextFunction.getDynamicArgsBuilder(),
OnByteBufferFunction.getDynamicArgsBuilder(),
OnByteArrayFunction.getDynamicArgsBuilder(),
OnInputStreamFunction.getDynamicArgsBuilder(),
OnReaderFunction.getDynamicArgsBuilder());
}
*/
}
}
}
private Encoder newEncoderFor(Class<?> type)
{
if ((type == Void.TYPE) || (type == Void.class))
{
return null;
}
Class<? extends Encoder> encoderClass = encoders.getEncoderFor(type);
if (encoderClass == null)
{
throw new InvalidWebSocketException("Unable to find Encoder for type " + type.getName());
}
try
{
Encoder encoder = encoderClass.newInstance();
encoder.init(this.endpointConfig);
return encoder;
}
catch (Throwable t)
{
throw new InvalidWebSocketException("Unable to initialize required Encoder: " + encoderClass.getName(), t);
}
}
private void assertSignatureValid(DynamicArgs.Signature sig, Class<? extends Annotation> annotationClass, Method method)
{
if (sig != null)
return;
StringBuilder err = new StringBuilder();
err.append('@').append(annotationClass.getSimpleName());
err.append(' ');
ReflectUtils.append(err, endpoint.getClass(), method);
throw new InvalidSignatureException(err.toString());
}
private Object[] newCallArgs(Arg[] callArgs) throws DecodeException
{
int len = callArgs.length;
Object[] args = new Object[callArgs.length];
for (int i = 0; i < len; i++)
{
Object staticValue = getDecodedStaticValue(callArgs[i].getName(), callArgs[i].getType());
if (staticValue != null)
{
args[i] = staticValue;
}
}
return args;
}
private Object getDecodedStaticValue(String name, Class<?> type) throws DecodeException
{
for (StaticArg args : staticArgs)
{
if (args.name.equals(name))
{
return AvailableDecoders.decodePrimitive(args.value, type);
}
}
return null;
}
private DynamicArgs.Builder createDynamicArgs(Arg... args)
{
DynamicArgs.Builder argBuilder = new DynamicArgs.Builder();
int argCount = args.length;
if (this.staticArgs != null)
argCount += this.staticArgs.size();
Arg callArgs[] = new Arg[argCount];
int idx = 0;
for (Arg arg : args)
{
callArgs[idx++] = arg;
}
if (this.staticArgs != null)
{
for (StaticArg staticArg : this.staticArgs)
{
// TODO: translate from UriParam String to method param type?
// TODO: shouldn't this be the Arg seen in the method?
// TODO: use decoder?
callArgs[idx++] = new Arg(staticArg.value.getClass()).setTag(staticArg.name);
}
}
argBuilder.addSignature(new UnorderedSignature(callArgs));
return argBuilder;
}
}

View File

@ -16,25 +16,24 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
package org.eclipse.jetty.websocket.jsr356.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.UnorderedSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnMessage} method {@link Function} for BINARY/byte[] types
*/
@Deprecated
public class JsrOnByteArrayFunction implements Function<byte[], Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
@ -102,14 +101,7 @@ public class JsrOnByteArrayFunction implements Function<byte[], Void>
params[idx++] = bin.length;
// TODO: add PathParam Arg Values?
try
{
this.callable.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call binary message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, params);
return null;
}
}

View File

@ -16,9 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
package org.eclipse.jetty.websocket.jsr356.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.function.Function;
@ -26,16 +25,16 @@ import java.util.function.Function;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.UnorderedSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnMessage} method {@link Function} for BINARY/{@link ByteBuffer} types
*/
@Deprecated
public class JsrOnByteBufferFunction implements Function<ByteBuffer, Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
@ -96,15 +95,7 @@ public class JsrOnByteBufferFunction implements Function<ByteBuffer, Void>
params[idx++] = session;
params[idx++] = bin;
// TODO: add PathParam Arg Values?
try
{
this.callable.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call binary message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, params);
return null;
}
}

View File

@ -16,27 +16,26 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
package org.eclipse.jetty.websocket.jsr356.function;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.UnorderedSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnMessage} method {@link Function} for BINARY/{@link InputStream} streaming
* types
*/
@Deprecated
public class JsrOnInputStreamFunction implements Function<InputStream, Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
@ -98,14 +97,7 @@ public class JsrOnInputStreamFunction implements Function<InputStream, Void>
params[idx++] = stream;
// TODO: add PathParam Arg Values?
try
{
this.callable.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call binary message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, params);
return null;
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
package org.eclipse.jetty.websocket.jsr356.function;
import java.io.Reader;
import java.lang.reflect.Method;
@ -25,7 +25,7 @@ import java.util.function.Function;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
package org.eclipse.jetty.websocket.jsr356.function;
import java.lang.reflect.Method;
import java.util.function.Function;

View File

@ -1,112 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.websocket.OnClose;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnClose} method {@link Function}
*/
public class JsrOnCloseFunction implements Function<CloseInfo, Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
private static final Arg ARG_STATUS_CODE = new Arg(int.class);
private static final Arg ARG_REASON = new Arg(String.class);
private final Arg[] extraArgs;
private final int paramCount;
private final Session session;
private final Object endpoint;
private final Method method;
private final DynamicArgs callable;
public JsrOnCloseFunction(Session session, Object endpoint, Method method, Arg[] extraArgs)
{
this.session = session;
this.endpoint = endpoint;
this.method = method;
this.extraArgs = extraArgs;
// Validate Method
ReflectUtils.assertIsAnnotated(method, OnClose.class);
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Build up dynamic callable
DynamicArgs.Builder argBuilder = new DynamicArgs.Builder();
int argCount = 3;
if (this.extraArgs != null)
argCount += extraArgs.length;
this.paramCount = argCount;
Arg[] callArgs = new Arg[argCount];
int idx = 0;
callArgs[idx++] = ARG_SESSION;
callArgs[idx++] = ARG_STATUS_CODE;
callArgs[idx++] = ARG_REASON;
for (Arg arg : this.extraArgs)
{
callArgs[idx++] = arg;
}
argBuilder.addSignature(new UnorderedSignature(callArgs));
// Attempt to build callable
this.callable = argBuilder.build(method, callArgs);
if (this.callable == null)
{
throw InvalidSignatureException.build(method, OnClose.class, argBuilder);
}
}
@Override
public Void apply(CloseInfo closeinfo)
{
Object params[] = new Object[paramCount];
int idx = 0;
params[idx++] = session;
params[idx++] = closeinfo.getStatusCode();
params[idx++] = closeinfo.getReason();
// TODO: add PathParam Arg Values?
try
{
this.callable.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}
}

View File

@ -1,108 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.websocket.OnError;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnError} method {@link Function}
*/
public class JsrOnErrorFunction implements Function<Throwable, Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
private static final Arg ARG_CAUSE = new Arg(Throwable.class);
private final Arg[] extraArgs;
private final int paramCount;
private final Session session;
private final Object endpoint;
private final Method method;
private final DynamicArgs callable;
public JsrOnErrorFunction(Session session, Object endpoint, Method method, Arg[] extraArgs)
{
this.session = session;
this.endpoint = endpoint;
this.method = method;
this.extraArgs = extraArgs;
// Validate Method
ReflectUtils.assertIsAnnotated(method, OnError.class);
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Build up dynamic callable
DynamicArgs.Builder argBuilder = new DynamicArgs.Builder();
int argCount = 2;
if (this.extraArgs != null)
argCount += extraArgs.length;
this.paramCount = argCount;
Arg[] callArgs = new Arg[argCount];
int idx = 0;
callArgs[idx++] = ARG_SESSION;
callArgs[idx++] = ARG_CAUSE;
for (Arg arg : this.extraArgs)
{
callArgs[idx++] = arg;
}
argBuilder.addSignature(new UnorderedSignature(callArgs));
// Attempt to build callable
this.callable = argBuilder.build(method);
if (this.callable == null)
{
throw InvalidSignatureException.build(method, OnError.class, argBuilder);
}
}
@Override
public Void apply(Throwable cause)
{
Object params[] = new Object[paramCount];
int idx = 0;
params[idx++] = session;
params[idx++] = cause;
// TODO: add PathParam Arg Values?
try
{
this.callable.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}
}

View File

@ -1,105 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.functions;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.common.util.UnorderedSignature;
/**
* javax.websocket {@link OnOpen} method {@link Function}
*/
public class JsrOnOpenFunction implements Function<org.eclipse.jetty.websocket.api.Session, Void>
{
private static final Arg ARG_SESSION = new Arg(Session.class);
private final Arg[] extraArgs;
private final int paramCount;
private final Session session;
private final Object endpoint;
private final Method method;
private final DynamicArgs callable;
public JsrOnOpenFunction(Session session, Object endpoint, Method method, DynamicArgs.Arg[] extraArgs)
{
this.session = session;
this.endpoint = endpoint;
this.method = method;
this.extraArgs = extraArgs;
// Validate Method
ReflectUtils.assertIsAnnotated(method, OnOpen.class);
ReflectUtils.assertIsPublicNonStatic(method);
ReflectUtils.assertIsReturn(method, Void.TYPE);
// Build up dynamic callable
DynamicArgs.Builder argBuilder = new DynamicArgs.Builder();
int argCount = 1;
if (this.extraArgs != null)
argCount += extraArgs.length;
this.paramCount = argCount;
Arg[] callArgs = new Arg[argCount];
int idx = 0;
callArgs[idx++] = ARG_SESSION;
for (Arg arg : this.extraArgs)
{
callArgs[idx++] = arg;
}
argBuilder.addSignature(new UnorderedSignature(callArgs));
// Attempt to build callable
this.callable = argBuilder.build(method, callArgs);
if (this.callable == null)
{
throw InvalidSignatureException.build(method, OnOpen.class, argBuilder);
}
}
@Override
public Void apply(org.eclipse.jetty.websocket.api.Session sess)
{
Object params[] = new Object[paramCount];
int idx = 0;
params[idx++] = session;
// TODO: add PathParam Arg Values?
try
{
method.invoke(endpoint, params);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
return null;
}
}

View File

@ -25,6 +25,7 @@ import org.eclipse.jetty.websocket.jsr356.MessageType;
/**
* Immutable Metadata for a {@link Encoder}
*/
@Deprecated
public class EncoderMetadata extends CoderMetadata<Encoder>
{
public EncoderMetadata(Class<? extends Encoder> coderClass, Class<?> objType, MessageType messageType, boolean streamed)

View File

@ -28,6 +28,7 @@ import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.jsr356.MessageType;
@Deprecated
public class EncoderMetadataSet extends CoderMetadataSet<Encoder, EncoderMetadata>
{
@Override

View File

@ -167,7 +167,7 @@ public class AnnotatedEndpointConfigTest
public void testEncoders() throws Exception
{
List<Class<? extends Encoder>> encoders = config.getEncoders();
Assert.assertThat("Encoders",encoders,notNullValue());
Assert.assertThat("AvailableEncoders",encoders,notNullValue());
Class<?> expectedClass = TimeEncoder.class;
boolean hasExpectedEncoder = false;
@ -179,7 +179,7 @@ public class AnnotatedEndpointConfigTest
}
}
Assert.assertTrue("Client Encoders has " + expectedClass.getName(),hasExpectedEncoder);
Assert.assertTrue("Client AvailableEncoders has " + expectedClass.getName(),hasExpectedEncoder);
}
@Test

View File

@ -38,7 +38,7 @@ import org.junit.Before;
import org.junit.Test;
/**
* Tests against the Encoders class
* Tests against the AvailableEncoders class
*/
public class EncoderFactoryTest
{

View File

@ -1,132 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import org.eclipse.jetty.websocket.jsr356.EndpointFunctions.Role;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidCloseIntSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorErrorSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorExceptionSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidErrorIntSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenCloseReasonSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenIntSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.InvalidOpenSessionIntSocket;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class EndpointFunctions_BadSignaturesTest
{
public static class Case
{
public static Case add(List<Case[]> data, Class<?> pojo, Class<? extends Annotation> methodAnnotation, Role role)
{
Case test = new Case(pojo, methodAnnotation, role);
data.add(new Case[]{test});
return test;
}
// The websocket pojo to test against
final Class<?> pojo;
final Class<? extends Annotation> methodAnnotation;
final Role role;
public Case(Class<?> pojo, Class<? extends Annotation> methodAnnotation, Role role)
{
this.pojo = pojo;
this.methodAnnotation = methodAnnotation;
this.role = role;
}
@Override
public String toString()
{
return String.format("%s @%s", pojo.getSimpleName(), methodAnnotation.getSimpleName());
}
}
@Parameterized.Parameters(name = "{0}")
public static Collection<Case[]> data() throws Exception
{
List<Case[]> data = new ArrayList<>();
Case.add(data, InvalidCloseIntSocket.class, OnClose.class, Role.CLOSE);
Case.add(data, InvalidErrorErrorSocket.class, OnError.class, Role.ERROR);
Case.add(data, InvalidErrorExceptionSocket.class, OnError.class, Role.ERROR);
Case.add(data, InvalidErrorIntSocket.class, OnError.class, Role.ERROR);
Case.add(data, InvalidOpenCloseReasonSocket.class, OnOpen.class, Role.OPEN);
Case.add(data, InvalidOpenIntSocket.class, OnOpen.class, Role.OPEN);
Case.add(data, InvalidOpenSessionIntSocket.class, OnOpen.class, Role.OPEN);
// TODO: invalid return types
// TODO: static methods
// TODO: private or protected methods
// TODO: abstract methods
return data;
}
@Parameterized.Parameter(0)
public Case testcase;
@Test
public void testInvalidSignature()
{
EndpointFunctions functions = new EndpointFunctions();
Method foundMethod = null;
for (Method method : testcase.pojo.getDeclaredMethods())
{
if (method.getAnnotation(testcase.methodAnnotation) != null)
{
foundMethod = method;
break;
}
}
assertThat("Found Method with @" + testcase.methodAnnotation.getSimpleName(), foundMethod, notNullValue());
try
{
EndpointFunctions.ArgRole argRole = functions.getArgRole(foundMethod, testcase.methodAnnotation, testcase.role);
fail("Expected " + InvalidSignatureException.class + " with message that references " + testcase.methodAnnotation.getSimpleName() + " annotation");
}
catch (InvalidSignatureException e)
{
assertThat("Message", e.getMessage(), containsString(testcase.methodAnnotation.getSimpleName()));
}
}
}

View File

@ -1,204 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.websocket.CloseReason;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.jsr356.EndpointFunctions.Role;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicBinaryMessageByteBufferSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSessionThrowableSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSessionSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicErrorThrowableSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicInputStreamWithThrowableSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSessionSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicOpenSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicPongMessageSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.BasicTextMessageStringSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSessionSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseReasonSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSessionReasonSocket;
import org.eclipse.jetty.websocket.jsr356.endpoints.samples.close.CloseSocket;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class EndpointFunctions_GoodSignaturesTest
{
static class ExpectedMethod
{
String methodName;
Class<?> params[];
}
static class ActualMethod
{
EndpointFunctions.ArgRole argRole;
Method method;
}
public static class Case
{
public static Case add(List<Case[]> data, Class<?> pojo)
{
Case test = new Case(pojo);
data.add(new Case[]{test});
return test;
}
// The websocket pojo to test against
final Class<?> pojo;
// The expected roles found, along with select methods that should
// have been identified
Map<Role, ExpectedMethod> expectedRoles;
public Case(Class<?> pojo)
{
this.pojo = pojo;
this.expectedRoles = new HashMap<>();
}
public void addExpected(Role role, String methodName, Class<?>... params)
{
ExpectedMethod expected = new ExpectedMethod();
expected.methodName = methodName;
expected.params = params;
expectedRoles.put(role, expected);
}
@Override
public String toString()
{
return String.format("%s [%d roles]", pojo.getSimpleName(), expectedRoles.size());
}
}
@Parameterized.Parameters(name = "{0}")
public static Collection<Case[]> data() throws Exception
{
List<Case[]> data = new ArrayList<>();
Case.add(data, BasicOpenSocket.class)
.addExpected(Role.OPEN, "onOpen");
Case.add(data, BasicOpenSessionSocket.class)
.addExpected(Role.OPEN, "onOpen", Session.class);
Case.add(data, CloseSocket.class)
.addExpected(Role.CLOSE, "onClose");
Case.add(data, CloseReasonSocket.class)
.addExpected(Role.CLOSE, "onClose", CloseReason.class);
Case.add(data, CloseReasonSessionSocket.class)
.addExpected(Role.CLOSE, "onClose", CloseReason.class, Session.class);
Case.add(data, CloseSessionReasonSocket.class)
.addExpected(Role.CLOSE, "onClose", Session.class, CloseReason.class);
Case.add(data, BasicErrorSocket.class)
.addExpected(Role.ERROR, "onError");
Case.add(data, BasicErrorSessionSocket.class)
.addExpected(Role.ERROR, "onError", Session.class);
Case.add(data, BasicErrorSessionThrowableSocket.class)
.addExpected(Role.ERROR, "onError", Session.class, Throwable.class);
Case.add(data, BasicErrorThrowableSocket.class)
.addExpected(Role.ERROR, "onError", Throwable.class);
Case.add(data, BasicErrorThrowableSessionSocket.class)
.addExpected(Role.ERROR, "onError", Throwable.class, Session.class);
Case.add(data, BasicTextMessageStringSocket.class)
.addExpected(Role.TEXT, "onText", String.class);
Case.add(data, BasicBinaryMessageByteBufferSocket.class)
.addExpected(Role.BINARY, "onBinary", ByteBuffer.class);
Case.add(data, BasicPongMessageSocket.class)
.addExpected(Role.PONG, "onPong", PongMessage.class);
Case.add(data, BasicInputStreamSocket.class)
.addExpected(Role.BINARY_STREAM, "onBinary", InputStream.class);
Case.add(data, BasicInputStreamWithThrowableSocket.class)
.addExpected(Role.BINARY_STREAM, "onBinary", InputStream.class);
return data;
}
@Parameterized.Parameter(0)
public Case testcase;
@Test
public void testFoundRoles()
{
EndpointFunctions functions = new EndpointFunctions();
// Walk all methods and see what is found
Map<Role, ActualMethod> actualMap = new HashMap<>();
for (Method method : testcase.pojo.getDeclaredMethods())
{
EndpointFunctions.ArgRole argRole = functions.findArgRole(method);
if (argRole != null)
{
ActualMethod actualMethod = new ActualMethod();
actualMethod.argRole = argRole;
actualMethod.method = method;
actualMap.put(argRole.role, actualMethod);
}
}
// Ensure that actual matches found
for (Map.Entry<Role, ExpectedMethod> expected : testcase.expectedRoles.entrySet())
{
// Expected
Role expectedRole = expected.getKey();
ExpectedMethod expectedMethod = expected.getValue();
// Actual
ActualMethod actual = actualMap.get(expectedRole);
assertThat("Role", actual.argRole.role, is(expectedRole));
assertThat("Method.name", actual.method.getName(), is(expectedMethod.methodName));
// validate parameters
Class<?> actualParams[] = actual.method.getParameterTypes();
Class<?> expectedParams[] = expectedMethod.params;
assertThat("param count", actualParams.length, is(expectedParams.length));
for (int i = 0; i < actualParams.length; i++)
{
assertThat("Param[" + i + "]", actualParams[i], equalTo(expectedParams[i]));
}
}
}
}

View File

@ -0,0 +1,284 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.decoders;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.common.util.Hex;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.junit.BeforeClass;
import org.junit.Test;
public class AvailableDecodersTest
{
private static EndpointConfig testConfig;
@BeforeClass
public static void initConfig()
{
testConfig = new EmptyClientEndpointConfig();
}
private AvailableDecoders decoders = new AvailableDecoders();
private <T> void assertTextDecoder(Class<T> type, String value, T expectedDecoded) throws IllegalAccessException, InstantiationException, DecodeException
{
Class<? extends Decoder> decoderClass = decoders.getDecoderFor(type);
assertThat("Decoder Class", decoderClass, notNullValue());
Decoder.Text<T> decoder = (Decoder.Text<T>) decoderClass.newInstance();
decoder.init(testConfig);
T decoded = decoder.decode(value);
assertThat("Decoded", decoded, is(expectedDecoded));
}
private <T> void assertBinaryDecoder(Class<T> type, ByteBuffer value, T expectedDecoded) throws IllegalAccessException, InstantiationException, DecodeException
{
Class<? extends Decoder> decoderClass = decoders.getDecoderFor(type);
assertThat("Decoder Class", decoderClass, notNullValue());
Decoder.Binary<T> decoder = (Decoder.Binary<T>) decoderClass.newInstance();
decoder.init(testConfig);
T decoded = decoder.decode(value);
assertThat("Decoded", decoded, equalTo(expectedDecoded));
}
@Test
public void testCoreDecode_Boolean() throws IllegalAccessException, InstantiationException, DecodeException
{
Boolean expected = Boolean.TRUE;
assertTextDecoder(Boolean.class, "true", expected);
}
@Test
public void testCoreDecode_boolean() throws IllegalAccessException, InstantiationException, DecodeException
{
boolean expected = false;
assertTextDecoder(Boolean.TYPE, "false", expected);
}
@Test
public void testCoreDecode_Byte() throws IllegalAccessException, InstantiationException, DecodeException
{
Byte expected = new Byte((byte) 0x21);
assertTextDecoder(Byte.class, "33", expected);
}
@Test
public void testCoreDecode_byte() throws IllegalAccessException, InstantiationException, DecodeException
{
byte expected = 0x21;
assertTextDecoder(Byte.TYPE, "33", expected);
}
@Test
public void testCoreDecode_Character() throws IllegalAccessException, InstantiationException, DecodeException
{
Character expected = new Character('!');
assertTextDecoder(Character.class, "!", expected);
}
@Test
public void testCoreDecode_char() throws IllegalAccessException, InstantiationException, DecodeException
{
char expected = '!';
assertTextDecoder(Character.TYPE, "!", expected);
}
@Test
public void testCoreDecode_Double() throws IllegalAccessException, InstantiationException, DecodeException
{
Double expected = new Double(123.45);
assertTextDecoder(Double.class, "123.45", expected);
}
@Test
public void testCoreDecode_double() throws IllegalAccessException, InstantiationException, DecodeException
{
double expected = 123.45;
assertTextDecoder(Double.TYPE, "123.45", expected);
}
@Test
public void testCoreDecode_Float() throws IllegalAccessException, InstantiationException, DecodeException
{
Float expected = new Float(123.4567);
assertTextDecoder(Float.class, "123.4567", expected);
}
@Test
public void testCoreDecode_float() throws IllegalAccessException, InstantiationException, DecodeException
{
float expected = 123.4567F;
assertTextDecoder(Float.TYPE, "123.4567", expected);
}
@Test
public void testCoreDecode_Integer() throws IllegalAccessException, InstantiationException, DecodeException
{
Integer expected = new Integer(1234);
assertTextDecoder(Integer.class, "1234", expected);
}
@Test
public void testCoreDecode_int() throws IllegalAccessException, InstantiationException, DecodeException
{
int expected = 1234;
assertTextDecoder(Integer.TYPE, "1234", expected);
}
@Test
public void testCoreDecode_Long() throws IllegalAccessException, InstantiationException, DecodeException
{
Long expected = new Long(123_456_789);
assertTextDecoder(Long.class, "123456789", expected);
}
@Test
public void testCoreDecode_long() throws IllegalAccessException, InstantiationException, DecodeException
{
long expected = 123_456_789L;
assertTextDecoder(Long.TYPE, "123456789", expected);
}
@Test
public void testCoreDecode_String() throws IllegalAccessException, InstantiationException, DecodeException
{
String expected = "Hello World";
assertTextDecoder(String.class, "Hello World", expected);
}
@Test
public void testCoreDecode_ByteBuffer() throws IllegalAccessException, InstantiationException, DecodeException
{
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
ByteBuffer expected = Hex.asByteBuffer("112233445566778899");
assertBinaryDecoder(ByteBuffer.class, val, expected);
}
@Test
public void testCoreDecode_ByteArray() throws IllegalAccessException, InstantiationException, DecodeException
{
ByteBuffer val = Hex.asByteBuffer("112233445566778899");
byte expected[] = Hex.asByteArray("112233445566778899");
assertBinaryDecoder(byte[].class, val, expected);
}
@Test
public void testCustomDecoder_Time() throws IllegalAccessException, InstantiationException, DecodeException
{
decoders.register(TimeDecoder.class);
String val = "12:34:56 GMT";
Date epoch = Date.from(Instant.EPOCH);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.setTime(epoch);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date expected = calendar.getTime();
assertTextDecoder(Date.class, val, expected);
}
@Test
public void testCustomDecoder_Date() throws IllegalAccessException, InstantiationException, DecodeException
{
decoders.register(DateDecoder.class);
String val = "2016.08.22";
Date epoch = Date.from(Instant.EPOCH);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.setTime(epoch);
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
Date expected = calendar.getTime();
assertTextDecoder(Date.class, val, expected);
}
@Test
public void testCustomDecoder_DateTime() throws IllegalAccessException, InstantiationException, DecodeException
{
decoders.register(DateTimeDecoder.class);
String val = "2016.08.22 AD at 12:34:56 GMT";
Date epoch = Date.from(Instant.EPOCH);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.setTime(epoch);
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date expected = calendar.getTime();
assertTextDecoder(Date.class, val, expected);
}
@Test
public void testCustomDecoder_ValidDual_Text() throws IllegalAccessException, InstantiationException, DecodeException
{
decoders.register(ValidDualDecoder.class);
String val = "[1,234,567]";
Integer expected = 1234567;
assertTextDecoder(Integer.class, val, expected);
}
@Test
public void testCustomDecoder_ValidDual_Binary() throws IllegalAccessException, InstantiationException, DecodeException
{
decoders.register(ValidDualDecoder.class);
ByteBuffer val = ByteBuffer.allocate(16);
val.put((byte) '[');
val.putLong(0x112233445566L);
val.put((byte) ']');
val.flip();
Long expected = 0x112233445566L;
assertBinaryDecoder(Long.class, val, expected);
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.decoders;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
@ -31,12 +32,16 @@ import javax.websocket.EndpointConfig;
*/
public class DateDecoder implements Decoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public Date decode(String s) throws DecodeException
{
try
{
return new SimpleDateFormat("yyyy.MM.dd").parse(s);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
dateFormat.setTimeZone(GMT);
return dateFormat.parse(s);
}
catch (ParseException e)
{

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.decoders;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
@ -31,16 +32,20 @@ import javax.websocket.EndpointConfig;
*/
public class DateTimeDecoder implements Decoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public Date decode(String s) throws DecodeException
{
try
{
return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").parse(s);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z");
dateFormat.setTimeZone(GMT);
return dateFormat.parse(s);
}
catch (ParseException e)
{
throw new DecodeException(s,e.getMessage(),e);
throw new DecodeException(s, e.getMessage(), e);
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.decoders;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
@ -31,16 +32,20 @@ import javax.websocket.EndpointConfig;
*/
public class TimeDecoder implements Decoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public Date decode(String s) throws DecodeException
{
try
{
return new SimpleDateFormat("HH:mm:ss z").parse(s);
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss z");
dateFormat.setTimeZone(GMT);
return dateFormat.parse(s);
}
catch (ParseException e)
{
throw new DecodeException(s,e.getMessage(),e);
throw new DecodeException(s, e.getMessage(), e);
}
}

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.websocket.jsr356.decoders;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
import java.text.ParseException;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
@ -32,13 +34,42 @@ public class ValidDualDecoder implements Decoder.Text<Integer>, Decoder.Binary<L
@Override
public Long decode(ByteBuffer bytes) throws DecodeException
{
return bytes.getLong();
if (bytes.get() != '[')
throw new DecodeException(bytes, "Unexpected opening byte");
long val = bytes.getLong();
if (bytes.get() != ']')
throw new DecodeException(bytes, "Unexpected closing byte");
return val;
}
@Override
public Integer decode(String s) throws DecodeException
{
return Integer.parseInt(s);
DecimalFormat numberFormat = new DecimalFormat("[#,###]");
try
{
Number number = numberFormat.parse(s);
if (number instanceof Long)
{
Long val = (Long) number;
if (val > Integer.MAX_VALUE)
{
throw new DecodeException(s, "Value exceeds Integer.MAX_VALUE");
}
return val.intValue();
}
if (number instanceof Integer)
{
return (Integer) number;
}
throw new DecodeException(s, "Unrecognized number format");
}
catch (ParseException e)
{
throw new DecodeException(s, "Unable to parse number", e);
}
}
@Override

View File

@ -0,0 +1,277 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.common.util.Hex;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.junit.BeforeClass;
import org.junit.Test;
public class AvailableEncodersTest
{
private static EndpointConfig testConfig;
@BeforeClass
public static void initConfig()
{
testConfig = new EmptyClientEndpointConfig();
}
private AvailableEncoders encoders = new AvailableEncoders();
public <T> void assertTextEncoder(Class<T> type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException
{
Class<? extends Encoder> encoderClass = encoders.getEncoderFor(type);
assertThat("Encoder Class", encoderClass, notNullValue());
Encoder.Text<T> encoder = (Encoder.Text<T>) encoderClass.newInstance();
encoder.init(testConfig);
String encoded = encoder.encode(value);
assertThat("Encoded", encoded, is(expectedEncoded));
}
public <T> void assertTextStreamEncoder(Class<T> type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
Class<? extends Encoder> encoderClass = encoders.getEncoderFor(type);
assertThat("Encoder Class", encoderClass, notNullValue());
Encoder.TextStream<T> encoder = (Encoder.TextStream<T>) encoderClass.newInstance();
encoder.init(testConfig);
StringWriter writer = new StringWriter();
encoder.encode(value, writer);
assertThat("Encoded", writer.toString(), is(expectedEncoded));
}
public <T> void assertBinaryEncoder(Class<T> type, T value, String expectedEncodedHex) throws IllegalAccessException, InstantiationException, EncodeException
{
AvailableEncoders encoders = new AvailableEncoders();
Class<? extends Encoder> encoderClass = encoders.getEncoderFor(type);
assertThat("Encoder Class", encoderClass, notNullValue());
Encoder.Binary<T> encoder = (Encoder.Binary<T>) encoderClass.newInstance();
encoder.init(testConfig);
ByteBuffer encoded = encoder.encode(value);
String hexEncoded = Hex.asHex(encoded);
assertThat("Encoded", hexEncoded, is(expectedEncodedHex));
}
public <T> void assertBinaryStreamEncoder(Class<T> type, T value, String expectedEncodedHex) throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
Class<? extends Encoder> encoderClass = encoders.getEncoderFor(type);
assertThat("Encoder Class", encoderClass, notNullValue());
Encoder.BinaryStream<T> encoder = (Encoder.BinaryStream<T>) encoderClass.newInstance();
encoder.init(testConfig);
ByteArrayOutputStream out = new ByteArrayOutputStream();
encoder.encode(value, out);
String hexEncoded = Hex.asHex(out.toByteArray());
assertThat("Encoded", hexEncoded, is(expectedEncodedHex));
}
@Test
public void testCoreEncoder_Boolean() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Boolean.class, Boolean.TRUE, "true");
}
@Test
public void testCoreEncoder_bool() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Boolean.TYPE, true, "true");
}
@Test
public void testCoreEncoder_Byte() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Byte.class, new Byte((byte) 0x21), "33");
}
@Test
public void testCoreEncoder_byte() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Byte.TYPE, (byte) 0x21, "33");
}
@Test
public void testCoreEncoder_Character() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Character.class, new Character('!'), "!");
}
@Test
public void testCoreEncoder_char() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Character.TYPE, '!', "!");
}
@Test
public void testCoreEncoder_Double() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Double.class, new Double(123.45), "123.45");
}
@Test
public void testCoreEncoder_double() throws IllegalAccessException, InstantiationException, EncodeException
{
//noinspection RedundantCast
assertTextEncoder(Double.TYPE, (double) 123.45, "123.45");
}
@Test
public void testCoreEncoder_Float() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Float.class, new Float(123.4567), "123.4567");
}
@Test
public void testCoreEncoder_float() throws IllegalAccessException, InstantiationException, EncodeException
{
//noinspection RedundantCast
assertTextEncoder(Float.TYPE, (float) 123.4567, "123.4567");
}
@Test
public void testCoreEncoder_Integer() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Integer.class, new Integer(123), "123");
}
@Test
public void testCoreEncoder_int() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Integer.TYPE, 123, "123");
}
@Test
public void testCoreEncoder_Long() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Long.class, new Long(123_456_789), "123456789");
}
@Test
public void testCoreEncoder_long() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(Long.TYPE, 123_456_789L, "123456789");
}
@Test
public void testCoreEncoder_String() throws IllegalAccessException, InstantiationException, EncodeException
{
assertTextEncoder(String.class, "Hello World", "Hello World");
}
@Test
public void testCoreEncoder_ByteBuffer() throws IllegalAccessException, InstantiationException, EncodeException
{
ByteBuffer buf = Hex.asByteBuffer("1122334455");
assertBinaryEncoder(ByteBuffer.class, buf, "1122334455");
}
@Test
public void testCoreEncoder_ByteArray() throws IllegalAccessException, InstantiationException, EncodeException
{
byte buf[] = Hex.asByteArray("998877665544332211");
assertBinaryEncoder(byte[].class, buf, "998877665544332211");
}
@Test
public void testCustomEncoder_Time() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(TimeEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "12:34:56 GMT");
}
@Test
public void testCustomEncoder_Date() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(DateEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "2016.08.22");
}
@Test
public void testCustomEncoder_DateTime() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(DateTimeEncoder.class);
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calendar.set(Calendar.YEAR, 2016);
calendar.set(Calendar.MONTH, Calendar.AUGUST);
calendar.set(Calendar.DAY_OF_MONTH, 22);
calendar.set(Calendar.HOUR_OF_DAY, 12);
calendar.set(Calendar.MINUTE, 34);
calendar.set(Calendar.SECOND, 56);
Date val = calendar.getTime();
assertTextEncoder(Date.class, val, "2016.08.22 AD at 12:34:56 GMT");
}
@Test
public void testCustomEncoder_ValidDual_Text() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(ValidDualEncoder.class);
assertTextEncoder(Integer.class, 1234567, "[1,234,567]");
}
@Test
public void testCustomEncoder_ValidDual_Binary() throws IllegalAccessException, InstantiationException, EncodeException, IOException
{
encoders.register(ValidDualEncoder.class);
long value = 0x112233445566L;
assertBinaryStreamEncoder(Long.class, value, "5B00001122334455665D");
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.encoders;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
@ -30,6 +31,8 @@ import javax.websocket.EndpointConfig;
*/
public class DateEncoder implements Encoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public void destroy()
{
@ -38,7 +41,9 @@ public class DateEncoder implements Encoder.Text<Date>
@Override
public String encode(Date object) throws EncodeException
{
return new SimpleDateFormat("yyyy.MM.dd").format(object);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
dateFormat.setTimeZone(GMT);
return dateFormat.format(object);
}
@Override

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.encoders;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
@ -30,6 +31,8 @@ import javax.websocket.EndpointConfig;
*/
public class DateTimeEncoder implements Encoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public void destroy()
{
@ -38,7 +41,9 @@ public class DateTimeEncoder implements Encoder.Text<Date>
@Override
public String encode(Date object) throws EncodeException
{
return new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z").format(object);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd G 'at' HH:mm:ss z");
dateFormat.setTimeZone(GMT);
return dateFormat.format(object);
}
@Override

View File

@ -1,58 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.io.IOException;
import java.io.Writer;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
/**
* Intentionally bad example of attempting to decode the same object to different message formats.
*/
public class DualEncoder implements Encoder.Text<Fruit>, Encoder.TextStream<Fruit>
{
@Override
public void destroy()
{
}
@Override
public String encode(Fruit fruit) throws EncodeException
{
return String.format("%s|%s",fruit.name,fruit.color);
}
@Override
public void encode(Fruit fruit, Writer writer) throws EncodeException, IOException
{
writer.write(fruit.name);
writer.write('|');
writer.write(fruit.color);
}
@Override
public void init(EndpointConfig config)
{
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.encoders;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
@ -30,6 +31,8 @@ import javax.websocket.EndpointConfig;
*/
public class TimeEncoder implements Encoder.Text<Date>
{
private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
@Override
public void destroy()
{
@ -38,7 +41,9 @@ public class TimeEncoder implements Encoder.Text<Date>
@Override
public String encode(Date object) throws EncodeException
{
return new SimpleDateFormat("HH:mm:ss z").format(object);
SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss z");
dateFormat.setTimeZone(GMT);
return dateFormat.format(object);
}
@Override

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@ -38,23 +39,17 @@ public class ValidDualEncoder implements Encoder.Text<Integer>, Encoder.BinarySt
@Override
public String encode(Integer object) throws EncodeException
{
return Integer.toString(object);
return String.format("[%,d]", object);
}
@Override
public void encode(Long object, OutputStream os) throws EncodeException, IOException
{
byte b[] = new byte[8];
long v = object;
b[0] = (byte)(v >>> 56);
b[1] = (byte)(v >>> 48);
b[2] = (byte)(v >>> 40);
b[3] = (byte)(v >>> 32);
b[4] = (byte)(v >>> 24);
b[5] = (byte)(v >>> 16);
b[6] = (byte)(v >>> 8);
b[7] = (byte)(v >>> 0);
os.write(b,0,8);
DataOutputStream data = new DataOutputStream(os);
data.writeByte((byte) '[');
data.writeLong(object);
data.writeByte((byte) ']');
data.flush();
}
@Override

View File

@ -0,0 +1,237 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import static org.hamcrest.Matchers.containsString;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class JsrEndpointFunctions_BadSignaturesTest
{
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String, String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
@Rule
public ExpectedException expectedException = ExpectedException.none();
private void assertBadSocket(TrackingSocket socket, String expectedString) throws Exception
{
JsrEndpointFunctions functions = new JsrEndpointFunctions(
socket,
container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
expectedException.expect(InvalidSignatureException.class);
expectedException.expectMessage(containsString(expectedString));
functions.start();
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public class InvalidOpenCloseReasonSocket extends TrackingSocket
{
/**
* Invalid Open Method Declaration (parameter type CloseReason)
* @param reason the close reason
*/
@OnOpen
public void onOpen(CloseReason reason)
{
/* no impl */
}
}
@Test
public void testInvalidOpenCloseReasonSocket() throws Exception
{
assertBadSocket(new InvalidOpenCloseReasonSocket(), "onOpen");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidOpenIntSocket extends TrackingSocket
{
/**
* Invalid Open Method Declaration (parameter type int)
* @param value the open value
*/
@OnOpen
public void onOpen(int value)
{
/* no impl */
}
}
@Test
public void testInvalidOpenIntSocket() throws Exception
{
assertBadSocket(new InvalidOpenIntSocket(), "onOpen");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidOpenSessionIntSocket extends TrackingSocket
{
/**
* Invalid Open Method Declaration (parameter of type int)
* @param session the session for the open
* @param count the open count
*/
@OnOpen
public void onOpen(Session session, int count)
{
/* no impl */
}
}
@Test
public void testInvalidOpenSessionIntSocket() throws Exception
{
assertBadSocket(new InvalidOpenSessionIntSocket(), "onOpen");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidCloseIntSocket extends TrackingSocket
{
/**
* Invalid Close Method Declaration (parameter type int)
*
* @param statusCode the status code
*/
@OnClose
public void onClose(int statusCode)
{
closeLatch.countDown();
}
}
@Test
public void testInvalidCloseIntSocket() throws Exception
{
assertBadSocket(new InvalidCloseIntSocket(), "onClose");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidErrorErrorSocket extends TrackingSocket
{
/**
* Invalid Error Method Declaration (parameter type Error)
*
* @param error the error
*/
@OnError
public void onError(Error error)
{
/* no impl */
}
}
@Test
public void testInvalidErrorErrorSocket() throws Exception
{
assertBadSocket(new InvalidErrorErrorSocket(), "onError");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidErrorExceptionSocket extends TrackingSocket
{
/**
* Invalid Error Method Declaration (parameter type Exception)
*
* @param e the extension
*/
@OnError
public void onError(Exception e)
{
/* no impl */
}
}
@Test
public void testInvalidErrorExceptionSocket() throws Exception
{
assertBadSocket(new InvalidErrorExceptionSocket(), "onError");
}
@SuppressWarnings("UnusedParameters")
@ClientEndpoint
public static class InvalidErrorIntSocket extends TrackingSocket
{
/**
* Invalid Error Method Declaration (parameter type int)
*
* @param errorCount the error count
*/
@OnError
public void onError(int errorCount)
{
/* no impl */
}
}
@Test
public void testInvalidErrorIntSocket() throws Exception
{
assertBadSocket(new InvalidErrorIntSocket(), "onError");
}
// TODO: invalid return types
// TODO: static methods
// TODO: private or protected methods
// TODO: abstract methods
}

View File

@ -0,0 +1,178 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Test;
public class JsrEndpointFunctions_OnCloseTest
{
private static final String EXPECTED_REASON = "CloseReason[1000,Normal]";
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String,String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnCloseTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void assertOnCloseInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws Exception
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
endpointFunctions.start();
// These invocations are the same for all tests
endpointFunctions.onOpen(newSession(socket));
CloseInfo closeInfo = new CloseInfo(StatusCode.NORMAL, "Normal");
endpointFunctions.onClose(closeInfo);
socket.assertEvent(String.format(expectedEventFormat, args));
}
@ClientEndpoint
public static class CloseSocket extends TrackingSocket
{
@OnClose
public void OnClose()
{
addEvent("OnClose()");
}
}
@Test
public void testInvokeClose() throws Exception
{
assertOnCloseInvocation(new CloseSocket(), "OnClose()");
}
@ClientEndpoint
public static class CloseSessionSocket extends TrackingSocket
{
@OnClose
public void OnClose(Session session)
{
addEvent("OnClose(%s)", session);
}
}
@Test
public void testInvokeCloseSession() throws Exception
{
assertOnCloseInvocation(new CloseSessionSocket(),
"OnClose(JsrSession[CLIENT,%s,DummyConnection])",
CloseSessionSocket.class.getName());
}
@ClientEndpoint
public static class CloseReasonSocket extends TrackingSocket
{
@OnClose
public void OnClose(CloseReason reason)
{
addEvent("OnClose(%s)", reason);
}
}
@Test
public void testInvokeCloseReason() throws Exception
{
assertOnCloseInvocation(new CloseReasonSocket(),
"OnClose(%s)", EXPECTED_REASON);
}
@ClientEndpoint
public static class CloseSessionReasonSocket extends TrackingSocket
{
@OnClose
public void OnClose(Session session, CloseReason reason)
{
addEvent("OnClose(%s, %s)", session, reason);
}
}
@Test
public void testInvokeCloseSessionReason() throws Exception
{
assertOnCloseInvocation(new CloseSessionReasonSocket(),
"OnClose(JsrSession[CLIENT,%s,DummyConnection], %s)",
CloseSessionReasonSocket.class.getName(), EXPECTED_REASON);
}
@ClientEndpoint
public static class CloseReasonSessionSocket extends TrackingSocket
{
@OnClose
public void OnClose(CloseReason reason, Session session)
{
addEvent("OnClose(%s, %s)", reason, session);
}
}
@Test
public void testInvokeCloseReasonSession() throws Exception
{
assertOnCloseInvocation(new CloseReasonSessionSocket(),
"OnClose(%s, JsrSession[CLIENT,%s,DummyConnection])",
EXPECTED_REASON, CloseReasonSessionSocket.class.getName());
}
}

View File

@ -0,0 +1,175 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnError;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Test;
public class JsrEndpointFunctions_OnErrorTest
{
private static final String EXPECTED_THROWABLE = "java.lang.RuntimeException: From Testcase";
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String, String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnErrorTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void assertOnErrorInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws Exception
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
endpointFunctions.start();
// These invocations are the same for all tests
endpointFunctions.onOpen(newSession(socket));
endpointFunctions.onError(new RuntimeException("From Testcase"));
socket.assertEvent(String.format(expectedEventFormat, args));
}
@ClientEndpoint
public static class ErrorSocket extends TrackingSocket
{
@OnError
public void onError()
{
addEvent("onError()");
}
}
@Test
public void testInvokeError() throws Exception
{
assertOnErrorInvocation(new ErrorSocket(), "onError()");
}
@ClientEndpoint
public static class ErrorSessionSocket extends TrackingSocket
{
@OnError
public void onError(Session session)
{
addEvent("onError(%s)", session);
}
}
@Test
public void testInvokeErrorSession() throws Exception
{
assertOnErrorInvocation(new ErrorSessionSocket(),
"onError(JsrSession[CLIENT,%s,DummyConnection])",
ErrorSessionSocket.class.getName());
}
@ClientEndpoint
public static class ErrorSessionThrowableSocket extends TrackingSocket
{
@OnError
public void onError(Session session, Throwable cause)
{
addEvent("onError(%s, %s)", session, cause);
}
}
@Test
public void testInvokeErrorSessionThrowable() throws Exception
{
assertOnErrorInvocation(new ErrorSessionThrowableSocket(),
"onError(JsrSession[CLIENT,%s,DummyConnection], %s)",
ErrorSessionThrowableSocket.class.getName(), EXPECTED_THROWABLE);
}
@ClientEndpoint
public static class ErrorThrowableSocket extends TrackingSocket
{
@OnError
public void onError(Throwable cause)
{
addEvent("onError(%s)", cause);
}
}
@Test
public void testInvokeErrorThrowable() throws Exception
{
assertOnErrorInvocation(new ErrorThrowableSocket(),
"onError(%s)", EXPECTED_THROWABLE);
}
@ClientEndpoint
public static class ErrorThrowableSessionSocket extends TrackingSocket
{
@OnError
public void onError(Throwable cause, Session session)
{
addEvent("onError(%s, %s)", cause, session);
}
}
@Test
public void testInvokeErrorThrowableSession() throws Exception
{
assertOnErrorInvocation(new ErrorThrowableSessionSocket(),
"onError(%s, JsrSession[CLIENT,%s,DummyConnection])",
EXPECTED_THROWABLE,
ErrorThrowableSessionSocket.class.getName());
}
}

View File

@ -0,0 +1,158 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Test;
public class JsrEndpointFunctions_OnMessage_BinaryTest
{
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String, String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
private String expectedBuffer;
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnMessage_BinaryTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void assertOnMessageInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws InvocationTargetException, IllegalAccessException
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
// This invocation is the same for all tests
ByteBuffer byteBuffer = ByteBuffer.wrap("Hello World".getBytes(StandardCharsets.UTF_8));
expectedBuffer = BufferUtil.toDetailString(byteBuffer);
endpointFunctions.onBinary(byteBuffer, true);
socket.assertEvent(String.format(expectedEventFormat, args));
}
public static class MessageSocket extends TrackingSocket
{
// TODO: Ambiguous declaration
@OnMessage
public void onMessage()
{
addEvent("onMessage()");
}
}
@Test
public void testInvokeMessage() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageSocket(), "onMessage()");
}
public static class MessageTextSocket extends TrackingSocket
{
@OnMessage
public void onMessage(String msg)
{
addEvent("onMessage(%s)", msg);
}
}
@Test
public void testInvokeMessageText() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageTextSocket(), "onMessage(Hello World)");
}
public static class MessageSessionSocket extends TrackingSocket
{
// TODO: Ambiguous declaration
@OnMessage
public void onMessage(Session session)
{
addEvent("onMessage(%s)", session);
}
}
@Test
public void testInvokeMessageSession() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageSessionSocket(),
"onMessage(JsrSession[CLIENT,%s,DummyConnection])",
MessageSessionSocket.class.getName());
}
public static class MessageSessionTextSocket extends TrackingSocket
{
@OnMessage
public void onMessage(Session session, String msg)
{
addEvent("onMessage(%s, %s)", session, msg);
}
}
@Test
public void testInvokeMessageSessionText() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageSessionTextSocket(),
"onMessage(JsrSession[CLIENT,%s,DummyConnection], Hello World)",
MessageSessionTextSocket.class.getName());
}
}

View File

@ -0,0 +1,177 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import static org.hamcrest.Matchers.containsString;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class JsrEndpointFunctions_OnMessage_TextTest
{
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String, String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
@Rule
public ExpectedException expectedException = ExpectedException.none();
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnMessage_TextTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void onText(TrackingSocket socket, String msg)
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
// This invocation is the same for all tests
ByteBuffer payload = BufferUtil.toBuffer(msg, StandardCharsets.UTF_8);
endpointFunctions.onText(payload, true);
}
private void assertOnMessageInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws InvocationTargetException, IllegalAccessException
{
onText(socket, "Hello World");
socket.assertEvent(String.format(expectedEventFormat, args));
}
public static class MessageSocket extends TrackingSocket
{
/**
* Invalid declaration - the type is ambiguous (is it TEXT / BINARY / PONG?)
*/
@OnMessage
public void onMessage()
{
addEvent("onMessage()");
}
}
@Test
public void testAmbiguousEmptyMessage() throws InvocationTargetException, IllegalAccessException
{
MessageSocket socket = new MessageSocket();
expectedException.expect(InvalidSignatureException.class);
expectedException.expectMessage(containsString("@OnMessage public void onMessage"));
onText(socket, "Hello World");
}
public static class MessageTextSocket extends TrackingSocket
{
@OnMessage
public void onMessage(String msg)
{
addEvent("onMessage(%s)", msg);
}
}
@Test
public void testInvokeMessageText() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageTextSocket(), "onMessage(Hello World)");
}
public static class MessageSessionSocket extends TrackingSocket
{
/**
* Invalid declaration - the type is ambiguous (is it TEXT / BINARY / PONG?)
*/
@OnMessage
public void onMessage(Session session)
{
addEvent("onMessage(%s)", session);
}
}
@Test
public void testAmbiguousMessageSession() throws InvocationTargetException, IllegalAccessException
{
MessageSessionSocket socket = new MessageSessionSocket();
expectedException.expect(InvalidSignatureException.class);
expectedException.expectMessage(containsString("@OnMessage public void onMessage"));
onText(socket, "Hello World");
}
public static class MessageSessionTextSocket extends TrackingSocket
{
@OnMessage
public void onMessage(Session session, String msg)
{
addEvent("onMessage(%s, %s)", session, msg);
}
}
@Test
public void testInvokeMessageSessionText() throws InvocationTargetException, IllegalAccessException
{
assertOnMessageInvocation(new MessageSessionTextSocket(),
"onMessage(JsrSession[CLIENT,%s,DummyConnection], Hello World)",
MessageSessionTextSocket.class.getName());
}
}

View File

@ -0,0 +1,120 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.function;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.EndpointConfig;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.test.DummyConnection;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.jsr356.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.jsr356.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.TrackingSocket;
import org.junit.BeforeClass;
import org.junit.Test;
public class JsrEndpointFunctions_OnOpenTest
{
private static ClientContainer container;
@BeforeClass
public static void initContainer()
{
container = new ClientContainer();
}
private AvailableEncoders encoders = new AvailableEncoders();
private AvailableDecoders decoders = new AvailableDecoders();
private Map<String,String> uriParams = new HashMap<>();
private EndpointConfig endpointConfig = new EmptyClientEndpointConfig();
public JsrSession newSession(Object websocket)
{
String id = JsrEndpointFunctions_OnOpenTest.class.getSimpleName();
URI requestURI = URI.create("ws://localhost/" + id);
WebSocketPolicy policy = WebSocketPolicy.newClientPolicy();
DummyConnection connection = new DummyConnection(policy);
ClientEndpointConfig config = new EmptyClientEndpointConfig();
ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket, config);
return new JsrSession(container, id, requestURI, ei, connection);
}
private void assertOnOpenInvocation(TrackingSocket socket, String expectedEventFormat, Object... args) throws Exception
{
JsrEndpointFunctions endpointFunctions = new JsrEndpointFunctions(
socket, container.getPolicy(),
container.getExecutor(),
encoders,
decoders,
uriParams,
endpointConfig
);
endpointFunctions.start();
// This invocation is the same for all tests
endpointFunctions.onOpen(newSession(socket));
socket.assertEvent(String.format(expectedEventFormat, args));
}
@ClientEndpoint
public static class OpenSocket extends TrackingSocket
{
@OnOpen
public void onOpen()
{
addEvent("onOpen()");
}
}
@Test
public void testInvokeOpen() throws Exception
{
assertOnOpenInvocation(new OpenSocket(), "onOpen()");
}
@ClientEndpoint
public static class OpenSessionSocket extends TrackingSocket
{
@OnOpen
public void onOpen(Session session)
{
addEvent("onOpen(%s)", session);
}
}
@Test
public void testInvokeOpenSession() throws Exception
{
assertOnOpenInvocation(new OpenSessionSocket(),
"onOpen(JsrSession[CLIENT,%s,DummyConnection])",
OpenSessionSocket.class.getName());
}
}

View File

@ -53,7 +53,7 @@ public class EncoderMetadataSetTest
// has duplicated support for the same target Type
coders.add(BadDualEncoder.class);
Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
Assert.fail("Should have thrown IllegalStateException for attempting to register AvailableEncoders with duplicate implementation");
}
catch (IllegalStateException e)
{
@ -73,7 +73,7 @@ public class EncoderMetadataSetTest
{
// Add TimeEncoder (which also wants to decode java.util.Date)
coders.add(TimeEncoder.class);
Assert.fail("Should have thrown IllegalStateException for attempting to register Encoders with duplicate implementation");
Assert.fail("Should have thrown IllegalStateException for attempting to register AvailableEncoders with duplicate implementation");
}
catch (IllegalStateException e)
{

View File

@ -71,7 +71,7 @@ public class AnnotatedServerEndpointConfig implements ServerEndpointConfig
this.decoders = Collections.unmodifiableList(Arrays.asList(anno.decoders()));
}
// Encoders (favor provided config over annotation)
// AvailableEncoders (favor provided config over annotation)
if (baseConfig != null && baseConfig.getEncoders() != null && baseConfig.getEncoders().size() > 0)
{
this.encoders = Collections.unmodifiableList(baseConfig.getEncoders());

View File

@ -22,12 +22,33 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
@SuppressWarnings("serial")
public class InvalidSignatureException extends InvalidWebSocketException
{
public static InvalidSignatureException build(Method method, Class<? extends Annotation> annoClass, DynamicArgs.Builder ... dynArgsBuilders)
public static InvalidSignatureException build(Class<?> pojo, Class<? extends Annotation> methodAnnotationClass, Method method)
{
StringBuilder err = new StringBuilder();
if (methodAnnotationClass != null)
{
err.append("@");
err.append(methodAnnotationClass.getSimpleName());
err.append(' ');
}
if (pojo != null)
{
ReflectUtils.append(err, method);
}
else
{
ReflectUtils.append(err, pojo, method);
}
return new InvalidSignatureException(err.toString());
}
public static InvalidSignatureException build(Method method, Class<? extends Annotation> annoClass, DynamicArgs.Builder... dynArgsBuilders)
{
// Build big detailed exception to help the developer
StringBuilder err = new StringBuilder();
@ -53,6 +74,6 @@ public class InvalidSignatureException extends InvalidWebSocketException
public InvalidSignatureException(String message, Throwable cause)
{
super(message,cause);
super(message, cause);
}
}

View File

@ -16,18 +16,12 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
package org.eclipse.jetty.websocket.common;
import java.nio.ByteBuffer;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultBinaryEncoder extends AbstractEncoder implements Encoder.Binary<ByteBuffer>
/**
* Interface tag for endpoints managed by other implementations (such as JSR356)
*/
public interface ManagedEndpoint
{
@Override
public ByteBuffer encode(ByteBuffer message) throws EncodeException
{
return message;
}
Object getRawEndpoint();
}

View File

@ -73,15 +73,16 @@ import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
import org.eclipse.jetty.websocket.common.frames.CloseFrame;
import org.eclipse.jetty.websocket.common.frames.ReadOnlyDelegatedFrame;
import org.eclipse.jetty.websocket.common.functions.OnByteArrayFunction;
import org.eclipse.jetty.websocket.common.functions.OnByteBufferFunction;
import org.eclipse.jetty.websocket.common.functions.OnCloseFunction;
import org.eclipse.jetty.websocket.common.functions.OnErrorFunction;
import org.eclipse.jetty.websocket.common.functions.OnFrameFunction;
import org.eclipse.jetty.websocket.common.functions.OnInputStreamFunction;
import org.eclipse.jetty.websocket.common.functions.OnOpenFunction;
import org.eclipse.jetty.websocket.common.functions.OnReaderFunction;
import org.eclipse.jetty.websocket.common.functions.OnTextFunction;
import org.eclipse.jetty.websocket.common.function.OnByteArrayFunction;
import org.eclipse.jetty.websocket.common.function.OnByteBufferFunction;
import org.eclipse.jetty.websocket.common.function.OnCloseFunction;
import org.eclipse.jetty.websocket.common.function.OnErrorFunction;
import org.eclipse.jetty.websocket.common.function.OnFrameFunction;
import org.eclipse.jetty.websocket.common.function.OnInputStreamFunction;
import org.eclipse.jetty.websocket.common.function.OnOpenFunction;
import org.eclipse.jetty.websocket.common.function.OnReaderFunction;
import org.eclipse.jetty.websocket.common.function.OnTextFunction;
import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection;
import org.eclipse.jetty.websocket.common.io.IOState;
import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
import org.eclipse.jetty.websocket.common.message.ByteArrayMessageSink;
@ -94,7 +95,7 @@ import org.eclipse.jetty.websocket.common.message.ReaderMessageSink;
import org.eclipse.jetty.websocket.common.message.StringMessageSink;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
import org.eclipse.jetty.websocket.common.scopes.WebSocketSessionScope;
import org.eclipse.jetty.websocket.common.util.DynamicArgsException;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgsException;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
@ManagedObject("A Jetty WebSocket Session")
@ -136,8 +137,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, Object endpoint, LogicalConnection connection)
{
Objects.requireNonNull(containerScope,"Container Scope cannot be null");
Objects.requireNonNull(requestURI,"Request URI cannot be null");
Objects.requireNonNull(containerScope, "Container Scope cannot be null");
Objects.requireNonNull(requestURI, "Request URI cannot be null");
this.classLoader = Thread.currentThread().getContextClassLoader();
this.containerScope = containerScope;
@ -160,13 +161,13 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (endpoint instanceof WebSocketConnectionListener)
{
WebSocketConnectionListener wslistener = (WebSocketConnectionListener)endpoint;
WebSocketConnectionListener wslistener = (WebSocketConnectionListener) endpoint;
onOpenFunction = (sess) -> {
wslistener.onWebSocketConnect(sess);
return null;
};
onCloseFunction = (closeinfo) -> {
wslistener.onWebSocketClose(closeinfo.getStatusCode(),closeinfo.getReason());
wslistener.onWebSocketClose(closeinfo.getStatusCode(), closeinfo.getReason());
return null;
};
onErrorFunction = (cause) -> {
@ -179,13 +180,13 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (endpoint instanceof WebSocketListener)
{
WebSocketListener wslistener = (WebSocketListener)endpoint;
onTextSink = new StringMessageSink(policy,(payload) -> {
WebSocketListener wslistener = (WebSocketListener) endpoint;
onTextSink = new StringMessageSink(policy, (payload) -> {
wslistener.onWebSocketText(payload);
return null;
});
onBinarySink = new ByteArrayMessageSink(policy,(payload) -> {
wslistener.onWebSocketBinary(payload,0,payload.length);
onBinarySink = new ByteArrayMessageSink(policy, (payload) -> {
wslistener.onWebSocketBinary(payload, 0, payload.length);
return null;
});
}
@ -194,7 +195,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (endpoint instanceof WebSocketPingPongListener)
{
WebSocketPingPongListener wslistener = (WebSocketPingPongListener)endpoint;
WebSocketPingPongListener wslistener = (WebSocketPingPongListener) endpoint;
onPongFunction = (pong) -> {
ByteBuffer payload = pong;
if (pong == null)
@ -215,21 +216,21 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (endpoint instanceof WebSocketPartialListener)
{
for(Method method: WebSocketPartialListener.class.getDeclaredMethods())
for (Method method : WebSocketPartialListener.class.getDeclaredMethods())
{
if(method.getName().equals("onWebSocketPartialText"))
if (method.getName().equals("onWebSocketPartialText"))
assertNotSet(onTextSink, "TEXT Message Handler", endpoint.getClass(), method);
else if(method.getName().equals("onWebSocketPartialBinary"))
else if (method.getName().equals("onWebSocketPartialBinary"))
assertNotSet(onBinarySink, "BINARY Message Handler", endpoint.getClass(), method);
}
WebSocketPartialListener wslistener = (WebSocketPartialListener)endpoint;
WebSocketPartialListener wslistener = (WebSocketPartialListener) endpoint;
onTextSink = new PartialTextMessageSink((partial) -> {
wslistener.onWebSocketPartialText(partial.getPayload(),partial.isFin());
wslistener.onWebSocketPartialText(partial.getPayload(), partial.isFin());
return null;
});
onBinarySink = new PartialBinaryMessageSink((partial) -> {
wslistener.onWebSocketPartialBinary(partial.getPayload(),partial.isFin());
wslistener.onWebSocketPartialBinary(partial.getPayload(), partial.isFin());
return null;
});
}
@ -238,7 +239,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
if (endpoint instanceof WebSocketFrameListener)
{
WebSocketFrameListener wslistener = (WebSocketFrameListener)endpoint;
WebSocketFrameListener wslistener = (WebSocketFrameListener) endpoint;
onFrameFunction = (frame) -> {
wslistener.onWebSocketFrame(new ReadOnlyDelegatedFrame(frame));
return null;
@ -261,35 +262,35 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
Method onmethod = null;
// OnWebSocketConnect [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketConnect.class);
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketConnect.class);
if (onmethod != null)
{
assertNotSet(onOpenFunction, "Open/Connect Handler", endpointClass, onmethod);
onOpenFunction = new OnOpenFunction(endpoint,onmethod);
onOpenFunction = new OnOpenFunction(endpoint, onmethod);
}
// OnWebSocketClose [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketClose.class);
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketClose.class);
if (onmethod != null)
{
assertNotSet(onCloseFunction, "Close Handler", endpointClass, onmethod);
onCloseFunction = new OnCloseFunction(this,endpoint,onmethod);
onCloseFunction = new OnCloseFunction(this, endpoint, onmethod);
}
// OnWebSocketError [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketError.class);
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketError.class);
if (onmethod != null)
{
assertNotSet(onErrorFunction, "Error Handler", endpointClass, onmethod);
onErrorFunction = new OnErrorFunction(this,endpoint,onmethod);
onErrorFunction = new OnErrorFunction(this, endpoint, onmethod);
}
// OnWebSocketFrame [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketFrame.class);
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketFrame.class);
if (onmethod != null)
{
assertNotSet(onFrameFunction, "Frame Handler", endpointClass, onmethod);
onFrameFunction = new OnFrameFunction(this,endpoint,onmethod);
onFrameFunction = new OnFrameFunction(this, endpoint, onmethod);
}
// OnWebSocketMessage [0..2]
Method onmessages[] = ReflectUtils.findAnnotatedMethods(endpointClass,OnWebSocketMessage.class);
Method onmessages[] = ReflectUtils.findAnnotatedMethods(endpointClass, OnWebSocketMessage.class);
if (onmessages != null && onmessages.length > 0)
{
for (Method onmsg : onmessages)
@ -298,36 +299,36 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{
assertNotSet(onTextSink, "TEXT Message Handler", endpointClass, onmsg);
// Normal Text Message
onTextSink = new StringMessageSink(policy,new OnTextFunction(this,endpoint,onmsg));
onTextSink = new StringMessageSink(policy, new OnTextFunction(this, endpoint, onmsg));
}
else if (OnByteBufferFunction.hasMatchingSignature(onmsg))
{
assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg);
// ByteBuffer Binary Message
onBinarySink = new ByteBufferMessageSink(policy,new OnByteBufferFunction(this,endpoint,onmsg));
onBinarySink = new ByteBufferMessageSink(policy, new OnByteBufferFunction(this, endpoint, onmsg));
}
else if (OnByteArrayFunction.hasMatchingSignature(onmsg))
{
assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg);
// byte[] Binary Message
onBinarySink = new ByteArrayMessageSink(policy,new OnByteArrayFunction(this,endpoint,onmsg));
onBinarySink = new ByteArrayMessageSink(policy, new OnByteArrayFunction(this, endpoint, onmsg));
}
else if (OnInputStreamFunction.hasMatchingSignature(onmsg))
{
assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg);
// InputStream Binary Message
onBinarySink = new InputStreamMessageSink(executor,new OnInputStreamFunction(this,endpoint,onmsg));
onBinarySink = new InputStreamMessageSink(executor, new OnInputStreamFunction(this, endpoint, onmsg));
}
else if (OnReaderFunction.hasMatchingSignature(onmsg))
{
assertNotSet(onTextSink, "TEXT Message Handler", endpointClass, onmsg);
// Reader Text Message
onTextSink = new ReaderMessageSink(executor,new OnReaderFunction(this,endpoint,onmsg));
onTextSink = new ReaderMessageSink(executor, new OnReaderFunction(this, endpoint, onmsg));
}
else
{
// Not a valid @OnWebSocketMessage declaration signature
throw InvalidSignatureException.build(onmsg,OnWebSocketMessage.class,
throw InvalidSignatureException.build(onmsg, OnWebSocketMessage.class,
OnTextFunction.getDynamicArgsBuilder(),
OnByteBufferFunction.getDynamicArgsBuilder(),
OnByteArrayFunction.getDynamicArgsBuilder(),
@ -341,14 +342,14 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
protected void assertNotSet(Object val, String role, Class<?> pojo, Method method)
{
if(val == null)
if (val == null)
return;
StringBuilder err = new StringBuilder();
err.append("Cannot replace previously assigned ");
err.append(role);
err.append(" with ");
ReflectUtils.append(err,pojo,method);
ReflectUtils.append(err, pojo, method);
throw new InvalidWebSocketException(err.toString());
}
@ -357,19 +358,19 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
public void close()
{
/* This is assumed to always be a NORMAL closure, no reason phrase */
connection.close(StatusCode.NORMAL,null);
connection.close(StatusCode.NORMAL, null);
}
@Override
public void close(CloseStatus closeStatus)
{
close(closeStatus.getCode(),closeStatus.getPhrase());
this.close(closeStatus.getCode(), closeStatus.getPhrase());
}
@Override
public void close(int statusCode, String reason)
{
connection.close(statusCode,reason);
connection.close(statusCode, reason);
}
/**
@ -381,7 +382,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
connection.disconnect();
// notify of harsh disconnect
notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect");
notifyClose(StatusCode.NO_CLOSE, "Harsh disconnect");
}
public void dispatch(Runnable runnable)
@ -393,7 +394,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
protected void doStart() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("starting - {}",this);
LOG.debug("starting - {}", this);
Iterator<RemoteEndpointFactory> iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator();
if (iter.hasNext())
@ -412,15 +413,18 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
protected void doStop() throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("stopping - {}",this);
LOG.debug("stopping - {}", this);
if (getConnection() != null)
{
close(StatusCode.SHUTDOWN,"Shutdown");
}
catch (Throwable t)
{
LOG.debug("During Connection Shutdown",t);
try
{
getConnection().close(StatusCode.SHUTDOWN, "Shutdown");
}
catch (Throwable t)
{
LOG.debug("During Connection Shutdown", t);
}
}
super.doStop();
}
@ -433,7 +437,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
out.append(indent).append(" +- outgoingHandler : ");
if (outgoingHandler instanceof Dumpable)
{
((Dumpable)outgoingHandler).dump(out,indent + " ");
((Dumpable) outgoingHandler).dump(out, indent + " ");
}
else
{
@ -456,7 +460,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{
return false;
}
WebSocketSession other = (WebSocketSession)obj;
WebSocketSession other = (WebSocketSession) obj;
if (connection == null)
{
if (other.connection != null)
@ -555,7 +559,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
public RemoteEndpoint getRemote()
{
if (LOG_OPEN.isDebugEnabled())
LOG_OPEN.debug("[{}] {}.getRemote()",policy.getBehavior(),this.getClass().getSimpleName());
LOG_OPEN.debug("[{}] {}.getRemote()", policy.getBehavior(), this.getClass().getSimpleName());
ConnectionState state = connection.getIOState().getConnectionState();
if ((state == ConnectionState.OPEN) || (state == ConnectionState.CONNECTED))
@ -636,8 +640,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
case OpCode.CLOSE:
{
boolean validate = true;
CloseFrame closeframe = (CloseFrame)frame;
CloseInfo close = new CloseInfo(closeframe,validate);
CloseFrame closeframe = (CloseFrame) frame;
CloseInfo close = new CloseInfo(closeframe, validate);
// process handshake
getConnection().getIOState().onCloseRemote(close);
@ -647,14 +651,14 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
case OpCode.PING:
{
if (LOG.isDebugEnabled())
LOG.debug("PING: {}",BufferUtil.toDetailString(frame.getPayload()));
LOG.debug("PING: {}", BufferUtil.toDetailString(frame.getPayload()));
ByteBuffer pongBuf;
if (frame.hasPayload())
{
pongBuf = ByteBuffer.allocate(frame.getPayload().remaining());
BufferUtil.put(frame.getPayload().slice(),pongBuf);
BufferUtil.flipToFlush(pongBuf,0);
BufferUtil.put(frame.getPayload().slice(), pongBuf);
BufferUtil.flipToFlush(pongBuf, 0);
}
else
{
@ -670,7 +674,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
case OpCode.PONG:
{
if (LOG.isDebugEnabled())
LOG.debug("PONG: {}",BufferUtil.toDetailString(frame.getPayload()));
LOG.debug("PONG: {}", BufferUtil.toDetailString(frame.getPayload()));
if (onPongFunction != null)
onPongFunction.apply(frame.getPayload());
@ -682,7 +686,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
activeMessageSink = onBinarySink;
if (activeMessageSink != null)
activeMessageSink.accept(frame.getPayload(),frame.isFin());
activeMessageSink.accept(frame.getPayload(), frame.isFin());
return;
}
case OpCode.TEXT:
@ -691,20 +695,20 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
activeMessageSink = onTextSink;
if (activeMessageSink != null)
activeMessageSink.accept(frame.getPayload(),frame.isFin());
activeMessageSink.accept(frame.getPayload(), frame.isFin());
return;
}
case OpCode.CONTINUATION:
{
if (activeMessageSink != null)
activeMessageSink.accept(frame.getPayload(),frame.isFin());
activeMessageSink.accept(frame.getPayload(), frame.isFin());
return;
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("Unhandled OpCode: {}",opcode);
LOG.debug("Unhandled OpCode: {}", opcode);
}
}
}
@ -717,17 +721,17 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
catch (NotUtf8Exception e)
{
notifyError(e);
close(StatusCode.BAD_PAYLOAD,e.getMessage());
close(StatusCode.BAD_PAYLOAD, e.getMessage());
}
catch (CloseException e)
{
close(e.getStatusCode(),e.getMessage());
close(e.getStatusCode(), e.getMessage());
}
catch (Throwable t)
{
Throwable cause = getInvokedCause(t);
LOG.warn("Unhandled Error (closing connection)",cause);
LOG.warn("Unhandled Error (closing connection)", cause);
notifyError(cause);
@ -735,10 +739,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
switch (policy.getBehavior())
{
case SERVER:
close(StatusCode.SERVER_ERROR,cause.getClass().getSimpleName());
close(StatusCode.SERVER_ERROR, cause.getClass().getSimpleName());
break;
case CLIENT:
close(StatusCode.POLICY_VIOLATION,cause.getClass().getSimpleName());
close(StatusCode.POLICY_VIOLATION, cause.getClass().getSimpleName());
break;
}
}
@ -779,10 +783,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{
if (LOG.isDebugEnabled())
{
LOG.debug("notifyClose({},{})",statusCode,reason);
LOG.debug("notifyClose({},{})", statusCode, reason);
}
if (onCloseFunction != null)
onCloseFunction.apply(new CloseInfo(statusCode,reason));
onCloseFunction.apply(new CloseInfo(statusCode, reason));
}
public void notifyError(Throwable cause)
@ -799,7 +803,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
public void onOpened(Connection connection)
{
if (LOG_OPEN.isDebugEnabled())
LOG_OPEN.debug("[{}] {}.onOpened()",policy.getBehavior(),this.getClass().getSimpleName());
LOG_OPEN.debug("[{}] {}.onOpened()", policy.getBehavior(), this.getClass().getSimpleName());
open();
}
@ -813,11 +817,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
IOState ioState = this.connection.getIOState();
CloseInfo close = ioState.getCloseInfo();
// confirmed close of local endpoint
notifyClose(close.getStatusCode(),close.getReason());
notifyClose(close.getStatusCode(), close.getReason());
try
{
if (LOG.isDebugEnabled())
LOG.debug("{}.onSessionClosed()",containerScope.getClass().getSimpleName());
LOG.debug("{}.onSessionClosed()", containerScope.getClass().getSimpleName());
containerScope.onSessionClosed(this);
}
catch (Throwable t)
@ -830,7 +834,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
try
{
if (LOG.isDebugEnabled())
LOG.debug("{}.onSessionOpened()",containerScope.getClass().getSimpleName());
LOG.debug("{}.onSessionOpened()", containerScope.getClass().getSimpleName());
containerScope.onSessionOpened(this);
}
catch (Throwable t)
@ -896,7 +900,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
{
statusCode = StatusCode.POLICY_VIOLATION;
}
close(statusCode,cause.getMessage());
close(statusCode, cause.getMessage());
}
}
@ -936,11 +940,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
List<String> values = entry.getValue();
if (values != null)
{
this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()]));
this.parameterMap.put(entry.getKey(), values.toArray(new String[values.size()]));
}
else
{
this.parameterMap.put(entry.getKey(),new String[0]);
this.parameterMap.put(entry.getKey(), new String[0]);
}
}
}
@ -968,15 +972,32 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("WebSocketSession[");
builder.append("websocket=").append(endpoint.getClass().getName());
builder.append(",behavior=").append(policy.getBehavior());
builder.append(",connection=").append(connection);
builder.append(",remote=").append(remote);
builder.append(",outgoing=").append(outgoingHandler);
builder.append("]");
return builder.toString();
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName());
sb.append('[');
sb.append(getPolicy().getBehavior());
Object endp = endpoint;
// unwrap
while (endp instanceof ManagedEndpoint)
{
endp = ((ManagedEndpoint) endp).getRawEndpoint();
}
sb.append(',').append(endp.getClass().getName());
sb.append(',').append(getConnection().getClass().getSimpleName());
if (getConnection() instanceof AbstractWebSocketConnection)
{
if(getConnection().getIOState().isOpen() && remote != null)
{
sb.append(',').append(getRemoteAddress());
if (getPolicy().getBehavior() == WebSocketBehavior.SERVER)
{
sb.append(',').append(getRequestURI());
sb.append(',').append(getLocalAddress());
}
}
}
sb.append(']');
return sb.toString();
}
public static interface Listener

View File

@ -16,22 +16,29 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
package org.eclipse.jetty.websocket.common.function;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.function.Predicate;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import org.eclipse.jetty.util.BufferUtil;
public class DefaultBinaryStreamEncoder extends AbstractEncoder implements Encoder.BinaryStream<ByteBuffer>
public class AndPredicates<T> implements Predicate<T>
{
@Override
public void encode(ByteBuffer message, OutputStream out) throws EncodeException, IOException
private final Predicate<T> predicates[];
public AndPredicates(Predicate<T>... predicates)
{
BufferUtil.writeTo(message,out);
this.predicates = predicates;
}
@Override
public boolean test(T t)
{
for (Predicate<T> predicate : predicates)
{
if (!predicate.test(t))
{
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,58 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.function;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
public class AnnotationPredicate implements Predicate<Method>
{
private static final Map<Class<? extends Annotation>, Predicate<Method>>
CACHE = new ConcurrentHashMap<>();
/**
* Get Predicate from Cache (add if not present)
*/
public static Predicate<Method> get(Class<? extends Annotation> annotation)
{
Predicate<Method> predicate = CACHE.get(annotation);
if (predicate == null)
{
predicate = new AnnotationPredicate(annotation);
CACHE.put(annotation, predicate);
}
return predicate;
}
private final Class<? extends Annotation> annotation;
public AnnotationPredicate(Class<? extends Annotation> annotation)
{
this.annotation = annotation;
}
@Override
public boolean test(Method method)
{
return (method.getAnnotation(annotation) != null);
}
}

View File

@ -0,0 +1,495 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Function;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.BatchMode;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketConnectionListener;
import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPartialListener;
import org.eclipse.jetty.websocket.api.WebSocketPingPongListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.ManagedEndpoint;
import org.eclipse.jetty.websocket.common.frames.ReadOnlyDelegatedFrame;
import org.eclipse.jetty.websocket.common.message.ByteArrayMessageSink;
import org.eclipse.jetty.websocket.common.message.ByteBufferMessageSink;
import org.eclipse.jetty.websocket.common.message.InputStreamMessageSink;
import org.eclipse.jetty.websocket.common.message.MessageSink;
import org.eclipse.jetty.websocket.common.message.PartialBinaryMessageSink;
import org.eclipse.jetty.websocket.common.message.PartialTextMessageSink;
import org.eclipse.jetty.websocket.common.message.ReaderMessageSink;
import org.eclipse.jetty.websocket.common.message.StringMessageSink;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
* The Common Implementation of EndpointFunctions
*
* @param <T> the Session object
*/
public class CommonEndpointFunctions<T extends Session> extends AbstractLifeCycle implements EndpointFunctions<T>
{
private static final Logger LOG = Log.getLogger(CommonEndpointFunctions.class);
protected final Object endpoint;
protected final WebSocketPolicy policy;
protected final Executor executor;
private T session;
private Function<T, Void> onOpenFunction;
private Function<CloseInfo, Void> onCloseFunction;
private Function<Throwable, Void> onErrorFunction;
private Function<Frame, Void> onFrameFunction;
private Function<ByteBuffer, Void> onPingFunction;
private Function<ByteBuffer, Void> onPongFunction;
private MessageSink onTextSink;
private MessageSink onBinarySink;
private BatchMode batchMode;
public CommonEndpointFunctions(Object endpoint, WebSocketPolicy policy, Executor executor)
{
Object e = endpoint;
// unwrap endpoint
while (e instanceof ManagedEndpoint)
e = ((ManagedEndpoint) e).getRawEndpoint();
Objects.requireNonNull(endpoint, "Endpoint cannot be null");
Objects.requireNonNull(policy, "WebSocketPolicy cannot be null");
Objects.requireNonNull(executor, "Executor cannot be null");
this.endpoint = e;
this.policy = policy;
this.executor = executor;
}
@Override
protected void doStart() throws Exception
{
super.doStart();
discoverEndpointFunctions(this.endpoint);
}
protected void discoverEndpointFunctions(Object endpoint)
{
boolean supportAnnotations = true;
// Connection Listener
if (endpoint instanceof WebSocketConnectionListener)
{
WebSocketConnectionListener listener = (WebSocketConnectionListener) endpoint;
setOnOpen((session) -> {
listener.onWebSocketConnect(session);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketConnect", Session.class)
);
setOnClose((close) -> {
listener.onWebSocketClose(close.getStatusCode(), close.getReason());
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketClose", int.class, String.class)
);
setOnError((cause) -> {
listener.onWebSocketError(cause);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketError", Throwable.class));
supportAnnotations = false;
}
// Simple Data Listener
if (endpoint instanceof WebSocketListener)
{
WebSocketListener listener = (WebSocketListener) endpoint;
setOnText(new StringMessageSink(policy, (payload) -> {
listener.onWebSocketText(payload);
return null;
}),
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketText", String.class));
setOnBinary(new ByteArrayMessageSink(policy, (payload) -> {
listener.onWebSocketBinary(payload, 0, payload.length);
return null;
}),
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketBinary", byte[].class, int.class, int.class));
supportAnnotations = false;
}
// Ping/Pong Listener
if (endpoint instanceof WebSocketPingPongListener)
{
WebSocketPingPongListener listener = (WebSocketPingPongListener) endpoint;
setOnPong((pong) -> {
ByteBuffer payload = pong;
if (pong == null)
payload = BufferUtil.EMPTY_BUFFER;
listener.onWebSocketPong(payload);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketPong", ByteBuffer.class));
setOnPing((ping) -> {
ByteBuffer payload = ping;
if (ping == null)
payload = BufferUtil.EMPTY_BUFFER;
listener.onWebSocketPing(payload);
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketPing", ByteBuffer.class));
supportAnnotations = false;
}
// Partial Data / Message Listener
if (endpoint instanceof WebSocketPartialListener)
{
WebSocketPartialListener listener = (WebSocketPartialListener) endpoint;
setOnText(new PartialTextMessageSink((partial) -> {
listener.onWebSocketPartialText(partial.getPayload(), partial.isFin());
return null;
}),
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketPartialText", String.class, boolean.class));
setOnBinary(new PartialBinaryMessageSink((partial) -> {
listener.onWebSocketPartialBinary(partial.getPayload(), partial.isFin());
return null;
}),
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketPartialBinary", ByteBuffer.class, boolean.class));
supportAnnotations = false;
}
// Frame Listener
if (endpoint instanceof WebSocketFrameListener)
{
WebSocketFrameListener listener = (WebSocketFrameListener) endpoint;
setOnFrame((frame) -> {
listener.onWebSocketFrame(new ReadOnlyDelegatedFrame(frame));
return null;
},
ReflectUtils.findMethod(endpoint.getClass(), "onWebSocketFrame", Frame.class));
supportAnnotations = false;
}
if (supportAnnotations)
discoverAnnotatedEndpointFunctions(endpoint);
}
protected void discoverAnnotatedEndpointFunctions(Object endpoint)
{
// Test for annotated websocket endpoint
Class<?> endpointClass = endpoint.getClass();
WebSocket websocket = endpointClass.getAnnotation(WebSocket.class);
if (websocket != null)
{
policy.setInputBufferSize(websocket.inputBufferSize());
policy.setMaxBinaryMessageSize(websocket.maxBinaryMessageSize());
policy.setMaxTextMessageSize(websocket.maxTextMessageSize());
policy.setIdleTimeout(websocket.maxIdleTime());
this.batchMode = websocket.batchMode();
Method onmethod = null;
// OnWebSocketConnect [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketConnect.class);
if (onmethod != null)
{
setOnOpen(new OnOpenFunction(endpoint, onmethod), onmethod);
}
// OnWebSocketClose [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketClose.class);
if (onmethod != null)
{
setOnClose(new OnCloseFunction(session, endpoint, onmethod), onmethod);
}
// OnWebSocketError [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketError.class);
if (onmethod != null)
{
setOnError(new OnErrorFunction(session, endpoint, onmethod), onmethod);
}
// OnWebSocketFrame [0..1]
onmethod = ReflectUtils.findAnnotatedMethod(endpointClass, OnWebSocketFrame.class);
if (onmethod != null)
{
setOnFrame(new OnFrameFunction(session, endpoint, onmethod), onmethod);
}
// OnWebSocketMessage [0..2]
Method onmessages[] = ReflectUtils.findAnnotatedMethods(endpointClass, OnWebSocketMessage.class);
if (onmessages != null && onmessages.length > 0)
{
for (Method onmsg : onmessages)
{
if (OnTextFunction.hasMatchingSignature(onmsg))
{
// Normal Text Message
setOnText(new StringMessageSink(policy, new OnTextFunction(session, endpoint, onmsg)), onmsg);
}
else if (OnByteBufferFunction.hasMatchingSignature(onmsg))
{
// ByteBuffer Binary Message
setOnBinary(new ByteBufferMessageSink(policy, new OnByteBufferFunction(session, endpoint, onmsg)), onmsg);
}
else if (OnByteArrayFunction.hasMatchingSignature(onmsg))
{
// byte[] Binary Message
setOnBinary(new ByteArrayMessageSink(policy, new OnByteArrayFunction(session, endpoint, onmsg)), onmsg);
}
else if (OnInputStreamFunction.hasMatchingSignature(onmsg))
{
// InputStream Binary Message
setOnBinary(new InputStreamMessageSink(executor, new OnInputStreamFunction(session, endpoint, onmsg)), onmsg);
}
else if (OnReaderFunction.hasMatchingSignature(onmsg))
{
// Reader Text Message
setOnText(new ReaderMessageSink(executor, new OnReaderFunction(session, endpoint, onmsg)), onmsg);
}
else
{
// Not a valid @OnWebSocketMessage declaration signature
throw InvalidSignatureException.build(onmsg, OnWebSocketMessage.class,
OnTextFunction.getDynamicArgsBuilder(),
OnByteBufferFunction.getDynamicArgsBuilder(),
OnByteArrayFunction.getDynamicArgsBuilder(),
OnInputStreamFunction.getDynamicArgsBuilder(),
OnReaderFunction.getDynamicArgsBuilder());
}
}
}
}
}
public BatchMode getBatchMode()
{
return batchMode;
}
public T getSession()
{
return session;
}
public void setOnOpen(Function<T, Void> function, Object origin)
{
assertNotSet(this.onOpenFunction, "Open Handler", origin);
this.onOpenFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onOpen to " + describeOrigin(origin));
}
}
public void setOnClose(Function<CloseInfo, Void> function, Object origin)
{
assertNotSet(this.onCloseFunction, "Close Handler", origin);
this.onCloseFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onClose to " + describeOrigin(origin));
}
}
public void setOnError(Function<Throwable, Void> function, Object origin)
{
assertNotSet(this.onErrorFunction, "Error Handler", origin);
this.onErrorFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onError to " + describeOrigin(origin));
}
}
public void setOnText(MessageSink messageSink, Object origin)
{
assertNotSet(this.onTextSink, "TEXT Handler", origin);
this.onTextSink = messageSink;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onText to " + describeOrigin(origin));
}
}
public void setOnBinary(MessageSink messageSink, Object origin)
{
assertNotSet(this.onBinarySink, "BINARY Handler", origin);
this.onBinarySink = messageSink;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onBinary to " + describeOrigin(origin));
}
}
public void setOnFrame(Function<Frame, Void> function, Object origin)
{
assertNotSet(this.onFrameFunction, "Frame Handler", origin);
this.onFrameFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onFrame to " + describeOrigin(origin));
}
}
public void setOnPing(Function<ByteBuffer, Void> function, Object origin)
{
assertNotSet(this.onPingFunction, "Ping Handler", origin);
this.onPingFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onPing to " + describeOrigin(origin));
}
}
public void setOnPong(Function<ByteBuffer, Void> function, Object origin)
{
assertNotSet(this.onPongFunction, "Pong Handler", origin);
this.onPongFunction = function;
if (LOG.isDebugEnabled())
{
LOG.debug("Assigned onPong to " + describeOrigin(origin));
}
}
private String describeOrigin(Object obj)
{
if (obj == null)
{
return "<undefined>";
}
return obj.toString();
}
protected void assertNotSet(Object val, String role, Object origin)
{
if (val == null)
return;
StringBuilder err = new StringBuilder();
err.append("Cannot replace previously assigned ");
err.append(role);
err.append(" with ");
err.append(describeOrigin(origin));
throw new InvalidWebSocketException(err.toString());
}
@Override
public void onOpen(T session)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
this.session = session;
if (onOpenFunction != null)
onOpenFunction.apply(this.session);
}
@Override
public void onClose(CloseInfo close)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onCloseFunction != null)
onCloseFunction.apply(close);
}
@Override
public void onFrame(Frame frame)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onFrameFunction != null)
onFrameFunction.apply(frame);
}
@Override
public void onError(Throwable cause)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onErrorFunction != null)
onErrorFunction.apply(cause);
else
LOG.debug(cause);
}
@Override
public void onText(ByteBuffer payload, boolean fin)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onTextSink != null)
onTextSink.accept(payload, fin);
}
@Override
public void onBinary(ByteBuffer payload, boolean fin)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onBinarySink != null)
onBinarySink.accept(payload, fin);
}
@Override
public void onPing(ByteBuffer payload)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onPingFunction != null)
onPingFunction.apply(payload);
}
@Override
public void onPong(ByteBuffer payload)
{
if (!isStarted())
throw new IllegalStateException(this.getClass().getName() + " not started");
if (onPongFunction != null)
onPongFunction.apply(payload);
}
}

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.function;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
/**
* The interface a WebSocket Connection and Session has to the User provided Endpoint.
*
* @param <T> the Session object
*/
public interface EndpointFunctions<T>
{
void onOpen(T session);
void onClose(CloseInfo close);
void onFrame(Frame frame);
void onError(Throwable cause);
void onText(ByteBuffer payload, boolean fin);
void onBinary(ByteBuffer payload, boolean fin);
void onPing(ByteBuffer payload);
void onPong(ByteBuffer payload);
}

View File

@ -16,20 +16,18 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -87,14 +85,7 @@ public class OnByteArrayFunction implements Function<byte[], Void>
@Override
public Void apply(byte[] bin)
{
try
{
this.callable.invoke(endpoint, this.session, bin, 0, bin.length);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, this.session, bin, 0, bin.length);
return null;
}
}

View File

@ -16,9 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.function.Function;
@ -26,11 +25,10 @@ import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -84,14 +82,7 @@ public class OnByteBufferFunction implements Function<ByteBuffer, Void>
@Override
public Void apply(ByteBuffer bin)
{
try
{
this.callable.invoke(endpoint, session, bin);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, bin);
return null;
}
}

View File

@ -16,9 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
@ -26,11 +25,10 @@ import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -77,14 +75,7 @@ public class OnCloseFunction implements Function<CloseInfo, Void>
@Override
public Void apply(CloseInfo closeinfo)
{
try
{
this.callable.invoke(endpoint, session, closeinfo.getStatusCode(), closeinfo.getReason());
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, closeinfo.getStatusCode(), closeinfo.getReason());
return null;
}
}

View File

@ -16,20 +16,18 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -73,14 +71,7 @@ public class OnErrorFunction implements Function<Throwable, Void>
@Override
public Void apply(Throwable cause)
{
try
{
this.callable.invoke(endpoint, session, cause);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, cause);
return null;
}
}

View File

@ -16,9 +16,8 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
@ -26,12 +25,11 @@ import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -76,14 +74,7 @@ public class OnFrameFunction implements Function<Frame, Void>
public Void apply(Frame frame)
{
WebSocketFrame copy = WebSocketFrame.copy(frame);
try
{
this.callable.invoke(endpoint, session, copy);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call frame method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, copy);
return null;
}
}

View File

@ -16,21 +16,19 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -85,14 +83,7 @@ public class OnInputStreamFunction implements Function<InputStream, Void>
@Override
public Void apply(InputStream stream)
{
try
{
this.callable.invoke(endpoint, session, stream);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, stream);
return null;
}
}

View File

@ -16,26 +16,24 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
* Jetty {@link WebSocket} {@link OnWebSocketConnect} method {@link Function}
*/
public class OnOpenFunction implements Function<Session, Void>
public class OnOpenFunction<T extends Session> implements Function<T, Void>
{
private static final DynamicArgs.Builder ARGBUILDER;
private static final Arg ARG_SESSION = new Arg(1, Session.class);
@ -70,14 +68,7 @@ public class OnOpenFunction implements Function<Session, Void>
@Override
public Void apply(Session session)
{
try
{
this.callable.invoke(endpoint, session);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session);
return null;
}
}

View File

@ -16,21 +16,19 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -84,14 +82,7 @@ public class OnReaderFunction implements Function<Reader, Void>
@Override
public Void apply(Reader stream)
{
try
{
this.callable.invoke(endpoint, session, stream);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, stream);
return null;
}
}

View File

@ -16,20 +16,18 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.functions;
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.FunctionCallException;
import org.eclipse.jetty.websocket.common.InvalidSignatureException;
import org.eclipse.jetty.websocket.common.util.DynamicArgs;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.ExactSignature;
import org.eclipse.jetty.websocket.common.reflect.Arg;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs;
import org.eclipse.jetty.websocket.common.reflect.ExactSignature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
/**
@ -83,14 +81,7 @@ public class OnTextFunction implements Function<String, Void>
@Override
public Void apply(String text)
{
try
{
this.callable.invoke(endpoint, session, text);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new FunctionCallException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(), method), e);
}
this.callable.invoke(endpoint, session, text);
return null;
}
}

View File

@ -0,0 +1,44 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.function;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Predicate;
public class PublicNonStaticPredicate implements Predicate<Method>
{
public static final Predicate<Method> INSTANCE = new PublicNonStaticPredicate();
@Override
public boolean test(Method method)
{
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
{
return false;
}
if (Modifier.isStatic(mods))
{
return false;
}
return true;
}
}

View File

@ -16,20 +16,25 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
package org.eclipse.jetty.websocket.common.function;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultTextStreamEncoder extends AbstractEncoder implements Encoder.TextStream<String>
public class ReturnTypePredicate implements Predicate<Method>
{
@Override
public void encode(String message, Writer writer) throws EncodeException, IOException
public static final Predicate<Method> VOID = new ReturnTypePredicate(Void.TYPE);
private final Class<?> type;
public ReturnTypePredicate(Class<?> type)
{
writer.append(message);
writer.flush();
this.type = type;
}
@Override
public boolean test(Method method)
{
return type.equals(method.getReturnType());
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@ -32,7 +33,7 @@ import org.eclipse.jetty.util.log.Logger;
/**
* Support class for reading a (single) WebSocket BINARY message via a InputStream.
* <p>
* <p/>
* An InputStream that can access a queue of ByteBuffer payloads, along with expected InputStream blocking behavior.
*/
public class MessageInputStream extends InputStream implements MessageSink
@ -41,8 +42,11 @@ public class MessageInputStream extends InputStream implements MessageSink
private static final ByteBuffer EOF = ByteBuffer.allocate(0).asReadOnlyBuffer();
private final BlockingDeque<ByteBuffer> buffers = new LinkedBlockingDeque<>();
private AtomicBoolean closed = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
private final long timeoutMs;
private final CountDownLatch closedLatch = new CountDownLatch(1);
private ByteBuffer activeBuffer = null;
public MessageInputStream()
@ -60,7 +64,7 @@ public class MessageInputStream extends InputStream implements MessageSink
{
if (LOG.isDebugEnabled())
{
LOG.debug("Appending {} chunk: {}",fin?"final":"non-final",BufferUtil.toDetailString(payload));
LOG.debug("Appending {} chunk: {}", fin ? "final" : "non-final", BufferUtil.toDetailString(payload));
}
// If closed, we should just toss incoming payloads into the bit bucket.
@ -87,7 +91,7 @@ public class MessageInputStream extends InputStream implements MessageSink
return;
}
// TODO: the copy buffer should be pooled too, but no buffer pool available from here.
ByteBuffer copy = payload.isDirect()?ByteBuffer.allocateDirect(capacity):ByteBuffer.allocate(capacity);
ByteBuffer copy = payload.isDirect() ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
copy.put(payload).flip();
buffers.put(copy);
}
@ -111,6 +115,7 @@ public class MessageInputStream extends InputStream implements MessageSink
{
buffers.offer(EOF);
super.close();
closedLatch.countDown();
}
}
@ -164,6 +169,7 @@ public class MessageInputStream extends InputStream implements MessageSink
LOG.debug("Reached EOF");
// Be sure that this stream cannot be reused.
closed.set(true);
closedLatch.countDown();
// Removed buffers that may have remained in the queue.
buffers.clear();
return -1;
@ -177,6 +183,7 @@ public class MessageInputStream extends InputStream implements MessageSink
if (LOG.isDebugEnabled())
LOG.debug("Interrupted while waiting to read", x);
closed.set(true);
closedLatch.countDown();
return -1;
}
}
@ -186,4 +193,16 @@ public class MessageInputStream extends InputStream implements MessageSink
{
throw new IOException("reset() not supported");
}
public void awaitClose()
{
try
{
closedLatch.await();
}
catch (InterruptedException e)
{
throw new RuntimeException("Stream Close wait interrupted", e);
}
}
}

View File

@ -42,4 +42,9 @@ public class MessageReader extends InputStreamReader implements MessageSink
{
this.stream.accept(payload, fin);
}
public void awaitClose()
{
stream.awaitClose();
}
}

View File

@ -51,15 +51,10 @@ public class ReaderMessageSink implements MessageSink
stream.accept(payload,fin);
if (first)
{
executor.execute(new Runnable()
{
@Override
public void run()
{
// processing of errors is the responsibility
// of the stream function
onStreamFunction.apply(stream);
}
executor.execute(() -> {
// processing of errors is the responsibility
// of the stream function
onStreamFunction.apply(stream);
});
}
}
@ -67,6 +62,7 @@ public class ReaderMessageSink implements MessageSink
{
if (fin)
{
stream.awaitClose();
stream = null;
}
}

View File

@ -0,0 +1,128 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* A single argument for a method
*/
public class Arg
{
private final Class<?> type;
private Method method;
private int index;
private Object tag;
private boolean required = false;
public Arg(Class<?> type)
{
this.type = type;
}
public Arg(int idx, Class<?> type)
{
this.index = idx;
this.type = type;
}
public Arg(Method method, int idx, Class<?> type)
{
this.method = method;
this.index = idx;
this.type = type;
}
public <T extends Annotation> T getAnnotation(Class<T> annoClass)
{
if (method == null)
return null;
Annotation annos[] = method.getParameterAnnotations()[index];
if (annos != null || (annos.length > 0))
{
for (Annotation anno : annos)
{
if (anno.annotationType().equals(annoClass))
{
return (T) anno;
}
}
}
return null;
}
public int getIndex()
{
return index;
}
public String getName()
{
return type.getName();
}
public Class<?> getType()
{
return type;
}
public boolean isArray()
{
return type.isArray();
}
public boolean isRequired()
{
return required;
}
public boolean matches(Arg other)
{
// If tags exist
if (this.tag != null)
{
// They have to match
return (this.tag.equals(other.tag));
}
// Lastly, if types match, use em
return (this.type.isAssignableFrom(other.type));
}
public Arg required()
{
this.required = true;
return this;
}
public Arg setTag(String tag)
{
this.tag = tag;
return this;
}
@Override
public String toString()
{
return String.format("%s[%s%d%s]", type.getName(),
required ? "!" : "", index, tag == null ? "" : "/" + tag);
}
}

View File

@ -16,9 +16,9 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import java.util.function.Function;
public interface ArgIdentifier extends Function<DynamicArgs.Arg,DynamicArgs.Arg> {
public interface ArgIdentifier extends Function<Arg,Arg> {
}

View File

@ -16,16 +16,15 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
package org.eclipse.jetty.websocket.common.reflect;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultTextEncoder extends AbstractEncoder implements Encoder.Text<String>
/**
* A set of potential arguments, only one of which is allowed
*/
public class ArgSwitch extends Arg
{
@Override
public String encode(String message) throws EncodeException
public ArgSwitch(Class<?> type)
{
return message;
super(type);
}
}

View File

@ -16,16 +16,14 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
/**
* Provide argument utilities for working with methods that
@ -39,107 +37,58 @@ import java.util.function.BiPredicate;
*/
public class DynamicArgs
{
public static interface Signature
public interface Signature
{
/**
* Predicate to test if signature matches
*
* @return the predicate to test if signature matches
*/
public BiPredicate<Method, Class<?>[]> getPredicate();
Predicate<Method> getPredicate();
/**
* Get Call Args
*
* @return the Call Args
*/
Arg[] getCallArgs();
/**
* BiFunction to use to invoke method
* against give object, with provided (potential) arguments,
* returning appropriate result from invocation.
*
* @param method
* the method to base BiFunction off of.
* @param callArgs
* the description of arguments passed into each {@link DynamicArgs#invoke(Object, Object...)}
* call in the future. Used to map the incoming arguments to the method arguments.
* @param method the method to base BiFunction off of.
* @param callArgs the description of arguments passed into each {@link DynamicArgs#invoke(Object, Object...)}
* call in the future. Used to map the incoming arguments to the method arguments.
* @return the return result of the invoked method
*/
public BiFunction<Object, Object[], Object> getInvoker(Method method, DynamicArgs.Arg... callArgs);
BiFunction<Object, Object[], Object> getInvoker(Method method, Arg... callArgs);
public void appendDescription(StringBuilder str);
void appendDescription(StringBuilder str);
}
public static class Arg
{
public final Class<?> type;
public Method method;
public int index;
public Object tag;
public Arg(Class<?> type)
{
this.type = type;
}
public Arg(int idx, Class<?> type)
{
this.index = idx;
this.type = type;
}
public Arg(Method method, int idx, Class<?> type)
{
this.method = method;
this.index = idx;
this.type = type;
}
public Arg setTag(String tag)
{
this.tag = tag;
return this;
}
@Override
public String toString()
{
return String.format("%s[%d%s]",type.getName(),index,tag == null ? "" : "/" + tag);
}
public <T extends Annotation> T getAnnotation(Class<T> annoClass)
{
if(method == null)
return null;
Annotation annos[] = method.getParameterAnnotations()[index];
if(annos != null || (annos.length > 0))
{
for(Annotation anno: annos)
{
if(anno.annotationType().equals(annoClass))
{
return (T) anno;
}
}
}
return null;
}
}
public static class Builder
public static class Builder implements Predicate<Method>
{
private List<Signature> signatures = new ArrayList<>();
public DynamicArgs build(Method method, Arg... callArgs)
{
// FIXME: add DynamicArgs build cache (key = method+callargs)
Signature signature = getMatchingSignature(method);
if (signature == null)
return null;
return build(method, signature);
}
Class<?> paramTypes[] = method.getParameterTypes();
for (Signature sig : signatures)
{
if (sig.getPredicate().test(method, paramTypes))
{
return new DynamicArgs(sig.getInvoker(method,callArgs));
}
}
public DynamicArgs build(Method method, Signature signature)
{
return new DynamicArgs(signature.getInvoker(method, signature.getCallArgs()));
}
return null;
@Override
public boolean test(Method method)
{
return hasMatchingSignature(method);
}
/**
@ -150,18 +99,28 @@ public class DynamicArgs
*/
public boolean hasMatchingSignature(Method method)
{
// FIXME: add match cache (key = method)
return getMatchingSignature(method) != null;
}
/**
* Get the {@link Signature} that matches the method
*
* @param method the method to inspect
* @return the Signature, or null if no signature match
*/
public Signature getMatchingSignature(Method method)
{
// FIXME: add match cache (key = method, value = signature)
Class<?> paramTypes[] = method.getParameterTypes();
for (Signature sig : signatures)
{
if (sig.getPredicate().test(method, paramTypes))
if (sig.getPredicate().test(method))
{
return true;
return sig;
}
}
return false;
return null;
}
public Builder addSignature(Signature sig)
@ -197,6 +156,14 @@ public class DynamicArgs
return argIdentifiers;
}
/**
* BiFunction invoker
* <ol>
* <li>First Arg</li>
* <li>Second Arg</li>
* <li>Result Type</li>
* </ol>
*/
private final BiFunction<Object, Object[], Object> invoker;
private DynamicArgs(BiFunction<Object, Object[], Object> invoker)
@ -207,20 +174,12 @@ public class DynamicArgs
/**
* Invoke the signature / method with the provided potential args.
*
* @param o
* the object to call method on
* @param potentialArgs
* the potential args in the same order as the FIXME
* @param o the object to call method on
* @param potentialArgs the potential args in the same order as the Call Args
* @return the response object from the invoke
* @throws IllegalAccessException
* if unable to access the method or object
* @throws IllegalArgumentException
* if call to method has invalid/illegal arguments
* @throws InvocationTargetException
* if unable to invoke the method on the object
*/
public Object invoke(Object o, Object... potentialArgs) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
public Object invoke(Object o, Object... potentialArgs)
{
return invoker.apply(o,potentialArgs);
return invoker.apply(o, potentialArgs);
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import java.lang.reflect.InvocationTargetException;

View File

@ -16,17 +16,17 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Signature;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs.Signature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
public class ExactSignature implements Signature, BiPredicate<Method,Class<?>[]>
public class ExactSignature implements Signature, Predicate<Method>
{
private final Arg[] params;
@ -41,20 +41,27 @@ public class ExactSignature implements Signature, BiPredicate<Method,Class<?>[]>
}
@Override
public BiPredicate<Method,Class<?>[]> getPredicate()
public Predicate<Method> getPredicate()
{
return this;
}
@Override
public boolean test(Method method, Class<?>[] types)
public Arg[] getCallArgs()
{
return this.params;
}
@Override
public boolean test(Method method)
{
Class<?>[] types = method.getParameterTypes();
if (types.length != params.length)
return false;
int len = params.length;
for (int i = 0; i < len; i++)
{
if (!params[i].type.equals(types[i]))
if (!params[i].getType().equals(types[i]))
return false;
}
return true;
@ -71,8 +78,8 @@ public class ExactSignature implements Signature, BiPredicate<Method,Class<?>[]>
str.append(',');
}
str.append(' ');
str.append(arg.type.getName());
if (arg.type.isArray())
str.append(arg.getName());
if (arg.isArray())
{
str.append("[]");
}
@ -82,7 +89,7 @@ public class ExactSignature implements Signature, BiPredicate<Method,Class<?>[]>
}
@Override
public BiFunction<Object, Object[], Object> getInvoker(Method method, DynamicArgs.Arg... callArgs)
public BiFunction<Object, Object[], Object> getInvoker(Method method, Arg... callArgs)
{
// Figure out mapping of calling args to method args
Class<?> paramTypes[] = method.getParameterTypes();
@ -98,7 +105,7 @@ public class ExactSignature implements Signature, BiPredicate<Method,Class<?>[]>
// Find reference to argument in callArgs
for (int ci = 0; ci < callArgsLen; ci++)
{
if (callArgs[ci].index == params[mi].index)
if (callArgs[ci].getIndex() == params[mi].getIndex())
{
ref = ci;
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import org.eclipse.jetty.util.annotation.Name;
@ -26,11 +26,11 @@ import org.eclipse.jetty.util.annotation.Name;
public class NameArgIdentifier implements ArgIdentifier
{
@Override
public DynamicArgs.Arg apply(DynamicArgs.Arg arg)
public Arg apply(Arg arg)
{
Name name = arg.getAnnotation(Name.class);
if (name != null)
arg.tag = name.value();
arg.setTag(name.value());
return arg;
}
}

View File

@ -16,19 +16,22 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Signature;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.common.reflect.DynamicArgs.Signature;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
public class UnorderedSignature implements Signature, BiPredicate<Method, Class<?>[]>
public class UnorderedSignature implements Signature, Predicate<Method>
{
private final static Logger LOG = Log.getLogger(UnorderedSignature.class);
private final Arg[] params;
public UnorderedSignature(Arg... args)
@ -37,53 +40,21 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
}
@Override
public BiPredicate<Method, Class<?>[]> getPredicate()
public Arg[] getCallArgs()
{
return this.params;
}
@Override
public Predicate<Method> getPredicate()
{
return this;
}
@Override
public boolean test(Method method, Class<?>[] types)
public boolean test(Method method)
{
// Matches if the provided types
// match the valid params in any order
// Figure out mapping of calling args to method args
Class<?> paramTypes[] = method.getParameterTypes();
int paramTypesLength = paramTypes.length;
// Method argument array pointing to index in calling array
int callArgsLen = params.length;
List<ArgIdentifier> argIdentifiers = DynamicArgs.lookupArgIdentifiers();
for (int mi = 0; mi < paramTypesLength; mi++)
{
DynamicArgs.Arg methodArg = new DynamicArgs.Arg(method, mi, paramTypes[mi]);
for (ArgIdentifier argId : argIdentifiers)
methodArg = argId.apply(methodArg);
int ref = -1;
// Find reference to argument in callArgs
for (int ci = 0; ci < callArgsLen; ci++)
{
if (methodArg.tag != null && methodArg.tag.equals(params[ci].tag))
{
ref = ci;
}
else if (methodArg.type == params[ci].type)
{
ref = ci;
}
}
if (ref < 0)
{
return false;
}
}
return true;
return getArgMapping(method, false, params) != null;
}
public void appendDescription(StringBuilder str)
@ -97,8 +68,8 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
str.append(',');
}
str.append(' ');
str.append(arg.type.getName());
if (arg.type.isArray())
str.append(arg.getName());
if (arg.isArray())
{
str.append("[]");
}
@ -107,8 +78,7 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
str.append(')');
}
@Override
public BiFunction<Object, Object[], Object> getInvoker(Method method, Arg... callArgs)
private int[] getArgMapping(Method method, boolean throwOnFailure, Arg... callArgs)
{
int callArgsLen = callArgs.length;
@ -122,10 +92,10 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
// ServiceLoader for argument identification plugins
List<ArgIdentifier> argIdentifiers = DynamicArgs.lookupArgIdentifiers();
DynamicArgs.Arg methodArgs[] = new DynamicArgs.Arg[paramTypesLength];
Arg methodArgs[] = new Arg[paramTypesLength];
for (int pi = 0; pi < paramTypesLength; pi++)
{
methodArgs[pi] = new DynamicArgs.Arg(method, pi, paramTypes[pi]);
methodArgs[pi] = new Arg(method, pi, paramTypes[pi]);
// Supplement method argument identification from plugins
for (ArgIdentifier argId : argIdentifiers)
@ -140,12 +110,7 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
// Find reference to argument in callArgs
for (int ci = 0; ci < callArgsLen; ci++)
{
if (methodArgs[ai].tag != null && methodArgs[ai].tag.equals(callArgs[ci].tag))
{
ref = ci;
break;
}
else if (methodArgs[ai].index == callArgs[ci].index)
if (methodArgs[ai].matches(callArgs[ci]))
{
ref = ci;
break;
@ -156,7 +121,7 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
{
StringBuilder err = new StringBuilder();
err.append("Unable to map type [");
err.append(methodArgs[ai].type);
err.append(methodArgs[ai].getType());
err.append("] in method ");
ReflectUtils.append(err, method);
err.append(" to calling args: (");
@ -170,14 +135,80 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
}
err.append(")");
throw new DynamicArgsException(err.toString());
if (throwOnFailure)
throw new DynamicArgsException(err.toString());
else
{
LOG.debug("{}", err.toString());
return null;
}
}
argMapping[ai] = ref;
}
// Ensure that required arguments are present in the mapping
for (int ci = 0; ci < callArgsLen; ci++)
{
if (callArgs[ci].isRequired())
{
boolean found = false;
for (int ai = 0; ai < argMappingLength; ai++)
{
if (argMapping[ai] == ci)
{
found = true;
break;
}
}
if (!found)
{
StringBuilder err = new StringBuilder();
err.append("Unable to find required type [");
err.append(callArgs[ci].getType());
err.append("] in method ");
ReflectUtils.append(err, method);
if (throwOnFailure)
throw new DynamicArgsException(err.toString());
else
{
LOG.debug("{}", err.toString());
return null;
}
}
}
}
return argMapping;
}
@Override
public BiFunction<Object, Object[], Object> getInvoker(Method method, Arg... callArgs)
{
int argMapping[] = getArgMapping(method, true, callArgs);
// Return function capable of calling method
return (obj, potentialArgs) -> {
return new UnorderedParamsFunction(method, argMapping);
}
public static class UnorderedParamsFunction
implements BiFunction<Object, Object[], Object>
{
private final Method method;
private final int paramTypesLength;
private final int argMapping[];
public UnorderedParamsFunction(Method method, int argMapping[])
{
this.method = method;
this.paramTypesLength = method.getParameterTypes().length;
this.argMapping = argMapping;
}
@Override
public Object apply(Object obj, Object[] potentialArgs)
{
Object args[] = new Object[paramTypesLength];
for (int i = 0; i < paramTypesLength; i++)
{
@ -211,6 +242,7 @@ public class UnorderedSignature implements Signature, BiPredicate<Method, Class<
err.append("]");
throw new DynamicArgsException(err.toString(), e);
}
};
}
}
}

View File

@ -39,12 +39,12 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
public SimpleContainerScope(WebSocketPolicy policy)
{
this(policy,new MappedByteBufferPool(),new DecoratedObjectFactory());
this(policy, new MappedByteBufferPool(), new DecoratedObjectFactory());
}
public SimpleContainerScope(WebSocketPolicy policy, ByteBufferPool bufferPool)
{
this(policy,bufferPool,new DecoratedObjectFactory());
this(policy, bufferPool, new DecoratedObjectFactory());
}
public SimpleContainerScope(WebSocketPolicy policy, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory)
@ -58,6 +58,15 @@ public class SimpleContainerScope extends ContainerLifeCycle implements WebSocke
threadPool.setName(name);
threadPool.setDaemon(true);
this.executor = threadPool;
try
{
threadPool.start();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override

View File

@ -65,7 +65,7 @@ public class ReflectUtils
this.genericIndex = index;
if (type instanceof Class)
{
this.genericClass = (Class<?>)type;
this.genericClass = (Class<?>) type;
}
}
@ -90,7 +90,7 @@ public class ReflectUtils
{
if (type instanceof Class<?>)
{
Class<?> ctype = (Class<?>)type;
Class<?> ctype = (Class<?>) type;
if (ctype.isArray())
{
try
@ -188,16 +188,29 @@ public class ReflectUtils
}
}
public static Method findMethod(Class<?> pojo, String methodName, Class<?>... params)
{
try
{
return pojo.getMethod(methodName, params);
}
catch (NoSuchMethodException e)
{
return null;
}
}
public static Method findAnnotatedMethod(Class<?> pojo, Class<? extends Annotation> anno)
{
Method methods[] = findAnnotatedMethods(pojo,anno);
if(methods == null) {
Method methods[] = findAnnotatedMethods(pojo, anno);
if (methods == null)
{
return null;
}
if(methods.length > 1)
if (methods.length > 1)
{
throw DuplicateAnnotationException.build(pojo,anno,methods);
throw DuplicateAnnotationException.build(pojo, anno, methods);
}
return methods[0];
@ -230,17 +243,15 @@ public class ReflectUtils
/**
* Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration.
*
* @param baseClass
* the base (concrete) class to look in
* @param ifaceClass
* the interface of interest
*
* @param baseClass the base (concrete) class to look in
* @param ifaceClass the interface of interest
* @return the (concrete) generic class that the interface exposes
*/
public static Class<?> findGenericClassFor(Class<?> baseClass, Class<?> ifaceClass)
{
GenericRef ref = new GenericRef(baseClass,ifaceClass);
if (resolveGenericRef(ref,baseClass))
GenericRef ref = new GenericRef(baseClass, ifaceClass);
if (resolveGenericRef(ref, baseClass))
{
// debug("Generic Found: %s",ref.genericClass);
return ref.genericClass;
@ -317,31 +328,31 @@ public class ReflectUtils
{
// is this a straight ref or a TypeVariable?
// debug("Found ref (as class): %s",toShortName(type));
ref.setGenericFromType(type,0);
ref.setGenericFromType(type, 0);
return true;
}
else
{
// Keep digging
return resolveGenericRef(ref,type);
return resolveGenericRef(ref, type);
}
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
ParameterizedType ptype = (ParameterizedType) type;
Type rawType = ptype.getRawType();
if (rawType == ref.ifaceClass)
{
// debug("Found ref on [%s] as ParameterizedType [%s]",toShortName(clazz),toShortName(ptype));
// Always get the raw type parameter, let unwrap() solve for what it is
ref.setGenericFromType(ptype.getActualTypeArguments()[0],0);
ref.setGenericFromType(ptype.getActualTypeArguments()[0], 0);
return true;
}
else
{
// Keep digging
return resolveGenericRef(ref,rawType);
return resolveGenericRef(ref, rawType);
}
}
return false;
@ -356,7 +367,7 @@ public class ReflectUtils
if (type instanceof Class)
{
Class<?> clazz = (Class<?>)type;
Class<?> clazz = (Class<?>) type;
// prevent spinning off into Serialization and other parts of the
// standard tree that we could care less about
if (clazz.getName().matches("^javax*\\..*"))
@ -368,16 +379,16 @@ public class ReflectUtils
for (Type iface : ifaces)
{
// debug("resolve %s interface[]: %s",toShortName(clazz),toShortName(iface));
if (resolveGenericRef(ref,clazz,iface))
if (resolveGenericRef(ref, clazz, iface))
{
if (ref.needsUnwrap())
{
// debug("## Unwrap class %s::%s",toShortName(clazz),toShortName(iface));
TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
TypeVariable<?> needVar = (TypeVariable<?>) ref.genericType;
// debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
// attempt to find typeParameter on class itself
int typeParamIdx = findTypeParameterIndex(clazz,needVar);
int typeParamIdx = findTypeParameterIndex(clazz, needVar);
// debug("type param index for %s[%s] is [%d]",toShortName(clazz),toShortName(needVar),typeParamIdx);
if (typeParamIdx >= 0)
@ -387,14 +398,14 @@ public class ReflectUtils
TypeVariable<?> params[] = clazz.getTypeParameters();
if (params.length >= typeParamIdx)
{
ref.setGenericFromType(params[typeParamIdx],typeParamIdx);
ref.setGenericFromType(params[typeParamIdx], typeParamIdx);
}
}
else if (iface instanceof ParameterizedType)
{
// use actual args on interface
Type arg = ((ParameterizedType)iface).getActualTypeArguments()[ref.genericIndex];
ref.setGenericFromType(arg,ref.genericIndex);
Type arg = ((ParameterizedType) iface).getActualTypeArguments()[ref.genericIndex];
ref.setGenericFromType(arg, ref.genericIndex);
}
}
return true;
@ -402,25 +413,25 @@ public class ReflectUtils
}
type = clazz.getGenericSuperclass();
return resolveGenericRef(ref,type);
return resolveGenericRef(ref, type);
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
Class<?> rawClass = (Class<?>)ptype.getRawType();
if (resolveGenericRef(ref,rawClass))
ParameterizedType ptype = (ParameterizedType) type;
Class<?> rawClass = (Class<?>) ptype.getRawType();
if (resolveGenericRef(ref, rawClass))
{
if (ref.needsUnwrap())
{
// debug("## Unwrap ParameterizedType %s::%s",toShortName(type),toShortName(rawClass));
TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
TypeVariable<?> needVar = (TypeVariable<?>) ref.genericType;
// debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
int typeParamIdx = findTypeParameterIndex(rawClass,needVar);
int typeParamIdx = findTypeParameterIndex(rawClass, needVar);
// debug("type paramIdx of %s::%s is index [%d]",toShortName(rawClass),toShortName(needVar),typeParamIdx);
Type arg = ptype.getActualTypeArguments()[typeParamIdx];
ref.setGenericFromType(arg,typeParamIdx);
ref.setGenericFromType(arg, typeParamIdx);
return true;
}
}
@ -438,15 +449,15 @@ public class ReflectUtils
if (type instanceof Class)
{
String name = ((Class<?>)type).getName();
String name = ((Class<?>) type).getName();
return trimClassName(name);
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
ParameterizedType ptype = (ParameterizedType) type;
StringBuilder str = new StringBuilder();
str.append(trimClassName(((Class<?>)ptype.getRawType()).getName()));
str.append(trimClassName(((Class<?>) ptype.getRawType()).getName()));
str.append("<");
Type args[] = ptype.getActualTypeArguments();
for (int i = 0; i < args.length; i++)
@ -468,7 +479,7 @@ public class ReflectUtils
{
StringBuilder str = new StringBuilder();
append(str,pojo,method);
append(str, pojo, method);
return str.toString();
}
@ -496,7 +507,7 @@ public class ReflectUtils
// return type
Type retType = method.getGenericReturnType();
appendTypeName(str,retType,false).append(' ');
appendTypeName(str, retType, false).append(' ');
if (pojo != null)
{
@ -514,7 +525,7 @@ public class ReflectUtils
for (int j = 0; j < params.length; j++)
{
boolean ellipses = method.isVarArgs() && (j == (params.length - 1));
appendTypeName(str,params[j],ellipses);
appendTypeName(str, params[j], ellipses);
if (j < (params.length - 1))
{
str.append(", ");

View File

@ -0,0 +1 @@
org.eclipse.jetty.websocket.common.reflect.NameArgIdentifier

View File

@ -1 +0,0 @@
org.eclipse.jetty.websocket.common.util.NameArgIdentifier

View File

@ -0,0 +1,247 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.function;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketConnectionListener;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPartialListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession;
import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
import org.eclipse.jetty.websocket.common.test.EventTracker;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
public class CommonEndpointFunctionsTest
{
private static final Charset UTF8 = StandardCharsets.UTF_8;
private WebSocketContainerScope containerScope = new SimpleContainerScope(WebSocketPolicy.newServerPolicy());
@Rule
public TestName testname = new TestName();
public Session initSession(Object websocket)
{
return new LocalWebSocketSession(containerScope, testname, websocket);
}
public static class ConnectionOnly extends EventTracker implements WebSocketConnectionListener
{
@Override
public void onWebSocketClose(int statusCode, String reason)
{
addEvent("onWebSocketClose(%d, %s)", statusCode, reason);
}
@Override
public void onWebSocketConnect(Session session)
{
addEvent("onWebSocketConnect(%s)", session);
}
@Override
public void onWebSocketError(Throwable cause)
{
addEvent("onWebSocketError(%s)", cause);
}
}
@Test
public void testWebSocketConnectionListener_OpenTextClose()
{
// Setup
ConnectionOnly socket = new ConnectionOnly();
Session session = initSession(socket);
EndpointFunctions<Session> endpointFunctions = new CommonEndpointFunctions(socket, containerScope.getPolicy(), containerScope.getExecutor());
// Trigger Events
endpointFunctions.onOpen(session);
endpointFunctions.onText(BufferUtil.toBuffer("Hello World", UTF8), true);
endpointFunctions.onClose(new CloseInfo(StatusCode.NORMAL, "Normal"));
// Validate Events
socket.assertCaptured(
"onWebSocketConnect\\([^\\)]*\\)",
"onWebSocketClose\\([^\\)]*\\)");
}
public static class DataConnection extends ConnectionOnly implements WebSocketListener
{
@Override
public void onWebSocketBinary(byte[] payload, int offset, int len)
{
addEvent("onWebSocketBinary(byte[%d], %d, %d)", payload.length, offset, len);
}
@Override
public void onWebSocketText(String message)
{
addEvent("onWebSocketText(%s)", message);
}
}
@Test
public void testWebSocketListener_OpenTextClose()
{
// Setup
DataConnection socket = new DataConnection();
Session session = initSession(socket);
EndpointFunctions<Session> endpointFunctions = new CommonEndpointFunctions(socket, containerScope.getPolicy(), containerScope.getExecutor());
// Trigger Events
endpointFunctions.onOpen(session);
endpointFunctions.onText(BufferUtil.toBuffer("Hello World", UTF8), true);
endpointFunctions.onClose(new CloseInfo(StatusCode.NORMAL, "Normal"));
// Validate Events
socket.assertCaptured(
"onWebSocketConnect\\([^\\)]*\\)",
"onWebSocketText\\(Hello World\\)",
"onWebSocketClose\\([^\\)]*\\)");
}
@WebSocket
public static class StreamedText extends EventTracker
{
public final CountDownLatch streamLatch;
public StreamedText(int expectedStreams)
{
this.streamLatch = new CountDownLatch(expectedStreams);
}
@SuppressWarnings("unused")
@OnWebSocketMessage
public void onTextStream(Reader reader) throws IOException
{
assertThat("Reader", reader, notNullValue());
StringWriter out = new StringWriter();
IO.copy(reader, out);
addEvent("onTextStream(%s)", out.toString());
streamLatch.countDown();
}
}
@Test
public void testAnnotatedStreamedText_Single() throws InterruptedException
{
// Setup
StreamedText socket = new StreamedText(1);
Session session = initSession(socket);
EndpointFunctions<Session> endpointFunctions = new CommonEndpointFunctions(socket, containerScope.getPolicy(), containerScope.getExecutor());
// Trigger Events
endpointFunctions.onOpen(session);
endpointFunctions.onText(BufferUtil.toBuffer("Hello World", UTF8), true);
endpointFunctions.onClose(new CloseInfo(StatusCode.NORMAL, "Normal"));
// Await completion (of threads)
socket.streamLatch.await(2, TimeUnit.SECONDS);
// Validate Events
socket.assertCaptured("onTextStream\\(Hello World\\)");
}
@Test
public void testAnnotatedStreamedText_MultipleParts() throws InterruptedException
{
// Setup
StreamedText socket = new StreamedText(1);
Session session = initSession(socket);
EndpointFunctions<Session> endpointFunctions = new CommonEndpointFunctions(socket, containerScope.getPolicy(), containerScope.getExecutor());
// Trigger Events
endpointFunctions.onOpen(session);
endpointFunctions.onText(BufferUtil.toBuffer("Hel"), false);
endpointFunctions.onText(BufferUtil.toBuffer("lo "), false);
endpointFunctions.onText(BufferUtil.toBuffer("Wor"), false);
endpointFunctions.onText(BufferUtil.toBuffer("ld"), true);
endpointFunctions.onClose(new CloseInfo(StatusCode.NORMAL, "Normal"));
// Await completion (of threads)
socket.streamLatch.await(2, TimeUnit.SECONDS);
// Validate Events
socket.assertCaptured("onTextStream\\(Hello World\\)");
}
public static class PartialData extends ConnectionOnly implements WebSocketPartialListener
{
@Override
public void onWebSocketPartialBinary(ByteBuffer payload, boolean fin)
{
addEvent("onWebSocketPartialBinary(%s, %b)", BufferUtil.toDetailString(payload), fin);
}
@Override
public void onWebSocketPartialText(String payload, boolean fin)
{
addEvent("onWebSocketPartialText(%s, %b)", payload, fin);
}
}
@Test
public void testWebSocketPartialListener()
{
// Setup
PartialData socket = new PartialData();
Session session = initSession(socket);
EndpointFunctions<Session> endpointFunctions = new CommonEndpointFunctions(socket, containerScope.getPolicy(), containerScope.getExecutor());
// Trigger Events
endpointFunctions.onOpen(session);
endpointFunctions.onText(BufferUtil.toBuffer("Hel"), false);
endpointFunctions.onText(BufferUtil.toBuffer("lo "), false);
endpointFunctions.onText(BufferUtil.toBuffer("Wor"), false);
endpointFunctions.onText(BufferUtil.toBuffer("ld"), true);
endpointFunctions.onClose(new CloseInfo(StatusCode.NORMAL, "Normal"));
// Validate Events
socket.assertCaptured(
"onWebSocketConnect\\([^\\)]*\\)",
"onWebSocketPartialText\\(Hel, false\\)",
"onWebSocketPartialText\\(lo , false\\)",
"onWebSocketPartialText\\(Wor, false\\)",
"onWebSocketPartialText\\(ld, true\\)",
"onWebSocketClose\\([^\\)]*\\)"
);
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.common.test.EventCapture;
import org.junit.Assert;
/**
@ -47,6 +48,7 @@ public class TrackingSocket extends WebSocketAdapter
public CountDownLatch dataLatch = new CountDownLatch(1);
public EventQueue<String> messageQueue = new EventQueue<>();
public EventQueue<Throwable> errorQueue = new EventQueue<>();
public EventCapture events = new EventCapture();
public TrackingSocket()
{
@ -116,6 +118,7 @@ public class TrackingSocket extends WebSocketAdapter
public void onWebSocketBinary(byte[] payload, int offset, int len)
{
LOG.debug("{} onWebSocketBinary(byte[{}],{},{})",id,payload.length,offset,len);
events.add("onWebSocketBinary(byte[{}],{},{})",payload.length,offset,len);
messageQueue.offer(MessageDebug.toDetailHint(payload,offset,len));
dataLatch.countDown();
}
@ -124,6 +127,7 @@ public class TrackingSocket extends WebSocketAdapter
public void onWebSocketClose(int statusCode, String reason)
{
LOG.debug("{} onWebSocketClose({},{})",id,statusCode,reason);
events.add("onWebSocketClose({},{})",statusCode,reason);
super.onWebSocketClose(statusCode,reason);
closeCode = statusCode;
closeMessage.append(reason);
@ -133,6 +137,7 @@ public class TrackingSocket extends WebSocketAdapter
@Override
public void onWebSocketConnect(Session session)
{
events.add("onWebSocketConnect({})",session);
super.onWebSocketConnect(session);
openLatch.countDown();
}
@ -141,6 +146,7 @@ public class TrackingSocket extends WebSocketAdapter
public void onWebSocketError(Throwable cause)
{
LOG.debug("{} onWebSocketError",id,cause);
events.add("onWebSocketError({})",cause);
Assert.assertThat("Error capture",errorQueue.offer(cause),is(true));
}
@ -148,6 +154,7 @@ public class TrackingSocket extends WebSocketAdapter
public void onWebSocketText(String message)
{
LOG.debug("{} onWebSocketText({})",id,message);
events.add("onWebSocketText({})",message);
messageQueue.offer(message);
dataLatch.countDown();
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -25,7 +25,6 @@ import static org.junit.Assert.assertThat;
import java.io.File;
import java.lang.reflect.Method;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.junit.Test;
public class ExactSignatureTest

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
package org.eclipse.jetty.websocket.common.reflect;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@ -26,11 +26,11 @@ import java.io.File;
import java.lang.reflect.Method;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.websocket.common.util.DynamicArgs.Arg;
import org.junit.Test;
public class UnorderedSignatureTest
{
@SuppressWarnings("unused")
public static class SampleSignatures
{
public String sigEmpty()
@ -95,13 +95,13 @@ public class UnorderedSignatureTest
DynamicArgs.Builder dab = new DynamicArgs.Builder();
dab.addSignature(new UnorderedSignature());
SampleSignatures ssigs = new SampleSignatures();
Method m = findMethodByName(ssigs, "sigEmpty");
DynamicArgs dargs = dab.build(m);
assertThat("DynamicArgs", dargs, notNullValue());
SampleSignatures samples = new SampleSignatures();
Method m = findMethodByName(samples, "sigEmpty");
DynamicArgs dynamicArgs = dab.build(m);
assertThat("DynamicArgs", dynamicArgs, notNullValue());
// Test with empty potential args
String result = (String) dargs.invoke(ssigs);
String result = (String) dynamicArgs.invoke(samples);
assertThat("result", result, is("sigEmpty<>"));
}
@ -117,13 +117,13 @@ public class UnorderedSignatureTest
DynamicArgs.Builder dab = new DynamicArgs.Builder();
dab.addSignature(new UnorderedSignature(ARG_STR));
SampleSignatures ssigs = new SampleSignatures();
Method m = findMethodByName(ssigs, "sigEmpty");
DynamicArgs dargs = dab.build(m);
assertThat("DynamicArgs", dargs, notNullValue());
SampleSignatures samples = new SampleSignatures();
Method m = findMethodByName(samples, "sigEmpty");
DynamicArgs dynamicArgs = dab.build(m);
assertThat("DynamicArgs", dynamicArgs, notNullValue());
// Test with empty potential args
String result = (String) dargs.invoke(ssigs, "Hello");
String result = (String) dynamicArgs.invoke(samples, "Hello");
assertThat("result", result, is("sigEmpty<>"));
}
@ -140,13 +140,13 @@ public class UnorderedSignatureTest
final Arg CALL_STR = new Arg(String.class);
SampleSignatures ssigs = new SampleSignatures();
Method m = findMethodByName(ssigs, "sigStr");
DynamicArgs dargs = dab.build(m, CALL_STR);
assertThat("DynamicArgs", dargs, notNullValue());
SampleSignatures samples = new SampleSignatures();
Method m = findMethodByName(samples, "sigStr");
DynamicArgs dynamicArgs = dab.build(m, CALL_STR);
assertThat("DynamicArgs", dynamicArgs, notNullValue());
// Test with potential args
String result = (String) dargs.invoke(ssigs, "Hello");
String result = (String) dynamicArgs.invoke(samples, "Hello");
assertThat("result", result, is("sigStr<Hello>"));
}
@ -167,18 +167,18 @@ public class UnorderedSignatureTest
SampleSignatures ssigs = new SampleSignatures();
Method m = findMethodByName(ssigs, "sigByteArray");
DynamicArgs dargs = dab.build(m,CALL_BYTEARRAY, CALL_OFFSET, CALL_LENGTH);
assertThat("DynamicArgs", dargs, notNullValue());
DynamicArgs dynamicArgs = dab.build(m,CALL_BYTEARRAY, CALL_OFFSET, CALL_LENGTH);
assertThat("DynamicArgs", dynamicArgs, notNullValue());
// Test with potential args
byte buf[] = new byte[222];
int offset = 3;
int len = 44;
String result = (String)dargs.invoke(ssigs,buf,offset,len);
String result = (String)dynamicArgs.invoke(ssigs,buf,offset,len);
assertThat("result", result, is("sigByteArray<[222],3,44>"));
// Test with empty potential args
result = (String)dargs.invoke(ssigs,null,123,456);
result = (String)dynamicArgs.invoke(ssigs,null,123,456);
assertThat("result", result, is("sigByteArray<<null>,123,456>"));
}
}

View File

@ -37,9 +37,17 @@ public class DummyConnection implements LogicalConnection
{
private static final Logger LOG = Log.getLogger(DummyConnection.class);
private IOState iostate;
private WebSocketPolicy policy;
@Deprecated
public DummyConnection()
{
this(WebSocketPolicy.newServerPolicy());
}
public DummyConnection(WebSocketPolicy policy)
{
this.policy = policy;
this.iostate = new IOState();
}
@ -103,7 +111,7 @@ public class DummyConnection implements LogicalConnection
@Override
public WebSocketPolicy getPolicy()
{
return null;
return policy;
}
@Override
@ -144,7 +152,7 @@ public class DummyConnection implements LogicalConnection
public void setNextIncomingFrames(IncomingFrames incoming)
{
if (LOG.isDebugEnabled())
LOG.debug("setNextIncomingFrames({})",incoming);
LOG.debug("setNextIncomingFrames({})", incoming);
}
@Override

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.test;
import static org.eclipse.jetty.websocket.common.test.RegexMatcher.matchesPattern;
import static org.junit.Assert.assertThat;
import java.util.Iterator;
public abstract class EventTracker
{
private EventCapture captured = new EventCapture();
protected void addEvent(String format, Object... args)
{
captured.add(format, args);
}
public void assertCaptured(String... regexEvents)
{
Iterator<String> capturedIterator = captured.iterator();
for (int i = 0; i < regexEvents.length; i++)
{
assertThat("Event [" + i + "]", capturedIterator.next(), matchesPattern(regexEvents[i]));
}
}
}

View File

@ -0,0 +1,66 @@
//
// ========================================================================
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.test;
import java.util.regex.Pattern;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.TypeSafeMatcher;
public class RegexMatcher extends TypeSafeMatcher
{
private final Pattern pattern;
public RegexMatcher(String pattern)
{
this(Pattern.compile(pattern));
}
public RegexMatcher(Pattern pattern)
{
this.pattern = pattern;
}
@Override
public void describeTo(Description description)
{
description.appendText("matches regular expression ").appendValue(pattern);
}
@Override
protected boolean matchesSafely(Object item)
{
if(item == null) return false;
return pattern.matcher(item.toString()).matches();
}
@Factory
public static RegexMatcher matchesPattern(Pattern pattern)
{
return new RegexMatcher(pattern);
}
@Factory
public static RegexMatcher matchesPattern(String pattern)
{
return new RegexMatcher(pattern);
}
}