393473 - Add support for JSR-356 (javax.websocket) draft
+ Rounding out the Encoders / Decoders base implementation
This commit is contained in:
parent
f2723404c6
commit
22fe9d419e
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.jsr356.decoders.samples;
|
||||
package org.eclipse.jetty.websocket.jsr356.samples;
|
||||
|
||||
import javax.websocket.Decoder;
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue