393473 - Add support for JSR-356 (javax.websocket) draft

+ Rounding out the Encoders / Decoders base implementation
This commit is contained in:
Joakim Erdfelt 2013-02-20 15:58:04 -07:00
parent f2723404c6
commit 22fe9d419e
18 changed files with 950 additions and 376 deletions

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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 org.eclipse.jetty.websocket.api.WebSocketException;
public class ConfigurationException extends WebSocketException
{
private static final long serialVersionUID = 3026803845657799372L;
public ConfigurationException(String message)
{
super(message);
}
public ConfigurationException(String message, Throwable cause)
{
super(message,cause);
}
}

View File

@ -0,0 +1,39 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.nio.ByteBuffer;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
public class ByteBufferDecoder implements Decoder.Binary<ByteBuffer>
{
@Override
public ByteBuffer decode(ByteBuffer bytes) throws DecodeException
{
return bytes;
}
@Override
public boolean willDecode(ByteBuffer bytes)
{
return true;
}
}

View File

@ -18,111 +18,166 @@
package org.eclipse.jetty.websocket.jsr356.decoders;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.ConfigurationException;
import org.eclipse.jetty.websocket.jsr356.utils.DeploymentTypeUtils;
public class Decoders
{
private static final Map<Class<?>, Class<? extends Decoder>> DEFAULTS;
static
private static class DecoderRef
{
DEFAULTS = new HashMap<>();
DEFAULTS.put(Boolean.class,BooleanDecoder.class);
DEFAULTS.put(Byte.class,ByteDecoder.class);
DEFAULTS.put(Character.class,CharacterDecoder.class);
DEFAULTS.put(Double.class,DoubleDecoder.class);
DEFAULTS.put(Float.class,FloatDecoder.class);
DEFAULTS.put(Integer.class,IntegerDecoder.class);
DEFAULTS.put(Long.class,LongDecoder.class);
DEFAULTS.put(Short.class,ShortDecoder.class);
DEFAULTS.put(String.class,StringDecoder.class);
Class<?> type;
Class<? extends Decoder> decoder;
public DecoderRef(Class<?> type, Class<? extends Decoder> decoder)
{
this.type = type;
this.decoder = decoder;
}
}
private final List<Class<? extends Decoder>> decoders;
public static List<ParameterizedType> getDecoderInterfaces(Class<? extends Decoder> decoder)
{
List<ParameterizedType> ret = new ArrayList<>();
for (Type type : decoder.getGenericInterfaces())
{
if (!(type instanceof ParameterizedType))
{
continue; // skip
}
ParameterizedType ptype = (ParameterizedType)type;
if (DeploymentTypeUtils.isAssignable(type,Decoder.Text.class) || DeploymentTypeUtils.isAssignable(type,Decoder.TextStream.class)
|| DeploymentTypeUtils.isAssignable(type,Decoder.Binary.class) || DeploymentTypeUtils.isAssignable(type,Decoder.BinaryStream.class))
{
ret.add(ptype);
}
}
return ret;
}
private final List<DecoderRef> decoders;
public Decoders()
{
this(null);
this.decoders = new ArrayList<>();
// Default TEXT Message Decoders
add(new DecoderRef(Boolean.class,BooleanDecoder.class));
add(new DecoderRef(Byte.class,ByteDecoder.class));
add(new DecoderRef(Character.class,CharacterDecoder.class));
add(new DecoderRef(Double.class,DoubleDecoder.class));
add(new DecoderRef(Float.class,FloatDecoder.class));
add(new DecoderRef(Integer.class,IntegerDecoder.class));
add(new DecoderRef(Long.class,LongDecoder.class));
add(new DecoderRef(Short.class,ShortDecoder.class));
add(new DecoderRef(String.class,StringDecoder.class));
// Default BINARY Message Decoders
add(new DecoderRef(ByteBuffer.class,ByteBufferDecoder.class));
}
public Decoders(Class<? extends Decoder>[] decoderClasses)
{
this();
if (decoderClasses != null)
{
// now add user provided
for (Class<? extends Decoder> decoder : decoderClasses)
{
add(decoder);
}
}
}
public Decoders(List<Class<? extends Decoder>> decoderClasses)
{
this.decoders = new ArrayList<>();
// now add user provided
addAll(decoderClasses);
this();
if (decoderClasses != null)
{
// now add user provided
for (Class<? extends Decoder> decoder : decoderClasses)
{
add(decoder);
}
}
}
public void add(Class<? extends Decoder> decoder)
{
this.decoders.add(decoder);
for (ParameterizedType idecoder : getDecoderInterfaces(decoder))
{
Type handledTypes[] = idecoder.getActualTypeArguments();
if (handledTypes == null)
{
throw new InvalidSignatureException(decoder + " has invalid signature for " + idecoder + " Generic type is null");
}
if (handledTypes.length != 1)
{
throw new InvalidSignatureException(decoder + " has invalid signature for " + idecoder + " - multi-value generic types not supported");
}
Type handledType = handledTypes[0];
if (handledType instanceof Class<?>)
{
Class<?> handler = (Class<?>)handledType;
add(handler,decoder);
}
else
{
throw new InvalidSignatureException(decoder + " has invalid signature for " + idecoder + " - only java.lang.Class based generics supported");
}
}
}
private void addAll(List<Class<? extends Decoder>> decoderClasses)
private void add(Class<?> handler, Class<? extends Decoder> decoder)
{
if (decoderClasses == null)
// verify that we are not adding a duplicate
for (DecoderRef ref : decoders)
{
return;
}
for (Class<? extends Decoder> decoder : decoderClasses)
{
add(decoder);
if (DeploymentTypeUtils.isAssignableClass(handler,ref.type))
{
throw new ConfigurationException("Duplicate Decoder handling for type " + ref.type + ": found in " + ref.decoder + " and " + decoder);
}
}
// add entry
this.decoders.add(new DecoderRef(handler,decoder));
}
public Decoder getBinaryDecoder(Type type)
private void add(DecoderRef ref)
{
// TODO Auto-generated method stub
return null;
this.decoders.add(ref);
}
public Decoder getTextDecoder(Type type) throws DeploymentException
public Decoder getDecoder(Class<?> type)
{
for (Class<? extends Decoder> decoderClass : decoders)
Class<?> targetType = type;
if (targetType.isPrimitive())
{
if (!DeploymentTypeUtils.isAssignable(decoderClass,Decoder.Text.class))
{
continue; // not a text decoder
}
targetType = DeploymentTypeUtils.getPrimitiveClass(targetType);
}
Type textType = DeploymentTypeUtils.getGenericType(decoderClass,Decoder.Text.class);
if (DeploymentTypeUtils.isAssignable(type,textType))
for (DecoderRef ref : decoders)
{
if (DeploymentTypeUtils.isAssignable(targetType,ref.type))
{
return instantiate(decoderClass);
return instantiate(ref.decoder);
}
}
// Found no user provided decoder, use default (if available)
Class<? extends Decoder> decoderClass = null;
if (type instanceof Class<?>)
{
Class<?> typeClass = (Class<?>)type;
if (typeClass.isPrimitive())
{
typeClass = DeploymentTypeUtils.getPrimitiveClass(typeClass);
}
decoderClass = DEFAULTS.get(typeClass);
if (decoderClass != null)
{
return instantiate(decoderClass);
}
}
throw new DeploymentException("Unable to find appropriate Decoder for type: " + type);
throw new InvalidSignatureException("Unable to find appropriate Decoder for type: " + type);
}
private Decoder instantiate(Class<? extends Decoder> decoderClass) throws DeploymentException
private Decoder instantiate(Class<? extends Decoder> decoderClass)
{
try
{
@ -130,7 +185,7 @@ public class Decoders
}
catch (InstantiationException | IllegalAccessException e)
{
throw new DeploymentException("Unable to instantiate Decoder: " + decoderClass,e);
throw new ConfigurationException("Unable to instantiate Decoder: " + decoderClass,e);
}
}
}

