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

+ Refactoring EventDriver to be more pluggable (for jsr endpoints)
This commit is contained in:
Joakim Erdfelt 2013-02-13 13:37:26 -07:00
parent 529820d411
commit 4fb1bc80da
32 changed files with 1942 additions and 816 deletions

View File

@ -0,0 +1,33 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.nio.ByteBuffer;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultBinaryEncoder implements Encoder.Binary<ByteBuffer>
{
@Override
public ByteBuffer encode(ByteBuffer message) throws EncodeException
{
return message;
}
}

View File

@ -0,0 +1,37 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import org.eclipse.jetty.util.BufferUtil;
public class DefaultBinaryStreamEncoder implements Encoder.BinaryStream<ByteBuffer>
{
@Override
public void encode(ByteBuffer message, OutputStream out) throws EncodeException, IOException
{
BufferUtil.writeTo(message,out);
}
}

View File

@ -0,0 +1,31 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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 javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultTextEncoder implements Encoder.Text<String>
{
@Override
public String encode(String message) throws EncodeException
{
return message;
}
}

View File

@ -0,0 +1,35 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.encoders;
import java.io.IOException;
import java.io.Writer;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
public class DefaultTextStreamEncoder implements Encoder.TextStream<String>
{
@Override
public void encode(String message, Writer writer) throws EncodeException, IOException
{
writer.append(message);
writer.flush();
}
}

View File

