Author: markt Date: Sat Mar 9 19:14:18 2013 New Revision: 1454758 URL: http://svn.apache.org/r1454758 Log: Get encoding and decoding working end to end with an associated unit test
Added: tomcat/trunk/test/org/apache/tomcat/websocket/pojo/ tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java (with props) Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat/trunk/java/org/apache/tomcat/websocket/Util.java tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java tomcat/trunk/java/org/apache/tomcat/websocket/server/LocalStrings.properties tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Sat Mar 9 19:14:18 2013 @@ -62,5 +62,6 @@ wsWebSocketContainer.invalidHeader=Unabl wsWebSocketContainer.invalidStatus=The HTTP response from the server [{0}] did not permit the HTTP upgrade to WebSocket wsWebSocketContainer.invalidSubProtocol=The WebSocket server returned multiple values for the Sec-WebSocket-Protocol header wsWebSocketContainer.maxBuffer=This implementation limits the maximum size of a buffer to Integer.MAX_VALUE +wsWebSocketContainer.missingAnnotation=Cannot use POJO class [{0}] as it is not annotated with @ClientEndpoint wsWebSocketContainer.pathNoHost=No host was specified in URI wsWebSocketContainer.pathWrongScheme=The scheme [{0}] is not supported \ No newline at end of file Modified: tomcat/trunk/java/org/apache/tomcat/websocket/Util.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/Util.java?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/Util.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/Util.java Sat Mar 9 19:14:18 2013 @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentLi import javax.websocket.CloseReason.CloseCode; import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.Decoder; import javax.websocket.Encoder; import javax.websocket.MessageHandler; @@ -33,7 +34,7 @@ import javax.websocket.MessageHandler; * Utility class for internal use only within the * {@link org.apache.tomcat.websocket} package. */ -class Util { +public class Util { private static final Queue<SecureRandom> randoms = new ConcurrentLinkedQueue<>(); @@ -142,6 +143,11 @@ class Util { } + public static Class<?> getDecoderType(Class<? extends Decoder> Decoder) { + return (Class<?>) Util.getGenericType(Decoder.class, Decoder); + } + + static Class<?> getEncoderType(Class<? extends Encoder> encoder) { return (Class<?>) Util.getGenericType(Encoder.class, encoder); } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsWebSocketContainer.java Sat Mar 9 19:14:18 2013 @@ -24,6 +24,7 @@ import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -80,7 +81,15 @@ public class WsWebSocketContainer public Session connectToServer(Object pojo, URI path) throws DeploymentException { - Endpoint ep = new PojoEndpointClient(pojo); + ClientEndpoint annotation = + pojo.getClass().getAnnotation(ClientEndpoint.class); + if (annotation == null) { + throw new DeploymentException( + sm.getString("wsWebSocketContainer.missingAnnotation", + pojo.getClass().getName())); + } + + Endpoint ep = new PojoEndpointClient(pojo, annotation.decoders()); Class<? extends ClientEndpointConfig.Configurator> configuratorClazz = pojo.getClass().getAnnotation( @@ -97,9 +106,11 @@ public class WsWebSocketContainer } } - ClientEndpointConfig config = - ClientEndpointConfig.Builder.create().configurator( - configurator).build(); + ClientEndpointConfig config = ClientEndpointConfig.Builder.create(). + configurator(configurator). + decoders(Arrays.asList(annotation.decoders())). + encoders(Arrays.asList(annotation.encoders())). + build(); return connectToServer(ep, config, path); } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoEndpointClient.java Sat Mar 9 19:14:18 2013 @@ -18,15 +18,19 @@ package org.apache.tomcat.websocket.pojo import java.util.Collections; +import javax.websocket.Decoder; +import javax.websocket.DeploymentException; import javax.websocket.EndpointConfig; import javax.websocket.Session; public class PojoEndpointClient extends PojoEndpointBase { - public PojoEndpointClient(Object pojo) { + public PojoEndpointClient(Object pojo, + Class<? extends Decoder>[] decoders) throws DeploymentException { setPojo(pojo); - setMethodMapping(new PojoMethodMapping(pojo.getClass(), null)); + setMethodMapping( + new PojoMethodMapping(pojo.getClass(), decoders, null)); setPathParameters(Collections.EMPTY_MAP); } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/pojo/PojoMethodMapping.java Sat Mar 9 19:14:18 2013 @@ -19,11 +19,17 @@ package org.apache.tomcat.websocket.pojo import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.websocket.Decoder; +import javax.websocket.Decoder.Binary; +import javax.websocket.Decoder.BinaryStream; +import javax.websocket.DeploymentException; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.OnClose; @@ -34,6 +40,9 @@ import javax.websocket.PongMessage; import javax.websocket.Session; import javax.websocket.server.PathParam; +import org.apache.tomcat.util.res.StringManager; +import org.apache.tomcat.websocket.Util; + /** * For a POJO class annotated with * {@link javax.websocket.server.ServerEndpoint}, an instance of this class @@ -42,6 +51,9 @@ import javax.websocket.server.PathParam; */ public class PojoMethodMapping { + private static final StringManager sm = + StringManager.getManager(Constants.PACKAGE_NAME); + private final Method onOpen; private final Method onClose; private final Method onError; @@ -52,8 +64,13 @@ public class PojoMethodMapping { private final String wsPath; - public PojoMethodMapping(Class<?> clazzPojo, String wsPath) { + public PojoMethodMapping(Class<?> clazzPojo, + Class<? extends Decoder>[] decoderClazzes, String wsPath) + throws DeploymentException { + this.wsPath = wsPath; + + List<DecoderEntry> decoders = getDecoders(decoderClazzes); Method open = null; Method close = null; Method error = null; @@ -68,7 +85,7 @@ public class PojoMethodMapping { method.getAnnotation(OnError.class) != null) { error = method; } else if (method.getAnnotation(OnMessage.class) != null) { - onMessage.add(new MessageMethod(method)); + onMessage.add(new MessageMethod(method, decoders)); } } this.onOpen = open; @@ -130,6 +147,29 @@ public class PojoMethodMapping { } + private static List<DecoderEntry> getDecoders( + Class<? extends Decoder>[] decoderClazzes) + throws DeploymentException{ + + List<DecoderEntry> result = new ArrayList<>(); + for (Class<? extends Decoder> decoderClazz : decoderClazzes) { + Decoder instance; + try { + instance = decoderClazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new DeploymentException( + sm.getString("wsRemoteEndpoint.invalidEncoder", + decoderClazz.getName()), e); + } + DecoderEntry entry = new DecoderEntry( + Util.getDecoderType(decoderClazz), instance); + result.add(entry); + } + + return result; + } + + private static PojoPathParam[] getPathParams(Method m, boolean isError) { if (m == null) { return new PojoPathParam[0]; @@ -232,7 +272,7 @@ public class PojoMethodMapping { private int indexPayload = -1; - public MessageMethod(Method m) { + public MessageMethod(Method m, List<DecoderEntry> decoderEntries) { this.m = m; Class<?>[] types = m.getParameterTypes(); @@ -291,6 +331,31 @@ public class PojoMethodMapping { // TODO i18n throw new IllegalArgumentException(); } + } else { + for (DecoderEntry decoderEntry : decoderEntries) { + if (decoderEntry.getClazz().isAssignableFrom( + types[i])) { + if (Binary.class.isAssignableFrom( + decoderEntry.getDecoder().getClass()) || + BinaryStream.class.isAssignableFrom( + decoderEntry.getDecoder().getClass())) { + if (indexByteBuffer == -1) { + indexByteBuffer = i; + } else { + // TODO i18n + throw new IllegalArgumentException(); + } + break; + } else { + if (indexString == -1) { + indexString = i; + } else { + // TODO i18n + throw new IllegalArgumentException(); + } + } + } + } } } // Additional checks required @@ -384,4 +449,24 @@ public class PojoMethodMapping { return mh; } } + + + private static class DecoderEntry { + + private final Class<?> clazz; + private final Decoder decoder; + + public DecoderEntry(Class<?> clazz, Decoder decoder) { + this.clazz = clazz; + this.decoder = decoder; + } + + public Class<?> getClazz() { + return clazz; + } + + public Decoder getDecoder() { + return decoder; + } + } } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/LocalStrings.properties?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/server/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/server/LocalStrings.properties Sat Mar 9 19:14:18 2013 @@ -13,12 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. sci.newInstance.fail=Failed to create an Endpoint instance of type [{0}] + +serverContainer.configuratorFail=Failed to create configurator of type [{0}] for POJO of type [{1}] serverContainer.endpointDeploy=Endpoint class [{0}] deploying to path [{1}] in ServletContext [{2}] serverContainer.missingAnnotation=Cannot deploy POJO class [{0}] as it is not annotated with @ServerEndpoint serverContainer.missingEndpoint=An Endpoint instance has been request for path [{0}] but no matching Endpoint class was found serverContainer.pojoDeploy=POJO class [{0}] deploying to path [{1}] in ServletContext [{2}] serverContainer.servletContextMismatch=Attempted to register a POJO annotated for WebSocket at path [{0}] in the ServletContext with context path [{1}] when the WebSocket ServerContainer is allocated to the ServletContext with context path [{2}] serverContainer.servletContextMissing=No ServletContext was specified + uriTemplate.noMatch=The input template [{0}] generated the pattern [{1}] which did not match the supplied pathInfo [{2}] + wsProtocolHandler.closeFailed=Failed to close the WebSocket connection cleanly + wsRemoteEndpointServer.closeFailed=Failed to close the ServletOutputStream connection cleanly \ No newline at end of file Modified: tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java?rev=1454758&r1=1454757&r2=1454758&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/server/WsServerContainer.java Sat Mar 9 19:14:18 2013 @@ -16,6 +16,7 @@ */ package org.apache.tomcat.websocket.server; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.WeakHashMap; @@ -27,6 +28,7 @@ import javax.websocket.DeploymentExcepti import javax.websocket.server.ServerContainer; import javax.websocket.server.ServerEndpoint; import javax.websocket.server.ServerEndpointConfig; +import javax.websocket.server.ServerEndpointConfig.Configurator; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -181,7 +183,8 @@ public class WsServerContainer extends W } pojoMap.put(mapPath, pojo); - pojoMethodMap.put(pojo, new PojoMethodMapping(pojo, wsPath)); + pojoMethodMap.put(pojo, + new PojoMethodMapping(pojo, annotation.decoders(), wsPath)); addWsServletMapping(servletPath); } @@ -205,14 +208,31 @@ public class WsServerContainer extends W } Class<?> pojo = pojoMap.get(servletPath); if (pojo != null) { + ServerEndpoint annotation = + pojo.getAnnotation(ServerEndpoint.class); PojoMethodMapping methodMapping = pojoMethodMap.get(pojo); if (methodMapping != null) { + Configurator configurator; + try { + configurator = annotation.configurator().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException(sm.getString( + "serverContainer.configuratorFail", + annotation.configurator().getName(), + pojo.getClass().getName()), e); + } sec = ServerEndpointConfig.Builder.create( - pojo, methodMapping.getWsPath()).build(); + pojo, methodMapping.getWsPath()). + decoders(Arrays.asList(annotation.decoders())). + encoders(Arrays.asList(annotation.encoders())). + configurator(configurator). + build(); sec.getUserProperties().put( - PojoEndpointServer.POJO_PATH_PARAM_KEY, pathParameters); + PojoEndpointServer.POJO_PATH_PARAM_KEY, + pathParameters); sec.getUserProperties().put( - PojoEndpointServer.POJO_METHOD_MAPPING_KEY, methodMapping); + PojoEndpointServer.POJO_METHOD_MAPPING_KEY, + methodMapping); return sec; } } Added: tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java?rev=1454758&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java (added) +++ tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java Sat Mar 9 19:14:18 2013 @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.websocket.pojo; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.servlet.ServletContextEvent; +import javax.websocket.ClientEndpoint; +import javax.websocket.ContainerProvider; +import javax.websocket.DecodeException; +import javax.websocket.Decoder; +import javax.websocket.DeploymentException; +import javax.websocket.EncodeException; +import javax.websocket.Encoder; +import javax.websocket.OnMessage; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig.Configurator; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.websocket.server.WsListener; +import org.apache.tomcat.websocket.server.WsServerContainer; + +public class TestEncodingDecoding extends TomcatBaseTest { + + private static final String MESSAGE_ONE = "message-one"; + + @Test + public void test() throws Exception { + Tomcat tomcat = getTomcatInstance(); + // Must have a real docBase - just use temp + Context ctx = + tomcat.addContext("", System.getProperty("java.io.tmpdir")); + ctx.addApplicationListener(ServerConfigListener.class.getName()); + + WebSocketContainer wsContainer = + ContainerProvider.getWebSocketContainer(); + + tomcat.start(); + + Client client = new Client(); + URI uri = new URI("http://localhost:" + getPort() + "/"); + Session session = wsContainer.connectToServer(client, uri); + + MsgString msg1 = new MsgString(); + msg1.setData(MESSAGE_ONE); + session.getBasicRemote().sendObject(msg1); + + Server server = ServerConfigurator.getServerInstance(); + + // Should not take very long + int i = 0; + while (i < 20) { + if (server.received.size() > 0 && client.received.size() > 0) { + break; + } + Thread.sleep(100); + } + + // Check messages were received + Assert.assertEquals(1, server.received.size()); + Assert.assertEquals(1, client.received.size()); + + // Check correct messages were received + Assert.assertEquals(MESSAGE_ONE, + ((MsgString) server.received.peek()).getData()); + Assert.assertEquals(MESSAGE_ONE, + ((MsgString) client.received.peek()).getData()); + } + + @ClientEndpoint(decoders={MsgStringDecoder.class, MsgByteDecoder.class}, + encoders={MsgStringEncoder.class, MsgByteEncoder.class}) + public static class Client { + private Queue<Object> received = new ConcurrentLinkedQueue<>(); + + @OnMessage + public void rx(MsgString in) { + received.add(in); + } + + @OnMessage + public void rx(MsgByte in) { + received.add(in); + } + } + + + @ServerEndpoint(value="/", + decoders={MsgStringDecoder.class, MsgByteDecoder.class}, + encoders={MsgStringEncoder.class, MsgByteEncoder.class}, + configurator=ServerConfigurator.class) + public static class Server { + private Queue<Object> received = new ConcurrentLinkedQueue<>(); + + public Server() { + System.out.println("Server created"); + } + + @OnMessage + public MsgString rx(MsgString in) { + received.add(in); + // Echo the message back + return in; + } + + @OnMessage + public MsgByte rx(MsgByte in) { + received.add(in); + // Echo the message back + return in; + } + } + + + public static class ServerConfigurator extends Configurator { + + private static final Server server = new Server(); + + @Override + public <T> T getEndpointInstance(Class<T> clazz) + throws InstantiationException { + @SuppressWarnings("unchecked") + T result = (T) server; + return result; + } + + public static Server getServerInstance() { + return server; + } + } + + public static class ServerConfigListener extends WsListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + super.contextInitialized(sce); + WsServerContainer sc = WsServerContainer.getServerContainer(); + sc.setServletContext(sce.getServletContext()); + try { + sc.addEndpoint(Server.class); + } catch (DeploymentException e) { + throw new IllegalStateException(e); + } + } + } + + public static class MsgString { + private String data; + + public String getData() { return data; } + public void setData(String data) { this.data = data; } + } + + + public static class MsgStringEncoder extends Encoder.Adapter + implements Encoder.Text<MsgString> { + + @Override + public String encode(MsgString msg) throws EncodeException { + return "MsgString:" + msg.getData(); + } + } + + + public static class MsgStringDecoder extends Decoder.Adapter + implements Decoder.Text<MsgString> { + + @Override + public MsgString decode(String s) throws DecodeException { + MsgString result = new MsgString(); + result.setData(s.substring(10)); + return result; + } + + @Override + public boolean willDecode(String s) { + return s.startsWith("MsgString:"); + } + } + + + public static class MsgByte { + private byte[] data; + + public byte[] getData() { return data; } + public void setData(byte[] data) { this.data = data; } + } + + + public static class MsgByteEncoder extends Encoder.Adapter + implements Encoder.Binary<MsgByte> { + + @Override + public ByteBuffer encode(MsgByte msg) throws EncodeException { + byte[] data = msg.getData(); + ByteBuffer reply = ByteBuffer.allocate(2 + data.length); + reply.put((byte) 0x12); + reply.put((byte) 0x34); + reply.put(data); + return reply; + } + } + + + public static class MsgByteDecoder extends Decoder.Adapter + implements Decoder.Binary<MsgByte> { + + @Override + public MsgByte decode(ByteBuffer bb) throws DecodeException { + MsgByte result = new MsgByte(); + bb.position(bb.position() + 2); + byte[] data = new byte[bb.limit() - bb.position()]; + bb.get(data); + result.setData(data); + return result; + } + + @Override + public boolean willDecode(ByteBuffer bb) { + bb.mark(); + if (bb.get() == 0x12 && bb.get() == 0x34) { + return true; + } + bb.reset(); + return false; + } + } +} Propchange: tomcat/trunk/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org