View File

@ -18,26 +18,168 @@
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import javax.websocket.Encoder;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.ConfigurationException;
import org.eclipse.jetty.websocket.jsr356.utils.DeploymentTypeUtils;
public class Encoders
{
private static final List<Class<? extends Encoder>> DEFAULTS;
static
private static class EncoderRef
{
DEFAULTS = new ArrayList<>();
DEFAULTS.add(BooleanEncoder.class);
DEFAULTS.add(ByteEncoder.class);
DEFAULTS.add(CharacterEncoder.class);
DEFAULTS.add(DoubleEncoder.class);
DEFAULTS.add(FloatEncoder.class);
DEFAULTS.add(IntegerEncoder.class);
DEFAULTS.add(LongEncoder.class);
DEFAULTS.add(ShortEncoder.class);
DEFAULTS.add(StringEncoder.class);
Class<?> type;
Class<? extends Encoder> encoder;
public EncoderRef(Class<?> type, Class<? extends Encoder> encoder)
{
this.type = type;
this.encoder = encoder;
}
}
public static List<ParameterizedType> getEncoderInterfaces(Class<? extends Encoder> encoder)
{
List<ParameterizedType> ret = new ArrayList<>();
for (Type type : encoder.getGenericInterfaces())
{
if (!(type instanceof ParameterizedType))
{
continue; // skip
}
ParameterizedType ptype = (ParameterizedType)type;
if (DeploymentTypeUtils.isAssignable(type,Encoder.Text.class) || DeploymentTypeUtils.isAssignable(type,Encoder.TextStream.class)
|| DeploymentTypeUtils.isAssignable(type,Encoder.Binary.class) || DeploymentTypeUtils.isAssignable(type,Encoder.BinaryStream.class))
{
ret.add(ptype);
}
}
return ret;
}
private final List<EncoderRef> encoders;
public Encoders()
{
this.encoders = new ArrayList<>();
add(new EncoderRef(Boolean.class,BooleanEncoder.class));
add(new EncoderRef(Byte.class,ByteEncoder.class));
add(new EncoderRef(Character.class,CharacterEncoder.class));
add(new EncoderRef(Double.class,DoubleEncoder.class));
add(new EncoderRef(Float.class,FloatEncoder.class));
add(new EncoderRef(Integer.class,IntegerEncoder.class));
add(new EncoderRef(Long.class,LongEncoder.class));
add(new EncoderRef(Short.class,ShortEncoder.class));
add(new EncoderRef(String.class,StringEncoder.class));
}
public Encoders(Class<? extends Encoder> encoderClasses[])
{
this();
if (encoderClasses != null)
{
// now add user provided encoders
for (Class<? extends Encoder> encoder : encoderClasses)
{
add(encoder);
}
}
}
public Encoders(List<Class<? extends Encoder>> encoderClasses)
{
this();
if (encoderClasses != null)
{
// now add user provided encoders
for (Class<? extends Encoder> encoder : encoderClasses)
{
add(encoder);
}
}
}
public void add(Class<? extends Encoder> encoder)
{
for (ParameterizedType iencoder : getEncoderInterfaces(encoder))
{
Type handledTypes[] = iencoder.getActualTypeArguments();
if (handledTypes == null)
{
throw new InvalidSignatureException(encoder + " has invalid signature for " + iencoder + " Generic type is null");
}
if (handledTypes.length != 1)
{
throw new InvalidSignatureException(encoder + " has invalid signature for " + iencoder + " - multi-value generic types not supported");
}
Type handledType = handledTypes[0];
if (handledType instanceof Class<?>)
{
Class<?> handler = (Class<?>)handledType;
add(handler,encoder);
}
else
{
throw new InvalidSignatureException(encoder + " has invalid signature for " + iencoder + " - only java.lang.Class based generics supported");
}
}
}
private void add(Class<?> handler, Class<? extends Encoder> encoder)
{
// verify that we are not adding a duplicate
for (EncoderRef ref : encoders)
{
if (DeploymentTypeUtils.isAssignableClass(handler,ref.type))
{
throw new ConfigurationException("Duplicate Encoder handling for type " + ref.type + ": found in " + ref.encoder + " and " + encoder);
}
}
// add entry
this.encoders.add(new EncoderRef(handler,encoder));
}
private void add(EncoderRef ref)
{
this.encoders.add(ref);
}
public Encoder getEncoder(Class<?> type)
{
Class<?> targetType = type;
if (targetType.isPrimitive())
{
targetType = DeploymentTypeUtils.getPrimitiveClass(targetType);
}
for (EncoderRef ref : encoders)
{
if (DeploymentTypeUtils.isAssignable(targetType,ref.type))
{
return instantiate(ref.encoder);
}
}
throw new InvalidSignatureException("Unable to find appropriate Encoder for type: " + type);
}
private Encoder instantiate(Class<? extends Encoder> encoderClass)
{
try
{
return encoderClass.newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
throw new ConfigurationException("Unable to instantiate Encoder: " + encoderClass,e);
}
}
}