@ -0,0 +1,75 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.endpoints;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
/**
* Cache for discovered javax.websocket {@link WebSocketEndpoint &#064;WebSocketEndpoint} annotated websockets
*/
public class JavaxPojoAnnotationCache extends AbstractMethodAnnotationScanner<JavaxPojoMetadata>
{
private static final Logger LOG = Log.getLogger(JavaxPojoAnnotationCache.class);
public static final JavaxPojoAnnotationCache INSTANCE = new JavaxPojoAnnotationCache();
public synchronized static JavaxPojoMetadata discover(Class<?> websocket)
{
// TODO: move to server side deployer
// WebSocketEndpoint anno = websocket.getAnnotation(WebSocketEndpoint.class);
// if (anno == null)
// {
// return null;
// }
JavaxPojoMetadata metadata = INSTANCE.cache.get(websocket);
if (metadata == null)
{
metadata = new JavaxPojoMetadata();
INSTANCE.scanMethodAnnotations(metadata,websocket);
INSTANCE.cache.put(websocket,metadata);
}
return metadata;
}
public static JavaxPojoMetadata discover(Object websocket)
{
return discover(websocket.getClass());
}
private ConcurrentHashMap<Class<?>, JavaxPojoMetadata> cache;
public JavaxPojoAnnotationCache()
{
cache = new ConcurrentHashMap<>();
}
@Override
public void onMethodAnnotation(JavaxPojoMetadata metadata, Class<?> pojo, Method method, Annotation annotation)
{
LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,27 @@
//
// ========================================================================
// Copyright (c) 1995-2012 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.endpoints;
/**
* Represents the metadata associated with Annotation discovery of a specific class.
*/
public class JavaxPojoMetadata
{
}

View File

@ -0,0 +1,125 @@
//
// ========================================================================
// 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.endpoints;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
public class JsrAnnotatedEventDriver implements EventDriver
{
@Override
public void incomingError(WebSocketException e)
{
// TODO Auto-generated method stub
}
@Override
public void incomingFrame(Frame frame)
{
// TODO Auto-generated method stub
}
@Override
public WebSocketPolicy getPolicy()
{
// TODO Auto-generated method stub
return null;
}
@Override
public WebSocketSession getSession()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
{
// TODO Auto-generated method stub
}
@Override
public void onBinaryMessage(byte[] data)
{
// TODO Auto-generated method stub
}
@Override
public void onClose(CloseInfo close)
{
// TODO Auto-generated method stub
}
@Override
public void onConnect()
{
// TODO Auto-generated method stub
}
@Override
public void onError(Throwable t)
{
// TODO Auto-generated method stub
}
@Override
public void onFrame(Frame frame)
{
// TODO Auto-generated method stub
}
@Override
public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
{
// TODO Auto-generated method stub
}
@Override
public void onTextMessage(String message)
{
// TODO Auto-generated method stub
}
@Override
public void openSession(WebSocketSession session)
{
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,31 @@
package org.eclipse.jetty.websocket.jsr356.endpoints;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
public class JsrAnnotatedImpl implements EventDriverImpl
{
@Override
public EventDriver create(Object websocket, WebSocketPolicy policy)
{
// TODO Auto-generated method stub
return null;
}
@Override
public String describeRule()
{
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(Object websocket)
{
// TODO Auto-generated method stub
return false;
}
}

View File

@ -0,0 +1,125 @@
//
// ========================================================================
// 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.endpoints;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.EventDriver;
public class JsrEndpointEventDriver implements EventDriver
{
@Override
public void incomingError(WebSocketException e)
{
// TODO Auto-generated method stub
}
@Override
public void incomingFrame(Frame frame)
{
// TODO Auto-generated method stub
}
@Override
public WebSocketPolicy getPolicy()
{
// TODO Auto-generated method stub
return null;
}
@Override
public WebSocketSession getSession()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException
{
// TODO Auto-generated method stub
}
@Override
public void onBinaryMessage(byte[] data)
{
// TODO Auto-generated method stub
}
@Override
public void onClose(CloseInfo close)
{
// TODO Auto-generated method stub
}
@Override
public void onConnect()
{
// TODO Auto-generated method stub
}
@Override
public void onError(Throwable t)
{
// TODO Auto-generated method stub
}
@Override
public void onFrame(Frame frame)
{
// TODO Auto-generated method stub
}
@Override
public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException
{
// TODO Auto-generated method stub
}
@Override
public void onTextMessage(String message)
{
// TODO Auto-generated method stub
}
@Override
public void openSession(WebSocketSession session)
{
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,31 @@
package org.eclipse.jetty.websocket.jsr356.endpoints;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriverImpl;
public class JsrEndpointImpl implements EventDriverImpl
{
@Override
public EventDriver create(Object websocket, WebSocketPolicy policy)
{
// TODO Auto-generated method stub
return null;
}
@Override
public String describeRule()
{
// TODO Auto-generated method stub
return null;
}
@Override
public boolean supports(Object websocket)
{
// TODO Auto-generated method stub
return false;
}
}

View File

@ -236,6 +236,11 @@ public class WebSocketClient extends ContainerLifeCycle
return cookieStore;
}
public EventDriverFactory getEventDriverFactory()
{
return eventDriverFactory;
}
public Executor getExecutor()
{
return executor;

View File

@ -0,0 +1,187 @@
//
// ========================================================================
// 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.common.events;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.CloseException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
/**
* EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket.
*/
public abstract class AbstractEventDriver implements IncomingFrames, EventDriver
{
private static final Logger LOG = Log.getLogger(EventDriver.class);
protected final WebSocketPolicy policy;
protected final Object websocket;
protected WebSocketSession session;
public AbstractEventDriver(WebSocketPolicy policy, Object websocket)
{
this.policy = policy;
this.websocket = websocket;
}
@Override
public WebSocketPolicy getPolicy()
{
return policy;
}
@Override
public WebSocketSession getSession()
{
return session;
}
@Override
public final void incomingError(WebSocketException e)
{
if (LOG.isDebugEnabled())
{
LOG.debug("incoming(WebSocketException)",e);
}
if (e instanceof CloseException)
{
CloseException close = (CloseException)e;
terminateConnection(close.getStatusCode(),close.getMessage());
}
onError(e);
}
@Override
public void incomingFrame(Frame frame)
{
if (LOG.isDebugEnabled())
{
LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame);
}
onFrame(frame);
try
{
switch (frame.getType().getOpCode())
{
case OpCode.CLOSE:
{
boolean validate = true;
CloseInfo close = new CloseInfo(frame,validate);
// notify user websocket pojo
onClose(close);
// process handshake
if (session.getConnection().getIOState().onCloseHandshake(true))
{
// handshake resolved, disconnect.
session.getConnection().disconnect();
}
else
{
// respond
session.close(close.getStatusCode(),close.getReason());
}
return;
}
case OpCode.PING:
{
byte pongBuf[] = new byte[0];
if (frame.hasPayload())
{
pongBuf = BufferUtil.toArray(frame.getPayload());
}
session.getRemote().sendPong(ByteBuffer.wrap(pongBuf));
break;
}
case OpCode.BINARY:
{
onBinaryFrame(frame.getPayload(),frame.isFin());
return;
}
case OpCode.TEXT:
{
onTextFrame(frame.getPayload(),frame.isFin());
return;
}
}
}
catch (NotUtf8Exception e)
{
terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage());
}
catch (CloseException e)
{
terminateConnection(e.getStatusCode(),e.getMessage());
}
catch (Throwable t)
{
unhandled(t);
}
}
@Override
public void openSession(WebSocketSession session)
{
LOG.debug("openSession({})",session);
this.session = session;
this.onConnect();
}
protected void terminateConnection(int statusCode, String rawreason)
{
String reason = rawreason;
reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
LOG.debug("terminateConnection({},{})",statusCode,rawreason);
session.close(statusCode,reason);
}
private void unhandled(Throwable t)
{
LOG.warn("Unhandled Error (closing connection)",t);
// Unhandled Error, close the connection.
switch (policy.getBehavior())
{
case SERVER:
terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
break;
case CLIENT:
terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
break;
}
}
}

View File

@ -1,201 +1,35 @@
//
// ========================================================================
// 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.common.events;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.CloseException;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
import org.eclipse.jetty.websocket.common.CloseInfo;
import org.eclipse.jetty.websocket.common.OpCode;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.WebSocketSession;
/**
* EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket.
*/
public abstract class EventDriver implements IncomingFrames
public interface EventDriver extends IncomingFrames
{
private static final Logger LOG = Log.getLogger(EventDriver.class);
protected final WebSocketPolicy policy;
protected final Object websocket;
protected WebSocketSession session;
public WebSocketPolicy getPolicy();
public EventDriver(WebSocketPolicy policy, Object websocket)
{
this.policy = policy;
this.websocket = websocket;
}
public WebSocketSession getSession();
public WebSocketPolicy getPolicy()
{
return policy;
}
public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;
public WebSocketSession getSession()
{
return session;
}
public void onBinaryMessage(byte[] data);
@Override
public final void incomingError(WebSocketException e)
{
if (LOG.isDebugEnabled())
{
LOG.debug("incoming(WebSocketException)",e);
}
public void onClose(CloseInfo close);
if (e instanceof CloseException)
{
CloseException close = (CloseException)e;
terminateConnection(close.getStatusCode(),close.getMessage());
}
public void onConnect();
onError(e);
}
public void onError(Throwable t);
@Override
public void incomingFrame(Frame frame)
{
if (LOG.isDebugEnabled())
{
LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame);
}
public void onFrame(Frame frame);
onFrame(frame);
public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;
try
{
switch (frame.getType().getOpCode())
{
case OpCode.CLOSE:
{
boolean validate = true;
CloseInfo close = new CloseInfo(frame,validate);
public void onTextMessage(String message);
// notify user websocket pojo
onClose(close);
// process handshake
if (session.getConnection().getIOState().onCloseHandshake(true))
{
// handshake resolved, disconnect.
session.getConnection().disconnect();
}
else
{
// respond
session.close(close.getStatusCode(),close.getReason());
}
return;
}
case OpCode.PING:
{
byte pongBuf[] = new byte[0];
if (frame.hasPayload())
{
pongBuf = BufferUtil.toArray(frame.getPayload());
}
session.getRemote().sendPong(ByteBuffer.wrap(pongBuf));
break;
}
case OpCode.BINARY:
{
onBinaryFrame(frame.getPayload(),frame.isFin());
return;
}
case OpCode.TEXT:
{
onTextFrame(frame.getPayload(),frame.isFin());
return;
}
}
}
catch (NotUtf8Exception e)
{
terminateConnection(StatusCode.BAD_PAYLOAD,e.getMessage());
}
catch (CloseException e)
{
terminateConnection(e.getStatusCode(),e.getMessage());
}
catch (Throwable t)
{
unhandled(t);
}
}
public abstract void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException;
public abstract void onBinaryMessage(byte[] data);
public abstract void onClose(CloseInfo close);
public abstract void onConnect();
public abstract void onError(Throwable t);
public abstract void onFrame(Frame frame);
public abstract void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException;
public abstract void onTextMessage(String message);
public void openSession(WebSocketSession session)
{
LOG.debug("openSession({})",session);
this.session = session;
this.onConnect();
}
protected void terminateConnection(int statusCode, String rawreason)
{
String reason = rawreason;
reason = StringUtil.truncate(reason,(WebSocketFrame.MAX_CONTROL_PAYLOAD - 2));
LOG.debug("terminateConnection({},{})",statusCode,rawreason);
session.close(statusCode,reason);
}
private void unhandled(Throwable t)
{
LOG.warn("Unhandled Error (closing connection)",t);
// Unhandled Error, close the connection.
switch (policy.getBehavior())
{
case SERVER:
terminateConnection(StatusCode.SERVER_ERROR,t.getClass().getSimpleName());
break;
case CLIENT:
terminateConnection(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName());
break;
}
}
public void openSession(WebSocketSession session);
}

View File

@ -18,340 +18,75 @@
package org.eclipse.jetty.websocket.common.events;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.api.extensions.Frame;
/**
* Create EventDriver implementations.
*/
public class EventDriverFactory
{
/**
* Parameter list for &#064;OnWebSocketMessage (Binary mode)
*/
private static final ParamList validBinaryParams;
/**
* Parameter list for &#064;OnWebSocketConnect
*/
private static final ParamList validConnectParams;
/**
* Parameter list for &#064;OnWebSocketClose
*/
private static final ParamList validCloseParams;
/**
* Parameter list for &#064;OnWebSocketError
*/
private static final ParamList validErrorParams;
/**
* Parameter list for &#064;OnWebSocketFrame
*/
private static final ParamList validFrameParams;
/**
* Parameter list for &#064;OnWebSocketMessage (Text mode)
*/
private static final ParamList validTextParams;
static
{
validConnectParams = new ParamList();
validConnectParams.addParams(Session.class);
validCloseParams = new ParamList();
validCloseParams.addParams(int.class,String.class);
validCloseParams.addParams(Session.class,int.class,String.class);
validErrorParams = new ParamList();
validErrorParams.addParams(Throwable.class);
validErrorParams.addParams(Session.class,Throwable.class);
validTextParams = new ParamList();
validTextParams.addParams(String.class);
validTextParams.addParams(Session.class,String.class);
validTextParams.addParams(Reader.class);
validTextParams.addParams(Session.class,Reader.class);
validBinaryParams = new ParamList();
validBinaryParams.addParams(byte[].class,int.class,int.class);
validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
validBinaryParams.addParams(InputStream.class);
validBinaryParams.addParams(Session.class,InputStream.class);
validFrameParams = new ParamList();
validFrameParams.addParams(Frame.class);
validFrameParams.addParams(Session.class,Frame.class);
}
private ConcurrentHashMap<Class<?>, EventMethods> cache;
private static final Logger LOG = Log.getLogger(EventDriverFactory.class);
private final WebSocketPolicy policy;
private final List<EventDriverImpl> implementations;
public EventDriverFactory(WebSocketPolicy policy)
{
this.policy = policy;
this.cache = new ConcurrentHashMap<>();
this.implementations = new ArrayList<>();
addImplementation(new JettyListenerImpl());
addImplementation(new JettyAnnotatedImpl());
}
private void assertIsPublicNonStatic(Method method)
public void addImplementation(EventDriverImpl impl)
{
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
if (implementations.contains(impl))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Method modifier must be public");
throw new InvalidWebSocketException(err.toString());
LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl);
return;
}
if (Modifier.isStatic(mods))
implementations.add(impl);
}
public List<EventDriverImpl> getImplementations()
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Method modifier may not be static");
throw new InvalidWebSocketException(err.toString());
}
return implementations;
}
private void assertIsReturn(Method method, Class<?> type)
public boolean removeImplementation(EventDriverImpl impl)
{
if (!type.equals(method.getReturnType()))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Return type must be ").append(type);
throw new InvalidWebSocketException(err.toString());
}
return this.implementations.remove(impl);
}
private void assertUnset(EventMethod event, Class<? extends Annotation> annoClass, Method method)
{
if (event != null)
{
// Attempt to add duplicate frame type (a no-no)
StringBuilder err = new StringBuilder();
err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
err.append(event.getMethod());
throw new InvalidWebSocketException(err.toString());
}
}
private void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
{
assertIsPublicNonStatic(method);
assertIsReturn(method,Void.TYPE);
boolean valid = false;
// validate parameters
Class<?> actual[] = method.getParameterTypes();
for (Class<?>[] params : validParams)
{
if (isSameParameters(actual,params))
{
valid = true;
break;
}
}
if (!valid)
{
throw InvalidSignatureException.build(method,annoClass,validParams);
}
}
/**
* Perform the basic discovery mechanism for WebSocket events from the provided pojo.
*
* @param pojo
* the pojo to scan
* @return the discovered event methods
* @throws InvalidWebSocketException
*/
private EventMethods discoverMethods(Class<?> pojo) throws InvalidWebSocketException
{
WebSocket anno = pojo.getAnnotation(WebSocket.class);
if (anno == null)
{
return null;
}
return scanAnnotatedMethods(pojo);
}
public EventMethods getMethods(Class<?> pojo) throws InvalidWebSocketException
{
if (pojo == null)
{
throw new InvalidWebSocketException("Cannot get methods for null class");
}
if (cache.containsKey(pojo))
{
return cache.get(pojo);
}
EventMethods methods = discoverMethods(pojo);
if (methods == null)
{
return null;
}
cache.put(pojo,methods);
return methods;
}
private boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
{
if (actual.length != params.length)
{
// skip
return false;
}
int len = params.length;
for (int i = 0; i < len; i++)
{
if (!actual[i].equals(params[i]))
{
return false; // not valid
}
}
return true;
}
private boolean isSignatureMatch(Method method, ParamList validParams)
{
assertIsPublicNonStatic(method);
assertIsReturn(method,Void.TYPE);
// validate parameters
Class<?> actual[] = method.getParameterTypes();
for (Class<?>[] params : validParams)
{
if (isSameParameters(actual,params))
{
return true;
}
}
return false;
}
private EventMethods scanAnnotatedMethods(Class<?> pojo)
{
Class<?> clazz = pojo;
EventMethods events = new EventMethods(pojo);
clazz = pojo;
while (clazz.getAnnotation(WebSocket.class) != null)
{
for (Method method : clazz.getDeclaredMethods())
{
if (method.getAnnotation(OnWebSocketConnect.class) != null)
{
assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
assertUnset(events.onConnect,OnWebSocketConnect.class,method);
events.onConnect = new EventMethod(pojo,method);
continue;
}
if (method.getAnnotation(OnWebSocketMessage.class) != null)
{
if (isSignatureMatch(method,validTextParams))
{
// Text mode
// TODO
assertUnset(events.onText,OnWebSocketMessage.class,method);
events.onText = new EventMethod(pojo,method);
continue;
}
if (isSignatureMatch(method,validBinaryParams))
{
// Binary Mode
// TODO
assertUnset(events.onBinary,OnWebSocketMessage.class,method);
events.onBinary = new EventMethod(pojo,method);
continue;
}
throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
}
if (method.getAnnotation(OnWebSocketClose.class) != null)
{
assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
assertUnset(events.onClose,OnWebSocketClose.class,method);
events.onClose = new EventMethod(pojo,method);
continue;
}
if (method.getAnnotation(OnWebSocketError.class) != null)
{
assertValidSignature(method,OnWebSocketError.class,validErrorParams);
assertUnset(events.onError,OnWebSocketError.class,method);
events.onError = new EventMethod(pojo,method);
continue;
}
if (method.getAnnotation(OnWebSocketFrame.class) != null)
{
assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
assertUnset(events.onFrame,OnWebSocketFrame.class,method);
events.onFrame = new EventMethod(pojo,method);
continue;
}
// Not a tagged method we are interested in, ignore
}
// try superclass now
clazz = clazz.getSuperclass();
}
return events;
}
@Override
public String toString()
{
return String.format("EventMethodsCache [cache.count=%d]",cache.size());
StringBuilder msg = new StringBuilder();
msg.append(this.getClass().getSimpleName());
msg.append("[implementations=[");
boolean delim = false;
for (EventDriverImpl impl : implementations)
{
if (delim)
{
msg.append(',');
}
msg.append(impl.toString());
delim = true;
}
msg.append("]");
return msg.toString();
}
/**
@ -368,21 +103,32 @@ public class EventDriverFactory
throw new InvalidWebSocketException("null websocket object");
}
if (websocket instanceof WebSocketListener)
for (EventDriverImpl impl : implementations)
{
WebSocketPolicy pojoPolicy = policy.clonePolicy();
WebSocketListener listener = (WebSocketListener)websocket;
return new ListenerEventDriver(pojoPolicy,listener);
if (impl.supports(websocket))
{
return impl.create(websocket,policy);
}
}
EventMethods methods = getMethods(websocket.getClass());
if (methods != null)
// Create a clear error message for the developer
StringBuilder err = new StringBuilder();
err.append(websocket.getClass().getName());
err.append(" is not a valid WebSocket object.");
err.append(" Object must obey one of the following rules: ");
int len = implementations.size();
for (int i = 0; i < len; i++)
{
WebSocketPolicy pojoPolicy = policy.clonePolicy();
return new AnnotatedEventDriver(pojoPolicy,websocket,methods);
EventDriverImpl impl = implementations.get(i);
if (i > 0)
{
err.append("or ");
}
err.append('(').append(i + 1).append(") ");
err.append(impl.describeRule());
}
throw new InvalidWebSocketException(websocket.getClass().getName() + " does not implement " + WebSocketListener.class.getName()
+ " or declare @WebSocket");
throw new InvalidWebSocketException(err.toString());
}
}

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.common.events;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
/**
* A specific implementation of a EventDriver.
*/
public interface EventDriverImpl
{
/**
* Create the EventDriver based on this implementation.
*
* @param websocket
* the websocket to wrap
* @param policy
* the policy to use
* @return the created EventDriver
*/
EventDriver create(Object websocket, WebSocketPolicy policy);
/**
* human readable string describing the rule that would support this EventDriver.
* <p>
* Used to help developer with possible object annotations, listeners, or base classes.
*
* @return the human readable description of this event driver rule(s).
*/
String describeRule();
/**
* Test for if this implementation can support the provided websocket.
*
* @param websocket
* the possible websocket to test
* @return true if implementation can support it, false if otherwise.
*/
boolean supports(Object websocket);
}

