diff --git a/clients/java/signalr/build.gradle b/clients/java/signalr/build.gradle index 6e83d121b3..e327fcbee3 100644 --- a/clients/java/signalr/build.gradle +++ b/clients/java/signalr/build.gradle @@ -13,4 +13,6 @@ repositories { dependencies { testImplementation group: 'junit', name: 'junit', version: '4.12' + compile "org.java-websocket:Java-WebSocket:1.3.8" + implementation 'com.google.code.gson:gson:2.8.5' } diff --git a/clients/java/signalr/src/main/java/Action.java b/clients/java/signalr/src/main/java/Action.java new file mode 100644 index 0000000000..79c4da61a2 --- /dev/null +++ b/clients/java/signalr/src/main/java/Action.java @@ -0,0 +1,6 @@ +// 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. + +public interface Action { + void invoke(T param); +} \ No newline at end of file diff --git a/clients/java/signalr/src/main/java/Chat.java b/clients/java/signalr/src/main/java/Chat.java new file mode 100644 index 0000000000..a64b107c94 --- /dev/null +++ b/clients/java/signalr/src/main/java/Chat.java @@ -0,0 +1,33 @@ +// 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. + +import com.google.gson.JsonArray; + +import java.net.URISyntaxException; +import java.util.Scanner; + +public class Chat { + public static void main(String[] args) throws URISyntaxException, InterruptedException { + System.out.println("Enter the URL of the SignalR Chat you want to join"); + Scanner reader = new Scanner(System.in); // Reading from System.in + String input; + input = reader.nextLine(); + HubConnection hubConnection = new HubConnection(input); + + hubConnection.On("Send", (message) -> { + String newMessage = ((JsonArray) message).get(0).getAsString(); + System.out.println("REGISTERED HANDLER: " + newMessage); + }); + + //This is a blocking call + hubConnection.start(); + + while (!input.equals("leave")){ + // Scans the next token of the input as an int. + input = reader.nextLine(); + hubConnection.send("Send", input); + } + + hubConnection.stop(); + } +} diff --git a/clients/java/signalr/src/main/java/DefaultJsonProtocolHandShakeMessage.java b/clients/java/signalr/src/main/java/DefaultJsonProtocolHandShakeMessage.java new file mode 100644 index 0000000000..985cf8f21e --- /dev/null +++ b/clients/java/signalr/src/main/java/DefaultJsonProtocolHandShakeMessage.java @@ -0,0 +1,7 @@ +// 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. + +public class DefaultJsonProtocolHandShakeMessage { + String protocol = "json"; + int version = 1; +} diff --git a/clients/java/signalr/src/main/java/HubConnection.java b/clients/java/signalr/src/main/java/HubConnection.java index b866c01857..ab4f0a1c52 100644 --- a/clients/java/signalr/src/main/java/HubConnection.java +++ b/clients/java/signalr/src/main/java/HubConnection.java @@ -1,8 +1,99 @@ // 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. +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.net.URISyntaxException; +import java.util.HashMap; + public class HubConnection { - public boolean methodToTest(){ - return true; + private String _url; + private ITransport _transport; + private OnReceiveCallBack callback; + private HashMap handlers = new HashMap<>(); + private JsonParser jsonParser = new JsonParser(); + private static final String RECORD_SEPARATOR = "\u001e"; + + public Boolean connected = false; + + public HubConnection(String url) { + _url = url; + callback = (payload) -> { + String[] messages = payload.split(RECORD_SEPARATOR); + + for (String splitMessage : messages) { + + // Empty handshake response "{}". We can ignore it + if (splitMessage.length() == 2) { + continue; + } + processMessage(splitMessage); + } + }; + + try { + _transport = new WebSocketTransport(_url); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + + private void processMessage(String message) { + JsonObject jsonMessage = jsonParser.parse(message).getAsJsonObject(); + String messageType = jsonMessage.get("type").toString(); + switch(messageType) { + case "1": + //Invocation Message + String target = jsonMessage.get("target").getAsString(); + if (handlers.containsKey(target)) { + handlers.get(target).invoke(jsonMessage.get("arguments")); + } + break; + case "2": + //Stream item + //Don't care yet + break; + case "3": + //Completion + //Don't care yet + break; + case "4": + //Stream invocation + //Don't care yet; + break; + case "5": + //Cancel invocation + //Don't care yet + break; + case "6": + //Ping + //Don't care yet + break; + case "7": + // Close message + //Don't care yet; + break; + } + } + + public void start() throws InterruptedException { + _transport.setOnReceive(this.callback); + _transport.start(); + connected = true; + } + + public void stop(){ + _transport.stop(); + connected = false; + } + + public void send(String method, Object arg1) { + InvocationMessage message = new InvocationMessage(method, new Object[]{ arg1 }); + _transport.send(message); + } + + public void On(String target, Action callback) { + handlers.put(target, callback); } } diff --git a/clients/java/signalr/src/main/java/ITransport.java b/clients/java/signalr/src/main/java/ITransport.java new file mode 100644 index 0000000000..bb582ed17a --- /dev/null +++ b/clients/java/signalr/src/main/java/ITransport.java @@ -0,0 +1,10 @@ +// 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. + +public interface ITransport { + void start() throws InterruptedException; + void send(InvocationMessage invocationMessage); + void setOnReceive(OnReceiveCallBack callback); + void onReceive(String message); + void stop(); +} diff --git a/clients/java/signalr/src/main/java/InvocationMessage.java b/clients/java/signalr/src/main/java/InvocationMessage.java new file mode 100644 index 0000000000..7abb277aaa --- /dev/null +++ b/clients/java/signalr/src/main/java/InvocationMessage.java @@ -0,0 +1,38 @@ +// 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. + +public class InvocationMessage { + private int type = 1; + String invocationId; + String target; + Object[] arguments; + + public InvocationMessage(String target, Object[] args) { + this.target = target; + arguments = args; + } + + public String getInvocationId() { + return invocationId; + } + + public void setInvocationId(String invocationId) { + this.invocationId = invocationId; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public Object getArguments() { + return arguments; + } + + public void setArguments(Object[] arguments) { + this.arguments = arguments; + } +} diff --git a/clients/java/signalr/src/main/java/OnReceiveCallBack.java b/clients/java/signalr/src/main/java/OnReceiveCallBack.java new file mode 100644 index 0000000000..e461b47c1a --- /dev/null +++ b/clients/java/signalr/src/main/java/OnReceiveCallBack.java @@ -0,0 +1,8 @@ +// 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. + +import com.google.gson.JsonObject; + +public interface OnReceiveCallBack { + void invoke(String message); +} diff --git a/clients/java/signalr/src/main/java/WebSocketTransport.java b/clients/java/signalr/src/main/java/WebSocketTransport.java new file mode 100644 index 0000000000..650032a5b7 --- /dev/null +++ b/clients/java/signalr/src/main/java/WebSocketTransport.java @@ -0,0 +1,79 @@ +// 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. + +import com.google.gson.Gson; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.net.URISyntaxException; + +public class WebSocketTransport implements ITransport { + private static final String RECORD_SEPARATOR = "\u001e"; + private WebSocketClient _webSocket; + private OnReceiveCallBack onReceiveCallBack; + private URI _url; + + public WebSocketTransport(String url) throws URISyntaxException { + // To Do: Format the incoming URL for a websocket connection. + _url = new URI(url); + _webSocket = createWebSocket(); + } + + @Override + public void start() throws InterruptedException { + _webSocket.connectBlocking(); + _webSocket.send(createHandshakeMessage() + RECORD_SEPARATOR); + } + + @Override + public void send(InvocationMessage invocationMessage) { + Gson gson = new Gson(); + String message = gson.toJson(invocationMessage) + RECORD_SEPARATOR; + _webSocket.send(message); + } + + @Override + public void setOnReceive(OnReceiveCallBack callback) { + this.onReceiveCallBack = callback; + } + + @Override + public void onReceive(String message) { + this.onReceiveCallBack.invoke(message); + } + + private String createHandshakeMessage() { + Gson gson = new Gson(); + return gson.toJson(new DefaultJsonProtocolHandShakeMessage()); + } + + @Override + public void stop() { + _webSocket.closeConnection(0, "HubConnection Stopped"); + } + + private WebSocketClient createWebSocket() { + return new WebSocketClient(_url) { + @Override + public void onOpen(ServerHandshake handshakedata) { + System.out.println("Connected to " + _url); + } + + @Override + public void onMessage(String message) { + onReceive(message); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + System.out.println("Connection Closed"); + } + + @Override + public void onError(Exception ex) { + System.out.println("Error: " + ex.getMessage()); + } + }; + } +} diff --git a/clients/java/signalr/src/test/java/HubConnectionTest.java b/clients/java/signalr/src/test/java/HubConnectionTest.java index 0f740a2183..b3868ac885 100644 --- a/clients/java/signalr/src/test/java/HubConnectionTest.java +++ b/clients/java/signalr/src/test/java/HubConnectionTest.java @@ -6,9 +6,8 @@ import org.junit.Test; import static org.junit.Assert.*; public class HubConnectionTest { + // Coming soon! @Test public void testEmptyCollection() { - HubConnection hubConnection = new HubConnection(); - assertTrue(hubConnection.methodToTest()); } } \ No newline at end of file