View File

@ -18,17 +18,11 @@
package org.eclipse.jetty.websocket.jsr356.endpoints;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import javax.websocket.CloseReason;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfiguration;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketClient;
import javax.websocket.WebSocketClose;
@ -43,6 +37,8 @@ import org.eclipse.jetty.websocket.common.events.ParamList;
import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.decoders.Decoders;
import org.eclipse.jetty.websocket.jsr356.encoders.Encoders;
import org.eclipse.jetty.websocket.jsr356.endpoints.JsrMethodParameters.Param;
/**
@ -55,9 +51,6 @@ public class JsrAnnotatedClientScanner extends AbstractMethodAnnotationScanner<J
private static final ParamList validOpenParams;
private static final ParamList validCloseParams;
private static final ParamList validErrorParams;
private static final ParamList validPongFormatParams;
private static final ParamList validTextFormatParams;
private static final ParamList validBinaryFormatParams;
static
{
@ -73,57 +66,11 @@ public class JsrAnnotatedClientScanner extends AbstractMethodAnnotationScanner<J
validErrorParams.addParams(Session.class);
validErrorParams.addParams(Throwable.class);
// TEXT Formats
validTextFormatParams = new ParamList();
// partial message
validTextFormatParams.addParams(String.class,Boolean.TYPE);
validTextFormatParams.addParams(String.class,Boolean.class);
// whole message
validTextFormatParams.addParams(String.class);
// java primitives
validTextFormatParams.addParams(Boolean.TYPE);
validTextFormatParams.addParams(Byte.TYPE);
validTextFormatParams.addParams(Character.TYPE);
validTextFormatParams.addParams(Double.TYPE);
validTextFormatParams.addParams(Float.TYPE);
validTextFormatParams.addParams(Integer.TYPE);
validTextFormatParams.addParams(Long.TYPE);
validTextFormatParams.addParams(Short.TYPE);
// java primitives class equivalents
validTextFormatParams.addParams(Boolean.class);
validTextFormatParams.addParams(Byte.class);
validTextFormatParams.addParams(Character.class);
validTextFormatParams.addParams(Double.class);
validTextFormatParams.addParams(Float.class);
validTextFormatParams.addParams(Integer.class);
validTextFormatParams.addParams(Long.class);
validTextFormatParams.addParams(Short.class);
// streaming
validTextFormatParams.addParams(Reader.class);
// BINARY Formats
validBinaryFormatParams = new ParamList();
// partial message
validBinaryFormatParams.addParams(ByteBuffer.class,Boolean.TYPE);
validBinaryFormatParams.addParams(ByteBuffer.class,Boolean.class);
validBinaryFormatParams.addParams(byte[].class,Boolean.TYPE);
validBinaryFormatParams.addParams(byte[].class,Boolean.class);
// whole message
validBinaryFormatParams.addParams(ByteBuffer.class);
validBinaryFormatParams.addParams(byte[].class);
// streaming
validBinaryFormatParams.addParams(InputStream.class);
// PONG Format
validPongFormatParams = new ParamList();
validPongFormatParams.addParams(PongMessage.class);
}
protected final Class<?> pojo;
protected final Class<? extends Encoder> encoders[];
protected final Class<? extends Decoder> decoders[];
protected final ParamList validTextDecoderParameters;
protected final ParamList validBinaryDecoderParameters;
protected final Encoders encoders;
protected final Decoders decoders;
public JsrAnnotatedClientScanner(Class<?> websocket)
{
@ -135,28 +82,8 @@ public class JsrAnnotatedClientScanner extends AbstractMethodAnnotationScanner<J
throw new InvalidWebSocketException("Unsupported WebSocket object, missing @" + WebSocketClient.class + " annotation");
}
this.encoders = anno.encoders();
this.decoders = anno.decoders();
this.validTextDecoderParameters = new ParamList();
this.validBinaryDecoderParameters = new ParamList();
// decoder based valid parameters
for (Class<? extends Decoder> decoder : this.decoders)
{
if (Decoder.Text.class.isAssignableFrom(decoder) || Decoder.TextStream.class.isAssignableFrom(decoder))
{
// Text decoder
decoder.getTypeParameters();
// TODO: Fixme
}
if (Decoder.Binary.class.isAssignableFrom(decoder) || Decoder.BinaryStream.class.isAssignableFrom(decoder))
{
// Binary decoder
// TODO: Fixme
}
}
this.encoders = new Encoders(anno.encoders());
this.decoders = new Decoders(anno.decoders());
}
private void assertValidJsrSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
@ -251,88 +178,32 @@ public class JsrAnnotatedClientScanner extends AbstractMethodAnnotationScanner<J
if (isAnnotation(annotation,WebSocketMessage.class))
{
assertIsPublicNonStatic(method);
/*
JsrMessageCallableMethod callable = new JsrMessageCallableMethod(pojo,method);
callable.setReturnType(method.getReturnType(),encoders);
JsrMethodParameters params = new JsrMethodParameters(method);
boolean foundSession = false;
boolean foundIsLast = false;
boolean foundFormat = false;
// Find the path-mapping and Session parameters
for (Param param : params)
{
// (optional) Path Mapping Parameters
String varname = getPathMappingParameterVariable(param.type);
if (varname != null)
{
param.setPathParamVariable(varname);
}
// (optional) Session parameter
if (Session.class.isAssignableFrom(param.type))
{
if(foundSession) {
throw new InvalidSignatureException("Duplicate "+Session.class+" parameter found in " + );
}
param.setValid(true);
}
// (optional) isLast parameter
if (Boolean.class.isAssignableFrom(param.type))
{
param.setValid(true);
}
}
// Ensure we identified all of the parameters
for (Param param : params)
{
if (param.isValid() == false)
{
StringBuilder err = new StringBuilder();
err.append("Encountered invalid/unhandled parameter <");
err.append(param.type.getName());
err.append("> (position ").append(param.index).append(") in method <");
err.append(method.getName());
err.append("> of object <");
err.append(pojo.getName());
err.append("> that doesn't fit the requirements for the @");
err.append(WebSocketMessage.class.getSimpleName());
err.append(" annotation");
throw new InvalidSignatureException(err.toString());
}
}
// Find the Message Format Parameters
Class<?> formatParams[] = null;
if ((formatParams = params.containsAny(validTextFormatParams)) != null)
JsrMessageCallableMethod callable = new JsrMessageCallableMethod(pojo,method,encoders,decoders);
if (callable.isTextFormat())
{
// TEXT
params.setValid(formatParams);
metadata.onText = callable;
assertUnset(metadata.onText,WebSocketMessage.class,method);
metadata.onText = callable;
return;
}
if ((formatParams = params.containsAny(validBinaryFormatParams)) != null)
if (callable.isBinaryFormat())
{
// BINARY
params.setValid(formatParams);
metadata.onBinary = callable;
assertUnset(metadata.onBinary,WebSocketMessage.class,method);
metadata.onBinary = callable;
return;
}
if ((formatParams = params.containsAny(validPongFormatParams)) != null)
if (callable.isPongFormat())
{
// PONG
params.setValid(formatParams);
metadata.onPong = callable;
assertUnset(metadata.onPong,WebSocketMessage.class,method);
metadata.onPong = callable;
return;
}
*/
return;
}
}