View File

@ -36,13 +36,13 @@ import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
/**
* Handler for Annotated User WebSocket objects.
*/
public class AnnotatedEventDriver extends EventDriver
public class JettyAnnotatedEventDriver extends AbstractEventDriver
{
private final EventMethods events;
private final JettyAnnotatedMetadata events;
private MessageAppender activeMessage;
private boolean hasCloseBeenCalled = false;
public AnnotatedEventDriver(WebSocketPolicy policy, Object websocket, EventMethods events)
public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events)
{
super(policy,websocket);
this.events = events;

View File

@ -0,0 +1,63 @@
//
// ========================================================================
// 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.common.events;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
public class JettyAnnotatedImpl implements EventDriverImpl
{
private ConcurrentHashMap<Class<?>, JettyAnnotatedMetadata> cache = new ConcurrentHashMap<>();
@Override
public EventDriver create(Object websocket, WebSocketPolicy policy)
{
Class<?> websocketClass = websocket.getClass();
JettyAnnotatedMetadata metadata = cache.get(websocketClass);
if (metadata == null)
{
JettyAnnotatedScanner scanner = new JettyAnnotatedScanner();
metadata = scanner.scan(websocketClass);
cache.put(websocketClass,metadata);
}
return new JettyAnnotatedEventDriver(policy,websocket,metadata);
}
@Override
public String describeRule()
{
return "class is annotated with @" + WebSocket.class.getName();
}
@Override
public boolean supports(Object websocket)
{
WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class);
return (anno != null);
}
@Override
public String toString()
{
return String.format("{} [cache.count=%d]",this.getClass().getSimpleName(),cache.size());
}
}

