diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/CallbackMap.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/CallbackMap.java index 088804075e..528aa8d327 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/CallbackMap.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/CallbackMap.java @@ -7,23 +7,27 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import java.util.Collections; class CallbackMap { - private ConcurrentHashMap> handlers = new ConcurrentHashMap<>(); + private ConcurrentHashMap> handlers = new ConcurrentHashMap<>(); + + public InvocationHandler put(String target, ActionBase action, ArrayList> classes) { + InvocationHandler handler = new InvocationHandler(action, Collections.unmodifiableList(classes)); - public void put(String target, ActionBase action) { handlers.computeIfPresent(target, (methodName, handlerList) -> { - handlerList.add(action); + handlerList.add(handler); return handlerList; }); - handlers.computeIfAbsent(target, (ac) -> new ArrayList<>(Arrays.asList(action))); + handlers.computeIfAbsent(target, (ac) -> new ArrayList<>(Arrays.asList(handler))); + return handler; } public Boolean containsKey(String key) { return handlers.containsKey(key); } - public List get(String key) { + public List get(String key) { return handlers.get(key); } diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java index c524a7e18b..361920280c 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java @@ -9,26 +9,24 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; -import com.google.gson.Gson; -import com.google.gson.JsonArray; - public class HubConnection { private String url; private Transport transport; private OnReceiveCallBack callback; private CallbackMap handlers = new CallbackMap(); private HubProtocol protocol; - private Gson gson = new Gson(); private Boolean handshakeReceived = false; private static final String RECORD_SEPARATOR = "\u001e"; - private HubConnectionState connectionState = HubConnectionState.DISCONNECTED; + private HubConnectionState hubConnectionState = HubConnectionState.DISCONNECTED; private Logger logger; private List> onClosedCallbackList; private boolean skipNegotiate = false; private NegotiateResponse negotiateResponse; private String accessToken; private Map headers = new HashMap<>(); + private ConnectionState connectionState = null; + private static ArrayList> emptyArray = new ArrayList<>(); private static int MAX_NEGOTIATE_ATTEMPTS = 100; public HubConnection(String url, Transport transport, Logger logger, boolean skipNegotiate) { @@ -61,24 +59,20 @@ public class HubConnection { } } - HubMessage[] messages = protocol.parseMessages(payload); + HubMessage[] messages = protocol.parseMessages(payload, connectionState); for (HubMessage message : messages) { - logger.log(LogLevel.Debug, "Received message of type %s", message.getMessageType()); + logger.log(LogLevel.Debug, "Received message of type %s.", message.getMessageType()); switch (message.getMessageType()) { case INVOCATION: InvocationMessage invocationMessage = (InvocationMessage) message; - if (handlers.containsKey(invocationMessage.target)) { - ArrayList args = gson.fromJson((JsonArray) invocationMessage.arguments[0], (new ArrayList<>()).getClass()); - List actions = handlers.get(invocationMessage.target); - if (actions != null) { - logger.log(LogLevel.Debug, "Invoking handlers for target %s", invocationMessage.target); - for (ActionBase action : actions) { - action.invoke(args.toArray()); - } + List handlers = this.handlers.get(invocationMessage.target); + if (handlers != null) { + for (InvocationHandler handler : handlers) { + handler.getAction().invoke(invocationMessage.arguments); } } else { - logger.log(LogLevel.Warning, "Failed to find handler for %s method", invocationMessage.target); + logger.log(LogLevel.Warning, "Failed to find handler for %s method.", invocationMessage.target); } break; case CLOSE: @@ -93,7 +87,7 @@ public class HubConnection { case STREAM_ITEM: case CANCEL_INVOCATION: case COMPLETION: - logger.log(LogLevel.Error, "This client does not support %s messages", message.getMessageType()); + logger.log(LogLevel.Error, "This client does not support %s messages.", message.getMessageType()); throw new UnsupportedOperationException(String.format("The message type %s is not supported yet.", message.getMessageType())); } @@ -148,7 +142,7 @@ public class HubConnection { * @return HubConnection state enum. */ public HubConnectionState getConnectionState() { - return connectionState; + return hubConnectionState; } /** @@ -157,7 +151,7 @@ public class HubConnection { * @throws Exception An error occurred while connecting. */ public void start() throws Exception { - if (connectionState != HubConnectionState.DISCONNECTED) { + if (hubConnectionState != HubConnectionState.DISCONNECTED) { return; } if (!skipNegotiate) { @@ -198,7 +192,8 @@ public class HubConnection { transport.start(); String handshake = HandshakeProtocol.createHandshakeRequestMessage(new HandshakeRequestMessage(protocol.getName(), protocol.getVersion())); transport.send(handshake); - connectionState = HubConnectionState.CONNECTED; + hubConnectionState = HubConnectionState.CONNECTED; + connectionState = new ConnectionState(this); logger.log(LogLevel.Information, "HubConnected started."); } @@ -206,7 +201,7 @@ public class HubConnection { * Stops a connection to the server. */ private void stop(String errorMessage) { - if (connectionState == HubConnectionState.DISCONNECTED) { + if (hubConnectionState == HubConnectionState.DISCONNECTED) { return; } @@ -217,7 +212,8 @@ public class HubConnection { } transport.stop(); - connectionState = HubConnectionState.DISCONNECTED; + hubConnectionState = HubConnectionState.DISCONNECTED; + connectionState = null; logger.log(LogLevel.Information, "HubConnection stopped."); if (onClosedCallbackList != null) { HubException hubException = new HubException(errorMessage); @@ -243,7 +239,7 @@ public class HubConnection { * @throws Exception If there was an error while sending. */ public void send(String method, Object... args) throws Exception { - if (connectionState != HubConnectionState.CONNECTED) { + if (hubConnectionState != HubConnectionState.CONNECTED) { throw new HubException("The 'send' method cannot be called if the connection is not active"); } @@ -262,9 +258,9 @@ public class HubConnection { */ public Subscription on(String target, Action callback) { ActionBase action = args -> callback.invoke(); - handlers.put(target, action); + InvocationHandler handler = handlers.put(target, action, emptyArray); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -278,9 +274,11 @@ public class HubConnection { */ public Subscription on(String target, Action1 callback, Class param1) { ActionBase action = params -> callback.invoke(param1.cast(params[0])); - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(1); + classes.add(param1); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -298,9 +296,12 @@ public class HubConnection { ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(2); + classes.add(param1); + classes.add(param2); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -321,9 +322,13 @@ public class HubConnection { ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(3); + classes.add(param1); + classes.add(param2); + classes.add(param3); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -346,9 +351,14 @@ public class HubConnection { ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(4); + classes.add(param1); + classes.add(param2); + classes.add(param3); + classes.add(param4); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -374,9 +384,15 @@ public class HubConnection { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(5); + classes.add(param1); + classes.add(param2); + classes.add(param3); + classes.add(param4); + classes.add(param5); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -404,9 +420,16 @@ public class HubConnection { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(6); + classes.add(param1); + classes.add(param2); + classes.add(param3); + classes.add(param4); + classes.add(param5); + classes.add(param6); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -436,9 +459,17 @@ public class HubConnection { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(7); + classes.add(param1); + classes.add(param2); + classes.add(param3); + classes.add(param4); + classes.add(param5); + classes.add(param6); + classes.add(param7); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -470,9 +501,18 @@ public class HubConnection { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]), param8.cast(params[7])); }; - handlers.put(target, action); + ArrayList> classes = new ArrayList<>(8); + classes.add(param1); + classes.add(param2); + classes.add(param3); + classes.add(param4); + classes.add(param5); + classes.add(param6); + classes.add(param7); + classes.add(param8); + InvocationHandler handler = handlers.put(target, action, classes); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); - return new Subscription(handlers, action, target); + return new Subscription(handlers, handler, target); } /** @@ -492,4 +532,32 @@ public class HubConnection { onClosedCallbackList.add(callback); } + + private class ConnectionState implements InvocationBinder { + HubConnection connection; + + public ConnectionState(HubConnection connection) { + this.connection = connection; + } + + @Override + public Class getReturnType(String invocationId) { + return null; + } + + @Override + public List> getParameterTypes(String methodName) throws Exception { + List handlers = connection.handlers.get(methodName); + if (handlers == null) { + logger.log(LogLevel.Warning, "Failed to find handler for '%s' method.", methodName); + return emptyArray; + } + + if (handlers.size() == 0) { + throw new Exception(String.format("There are no callbacks registered for the method '%s'.", methodName)); + } + + return handlers.get(0).getClasses(); + } + } } \ No newline at end of file diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubProtocol.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubProtocol.java index f5eb061388..82244730fd 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubProtocol.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubProtocol.java @@ -3,6 +3,8 @@ package com.microsoft.aspnet.signalr; +import java.io.IOException; + /** * A protocol abstraction for communicating with SignalR hubs. */ @@ -16,7 +18,7 @@ interface HubProtocol { * @param message A string representation of one or more {@link HubMessage}s. * @return A list of {@link HubMessage}s. */ - HubMessage[] parseMessages(String message); + HubMessage[] parseMessages(String message, InvocationBinder binder) throws Exception; /** * Writes the specified {@link HubMessage} to a String. diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationBinder.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationBinder.java new file mode 100644 index 0000000000..cd0293d7b8 --- /dev/null +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationBinder.java @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.aspnet.signalr; + +import java.util.List; + +interface InvocationBinder { + Class getReturnType(String invocationId); + List> getParameterTypes(String methodName) throws Exception; +} \ No newline at end of file diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationHandler.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationHandler.java new file mode 100644 index 0000000000..34d0847b1b --- /dev/null +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationHandler.java @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.aspnet.signalr; + +import java.util.List; + +class InvocationHandler { + private List> classes; + private ActionBase action; + + InvocationHandler(ActionBase action, List> classes) { + this.action = action; + this.classes = classes; + } + + public List> getClasses() { + return classes; + } + + public ActionBase getAction() { + return action; + } +} \ No newline at end of file diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationMessage.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationMessage.java index 3cbe33183a..cde7f9feac 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationMessage.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/InvocationMessage.java @@ -11,7 +11,7 @@ class InvocationMessage extends HubMessage { public InvocationMessage(String target, Object[] args) { this.target = target; - arguments = args; + this.arguments = args; } public String getInvocationId() { diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/JsonHubProtocol.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/JsonHubProtocol.java index 212bb99003..29ed3a5a89 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/JsonHubProtocol.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/JsonHubProtocol.java @@ -3,13 +3,16 @@ package com.microsoft.aspnet.signalr; +import java.io.IOException; +import java.io.StringReader; import java.util.ArrayList; import java.util.List; import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import com.google.gson.JsonArray; import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; class JsonHubProtocol implements HubProtocol { private final JsonParser jsonParser = new JsonParser(); @@ -32,41 +35,105 @@ class JsonHubProtocol implements HubProtocol { } @Override - public HubMessage[] parseMessages(String payload) { + public HubMessage[] parseMessages(String payload, InvocationBinder binder) throws Exception { String[] messages = payload.split(RECORD_SEPARATOR); List hubMessages = new ArrayList<>(); - for (String splitMessage : messages) { + for (String str : messages) { + HubMessageType messageType = null; + String invocationId = null; + String target = null; + String error = null; + ArrayList arguments = null; + JsonArray argumentsToken = null; + + JsonReader reader = new JsonReader(new StringReader(str)); + reader.beginObject(); + + do { + String name = reader.nextName(); + switch (name) { + case "type": + messageType = HubMessageType.values()[reader.nextInt() - 1]; + break; + case "invocationId": + invocationId = reader.nextString(); + break; + case "target": + target = reader.nextString(); + break; + case "error": + error = reader.nextString(); + break; + case "result": + reader.skipValue(); + break; + case "item": + reader.skipValue(); + break; + case "arguments": + if (target != null) { + reader.beginArray(); + List> types = binder.getParameterTypes(target); + if (types != null && types.size() >= 1) { + arguments = new ArrayList<>(); + for (int i = 0; i < types.size(); i++) { + arguments.add(gson.fromJson(reader, types.get(i))); + } + } + reader.endArray(); + } else { + argumentsToken = (JsonArray)jsonParser.parse(reader); + } + break; + case "headers": + throw new HubException("Headers not implemented yet."); + default: + // Skip unknown property, allows new clients to still work with old protocols + reader.skipValue(); + break; + } + } while (reader.hasNext()); + + reader.endObject(); + reader.close(); - JsonObject jsonMessage = jsonParser.parse(splitMessage).getAsJsonObject(); - HubMessageType messageType = HubMessageType.values()[jsonMessage.get("type").getAsInt() -1]; switch (messageType) { case INVOCATION: - //Invocation Message - String target = jsonMessage.get("target").getAsString(); - JsonElement args = jsonMessage.get("arguments"); - hubMessages.add(new InvocationMessage(target, new Object[] {args})); + if (argumentsToken != null) { + List> types = binder.getParameterTypes(target); + if (types != null && types.size() >= 1) { + arguments = new ArrayList<>(); + for (int i = 0; i < types.size(); i++) { + arguments.add(gson.fromJson(argumentsToken.get(i), types.get(i))); + } + } + } + if (arguments == null) { + hubMessages.add(new InvocationMessage(target, new Object[0])); + } else { + hubMessages.add(new InvocationMessage(target, arguments.toArray())); + } break; + case STREAM_INVOCATION: case STREAM_ITEM: case COMPLETION: - case STREAM_INVOCATION: case CANCEL_INVOCATION: throw new UnsupportedOperationException(String.format("The message type %s is not supported yet.", messageType)); case PING: - //Ping hubMessages.add(new PingMessage()); break; case CLOSE: - CloseMessage closeMessage; - if (jsonMessage.has("error")) { - String error = jsonMessage.get("error").getAsString(); - closeMessage = new CloseMessage(error); + if (error != null) { + hubMessages.add(new CloseMessage(error)); } else { - closeMessage = new CloseMessage(); + hubMessages.add(new CloseMessage()); } - hubMessages.add(closeMessage); + break; + default: break; } } + return hubMessages.toArray(new HubMessage[hubMessages.size()]); } diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/StreamInvocationMessage.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/StreamInvocationMessage.java new file mode 100644 index 0000000000..ab4f07983c --- /dev/null +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/StreamInvocationMessage.java @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +package com.microsoft.aspnet.signalr; + +class StreamInvocationMessage extends InvocationMessage { + + int type = HubMessageType.STREAM_INVOCATION.value; + + public StreamInvocationMessage(String invocationId, String target, Object[] arguments) { + super(target, arguments); + this.invocationId = invocationId; + } + + @Override + public HubMessageType getMessageType() { + return HubMessageType.STREAM_INVOCATION; + } +} diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Subscription.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Subscription.java index 32e04f9513..656788c41f 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Subscription.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Subscription.java @@ -7,19 +7,19 @@ import java.util.List; public class Subscription { private CallbackMap handlers; - private ActionBase action; + private InvocationHandler handler; private String target; - public Subscription(CallbackMap handlers, ActionBase action, String target) { + public Subscription(CallbackMap handlers, InvocationHandler handler, String target) { this.handlers = handlers; - this.action = action; + this.handler = handler; this.target = target; } public void unsubscribe() { - List actions = this.handlers.get(target); - if (actions != null) { - actions.remove(action); + List handler = this.handlers.get(target); + if (handler != null) { + handler.remove(this.handler); } } } diff --git a/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/HubConnectionTest.java b/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/HubConnectionTest.java index 846de3e657..655cb11506 100644 --- a/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/HubConnectionTest.java +++ b/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/HubConnectionTest.java @@ -31,7 +31,7 @@ public class HubConnectionTest { } @Test - public void HubConnectionClosesAfterCloseMessage() throws Exception { + public void hubConnectionClosesAfterCloseMessage() throws Exception { MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -46,7 +46,7 @@ public class HubConnectionTest { } @Test - public void HubConnectionReceiveHandshakeResponseWithError() throws Exception { + public void hubConnectionReceiveHandshakeResponseWithError() throws Exception { exceptionRule.expect(HubException.class); exceptionRule.expectMessage("Requested protocol 'messagepack' is not available."); @@ -58,7 +58,7 @@ public class HubConnectionTest { } @Test - public void RegisteringMultipleHandlersAndBothGetTriggered() throws Exception { + public void registeringMultipleHandlersAndBothGetTriggered() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -84,7 +84,7 @@ public class HubConnectionTest { } @Test - public void RemoveHandlerByName() throws Exception { + public void removeHandlerByName() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -111,7 +111,7 @@ public class HubConnectionTest { } @Test - public void AddAndRemoveHandlerImmediately() throws Exception { + public void addAndRemoveHandlerImmediately() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -132,11 +132,11 @@ public class HubConnectionTest { mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); // Confirming that the handler was removed. - assertEquals(0, value.get(), 0); + assertEquals(0.0, value.get(), 0); } @Test - public void RemovingMultipleHandlersWithOneCallToRemove() throws Exception { + public void removingMultipleHandlersWithOneCallToRemove() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -168,7 +168,7 @@ public class HubConnectionTest { } @Test - public void RemoveHandlerWithUnsubscribe() throws Exception { + public void removeHandlerWithUnsubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -191,12 +191,17 @@ public class HubConnectionTest { assertEquals(1, value.get(), 0); subscription.unsubscribe(); - mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + try { + mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + } catch (Exception ex) { + assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage()); + } + assertEquals(1, value.get(), 0); } @Test - public void UnsubscribeTwice() throws Exception { + public void unsubscribeTwice() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -220,12 +225,17 @@ public class HubConnectionTest { subscription.unsubscribe(); subscription.unsubscribe(); - mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + try { + mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + } catch (Exception ex) { + assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage()); + } + assertEquals(1, value.get(), 0); } @Test - public void RemoveSingleHandlerWithUnsubscribe() throws Exception { + public void removeSingleHandlerWithUnsubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -255,7 +265,7 @@ public class HubConnectionTest { } @Test - public void AddAndRemoveHandlerImmediatelyWithSubscribe() throws Exception { + public void addAndRemoveHandlerImmediatelyWithSubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -268,13 +278,19 @@ public class HubConnectionTest { hubConnection.start(); mockTransport.receiveMessage("{}" + RECORD_SEPARATOR); - mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + + try { + mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + } catch (Exception ex) { + assertEquals("There are no callbacks registered for the method 'inc'.", ex.getMessage()); + } + // Confirming that the handler was removed. assertEquals(0, value.get(), 0); } @Test - public void RegisteringMultipleHandlersThatTakeParamsAndBothGetTriggered() throws Exception { + public void registeringMultipleHandlersThatTakeParamsAndBothGetTriggered() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -296,7 +312,7 @@ public class HubConnectionTest { // We're using AtomicReference in the send tests instead of int here because Gson has trouble deserializing to Integer @Test - public void SendWithNoParamsTriggersOnHandler() throws Exception { + public void sendWithNoParamsTriggersOnHandler() throws Exception { AtomicReference value = new AtomicReference(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -315,7 +331,7 @@ public class HubConnectionTest { } @Test - public void SendWithParamTriggersOnHandler() throws Exception { + public void sendWithParamTriggersOnHandler() throws Exception { AtomicReference value = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -335,7 +351,7 @@ public class HubConnectionTest { } @Test - public void SendWithTwoParamsTriggersOnHandler() throws Exception { + public void sendWithTwoParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); @@ -361,7 +377,7 @@ public class HubConnectionTest { } @Test - public void SendWithThreeParamsTriggersOnHandler() throws Exception { + public void sendWithThreeParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -391,7 +407,7 @@ public class HubConnectionTest { } @Test - public void SendWithFourParamsTriggersOnHandler() throws Exception { + public void sendWithFourParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -424,7 +440,7 @@ public class HubConnectionTest { } @Test - public void SendWithFiveParamsTriggersOnHandler() throws Exception { + public void sendWithFiveParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -461,7 +477,7 @@ public class HubConnectionTest { } @Test - public void SendWithSixParamsTriggersOnHandler() throws Exception { + public void sendWithSixParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -502,7 +518,7 @@ public class HubConnectionTest { } @Test - public void SendWithSevenParamsTriggersOnHandler() throws Exception { + public void sendWithSevenParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -547,7 +563,7 @@ public class HubConnectionTest { } @Test - public void SendWithEightParamsTriggersOnHandler() throws Exception { + public void sendWithEightParamsTriggersOnHandler() throws Exception { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); AtomicReference value3 = new AtomicReference<>(); @@ -594,8 +610,40 @@ public class HubConnectionTest { assertEquals("F", value8.get()); } + private class Custom { + public int number; + public String str; + public boolean[] bools; + } + @Test - public void ReceiveHandshakeResponseAndMessage() throws Exception { + public void sendWithCustomObjectTriggersOnHandler() throws Exception { + AtomicReference value1 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); + + hubConnection.on("inc", (param1) -> { + assertNull(value1.get()); + + value1.set(param1); + }, Custom.class); + + hubConnection.start(); + mockTransport.receiveMessage("{}" + RECORD_SEPARATOR); + mockTransport.receiveMessage("{\"type\":1,\"target\":\"inc\",\"arguments\":[{\"number\":1,\"str\":\"A\",\"bools\":[true,false]}]}" + RECORD_SEPARATOR); + + // Confirming that our handler was called and the correct message was passed in. + Custom custom = value1.get(); + assertEquals(1, custom.number); + assertEquals("A", custom.str); + assertEquals(2, custom.bools.length); + assertEquals(true, custom.bools[0]); + assertEquals(false, custom.bools[1]); + } + + @Test + public void receiveHandshakeResponseAndMessage() throws Exception { AtomicReference value = new AtomicReference(0.0); MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); @@ -660,7 +708,7 @@ public class HubConnectionTest { } @Test - public void HubConnectionClosesAndRunsOnClosedCallbackAfterCloseMessageWithError() throws Exception { + public void hubConnectionClosesAndRunsOnClosedCallbackAfterCloseMessageWithError() throws Exception { MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.onClosed((ex) -> { @@ -677,7 +725,7 @@ public class HubConnectionTest { } @Test - public void CallingStartOnStartedHubConnectionNoOps() throws Exception { + public void callingStartOnStartedHubConnectionNoOps() throws Exception { Transport mockTransport = new MockTransport(); HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); @@ -691,7 +739,7 @@ public class HubConnectionTest { } @Test - public void CannotSendBeforeStart() throws Exception { + public void cannotSendBeforeStart() throws Exception { exceptionRule.expect(HubException.class); exceptionRule.expectMessage("The 'send' method cannot be called if the connection is not active"); diff --git a/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/JsonHubProtocolTest.java b/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/JsonHubProtocolTest.java index 349d249af6..2c8f6e8d67 100644 --- a/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/JsonHubProtocolTest.java +++ b/clients/java/signalr/src/test/java/com/microsoft/aspnet/signalr/JsonHubProtocolTest.java @@ -5,6 +5,11 @@ package com.microsoft.aspnet.signalr; import static org.junit.Assert.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.PriorityBlockingQueue; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -30,7 +35,7 @@ public class JsonHubProtocolTest { } @Test - public void VerifyWriteMessage() { + public void verifyWriteMessage() { InvocationMessage invocationMessage = new InvocationMessage("test", new Object[] {"42"}); String result = jsonHubProtocol.writeMessage(invocationMessage); String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E"; @@ -38,9 +43,11 @@ public class JsonHubProtocolTest { } @Test - public void ParsePingMessage() { + public void parsePingMessage() throws Exception { String stringifiedMessage = "{\"type\":6}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(new PingMessage()); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message assertEquals(1, messages.length); @@ -48,9 +55,11 @@ public class JsonHubProtocolTest { } @Test - public void ParseCloseMessage() { + public void parseCloseMessage() throws Exception { String stringifiedMessage = "{\"type\":7}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(new CloseMessage()); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message assertEquals(1, messages.length); @@ -64,9 +73,11 @@ public class JsonHubProtocolTest { } @Test - public void ParseCloseMessageWithError() { + public void parseCloseMessageWithError() throws Exception { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(new CloseMessage("There was an error")); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message assertEquals(1, messages.length); @@ -80,9 +91,11 @@ public class JsonHubProtocolTest { } @Test - public void ParseSingleMessage() { + public void parseSingleMessage() throws Exception { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42 })); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message assertEquals(1, messages.length); @@ -95,50 +108,59 @@ public class JsonHubProtocolTest { assertEquals("test", invocationMessage.getTarget()); assertEquals(null, invocationMessage.getInvocationId()); - JsonArray messageResult = (JsonArray) invocationMessage.getArguments()[0]; - assertEquals(42, messageResult.getAsInt()); + int messageResult = (int)invocationMessage.getArguments()[0]; + assertEquals(42, messageResult); } @Rule public ExpectedException exceptionRule = ExpectedException.none(); @Test - public void ParseSingleUnsupportedStreamItemMessage() { + public void parseSingleUnsupportedStreamItemMessage() throws Exception { exceptionRule.expect(UnsupportedOperationException.class); exceptionRule.expectMessage("The message type STREAM_ITEM is not supported yet."); String stringifiedMessage = "{\"type\":2,\"Id\":1,\"Item\":42}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(null); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); } @Test - public void ParseSingleUnsupportedStreamInvocationMessage() { + public void parseSingleUnsupportedStreamInvocationMessage() throws Exception { exceptionRule.expect(UnsupportedOperationException.class); exceptionRule.expectMessage("The message type STREAM_INVOCATION is not supported yet."); String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; + TestBinder binder = new TestBinder(new StreamInvocationMessage("1", "test", new Object[] { 42 })); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); } @Test - public void ParseSingleUnsupportedCancelInvocationMessage() { + public void parseSingleUnsupportedCancelInvocationMessage() throws Exception { exceptionRule.expect(UnsupportedOperationException.class); exceptionRule.expectMessage("The message type CANCEL_INVOCATION is not supported yet."); String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(null); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); } @Test - public void ParseSingleUnsupportedCompletionMessage() { + public void parseSingleUnsupportedCompletionMessage() throws Exception { exceptionRule.expect(UnsupportedOperationException.class); exceptionRule.expectMessage("The message type COMPLETION is not supported yet."); String stringifiedMessage = "{\"type\":3,\"invocationId\":123}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(null); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); } @Test - public void ParseTwoMessages() { + public void parseTwoMessages() throws Exception { String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages); + TestBinder binder = new TestBinder(new InvocationMessage("one", new Object[] { 42 })); + + HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages, binder); assertEquals(2, messages.length); // Check the first message @@ -149,8 +171,8 @@ public class JsonHubProtocolTest { assertEquals("one", invocationMessage.getTarget()); assertEquals(null, invocationMessage.getInvocationId()); - JsonArray messageResult = (JsonArray) invocationMessage.getArguments()[0]; - assertEquals(42, messageResult.getAsInt()); + int messageResult = (int)invocationMessage.getArguments()[0]; + assertEquals(42, messageResult); // Check the second message assertEquals(HubMessageType.INVOCATION, messages[1].getMessageType()); @@ -160,14 +182,16 @@ public class JsonHubProtocolTest { assertEquals("two", invocationMessage2.getTarget()); assertEquals(null, invocationMessage2.getInvocationId()); - JsonArray secondMessageResult = (JsonArray) invocationMessage2.getArguments()[0]; - assertEquals(43, secondMessageResult.getAsInt()); + int secondMessageResult = (int)invocationMessage2.getArguments()[0]; + assertEquals(43, secondMessageResult); } @Test - public void ParseSingleMessageMutipleArgs() { + public void parseSingleMessageMutipleArgs() throws Exception { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage); + TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42, 24 })); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); @@ -175,8 +199,72 @@ public class JsonHubProtocolTest { InvocationMessage message = (InvocationMessage)messages[0]; assertEquals("test", message.getTarget()); assertEquals(null, message.getInvocationId()); - JsonArray messageResult = ((JsonArray) message.getArguments()[0]); - assertEquals(42, messageResult.get(0).getAsInt()); - assertEquals(24, messageResult.get(1).getAsInt()); + int messageResult = (int) message.getArguments()[0]; + int messageResult2 = (int) message.getArguments()[1]; + assertEquals(42, messageResult); + assertEquals(24, messageResult2); + } + + @Test + public void parseMessageWithOutOfOrderProperties() throws Exception { + String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; + TestBinder binder = new TestBinder(new InvocationMessage("test", new Object[] { 42, 24 })); + + HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + // We know it's only one message + assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); + + InvocationMessage message = (InvocationMessage) messages[0]; + assertEquals("test", message.getTarget()); + assertEquals(null, message.getInvocationId()); + int messageResult = (int) message.getArguments()[0]; + int messageResult2 = (int) message.getArguments()[1]; + assertEquals(42, messageResult); + assertEquals(24, messageResult2); + } + + private class TestBinder implements InvocationBinder { + private Class[] paramTypes = null; + + public TestBinder(HubMessage expectedMessage) { + if (expectedMessage == null) { + return; + } + + switch (expectedMessage.getMessageType()) { + case STREAM_INVOCATION: + ArrayList> streamTypes = new ArrayList<>(); + for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) { + streamTypes.add(obj.getClass()); + } + paramTypes = streamTypes.toArray(new Class[streamTypes.size()]); + break; + case INVOCATION: + ArrayList> types = new ArrayList<>(); + for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) { + types.add(obj.getClass()); + } + paramTypes = types.toArray(new Class[types.size()]); + break; + case STREAM_ITEM: + break; + default: + break; + } + } + + @Override + public Class getReturnType(String invocationId) { + return null; + } + + @Override + public List> getParameterTypes(String methodName) { + if (paramTypes == null) { + return new ArrayList<>(); + } + return new ArrayList>(Arrays.asList(paramTypes)); + } } } \ No newline at end of file