View File

@ -18,60 +18,203 @@
package org.eclipse.jetty.websocket.jsr356.endpoints;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Map;
import org.eclipse.jetty.util.TypeUtil;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.decoders.Decoders;
import org.eclipse.jetty.websocket.jsr356.encoders.Encoders;
import org.eclipse.jetty.websocket.jsr356.utils.DeploymentTypeUtils;
public class JsrMessageCallableMethod extends CallableMethod
{
private static class ParamRef
{
int index;
Class<?> type;
Decoder decoder;
public ParamRef(int idx, Class<?> type)
{
this.index = idx;
this.type = type;
}
}
public static boolean isBinaryFormat(Class<?> type)
{
return ByteBuffer.class.isAssignableFrom(type) || byte[].class.isAssignableFrom(type) || InputStream.class.isAssignableFrom(type);
}
public static boolean isPongFormat(Class<?> type)
{
return PongMessage.class.isAssignableFrom(type);
}
public static boolean isTextFormat(Class<?> type)
{
return DeploymentTypeUtils.getPrimitives().contains(type) || DeploymentTypeUtils.getPrimitiveClasses().contains(type)
|| Reader.class.isAssignableFrom(type);
}
private Class<?> returnType;
// Index of Session Parameter
private int idxSession = -1;
private int idxIsLast = -1;
private int idxFormat = -1;
private Encoder returnEncoder;
// optional Session Parameter
private ParamRef paramSession;
// optional islast boolean used for partial message support
private ParamRef paramIsLast;
// mandatory format specifier
private ParamRef paramFormat;
private Map<String, ParamRef> pathParams;
public JsrMessageCallableMethod(Class<?> pojo, Method method, Encoders encoders, Decoders decoders) throws InvalidSignatureException
{
super(pojo,method);
setReturnType(method.getReturnType());
setReturnType(method.getReturnType(),encoders);
// walk through parameters
Class<?> paramTypes[] = method.getParameterTypes();
int len = paramTypes.length;
for (int i = 0; i < len; i++)
{
Class<?> paramType = paramTypes[i];
// Path Param
String pathParam = getPathParam(paramType);
if (StringUtil.isNotBlank(pathParam))
{
// TODO: limit to allowable path param types
ParamRef paramRef = new ParamRef(i,paramType);
Decoder decoder = decoders.getDecoder(paramType);
if (!(decoder instanceof Decoder.Text))
{
throw new WebSocketException("Unable to convert to PathParam with type: " + paramType);
}
paramRef.decoder = decoder;
if (pathParams.containsKey(pathParam))
{
throw new InvalidSignatureException("WebSocketPathParam of value [" + pathParam + "] is defined more than once");
}
pathParams.put(pathParam,paramRef);
continue; // next param
}
// Session param
if (Session.class.isAssignableFrom(paramType))
{
if (paramSession != null)
{
throw new InvalidSignatureException("Session cannot appear multiple times as a parameter");
}
paramSession = new ParamRef(i,paramType);
continue; // next param
}
// IsLast Boolean (for partial message support)
if (Boolean.class.isAssignableFrom(paramType))
{
if (paramIsLast != null)
{
throw new InvalidSignatureException("Boolean isLast cannot appear multiple times as a parameter");
}
paramIsLast = new ParamRef(i,paramType);
continue; // next param
}
// Pong Format
if (isPongFormat(paramType))
{
if (paramFormat != null)
{
throw new InvalidSignatureException("Multiple Message Formats cannot appear as a separate parameters");
}
paramFormat = new ParamRef(i,paramType);
continue; // next param
}
// Text or Binary
Decoder decoder = decoders.getDecoder(paramType);
if (decoder != null)
{
if (paramFormat != null)
{
throw new InvalidSignatureException("Multiple Message Formats cannot appear as a separate parameters");
}
paramFormat = new ParamRef(i,paramType);
paramFormat.decoder = decoder;
continue; // next param
}
throw new InvalidSignatureException("Unknown parameter type: " + paramType);
}
}
public void setReturnType(Class<?> returnType)
public String getPathParam(Class<?> paramType)
{
if (Void.TYPE.equals(returnType))
// override to implement on server side
return null;
}
public boolean isBinaryFormat()
{
if (paramFormat == null)
{
// Void type
this.returnType = returnType;
return false;
}
if (paramFormat.decoder == null)
{
return false;
}
return (paramFormat.decoder instanceof Decoder.Binary) || (paramFormat.decoder instanceof Decoder.BinaryStream);
}
public boolean isPongFormat()
{
if (paramFormat == null)
{
return false;
}
return PongMessage.class.isAssignableFrom(paramFormat.type);
}
public boolean isTextFormat()
{
if (paramFormat == null)
{
return false;
}
if (paramFormat.decoder == null)
{
return false;
}
return (paramFormat.decoder instanceof Decoder.Text) || (paramFormat.decoder instanceof Decoder.TextStream);
}
public void setReturnType(Class<?> returnType, Encoders encoders)
{
this.returnType = returnType;
if (returnType.equals(Void.TYPE))
{
// can't have encoder for Void
return;
}
this.returnEncoder = encoders.getEncoder(returnType);
if (returnType.isArray() && Byte.TYPE.equals(returnType))
if (this.returnEncoder == null)
{
// A byte array
this.returnType = returnType;
return;
throw new InvalidSignatureException("Unable to find encoder for return type: " + returnType);
}
if (TypeUtil.toName(returnType) != null)
{
// A primitive (including String)
this.returnType = returnType;
return;
}
if (ByteBuffer.class.isAssignableFrom(returnType))
{
// A nio ByteBuffer
this.returnType = returnType;
return;
}
// TODO: Determine if encoder exists for this return type
}
}