View File

@ -0,0 +1,53 @@
//
// ========================================================================
// 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.common.events;
import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
import org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod;
public class JettyAnnotatedMetadata
{
/** &#064;OnWebSocketConnect () */
public CallableMethod onConnect;
/** &#064;OnWebSocketMessage (byte[], or ByteBuffer, or InputStream) */
public OptionalSessionCallableMethod onBinary;
/** &#064;OnWebSocketMessage (String, or Reader) */
public OptionalSessionCallableMethod onText;
/** &#064;OnWebSocketFrame (Frame) */
public OptionalSessionCallableMethod onFrame;
/** &#064;OnWebSocketError (Throwable) */
public OptionalSessionCallableMethod onError;
/** &#064;OnWebSocketClose (Frame) */
public OptionalSessionCallableMethod onClose;
@Override
public String toString()
{
StringBuilder s = new StringBuilder();
s.append("JettyPojoMetadata[");
s.append("onConnect=").append(onConnect);
s.append(",onBinary=").append(onBinary);
s.append(",onText=").append(onText);
s.append(",onFrame=").append(onFrame);
s.append(",onError=").append(onError);
s.append(",onClose=").append(onClose);
s.append("]");
return s.toString();
}
}

View File

@ -0,0 +1,170 @@
//
// ========================================================================
// 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.common.events;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.extensions.Frame;
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.common.events.annotated.OptionalSessionCallableMethod;
public class JettyAnnotatedScanner extends AbstractMethodAnnotationScanner<JettyAnnotatedMetadata>
{
private static final Logger LOG = Log.getLogger(JettyAnnotatedScanner.class);
/**
* Parameter list for &#064;OnWebSocketMessage (Binary mode)
*/
private static final ParamList validBinaryParams;
/**
* Parameter list for &#064;OnWebSocketConnect
*/
private static final ParamList validConnectParams;
/**
* Parameter list for &#064;OnWebSocketClose
*/
private static final ParamList validCloseParams;
/**
* Parameter list for &#064;OnWebSocketError
*/
private static final ParamList validErrorParams;
/**
* Parameter list for &#064;OnWebSocketFrame
*/
private static final ParamList validFrameParams;
/**
* Parameter list for &#064;OnWebSocketMessage (Text mode)
*/
private static final ParamList validTextParams;
static
{
validConnectParams = new ParamList();
validConnectParams.addParams(Session.class);
validCloseParams = new ParamList();
validCloseParams.addParams(int.class,String.class);
validCloseParams.addParams(Session.class,int.class,String.class);
validErrorParams = new ParamList();
validErrorParams.addParams(Throwable.class);
validErrorParams.addParams(Session.class,Throwable.class);
validTextParams = new ParamList();
validTextParams.addParams(String.class);
validTextParams.addParams(Session.class,String.class);
validTextParams.addParams(Reader.class);
validTextParams.addParams(Session.class,Reader.class);
validBinaryParams = new ParamList();
validBinaryParams.addParams(byte[].class,int.class,int.class);
validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
validBinaryParams.addParams(InputStream.class);
validBinaryParams.addParams(Session.class,InputStream.class);
validFrameParams = new ParamList();
validFrameParams.addParams(Frame.class);
validFrameParams.addParams(Session.class,Frame.class);
}
@Override
public void onMethodAnnotation(JettyAnnotatedMetadata metadata, Class<?> pojo, Method method, Annotation annotation)
{
LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
if (isAnnotation(annotation,OnWebSocketConnect.class))
{
assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
assertUnset(metadata.onConnect,OnWebSocketConnect.class,method);
metadata.onConnect = new CallableMethod(pojo,method);
return;
}
if (isAnnotation(annotation,OnWebSocketMessage.class))
{
if (isSignatureMatch(method,validTextParams))
{
// Text mode
assertUnset(metadata.onText,OnWebSocketMessage.class,method);
metadata.onText = new OptionalSessionCallableMethod(pojo,method);
return;
}
if (isSignatureMatch(method,validBinaryParams))
{
// Binary Mode
// TODO
assertUnset(metadata.onBinary,OnWebSocketMessage.class,method);
metadata.onBinary = new OptionalSessionCallableMethod(pojo,method);
return;
}
throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
}
if (isAnnotation(annotation,OnWebSocketClose.class))
{
assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
assertUnset(metadata.onClose,OnWebSocketClose.class,method);
metadata.onClose = new OptionalSessionCallableMethod(pojo,method);
return;
}
if (isAnnotation(annotation,OnWebSocketError.class))
{
assertValidSignature(method,OnWebSocketError.class,validErrorParams);
assertUnset(metadata.onError,OnWebSocketError.class,method);
metadata.onError = new OptionalSessionCallableMethod(pojo,method);
return;
}
if (isAnnotation(annotation,OnWebSocketFrame.class))
{
assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
assertUnset(metadata.onFrame,OnWebSocketFrame.class,method);
metadata.onFrame = new OptionalSessionCallableMethod(pojo,method);
return;
}
}
public JettyAnnotatedMetadata scan(Class<?> pojo)
{
JettyAnnotatedMetadata metadata = new JettyAnnotatedMetadata();
scanMethodAnnotations(metadata,pojo);
return metadata;
}
}