View File

@ -25,8 +25,9 @@ import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.websocket.DeploymentException;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
/**
* Collection of common {@link Type} utility methods used during Deployment of the Endpoint
@ -63,7 +64,7 @@ public class DeploymentTypeUtils
PRIMITIVE_CLASS_MAP = Collections.unmodifiableMap(types);
}
public static Type getGenericType(Class<?> clazz, Class<?> interfaceClass) throws DeploymentException
public static Type getGenericType(Class<?> clazz, Class<?> interfaceClass)
{
for (Type type : clazz.getGenericInterfaces())
{
@ -85,7 +86,17 @@ public class DeploymentTypeUtils
return PRIMITIVE_CLASS_MAP.get(primitiveType);
}
public static Class<?> getRawTypeClass(ParameterizedType type) throws DeploymentException
public static Set<Class<?>> getPrimitiveClasses()
{
return CLASS_PRIMITIVE_MAP.keySet();
}
public static Set<Class<?>> getPrimitives()
{
return PRIMITIVE_CLASS_MAP.keySet();
}
public static Class<?> getRawTypeClass(ParameterizedType type)
{
Type raw = type.getRawType();
if (raw instanceof Class<?>)
@ -93,7 +104,7 @@ public class DeploymentTypeUtils
return (Class<?>)raw;
}
// bail on non-class raw types
throw new DeploymentException("Unexpected, Non Class Raw Type: " + raw);
throw new InvalidSignatureException("Unexpected, Non Class Raw Type: " + raw);
}
/**
@ -104,14 +115,13 @@ public class DeploymentTypeUtils
* @param targetType
* the target type
* @return
* @throws DeploymentException
*/
public static boolean isAssignable(Type type, Type targetType) throws DeploymentException
public static boolean isAssignable(Type type, Type targetType)
{
return isAssignable(type,targetType,null);
}
private static boolean isAssignable(Type type, Type targetType, Map<TypeVariable<?>, Type> typeVars) throws DeploymentException
private static boolean isAssignable(Type type, Type targetType, Map<TypeVariable<?>, Type> typeVars)
{
if ((type == null) || (targetType == null))
{
@ -197,7 +207,7 @@ public class DeploymentTypeUtils
return targetClass.isAssignableFrom(type);
}
private static boolean isAssignableToClass(Type type, Class<?> targetClass) throws DeploymentException
private static boolean isAssignableToClass(Type type, Class<?> targetClass)
{
if (targetClass.equals(type))
{
@ -226,6 +236,6 @@ public class DeploymentTypeUtils
return false;
}
throw new DeploymentException("Unhandled Type: " + type);
throw new InvalidSignatureException("Unhandled Type: " + type);
}
}

View File

@ -23,32 +23,49 @@ import static org.hamcrest.Matchers.*;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import org.eclipse.jetty.websocket.jsr356.decoders.samples.DualDecoder;
import org.eclipse.jetty.websocket.jsr356.decoders.samples.SecondDecoder;
import org.eclipse.jetty.websocket.jsr356.ConfigurationException;
import org.eclipse.jetty.websocket.jsr356.samples.DualDecoder;
import org.eclipse.jetty.websocket.jsr356.samples.Fruit;
import org.eclipse.jetty.websocket.jsr356.samples.FruitDecoder;
import org.junit.Assert;
import org.junit.Test;
public class DecodersTest
{
@Test
public void testGetDecoders_Dual() throws DeploymentException
public void testGetTextDecoder_Character() throws DeploymentException
{
Decoders decoders = new Decoders();
decoders.add(DualDecoder.class);
decoders.add(FruitDecoder.class);
Decoder txtDecoder = decoders.getTextDecoder(Integer.class);
Decoder txtDecoder = decoders.getDecoder(Character.class);
Assert.assertThat("Text Decoder",txtDecoder,notNullValue());
Assert.assertThat("Text Decoder",txtDecoder,instanceOf(DualDecoder.class));
Assert.assertThat("Text Decoder",txtDecoder,instanceOf(CharacterDecoder.class));
}
@Test
public void testGetDecoders_Second() throws DeploymentException
public void testGetTextDecoder_Dual()
{
try
{
Decoders decoders = new Decoders();
decoders.add(DualDecoder.class); // has duplicated support for the same target Type
Assert.fail("Should have thrown ConfigurationException");
}
catch (ConfigurationException e)
{
Assert.assertThat("Error Message",e.getMessage(),containsString("Duplicate"));
}
}
@Test
public void testGetTextDecoder_Fruit() throws DeploymentException
{
Decoders decoders = new Decoders();
decoders.add(SecondDecoder.class);
decoders.add(FruitDecoder.class);
Decoder txtDecoder = decoders.getTextDecoder(Integer.class);
Decoder txtDecoder = decoders.getDecoder(Fruit.class);
Assert.assertThat("Text Decoder",txtDecoder,notNullValue());
Assert.assertThat("Text Decoder",txtDecoder,instanceOf(SecondDecoder.class));
Assert.assertThat("Text Decoder",txtDecoder,instanceOf(FruitDecoder.class));
}
}