View File

@ -34,14 +34,14 @@ import org.eclipse.jetty.websocket.common.message.SimpleTextMessage;
/**
* Handler for {@link WebSocketListener} based User WebSocket implementations.
*/
public class ListenerEventDriver extends EventDriver
public class JettyListenerEventDriver extends AbstractEventDriver
{
private static final Logger LOG = Log.getLogger(ListenerEventDriver.class);
private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class);
private final WebSocketListener listener;
private MessageAppender activeMessage;
private boolean hasCloseBeenCalled = false;
public ListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener)
public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketListener listener)
{
super(policy,listener);
this.listener = listener;

View File

@ -0,0 +1,44 @@
//
// ========================================================================
// 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.common.events;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
public class JettyListenerImpl implements EventDriverImpl
{
@Override
public EventDriver create(Object websocket, WebSocketPolicy policy)
{
WebSocketListener listener = (WebSocketListener)websocket;
return new JettyListenerEventDriver(policy,listener);
}
@Override
public String describeRule()
{
return "class implements " + WebSocketListener.class.getName();
}
@Override
public boolean supports(Object websocket)
{
return (websocket instanceof WebSocketListener);
}
}

View File

@ -0,0 +1,195 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events.annotated;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.events.ParamList;
/**
* Basic scanner for Annotated Methods
*/
public abstract class AbstractMethodAnnotationScanner<T>
{
protected void assertIsPublicNonStatic(Method method)
{
int mods = method.getModifiers();
if (!Modifier.isPublic(mods))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Method modifier must be public");
throw new InvalidWebSocketException(err.toString());
}
if (Modifier.isStatic(mods))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Method modifier may not be static");
throw new InvalidWebSocketException(err.toString());
}
}
protected void assertIsReturn(Method method, Class<?> type)
{
if (!type.equals(method.getReturnType()))
{
StringBuilder err = new StringBuilder();
err.append("Invalid declaration of ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("Return type must be ").append(type);
throw new InvalidWebSocketException(err.toString());
}
}
protected void assertIsVoidReturn(Method method)
{
assertIsReturn(method,Void.TYPE);
}
protected void assertUnset(CallableMethod callable, Class<? extends Annotation> annoClass, Method method)
{
if (callable != null)
{
// Attempt to add duplicate frame type (a no-no)
StringBuilder err = new StringBuilder();
err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
err.append(method);
err.append(StringUtil.__LINE_SEPARATOR);
err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
err.append(callable.getMethod());
throw new InvalidWebSocketException(err.toString());
}
}
protected void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
{
assertIsPublicNonStatic(method);
assertIsReturn(method,Void.TYPE);
boolean valid = false;
// validate parameters
Class<?> actual[] = method.getParameterTypes();
for (Class<?>[] params : validParams)
{
if (isSameParameters(actual,params))
{
valid = true;
break;
}
}
if (!valid)
{
throw InvalidSignatureException.build(method,annoClass,validParams);
}
}
public boolean isAnnotation(Annotation annotation, Class<? extends Annotation> annotationClass)
{
return annotation.annotationType().equals(annotationClass);
}
public boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
{
if (actual.length != params.length)
{
// skip
return false;
}
int len = params.length;
for (int i = 0; i < len; i++)
{
if (!actual[i].equals(params[i]))
{
return false; // not valid
}
}
return true;
}
protected boolean isSignatureMatch(Method method, ParamList validParams)
{
assertIsPublicNonStatic(method);
assertIsReturn(method,Void.TYPE);
// validate parameters
Class<?> actual[] = method.getParameterTypes();
for (Class<?>[] params : validParams)
{
if (isSameParameters(actual,params))
{
return true;
}
}
return false;
}
protected boolean isTypeAnnotated(Class<?> pojo, Class<? extends Annotation> expectedAnnotation)
{
return pojo.getAnnotation(expectedAnnotation) != null;
}
public abstract void onMethodAnnotation(T metadata, Class<?> pojo, Method method, Annotation annotation);
public void scanMethodAnnotations(T metadata, Class<?> pojo)
{
Class<?> clazz = pojo;
while ((clazz != null) && Object.class.isAssignableFrom(clazz))
{
for (Method method : clazz.getDeclaredMethods())
{
Annotation annotations[] = method.getAnnotations();
if ((annotations == null) || (annotations.length <= 0))
{
continue; // skip
}
for (Annotation annotation : annotations)
{
onMethodAnnotation(metadata,clazz,method,annotation);
}
}
clazz = clazz.getSuperclass();
}
}
}