View File

@ -1,15 +1,37 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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 javax.websocket.Decoder;
import static org.hamcrest.Matchers.*;
import javax.websocket.DecodeException;
import org.junit.Assert;
import org.junit.Test;
public class IntegerDecoderTest
{
@Test
public void test()
public void testDecode() throws DecodeException
{
Class<? extends Decoder> dc = IntegerDecoder.class;
IntegerDecoder decoder = new IntegerDecoder();
Integer val = decoder.decode("123");
Assert.assertThat("Decoded value",val,is(123));
}
}

View File

@ -1,83 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
public class DualDecoder implements Decoder.Text<Integer>, Decoder.Binary<Integer>
{
@Override
public Integer decode(ByteBuffer bytes) throws DecodeException
{
try
{
return bytes.getInt();
}
catch (BufferUnderflowException e)
{
throw new DecodeException(bytes,"Unable to read int from binary message",e);
}
}
@Override
public Integer decode(String s) throws DecodeException
{
try
{
return Integer.parseInt(s);
}
catch (NumberFormatException e)
{
throw new DecodeException(s,"Unable to parse Integer",e);
}
}
@Override
public boolean willDecode(ByteBuffer bytes)
{
if (bytes == null)
{
return false;
}
return bytes.remaining() >= 4;
}
@Override
public boolean willDecode(String s)
{
if (s == null)
{
return false;
}
try
{
Integer.parseInt(s);
return true;
}
catch (NumberFormatException e)
{
return false;
}
}
}

View File

@ -0,0 +1,56 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.*;
import org.eclipse.jetty.websocket.jsr356.ConfigurationException;
import org.eclipse.jetty.websocket.jsr356.samples.FruitBinaryEncoder;
import org.eclipse.jetty.websocket.jsr356.samples.FruitTextEncoder;
import org.junit.Assert;
import org.junit.Test;
/**
* Tests against the Encoders class
*/
public class EncodersTest
{
@Test
public void testAddDuplicateEncoder()
{
Encoders encoders = new Encoders();
encoders.add(FruitBinaryEncoder.class);
try
{
encoders.add(FruitTextEncoder.class); // throws exception
Assert.fail("Should have thrown ConfigurationException");
}
catch (ConfigurationException e)
{
Assert.assertThat("Error Message",e.getMessage(),containsString("Duplicate"));
}
}
@Test
public void testAddEncoder()
{
Encoders encoders = new Encoders();
encoders.add(FruitBinaryEncoder.class);
}
}

View File