View File

@ -0,0 +1,96 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events.annotated;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketException;
/**
* A Callable Method
*/
public class CallableMethod
{
private static final Logger LOG = Log.getLogger(CallableMethod.class);
protected final Class<?> pojo;
protected final Method method;
protected Class<?>[] paramTypes;
public CallableMethod(Class<?> pojo, Method method)
{
this.pojo = pojo;
this.method = method;
this.paramTypes = method.getParameterTypes();
}
public void call(Object obj, Object... args)
{
if ((this.pojo == null) || (this.method == null))
{
LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method);
return; // no call event method determined
}
if (obj == null)
{
LOG.warn("Cannot call {} on null object",this.method);
return;
}
if (args.length < paramTypes.length)
{
throw new IllegalArgumentException("Call arguments length [" + args.length + "] must always be greater than or equal to captured args length ["
+ paramTypes.length + "]");
}
try
{
this.method.invoke(obj,args);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
String err = String.format("Cannot call method %s on %s with args: %s",method,pojo,args);
throw new WebSocketException(err,e);
}
}
public Method getMethod()
{
return method;
}
public Class<?>[] getParamTypes()
{
return paramTypes;
}
public Class<?> getPojo()
{
return pojo;
}
@Override
public String toString()
{
return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events;
package org.eclipse.jetty.websocket.common.events.annotated;
import java.io.InputStream;
import java.io.Reader;
@ -108,7 +108,7 @@ public class EventMethod
}
}
protected Method getMethod()
public Method getMethod()
{
return method;
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events;
package org.eclipse.jetty.websocket.common.events.annotated;
/**
* A representation of the methods available to call for a particular class.

View File

@ -16,13 +16,14 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events;
package org.eclipse.jetty.websocket.common.events.annotated;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.events.ParamList;
@SuppressWarnings("serial")
public class InvalidSignatureException extends InvalidWebSocketException

View File

@ -0,0 +1,91 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.events.annotated;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import org.eclipse.jetty.websocket.api.Session;
/**
* Simple CallableMethod that manages the optional {@link Session} argument
*/
public class OptionalSessionCallableMethod extends CallableMethod
{
private final boolean wantsSession;
private final boolean streaming;
public OptionalSessionCallableMethod(Class<?> pojo, Method method)
{
super(pojo,method);
boolean foundConnection = false;
boolean foundStreaming = false;
if (paramTypes != null)
{
for (Class<?> paramType : paramTypes)
{
if (Session.class.isAssignableFrom(paramType))
{
foundConnection = true;
}
if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType))
{
foundStreaming = true;
}
}
}
this.wantsSession = foundConnection;
this.streaming = foundStreaming;
}
public void call(Object obj, Session connection, Object... args)
{
if (wantsSession)
{
Object fullArgs[] = new Object[args.length + 1];
fullArgs[0] = connection;
System.arraycopy(args,0,fullArgs,1,args.length);
call(obj,fullArgs);
}
else
{
call(obj,args);
}
}
public boolean isSessionAware()
{
return wantsSession;
}
public boolean isStreaming()
{
return streaming;
}
@Override
public String toString()
{
return String.format("%s[%s]",this.getClass().getSimpleName(),method.toGenericString());
}
}

View File

@ -23,7 +23,7 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver;
import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver;
/**
* Support class for reading binary message data as an InputStream.
@ -35,14 +35,14 @@ public class MessageInputStream extends InputStream implements MessageAppender
* Threshold (of bytes) to perform compaction at
*/
private static final int COMPACT_THRESHOLD = 5;
private final AnnotatedEventDriver driver;
private final JettyAnnotatedEventDriver driver;
private final ByteBuffer buf;
private int size;
private boolean finished;
private boolean needsNotification;
private int readPosition;
public MessageInputStream(AnnotatedEventDriver driver)
public MessageInputStream(JettyAnnotatedEventDriver driver)
{
this.driver = driver;
this.buf = ByteBuffer.allocate(BUFFER_SIZE);

View File

@ -23,7 +23,7 @@ import java.io.Reader;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver;
import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver;
/**
* Support class for reading text message data as an Reader.
@ -32,13 +32,13 @@ import org.eclipse.jetty.websocket.common.events.AnnotatedEventDriver;
*/
public class MessageReader extends Reader implements MessageAppender
{
private final AnnotatedEventDriver driver;
private final JettyAnnotatedEventDriver driver;
private final Utf8StringBuilder utf;
private int size;
private boolean finished;
private boolean needsNotification;
public MessageReader(AnnotatedEventDriver driver)
public MessageReader(JettyAnnotatedEventDriver driver)
{
this.driver = driver;
this.utf = new Utf8StringBuilder();

View File

@ -24,41 +24,15 @@ import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket;
import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket;
import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket;
import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket;
import org.eclipse.jetty.websocket.common.annotations.FrameSocket;
import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket;
import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket;
import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket;
import org.eclipse.jetty.websocket.common.annotations.NoopSocket;
import org.eclipse.jetty.websocket.common.annotations.NotASocket;
import org.junit.Assert;
import org.junit.Test;
import examples.AdapterConnectCloseSocket;
import examples.AnnotatedBinaryArraySocket;
import examples.AnnotatedBinaryStreamSocket;
import examples.AnnotatedTextSocket;
import examples.AnnotatedTextStreamSocket;
import examples.ListenerBasicSocket;
public class EventDriverFactoryTest
{
private void assertHasEventMethod(String message, EventMethod actual)
{
Assert.assertThat(message + " EventMethod",actual,notNullValue());
Assert.assertThat(message + " EventMethod.pojo",actual.pojo,notNullValue());
Assert.assertThat(message + " EventMethod.method",actual.method,notNullValue());
}
private void assertNoEventMethod(String message, EventMethod actual)
{
Assert.assertThat(message + "Event method",actual,nullValue());
}
/**
* Test Case for no exceptions and 5 methods (extends WebSocketAdapter)
*/
@ -70,291 +44,7 @@ public class EventDriverFactoryTest
EventDriver driver = factory.wrap(socket);
String classId = AdapterConnectCloseSocket.class.getSimpleName();
Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class));
}
/**
* Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
*/
@Test
public void testAnnotatedBadDuplicateBinarySocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
try
{
// Should toss exception
factory.getMethods(BadDuplicateBinarySocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration"));
}
}
/**
* Test Case for bad declaration (duplicate frame type methods)
*/
@Test
public void testAnnotatedBadDuplicateFrameSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
try
{
// Should toss exception
factory.getMethods(BadDuplicateFrameSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame"));
}
}
/**
* Test Case for bad declaration a method with a non-void return type
*/
@Test
public void testAnnotatedBadSignature_NonVoidReturn()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
try
{
// Should toss exception
factory.getMethods(BadBinarySignatureSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("must be void"));
}
}
/**
* Test Case for bad declaration a method with a public static method
*/
@Test
public void testAnnotatedBadSignature_Static()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
try
{
// Should toss exception
factory.getMethods(BadTextSignatureSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("may not be static"));
}
}
/**
* Test Case for socket for binary array messages
*/
@Test
public void testAnnotatedBinaryArraySocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(AnnotatedBinaryArraySocket.class);
String classId = AnnotatedBinaryArraySocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertHasEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertNoEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession());
Assert.assertFalse(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming());
}
/**
* Test Case for socket for binary stream messages
*/
@Test
public void testAnnotatedBinaryStreamSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(AnnotatedBinaryStreamSocket.class);
String classId = AnnotatedBinaryStreamSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertHasEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertNoEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
Assert.assertFalse(classId + ".onBinary.hasSession",methods.onBinary.isHasSession());
Assert.assertTrue(classId + ".onBinary.isStreaming",methods.onBinary.isStreaming());
}
/**
* Test Case for no exceptions and 4 methods (3 methods from parent)
*/
@Test
public void testAnnotatedMyEchoBinarySocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(MyEchoBinarySocket.class);
String classId = MyEchoBinarySocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertHasEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertHasEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
}
/**
* Test Case for no exceptions and 3 methods
*/
@Test
public void testAnnotatedMyEchoSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(MyEchoSocket.class);
String classId = MyEchoSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertHasEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
}
/**
* Test Case for annotated for text messages w/connection param
*/
@Test
public void testAnnotatedMyStatelessEchoSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(MyStatelessEchoSocket.class);
String classId = MyStatelessEchoSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertNoEventMethod(classId + ".onClose",methods.onClose);
assertNoEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertHasEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
Assert.assertTrue(classId + ".onText.hasSession",methods.onText.isHasSession());
Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming());
}
/**
* Test Case for no exceptions and no methods
*/
@Test
public void testAnnotatedNoop()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(NoopSocket.class);
String classId = NoopSocket.class.getSimpleName();
Assert.assertThat("Methods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertNoEventMethod(classId + ".onClose",methods.onClose);
assertNoEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertNoEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
}
/**
* Test Case for no exceptions and 1 methods
*/
@Test
public void testAnnotatedOnFrame()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(FrameSocket.class);
String classId = FrameSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertNoEventMethod(classId + ".onClose",methods.onClose);
assertNoEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertNoEventMethod(classId + ".onText",methods.onText);
assertHasEventMethod(classId + ".onFrame",methods.onFrame);
}
/**
* Test Case for socket for simple text messages
*/
@Test
public void testAnnotatedTextSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(AnnotatedTextSocket.class);
String classId = AnnotatedTextSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertHasEventMethod(classId + ".onException",methods.onError);
assertHasEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession());
Assert.assertFalse(classId + ".onText.isStreaming",methods.onText.isStreaming());
}
/**
* Test Case for socket for text stream messages
*/
@Test
public void testAnnotatedTextStreamSocket()
{
EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy());
EventMethods methods = factory.getMethods(AnnotatedTextStreamSocket.class);
String classId = AnnotatedTextStreamSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,methods,notNullValue());
assertNoEventMethod(classId + ".onBinary",methods.onBinary);
assertHasEventMethod(classId + ".onClose",methods.onClose);
assertHasEventMethod(classId + ".onConnect",methods.onConnect);
assertNoEventMethod(classId + ".onException",methods.onError);
assertHasEventMethod(classId + ".onText",methods.onText);
assertNoEventMethod(classId + ".onFrame",methods.onFrame);
Assert.assertFalse(classId + ".onText.hasSession",methods.onText.isHasSession());
Assert.assertTrue(classId + ".onText.isStreaming",methods.onText.isStreaming());
Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
}
/**
@ -388,6 +78,6 @@ public class EventDriverFactoryTest
EventDriver driver = factory.wrap(socket);
String classId = ListenerBasicSocket.class.getSimpleName();
Assert.assertThat("EventDriver for " + classId,driver,instanceOf(ListenerEventDriver.class));
Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class));
}
}

View File

@ -0,0 +1,340 @@
//
// ========================================================================
// 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.common.events;
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.common.annotations.BadBinarySignatureSocket;
import org.eclipse.jetty.websocket.common.annotations.BadDuplicateBinarySocket;
import org.eclipse.jetty.websocket.common.annotations.BadDuplicateFrameSocket;
import org.eclipse.jetty.websocket.common.annotations.BadTextSignatureSocket;
import org.eclipse.jetty.websocket.common.annotations.FrameSocket;
import org.eclipse.jetty.websocket.common.annotations.MyEchoBinarySocket;
import org.eclipse.jetty.websocket.common.annotations.MyEchoSocket;
import org.eclipse.jetty.websocket.common.annotations.MyStatelessEchoSocket;
import org.eclipse.jetty.websocket.common.annotations.NoopSocket;
import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod;
import org.junit.Assert;
import org.junit.Test;
import examples.AnnotatedBinaryArraySocket;
import examples.AnnotatedBinaryStreamSocket;
import examples.AnnotatedTextSocket;
import examples.AnnotatedTextStreamSocket;
public class JettyAnnotatedScannerTest
{
private void assertHasEventMethod(String message, CallableMethod actual)
{
Assert.assertThat(message + " CallableMethod",actual,notNullValue());
Assert.assertThat(message + " CallableMethod.pojo",actual.getPojo(),notNullValue());
Assert.assertThat(message + " CallableMethod.method",actual.getMethod(),notNullValue());
}
private void assertNoEventMethod(String message, CallableMethod actual)
{
Assert.assertThat(message + " CallableMethod",actual,nullValue());
}
/**
* Test Case for bad declaration (duplicate OnWebSocketBinary declarations)
*/
@Test
public void testAnnotatedBadDuplicateBinarySocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
try
{
// Should toss exception
impl.scan(BadDuplicateBinarySocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketMessage declaration"));
}
}
/**
* Test Case for bad declaration (duplicate frame type methods)
*/
@Test
public void testAnnotatedBadDuplicateFrameSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
try
{
// Should toss exception
impl.scan(BadDuplicateFrameSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("Duplicate @OnWebSocketFrame"));
}
}
/**
* Test Case for bad declaration a method with a non-void return type
*/
@Test
public void testAnnotatedBadSignature_NonVoidReturn()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
try
{
// Should toss exception
impl.scan(BadBinarySignatureSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("must be void"));
}
}
/**
* Test Case for bad declaration a method with a public static method
*/
@Test
public void testAnnotatedBadSignature_Static()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
try
{
// Should toss exception
impl.scan(BadTextSignatureSocket.class);
Assert.fail("Should have thrown " + InvalidWebSocketException.class);
}
catch (InvalidWebSocketException e)
{
// Validate that we have clear error message to the developer
Assert.assertThat(e.getMessage(),containsString("may not be static"));
}
}
/**
* Test Case for socket for binary array messages
*/
@Test
public void testAnnotatedBinaryArraySocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryArraySocket.class);
String classId = AnnotatedBinaryArraySocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertNoEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
Assert.assertFalse(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
}
/**
* Test Case for socket for binary stream messages
*/
@Test
public void testAnnotatedBinaryStreamSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(AnnotatedBinaryStreamSocket.class);
String classId = AnnotatedBinaryStreamSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertNoEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
Assert.assertFalse(classId + ".onBinary.isSessionAware",metadata.onBinary.isSessionAware());
Assert.assertTrue(classId + ".onBinary.isStreaming",metadata.onBinary.isStreaming());
}
/**
* Test Case for no exceptions and 4 methods (3 methods from parent)
*/
@Test
public void testAnnotatedMyEchoBinarySocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(MyEchoBinarySocket.class);
String classId = MyEchoBinarySocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertHasEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertHasEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
}
/**
* Test Case for no exceptions and 3 methods
*/
@Test
public void testAnnotatedMyEchoSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(MyEchoSocket.class);
String classId = MyEchoSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertHasEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
}
/**
* Test Case for annotated for text messages w/connection param
*/
@Test
public void testAnnotatedMyStatelessEchoSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(MyStatelessEchoSocket.class);
String classId = MyStatelessEchoSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertNoEventMethod(classId + ".onClose",metadata.onClose);
assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertHasEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
Assert.assertTrue(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
}
/**
* Test Case for no exceptions and no methods
*/
@Test
public void testAnnotatedNoop()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(NoopSocket.class);
String classId = NoopSocket.class.getSimpleName();
Assert.assertThat("Methods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertNoEventMethod(classId + ".onClose",metadata.onClose);
assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertNoEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
}
/**
* Test Case for no exceptions and 1 methods
*/
@Test
public void testAnnotatedOnFrame()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(FrameSocket.class);
String classId = FrameSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertNoEventMethod(classId + ".onClose",metadata.onClose);
assertNoEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertNoEventMethod(classId + ".onText",metadata.onText);
assertHasEventMethod(classId + ".onFrame",metadata.onFrame);
}
/**
* Test Case for socket for simple text messages
*/
@Test
public void testAnnotatedTextSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextSocket.class);
String classId = AnnotatedTextSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertHasEventMethod(classId + ".onException",metadata.onError);
assertHasEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
Assert.assertFalse(classId + ".onText.isStreaming",metadata.onText.isStreaming());
}
/**
* Test Case for socket for text stream messages
*/
@Test
public void testAnnotatedTextStreamSocket()
{
JettyAnnotatedScanner impl = new JettyAnnotatedScanner();
JettyAnnotatedMetadata metadata = impl.scan(AnnotatedTextStreamSocket.class);
String classId = AnnotatedTextStreamSocket.class.getSimpleName();
Assert.assertThat("EventMethods for " + classId,metadata,notNullValue());
assertNoEventMethod(classId + ".onBinary",metadata.onBinary);
assertHasEventMethod(classId + ".onClose",metadata.onClose);
assertHasEventMethod(classId + ".onConnect",metadata.onConnect);
assertNoEventMethod(classId + ".onException",metadata.onError);
assertHasEventMethod(classId + ".onText",metadata.onText);
assertNoEventMethod(classId + ".onFrame",metadata.onFrame);
Assert.assertFalse(classId + ".onText.isSessionAware",metadata.onText.isSessionAware());
Assert.assertTrue(classId + ".onText.isStreaming",metadata.onText.isStreaming());
}
}

View File

@ -244,6 +244,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
return this.creator;
}
public EventDriverFactory getEventDriverFactory()
{
return eventDriverFactory;
}
@Override
public ExtensionFactory getExtensionFactory()
{