@ -0,0 +1,109 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import org.eclipse.jetty.util.BufferUtil;
/**
* Intentionally bad example of attempting to decode the same object to different message formats.
*/
public class DualDecoder implements Decoder.Text<Fruit>, Decoder.Binary<Fruit>
{
@Override
public Fruit decode(ByteBuffer bytes) throws DecodeException
{
try
{
int id = bytes.get(bytes.position());
if (id != FruitBinaryEncoder.FRUIT_ID_BYTE)
{
// not a binary fruit object
throw new DecodeException(bytes,"Not an encoded Binary Fruit object");
}
Fruit fruit = new Fruit();
fruit.name = getUTF8String(bytes);
fruit.color = getUTF8String(bytes);
return fruit;
}
catch (BufferUnderflowException e)
{
throw new DecodeException(bytes,"Unable to read Fruit from binary message",e);
}
}
@Override
public Fruit decode(String s) throws DecodeException
{
Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
Matcher mat = pat.matcher(s);
if (!mat.find())
{
throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
}
Fruit fruit = new Fruit();
fruit.name = mat.group(1);
fruit.color = mat.group(2);
return fruit;
}
private String getUTF8String(ByteBuffer buf)
{
int strLen = buf.getInt();
ByteBuffer slice = buf.slice();
slice.limit(slice.position() + strLen);
String str = BufferUtil.toUTF8String(slice);
buf.position(buf.position() + strLen);
return str;
}
@Override
public boolean willDecode(ByteBuffer bytes)
{
if (bytes == null)
{
return false;
}
int id = bytes.get(bytes.position());
return (id != FruitBinaryEncoder.FRUIT_ID_BYTE);
}
@Override
public boolean willDecode(String s)
{
if (s == null)
{
return false;
}
Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
Matcher mat = pat.matcher(s);
return (mat.find());
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.decoders.samples;
package org.eclipse.jetty.websocket.jsr356.samples;
import javax.websocket.Decoder;

View File

@ -0,0 +1,25 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
public class Fruit
{
public String name;
public String color;
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import org.eclipse.jetty.util.BufferUtil;
public class FruitBinaryEncoder implements Encoder.Binary<Fruit>
{
public static final byte FRUIT_ID_BYTE = (byte)0xAF;
// the number of bytes to store a string (1 int)
public static final int STRLEN_STORAGE = 4;
@Override
public ByteBuffer encode(Fruit fruit) throws EncodeException
{
int len = 1; // id byte
len += STRLEN_STORAGE + fruit.name.length();
len += STRLEN_STORAGE + fruit.color.length();
ByteBuffer buf = ByteBuffer.allocate(len + 64);
buf.flip();
buf.put(FRUIT_ID_BYTE);
putString(buf,fruit.name);
putString(buf,fruit.color);
buf.flip();
return buf;
}
private void putString(ByteBuffer buf, String str)
{
buf.putInt(str.length());
BufferUtil.toBuffer(str,Charset.forName("UTF-8"));
}
}

View File

@ -16,25 +16,32 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.decoders.samples;
package org.eclipse.jetty.websocket.jsr356.samples;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.websocket.DecodeException;
public class SecondDecoder implements ExtDecoder<Integer>
public class FruitDecoder implements ExtDecoder<Fruit>
{
private String id;
@Override
public Integer decode(String s) throws DecodeException
public Fruit decode(String s) throws DecodeException
{
try
Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
Matcher mat = pat.matcher(s);
if (!mat.find())
{
return Integer.parseInt(s);
}
catch (NumberFormatException e)
{
throw new DecodeException(s,"Unable to parse Integer",e);
throw new DecodeException(s,"Unable to find Fruit reference encoded in text message");
}
Fruit fruit = new Fruit();
fruit.name = mat.group(1);
fruit.color = mat.group(2);
return fruit;
}
@Override
@ -57,14 +64,8 @@ public class SecondDecoder implements ExtDecoder<Integer>
return false;
}
try
{
Integer.parseInt(s);
return true;
}
catch (NumberFormatException e)
{
return false;
}
Pattern pat = Pattern.compile("([^|]*)|([^|]*)");
Matcher mat = pat.matcher(s);
return (mat.find());
}
}

View File

@ -0,0 +1,31 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class FruitTextEncoder implements Encoder.Text<Fruit>
{
@Override
public String encode(Fruit fruit) throws EncodeException
{
return String.format("%s|%s",fruit.name,fruit.color);
}
}

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.samples;
import java.io.IOException;
import javax.websocket.EncodeException;
import javax.websocket.Session;
import javax.websocket.WebSocketClient;
import javax.websocket.WebSocketMessage;
@WebSocketClient(decoders = { DualDecoder.class })
public class IntSocket
{
@WebSocketMessage
public void onInt(Session session, int value)
{
try
{
session.getRemote().sendObject(value);
}
catch (IOException | EncodeException e)
{
e.printStackTrace();
}
}
}