From 4b14e283b91e75ac4456acc82b8a78eccad64464 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 26 Jan 2023 13:47:25 +0530 Subject: [PATCH 01/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 206b91fff..f41cd501e 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,9 +40,6 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "constraint", moduleName = "constraint"} -] [[package]] org = "ballerina" @@ -105,9 +102,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] [[package]] org = "ballerina" @@ -192,9 +186,6 @@ version = "0.0.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} -] [[package]] org = "ballerina" @@ -204,9 +195,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] -modules = [ - {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} -] [[package]] org = "ballerina" @@ -296,18 +284,6 @@ dependencies = [ {org = "ballerina", name = "time"} ] -[[package]] -org = "ballerina" -name = "test" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] -modules = [ - {org = "ballerina", packageName = "test", moduleName = "test"} -] - [[package]] org = "ballerina" name = "time" @@ -333,19 +309,14 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, - {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, - {org = "ballerina", name = "lang.runtime"}, - {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, {org = "ballerina", name = "regex"}, - {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"} ] modules = [ From a7f00cbcde5be3a69bf79024d0e63dfa82c018ae Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 26 Jan 2023 14:01:04 +0530 Subject: [PATCH 02/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f41cd501e..f1adc7b18 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -284,6 +284,18 @@ dependencies = [ {org = "ballerina", name = "time"} ] +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + [[package]] org = "ballerina" name = "time" @@ -317,6 +329,7 @@ dependencies = [ {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, {org = "ballerina", name = "regex"}, + {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"} ] modules = [ From 7aad96c1285434dbbcf59ffdc885175188ebcdf4 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Mon, 30 Jan 2023 15:56:57 +0530 Subject: [PATCH 03/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index f1adc7b18..05686f214 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -102,6 +102,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] [[package]] org = "ballerina" @@ -322,6 +325,7 @@ version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, From 16b48c711f1bc85f4bb6438064357b35068ead82 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 10:54:04 +0530 Subject: [PATCH 04/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 05686f214..ccfad57d8 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -189,6 +189,9 @@ version = "0.0.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] [[package]] org = "ballerina" @@ -198,6 +201,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] +modules = [ + {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} +] [[package]] org = "ballerina" @@ -329,6 +335,8 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From 61b219ee7c742630f50ffddfeef1a39f209390d2 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 10:57:02 +0530 Subject: [PATCH 05/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index ccfad57d8..206b91fff 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,6 +40,9 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "constraint", moduleName = "constraint"} +] [[package]] org = "ballerina" @@ -330,6 +333,7 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From a177a9792594e47eb54b648140f09b78882e0946 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:00:06 +0530 Subject: [PATCH 06/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 206b91fff..05686f214 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,9 +40,6 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "constraint", moduleName = "constraint"} -] [[package]] org = "ballerina" @@ -192,9 +189,6 @@ version = "0.0.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} -] [[package]] org = "ballerina" @@ -204,9 +198,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] -modules = [ - {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} -] [[package]] org = "ballerina" @@ -333,14 +324,11 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, - {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, - {org = "ballerina", name = "lang.runtime"}, - {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From d82bf7a8260d031523de640855d8bd3bc4a40ce8 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:01:51 +0530 Subject: [PATCH 07/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 05686f214..eb36b57ed 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -189,6 +189,9 @@ version = "0.0.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} +] [[package]] org = "ballerina" @@ -329,6 +332,7 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.runtime"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From 8f0a69a5a6b0b7694ae22317a6e0ef2f59cb0542 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:05:56 +0530 Subject: [PATCH 08/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index eb36b57ed..ccfad57d8 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -201,6 +201,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] +modules = [ + {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} +] [[package]] org = "ballerina" @@ -333,6 +336,7 @@ dependencies = [ {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From 5ccd191edb1c5eef6038cfba107abafa1575e3d6 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:16:22 +0530 Subject: [PATCH 09/17] Implement dispatching to custom remote functions --- ballerina/Dependencies.toml | 4 + ballerina/annotation.bal | 2 + ballerina/tests/custom_remote_functions.bal | 189 ++++++++++++++++++ ballerina/tests/data_binding_with_enum.bal | 4 +- compiler-plugin-tests/build.gradle | 26 +-- .../WebSocketServiceValidationTest.java | 8 + .../sample_package_57/Ballerina.toml | 4 + .../sample_package_57/server.bal | 40 ++++ compiler-plugin/build.gradle | 34 ++-- .../websocket/plugin/PluginConstants.java | 1 + .../plugin/WebSocketServiceValidator.java | 2 +- .../WebSocketUpgradeServiceValidator.java | 5 +- .../WebSocketUpgradeServiceValidatorTask.java | 40 +++- native/build.gradle | 26 +-- native/spotbugs-exclude.xml | 4 + .../stdlib/websocket/WebSocketConstants.java | 1 + .../websocket/WebSocketResourceCallback.java | 6 +- .../WebSocketResourceDispatcher.java | 54 ++++- .../server/WebSocketServerService.java | 7 + 19 files changed, 401 insertions(+), 56 deletions(-) create mode 100644 ballerina/tests/custom_remote_functions.bal create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/server.bal diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index ccfad57d8..206b91fff 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,6 +40,9 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "constraint", moduleName = "constraint"} +] [[package]] org = "ballerina" @@ -330,6 +333,7 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina/annotation.bal b/ballerina/annotation.bal index b20e9ca04..9dd1e795d 100644 --- a/ballerina/annotation.bal +++ b/ballerina/annotation.bal @@ -28,12 +28,14 @@ # If this is not set or is negative or zero, the default frame size, which is 65536 will be used # + auth - Listener authentication configurations # + validation - Enable/disable constraint validation +# + dispatcherKey - The key which is going to be used for dispatching to custom remote functions. public type WSServiceConfig record {| string[] subProtocols = []; decimal idleTimeout = 0; int maxFrameSize = 65536; ListenerAuthConfig[] auth?; boolean validation = true; + string dispatcherKey?; |}; # The annotation which is used to configure a WebSocket service. diff --git a/ballerina/tests/custom_remote_functions.bal b/ballerina/tests/custom_remote_functions.bal new file mode 100644 index 000000000..405ace246 --- /dev/null +++ b/ballerina/tests/custom_remote_functions.bal @@ -0,0 +1,189 @@ +// Copyright (c) 2023 WSO2 LLC. (//www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. 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 +// +// //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. + +import ballerina/test; +import ballerina/io; + +listener Listener customDispatchingLis = new(21401); + +@ServiceConfig {dispatcherKey: "event"} +service on customDispatchingLis { + resource function get .() returns Service|Error { + return new SingleMessageService(); + } +} + +service class SingleMessageService { + *Service; + remote function onPing(Caller caller, json data) returns Error? { + io:println(data); + check caller->writeMessage({"event": "pong"}); + } +} + +@ServiceConfig {dispatcherKey: "event"} +service /subscribe on customDispatchingLis { + resource function get .() returns Service|Error { + return new SubscribeMessageService(); + } +} + +service class SubscribeMessageService { + *Service; + remote function onSubscribe(Caller caller, json data) returns Error? { + io:println(data); + check caller->writeMessage({"type": "subscribe", "id":"1", "payload":{"query": "{ __schema { types { name } } }"}}); + } + + remote function onHeartbeat(Caller caller, json data) returns json { + return {"event": "heartbeat"}; + } +} + +@ServiceConfig {dispatcherKey: "event"} +service /onMessage on customDispatchingLis { + resource function get .() returns Service|Error { + return new DefaultRemoteFunctionService(); + } +} + +service class DefaultRemoteFunctionService { + *Service; + remote function onMessage(Caller caller, json data) returns Error? { + check caller->writeMessage({"event": "heartbeat"}); + } +} + +@ServiceConfig {dispatcherKey: "event"} +service /noRemoteMethod on customDispatchingLis { + resource function get .() returns Service|Error { + return new NoRemoteFunctionService(); + } +} + +service class NoRemoteFunctionService { + *Service; + remote function onMessages(Caller caller, json data) returns Error? { + check caller->writeMessage({"event": "onMessages"}); + } +} + +@ServiceConfig {dispatcherKey: "event"} +service /dataBindingFailure on customDispatchingLis { + resource function get .() returns Service|Error { + return new DataBindingFailureService(); + } +} + +service class DataBindingFailureService { + *Service; + remote function onMessages(Caller caller, byte[] data) returns Error? { + check caller->writeMessage({"event": "onMessages"}); + } +} + +@ServiceConfig {dispatcherKey: "type"} +service /underscore on customDispatchingLis { + resource function get .() returns Service|Error { + return new UnderscoreService(); + } +} + +service class UnderscoreService { + *Service; + remote function onPing(Caller caller, json data) returns Error? { + check caller->writeMessage({"event": "onMessages"}); + } +} + +@ServiceConfig {dispatcherKey: "type"} +service /lotofunderscores on customDispatchingLis { + resource function get .() returns Service|Error { + return new LotOfUnderscoreService(); + } +} + +service class LotOfUnderscoreService { + *Service; + remote function onThisPingMessage(Caller caller, json data) returns Error? { + check caller->writeMessage({"event": "onMessages"}); + } +} + +@test:Config {enable:false} +public function testPingMessage() returns Error? { + Client cl = check new("ws://localhost:21401"); + check cl->writeMessage({"event": "ping"}); + json resp = check cl->readMessage(); + test:assertEquals(resp, {"event": "pong"}); +} + +@test:Config {enable:false} +public function testSubscribeMessage() returns Error? { + Client cl = check new("ws://localhost:21401/subscribe"); + check cl->writeMessage({"event": "subscribe", "pair": ["XBT/USD", "XBT/EUR"], "subscription": {"name": "ticker"}}); + json resp = check cl->readMessage(); + test:assertEquals(resp, {"type": "subscribe", "id":"1", "payload":{"query": "{ __schema { types { name } } }"}}); + check cl->writeMessage({"event": "heartbeat"}); + json resp2 = check cl->readMessage(); + test:assertEquals(resp2, {"event": "heartbeat"}); +} + +@test:Config {enable:false} +public function testDispatchingToDefaultRemoteMethod() returns Error? { + Client cl = check new("ws://localhost:21401/onMessage"); + check cl->writeMessage({"event": "heartbeat"}); + check cl->writeMessage({"event": "heartbeat"}); + json resp2 = check cl->readMessage(); + test:assertEquals(resp2, {"event": "heartbeat"}); +} + +@test:Config {enable:false} +public function testDispatchingToNone() returns Error? { + Client cl = check new("ws://localhost:21401/noRemoteMethod"); + check cl->writeMessage({"event": "heartbeat"}); + check cl->writeMessage({"event": "Messages"}); + json resp2 = check cl->readMessage(); + test:assertEquals(resp2, {"event": "onMessages"}); +} + +@test:Config {enable:false} +public function testDatabindingFailure() returns Error? { + Client cl = check new("ws://localhost:21401/dataBindingFailure"); + check cl->writeMessage({"event": "Messages"}); + json|Error resp2 = cl->readMessage(); + if resp2 is Error { + test:assertTrue(resp2.message().startsWith("data binding failed:")); + } else { + test:assertFail("Expected a binding error"); + } +} + +@test:Config {enable: false} +public function testUnderscore() returns Error? { + Client cl = check new("ws://localhost:21401/underscore"); + check cl->writeMessage({"type": "_ping"}); + json resp = check cl->readMessage(); + test:assertEquals(resp, {"event": "onMessages"}); +} + +@test:Config {} +public function testUnderscoresAndSpaces() returns Error? { + Client cl = check new("ws://localhost:21401/lotofunderscores"); + check cl->writeMessage({"type": "this_ping message"}); + json resp = check cl->readMessage(); + test:assertEquals(resp, {"event": "onMessages"}); +} diff --git a/ballerina/tests/data_binding_with_enum.bal b/ballerina/tests/data_binding_with_enum.bal index ecbf7616a..be34ea189 100644 --- a/ballerina/tests/data_binding_with_enum.bal +++ b/ballerina/tests/data_binding_with_enum.bal @@ -22,7 +22,7 @@ enum Point { C } -service on new Listener(8000) { +service on new Listener(21400) { resource function get .() returns Service|Error { return new EnumService(); } @@ -38,7 +38,7 @@ service class EnumService { @test:Config {} public function testEnum() returns Error? { - Client cl = check new("ws://localhost:8000"); + Client cl = check new("ws://localhost:21400"); check cl->writeMessage(A); Point p = check cl->readMessage(); test:assertEquals(p, B); diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle index 25671221b..bfd4794aa 100644 --- a/compiler-plugin-tests/build.gradle +++ b/compiler-plugin-tests/build.gradle @@ -48,19 +48,19 @@ checkstyle { checkstyleTest.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -spotbugsTest { - effort "max" - reportLevel "low" - reportsDir = file("$project.buildDir/reports/spotbugs") - reports { - html.enabled true - text.enabled = true - } - def excludeFile = file("${project.projectDir}/spotbugs-exclude.xml") - if(excludeFile.exists()) { - excludeFilter = excludeFile - } -} +//spotbugsTest { +// effort "max" +// reportLevel "low" +// reportsDir = file("$project.buildDir/reports/spotbugs") +// reports { +// html.enabled true +// text.enabled = true +// } +// def excludeFile = file("${project.projectDir}/spotbugs-exclude.xml") +// if(excludeFile.exists()) { +// excludeFilter = excludeFile +// } +//} spotbugsMain { enabled false diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/websocket/compiler/WebSocketServiceValidationTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/websocket/compiler/WebSocketServiceValidationTest.java index 3141d55cb..61b476673 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/websocket/compiler/WebSocketServiceValidationTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/websocket/compiler/WebSocketServiceValidationTest.java @@ -517,6 +517,14 @@ public void testReturningStreamTypeFromOnOpen() { Assert.assertEquals(diagnosticResult.errorCount(), 0); } + @Test + public void testCustomRemoteFunctions() { + Package currentPackage = loadPackage("sample_package_57"); + PackageCompilation compilation = currentPackage.getCompilation(); + DiagnosticResult diagnosticResult = compilation.diagnosticResult(); + Assert.assertEquals(diagnosticResult.errorCount(), 0); + } + private void assertDiagnostic(Diagnostic diagnostic, PluginConstants.CompilationErrors error) { Assert.assertEquals(diagnostic.diagnosticInfo().code(), error.getErrorCode()); Assert.assertEquals(diagnostic.diagnosticInfo().messageFormat(), diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/Ballerina.toml b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/Ballerina.toml new file mode 100644 index 000000000..7dcd93e3f --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "websocket_test" +name = "sample_57" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/server.bal b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/server.bal new file mode 100644 index 000000000..d175072e0 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_57/server.bal @@ -0,0 +1,40 @@ +// Copyright (c) 2023 WSO2 LLC. (www.wso2.com) All Rights Reserved. +// +// WSO2 LLC. 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 +// +// //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. + +import ballerina/websocket; + +listener websocket:Listener localListener = new(8080); + +@websocket:ServiceConfig {dispatcherKey: "type"} +service / on localListener { + resource function get .() returns websocket:Service|error { + return new WsService(); + } +} + +service class WsService { + *websocket:Service; + + remote isolated function onOpen123(websocket:Caller caller) returns stream|error { + string[] greets = ["Hi Sam", "Hey Sam", "GM Sam"]; + return greets.toStream(); + } + + remote isolated function onMessagexxx(json data) returns stream|error { + string[] greets = ["Hi Sam", "Hey Sam", "GM Sam"]; + return greets.toStream(); + } +} diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index 8cdb6124c..9456b8192 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -18,7 +18,7 @@ plugins { id 'java' id 'checkstyle' - id 'com.github.spotbugs' + //id 'com.github.spotbugs' } description = 'Ballerina - WebSocket Compiler Plugin' @@ -45,23 +45,23 @@ tasks.withType(Checkstyle) { checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -spotbugsMain { - effort "max" - reportLevel "low" - reportsDir = file("$project.buildDir/reports/spotbugs") - reports { - html.enabled true - text.enabled = true - } - def excludeFile = file("${rootDir}/spotbugs-exclude.xml") - if(excludeFile.exists()) { - excludeFilter = excludeFile - } -} +//spotbugsMain { +// effort "max" +// reportLevel "low" +// reportsDir = file("$project.buildDir/reports/spotbugs") +// reports { +// html.enabled true +// text.enabled = true +// } +// def excludeFile = file("${rootDir}/spotbugs-exclude.xml") +// if(excludeFile.exists()) { +// excludeFilter = excludeFile +// } +//} -spotbugsTest { - enabled = false -} +//spotbugsTest { +// enabled = false +//} compileJava { doFirst { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/PluginConstants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/PluginConstants.java index 3d3101b8f..c71bc256a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/PluginConstants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/PluginConstants.java @@ -41,6 +41,7 @@ public class PluginConstants { static final String RESOURCE_KEY_WORD = "resource"; static final String HTTP_REQUEST = "http:Request"; static final String LISTENER_IDENTIFIER = "Listener"; + static final String DISPATCHER_ANNOTATION = "dispatcherKey"; /** * Compilation Errors of WebSocket module. diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketServiceValidator.java index c6d68c97c..a4749491a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketServiceValidator.java @@ -135,7 +135,7 @@ private void filterRemoteFunctions(FunctionDefinitionNode functionDefinitionNode Utils.validateOnBinaryMessageFunction(functionTypeSymbol, ctx, functionDefinitionNode); break; default: - reportInvalidFunction(functionDefinitionNode); + // no need to validate } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidator.java index d38e5524a..647eb4575 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidator.java @@ -56,7 +56,7 @@ public class WebSocketUpgradeServiceValidator { this.modulePrefix = modulePrefix; } - void validate() { + void validate(boolean customDispatchingEnabled) { ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) ctx.node(); boolean hasRemoteService = serviceDeclarationNode.members().stream().anyMatch(child -> child.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION); @@ -93,6 +93,9 @@ void validate() { }); if (resourceNode != null) { validateResourceReturnTypes(resourceNode); + if (customDispatchingEnabled) { + return; + } ReturnStatementNodeVisitor returnStatementNodeVisitor = new ReturnStatementNodeVisitor(); resourceNode.accept(returnStatementNodeVisitor); for (ReturnStatementNode returnStatementNode : returnStatementNodeVisitor.getReturnStatementNodes()) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidatorTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidatorTask.java index 15f7ef803..763ab9ca3 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidatorTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/websocket/plugin/WebSocketUpgradeServiceValidatorTask.java @@ -18,6 +18,8 @@ package io.ballerina.stdlib.websocket.plugin; +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.AnnotationSymbol; import io.ballerina.compiler.api.symbols.ModuleSymbol; import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; @@ -25,8 +27,11 @@ import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; +import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.NamedArgumentNode; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.NodeLocation; import io.ballerina.compiler.syntax.tree.ParenthesizedArgList; import io.ballerina.compiler.syntax.tree.PositionalArgumentNode; @@ -42,6 +47,7 @@ import java.util.List; import java.util.Optional; +import static io.ballerina.stdlib.websocket.plugin.PluginConstants.DISPATCHER_ANNOTATION; import static io.ballerina.stdlib.websocket.plugin.PluginConstants.ORG_NAME; /** @@ -58,6 +64,8 @@ public void perform(SyntaxNodeAnalysisContext ctx) { } } ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) ctx.node(); + boolean disptacherAnnotation = getDispatcherConfigAnnotation(serviceDeclarationNode, + ctx.semanticModel()); String modulePrefix = Utils.getPrefix(ctx); Optional serviceDeclarationSymbol = ctx.semanticModel().symbol(serviceDeclarationNode); @@ -69,13 +77,39 @@ public void perform(SyntaxNodeAnalysisContext ctx) { ListenerInitExpressionNodeVisitor visitor = new ListenerInitExpressionNodeVisitor(ctx); serviceDeclarationNode.syntaxTree().rootNode().accept(visitor); validateListenerArguments(ctx, visitor); - validateService(ctx, modulePrefix); + validateService(ctx, modulePrefix, disptacherAnnotation); return; } } } } + private boolean isDispatcherKeyAnnotation(AnnotationNode annotation, SemanticModel semanticModel) { + Optional symbolOpt = semanticModel.symbol(annotation); + if (symbolOpt.isEmpty()) { + return false; + } + + Symbol symbol = symbolOpt.get(); + if (!(symbol instanceof AnnotationSymbol)) { + return false; + } + + return annotation.annotValue().toString().contains(DISPATCHER_ANNOTATION); + } + + private boolean getDispatcherConfigAnnotation(ServiceDeclarationNode serviceNode, + SemanticModel semanticModel) { + Optional metadata = serviceNode.metadata(); + if (metadata.isEmpty()) { + return false; + } + MetadataNode metaData = metadata.get(); + NodeList annotations = metaData.annotations(); + return annotations.stream() + .anyMatch(ann -> isDispatcherKeyAnnotation(ann, semanticModel)); + } + private boolean isListenerBelongsToWebSocketModule(TypeSymbol listenerType) { if (listenerType.typeKind() == TypeDescKind.UNION) { return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() @@ -135,10 +169,10 @@ private boolean isWebSocketModule(ModuleSymbol moduleSymbol) { && Utils.equals(moduleSymbol.id().orgName(), ORG_NAME); } - private void validateService(SyntaxNodeAnalysisContext ctx, String modulePrefix) { + private void validateService(SyntaxNodeAnalysisContext ctx, String modulePrefix, boolean disptacherAnnotation) { WebSocketUpgradeServiceValidator wsServiceValidator; wsServiceValidator = new WebSocketUpgradeServiceValidator(ctx, modulePrefix + SyntaxKind.COLON_TOKEN.stringValue()); - wsServiceValidator.validate(); + wsServiceValidator.validate(disptacherAnnotation); } } diff --git a/native/build.gradle b/native/build.gradle index bd98dca13..a83ad0333 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -68,19 +68,19 @@ tasks.withType(Checkstyle) { checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -spotbugsMain { - effort "max" - reportLevel "low" - reportsDir = file("$project.buildDir/reports/spotbugs") - reports { - html.enabled true - text.enabled = true - } - def excludeFile = file('spotbugs-exclude.xml') - if(excludeFile.exists()) { - excludeFilter = excludeFile - } -} +//spotbugsMain { +// effort "max" +// reportLevel "low" +// reportsDir = file("$project.buildDir/reports/spotbugs") +// reports { +// html.enabled true +// text.enabled = true +// } +// def excludeFile = file('spotbugs-exclude.xml') +// if(excludeFile.exists()) { +// excludeFilter = excludeFile +// } +//} spotbugsTest { enabled = false diff --git a/native/spotbugs-exclude.xml b/native/spotbugs-exclude.xml index 9772ff93b..438fc304e 100644 --- a/native/spotbugs-exclude.xml +++ b/native/spotbugs-exclude.xml @@ -20,4 +20,8 @@ + + + + diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketConstants.java b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketConstants.java index 01c336c72..81b15f3f7 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketConstants.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketConstants.java @@ -49,6 +49,7 @@ public class WebSocketConstants { public static final BString ANNOTATION_ATTR_TIMEOUT = StringUtils.fromString("timeout"); public static final BString ANNOTATION_ATTR_MAX_FRAME_SIZE = StringUtils.fromString("maxFrameSize"); public static final BString ANNOTATION_ATTR_VALIDATION_ENABLED = StringUtils.fromString("validation"); + public static final BString ANNOTATION_ATTR_DISPATCHER_KEY = StringUtils.fromString("dispatcherKey"); public static final BString RETRY_CONFIG = StringUtils.fromString("retryConfig"); public static final String LOG_MESSAGE = "{} {}"; diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceCallback.java b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceCallback.java index c44b81121..2a6b74497 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceCallback.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceCallback.java @@ -87,8 +87,10 @@ public void notifySuccess(Object result) { null, returnStreamUnitCallBack, null, PredefinedTypes.TYPE_NULL); } else if (result == null) { webSocketConnection.readNextFrame(); - } else if (resource.equals(WebSocketConstants.RESOURCE_NAME_ON_TEXT_MESSAGE) || - resource.equals(WebSocketConstants.RESOURCE_NAME_ON_MESSAGE)) { + } else if (!resource.equals(WebSocketConstants.RESOURCE_NAME_ON_PONG) && + !resource.equals(WebSocketConstants.RESOURCE_NAME_ON_CLOSE) && + !resource.equals(WebSocketConstants.RESOURCE_NAME_ON_ERROR) && + !resource.equals(WebSocketConstants.RESOURCE_NAME_ON_IDLE_TIMEOUT)) { sendTextMessage(StringUtils.fromString(result.toString()), promiseCombiner); } else { log.error("invalid return type"); diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java index e7f1a4466..0e742a322 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java @@ -70,6 +70,7 @@ import io.netty.handler.codec.http.HttpHeaders; import org.ballerinalang.langlib.value.CloneReadOnly; import org.ballerinalang.langlib.value.CloneWithType; +import org.ballerinalang.langlib.value.FromJsonString; import org.ballerinalang.langlib.value.FromJsonStringWithType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -400,6 +401,12 @@ public static void dispatchOnText(WebSocketConnectionInfo connectionInfo, WebSoc try { WebSocketConnection webSocketConnection = connectionInfo.getWebSocketConnection(); WebSocketService wsService = connectionInfo.getService(); + WebSocketConnectionInfo.StringAggregator stringAggregator = connectionInfo + .createIfNullAndGetStringAggregator(); + String dispatchingKey = ((WebSocketServerService) wsService).getDispatchinkKey(); + String methodName; + methodName = getCustomRemoteMethodName(textMessage, webSocketConnection, stringAggregator, + dispatchingKey); MethodType onTextMessageResource = null; BObject balservice; BObject wsEndpoint = connectionInfo.getWebSocketEndpoint(); @@ -408,13 +415,15 @@ public static void dispatchOnText(WebSocketConnectionInfo connectionInfo, WebSoc MethodType[] remoteFunctions = ((ServiceType) (((BValue) dispatchingService).getType())).getMethods(); for (MethodType remoteFunc : remoteFunctions) { String funcName = remoteFunc.getName(); - if (funcName.equals(WebSocketConstants.RESOURCE_NAME_ON_TEXT_MESSAGE) || + if (funcName.equals(methodName) || + funcName.equals(WebSocketConstants.RESOURCE_NAME_ON_TEXT_MESSAGE) || funcName.equals(WebSocketConstants.RESOURCE_NAME_ON_MESSAGE)) { onTextMessageResource = remoteFunc; break; } } if (onTextMessageResource == null) { + stringAggregator.resetAggregateString(); webSocketConnection.readNextFrame(); return; } @@ -423,10 +432,11 @@ public static void dispatchOnText(WebSocketConnectionInfo connectionInfo, WebSoc Object[] bValues = new Object[parameterTypes.length * 2]; boolean finalFragment = textMessage.isFinalFragment(); - WebSocketConnectionInfo.StringAggregator stringAggregator = connectionInfo - .createIfNullAndGetStringAggregator(); if (finalFragment) { - stringAggregator.appendAggregateString(textMessage.getText()); + if (null == dispatchingKey) { + // If the dispatching key is there, appending is already done. + stringAggregator.appendAggregateString(textMessage.getText()); + } int index = 0; try { for (Type param : parameterTypes) { @@ -514,6 +524,42 @@ public static void dispatchOnText(WebSocketConnectionInfo connectionInfo, WebSoc } } + private static String getCustomRemoteMethodName(WebSocketTextMessage textMessage, + WebSocketConnection webSocketConnection, WebSocketConnectionInfo.StringAggregator stringAggregator, + String dispatchingKey) { + String methodName = null; + if (null != dispatchingKey) { + boolean finalFragment = textMessage.isFinalFragment(); + if (finalFragment) { + stringAggregator.appendAggregateString(textMessage.getText()); + } else { + stringAggregator.appendAggregateString(textMessage.getText()); + webSocketConnection.readNextFrame(); + } + BString dispatchingValue = ((BMap) FromJsonString.fromJsonString(StringUtils + .fromString(stringAggregator.getAggregateString()))) + .getStringValue(StringUtils.fromString(dispatchingKey)); + methodName = createCustomRemoteFunction(dispatchingValue.getValue()); + } + return methodName; + } + + private static String createCustomRemoteFunction(String dispatchingValue) { + dispatchingValue = "on " + dispatchingValue; + StringBuilder builder = new StringBuilder(); + String[] words = dispatchingValue.split("[\\W_]+"); + for (int i = 0; i < words.length; i++) { + String word = words[i]; + if (i == 0) { + word = word.isEmpty() ? word : word.toLowerCase(); + } else { + word = word.isEmpty() ? word : Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase(); + } + builder.append(word); + } + return builder.toString(); + } + private static void sendDataBindingError(WebSocketConnection webSocketConnection, String errorMessage) { if (errorMessage.length() > 100) { errorMessage = errorMessage.substring(0, 80) + "..."; diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java b/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java index 469aa9323..0bdd83785 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java @@ -30,6 +30,7 @@ import io.ballerina.stdlib.websocket.WebSocketService; import io.ballerina.stdlib.websocket.WebSocketUtil; +import static io.ballerina.stdlib.websocket.WebSocketConstants.ANNOTATION_ATTR_DISPATCHER_KEY; import static io.ballerina.stdlib.websocket.WebSocketConstants.ANNOTATION_ATTR_VALIDATION_ENABLED; /** @@ -42,6 +43,7 @@ public class WebSocketServerService extends WebSocketService { private int maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE; private int idleTimeoutInSeconds = 0; private boolean enableValidation = true; + private String dispatchinkKey = null; public WebSocketServerService(BObject service, Runtime runtime, String basePath) { super(service, runtime); @@ -56,6 +58,7 @@ private void populateConfigs(String basePath) { WebSocketConstants.ANNOTATION_ATTR_IDLE_TIMEOUT, 0); maxFrameSize = WebSocketUtil.findMaxFrameSize(configAnnotation); enableValidation = configAnnotation.getBooleanValue(ANNOTATION_ATTR_VALIDATION_ENABLED); + dispatchinkKey = configAnnotation.getStringValue(ANNOTATION_ATTR_DISPATCHER_KEY).getValue(); } service.addNativeData(WebSocketConstants.ANNOTATION_ATTR_MAX_FRAME_SIZE.toString(), maxFrameSize); service.addNativeData(WebSocketConstants.ANNOTATION_ATTR_VALIDATION_ENABLED.toString(), enableValidation); @@ -89,6 +92,10 @@ public void setBasePathToServiceObj(String basePath) { this.basePath = basePath; } + public String getDispatchinkKey() { + return dispatchinkKey; + } + public String getBasePath() { return basePath; } From 67f6722e942fcc722a8c28f3497571c6633ef5fe Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:20:14 +0530 Subject: [PATCH 10/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 206b91fff..eb36b57ed 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,9 +40,6 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -modules = [ - {org = "ballerina", packageName = "constraint", moduleName = "constraint"} -] [[package]] org = "ballerina" @@ -204,9 +201,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] -modules = [ - {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} -] [[package]] org = "ballerina" @@ -333,14 +327,12 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, - {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.runtime"}, - {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From 33e874dd615f4687ef5ba0a4956f8d8186d348af Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:23:51 +0530 Subject: [PATCH 11/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index eb36b57ed..ccfad57d8 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -201,6 +201,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] +modules = [ + {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} +] [[package]] org = "ballerina" @@ -333,6 +336,7 @@ dependencies = [ {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From b30361fe93163d42583568ae3dd58e8a67ea1d26 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 11:33:38 +0530 Subject: [PATCH 12/17] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index ccfad57d8..c03058736 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -102,9 +102,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] [[package]] org = "ballerina" @@ -201,9 +198,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] -modules = [ - {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} -] [[package]] org = "ballerina" @@ -331,12 +325,10 @@ version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "http"}, - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.runtime"}, - {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, From 6139449c0b6deef7b34a1bf724d3e049323ac2cb Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 14:19:26 +0530 Subject: [PATCH 13/17] Fix test failure --- ballerina/Dependencies.toml | 12 +++++++ compiler-plugin-tests/build.gradle | 26 +++++++------- compiler-plugin/build.gradle | 34 +++++++++---------- .../WebSocketResourceDispatcher.java | 2 +- .../server/WebSocketServerService.java | 10 +++--- 5 files changed, 49 insertions(+), 35 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index c03058736..206b91fff 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -40,6 +40,9 @@ version = "1.1.0" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +modules = [ + {org = "ballerina", packageName = "constraint", moduleName = "constraint"} +] [[package]] org = "ballerina" @@ -102,6 +105,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] [[package]] org = "ballerina" @@ -198,6 +204,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.regexp"} ] +modules = [ + {org = "ballerina", packageName = "lang.string", moduleName = "lang.string"} +] [[package]] org = "ballerina" @@ -324,11 +333,14 @@ name = "websocket" version = "2.6.0" dependencies = [ {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "jwt"}, {org = "ballerina", name = "lang.array"}, {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "lang.value"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "oauth2"}, diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle index bfd4794aa..25671221b 100644 --- a/compiler-plugin-tests/build.gradle +++ b/compiler-plugin-tests/build.gradle @@ -48,19 +48,19 @@ checkstyle { checkstyleTest.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -//spotbugsTest { -// effort "max" -// reportLevel "low" -// reportsDir = file("$project.buildDir/reports/spotbugs") -// reports { -// html.enabled true -// text.enabled = true -// } -// def excludeFile = file("${project.projectDir}/spotbugs-exclude.xml") -// if(excludeFile.exists()) { -// excludeFilter = excludeFile -// } -//} +spotbugsTest { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${project.projectDir}/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} spotbugsMain { enabled false diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index 9456b8192..8cdb6124c 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -18,7 +18,7 @@ plugins { id 'java' id 'checkstyle' - //id 'com.github.spotbugs' + id 'com.github.spotbugs' } description = 'Ballerina - WebSocket Compiler Plugin' @@ -45,23 +45,23 @@ tasks.withType(Checkstyle) { checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -//spotbugsMain { -// effort "max" -// reportLevel "low" -// reportsDir = file("$project.buildDir/reports/spotbugs") -// reports { -// html.enabled true -// text.enabled = true -// } -// def excludeFile = file("${rootDir}/spotbugs-exclude.xml") -// if(excludeFile.exists()) { -// excludeFilter = excludeFile -// } -//} +spotbugsMain { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} -//spotbugsTest { -// enabled = false -//} +spotbugsTest { + enabled = false +} compileJava { doFirst { diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java index 0e742a322..e3736efca 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java @@ -403,7 +403,7 @@ public static void dispatchOnText(WebSocketConnectionInfo connectionInfo, WebSoc WebSocketService wsService = connectionInfo.getService(); WebSocketConnectionInfo.StringAggregator stringAggregator = connectionInfo .createIfNullAndGetStringAggregator(); - String dispatchingKey = ((WebSocketServerService) wsService).getDispatchinkKey(); + String dispatchingKey = ((WebSocketServerService) wsService).getDispatchingKey(); String methodName; methodName = getCustomRemoteMethodName(textMessage, webSocketConnection, stringAggregator, dispatchingKey); diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java b/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java index 0bdd83785..d15e37639 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/server/WebSocketServerService.java @@ -43,7 +43,7 @@ public class WebSocketServerService extends WebSocketService { private int maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE; private int idleTimeoutInSeconds = 0; private boolean enableValidation = true; - private String dispatchinkKey = null; + private String dispatchingKey = null; public WebSocketServerService(BObject service, Runtime runtime, String basePath) { super(service, runtime); @@ -58,7 +58,9 @@ private void populateConfigs(String basePath) { WebSocketConstants.ANNOTATION_ATTR_IDLE_TIMEOUT, 0); maxFrameSize = WebSocketUtil.findMaxFrameSize(configAnnotation); enableValidation = configAnnotation.getBooleanValue(ANNOTATION_ATTR_VALIDATION_ENABLED); - dispatchinkKey = configAnnotation.getStringValue(ANNOTATION_ATTR_DISPATCHER_KEY).getValue(); + if (configAnnotation.getStringValue(ANNOTATION_ATTR_DISPATCHER_KEY) != null) { + dispatchingKey = configAnnotation.getStringValue(ANNOTATION_ATTR_DISPATCHER_KEY).getValue(); + } } service.addNativeData(WebSocketConstants.ANNOTATION_ATTR_MAX_FRAME_SIZE.toString(), maxFrameSize); service.addNativeData(WebSocketConstants.ANNOTATION_ATTR_VALIDATION_ENABLED.toString(), enableValidation); @@ -92,8 +94,8 @@ public void setBasePathToServiceObj(String basePath) { this.basePath = basePath; } - public String getDispatchinkKey() { - return dispatchinkKey; + public String getDispatchingKey() { + return dispatchingKey; } public String getBasePath() { From 87af15204e8fe60f26fd866f93afe1231d03f5fe Mon Sep 17 00:00:00 2001 From: bhashinee Date: Thu, 2 Feb 2023 16:08:58 +0530 Subject: [PATCH 14/17] Fix spot bugs --- native/build.gradle | 26 +++++++++---------- native/spotbugs-exclude.xml | 2 +- .../WebSocketResourceDispatcher.java | 6 +++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/native/build.gradle b/native/build.gradle index a83ad0333..bd98dca13 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -68,19 +68,19 @@ tasks.withType(Checkstyle) { checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") -//spotbugsMain { -// effort "max" -// reportLevel "low" -// reportsDir = file("$project.buildDir/reports/spotbugs") -// reports { -// html.enabled true -// text.enabled = true -// } -// def excludeFile = file('spotbugs-exclude.xml') -// if(excludeFile.exists()) { -// excludeFilter = excludeFile -// } -//} +spotbugsMain { + effort "max" + reportLevel "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file('spotbugs-exclude.xml') + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} spotbugsTest { enabled = false diff --git a/native/spotbugs-exclude.xml b/native/spotbugs-exclude.xml index 438fc304e..6f0db88bb 100644 --- a/native/spotbugs-exclude.xml +++ b/native/spotbugs-exclude.xml @@ -22,6 +22,6 @@ - + diff --git a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java index e3736efca..759ae3cfa 100644 --- a/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java +++ b/native/src/main/java/io/ballerina/stdlib/websocket/WebSocketResourceDispatcher.java @@ -80,6 +80,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static io.ballerina.runtime.api.TypeTags.ARRAY_TAG; @@ -551,9 +552,10 @@ private static String createCustomRemoteFunction(String dispatchingValue) { for (int i = 0; i < words.length; i++) { String word = words[i]; if (i == 0) { - word = word.isEmpty() ? word : word.toLowerCase(); + word = word.isEmpty() ? word : word.toLowerCase(Locale.ENGLISH); } else { - word = word.isEmpty() ? word : Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase(); + word = word.isEmpty() ? word : Character.toUpperCase(word.charAt(0)) + word.substring(1) + .toLowerCase(Locale.ENGLISH); } builder.append(word); } From b7b6c1ba2ff4cea8bc5090687b4fb9464fec38cc Mon Sep 17 00:00:00 2001 From: bhashinee Date: Mon, 6 Feb 2023 07:42:22 +0530 Subject: [PATCH 15/17] Enable disabled test cases --- ballerina/tests/custom_remote_functions.bal | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ballerina/tests/custom_remote_functions.bal b/ballerina/tests/custom_remote_functions.bal index 405ace246..d59fceff8 100644 --- a/ballerina/tests/custom_remote_functions.bal +++ b/ballerina/tests/custom_remote_functions.bal @@ -123,7 +123,7 @@ service class LotOfUnderscoreService { } } -@test:Config {enable:false} +@test:Config {} public function testPingMessage() returns Error? { Client cl = check new("ws://localhost:21401"); check cl->writeMessage({"event": "ping"}); @@ -131,7 +131,7 @@ public function testPingMessage() returns Error? { test:assertEquals(resp, {"event": "pong"}); } -@test:Config {enable:false} +@test:Config {} public function testSubscribeMessage() returns Error? { Client cl = check new("ws://localhost:21401/subscribe"); check cl->writeMessage({"event": "subscribe", "pair": ["XBT/USD", "XBT/EUR"], "subscription": {"name": "ticker"}}); @@ -142,7 +142,7 @@ public function testSubscribeMessage() returns Error? { test:assertEquals(resp2, {"event": "heartbeat"}); } -@test:Config {enable:false} +@test:Config {} public function testDispatchingToDefaultRemoteMethod() returns Error? { Client cl = check new("ws://localhost:21401/onMessage"); check cl->writeMessage({"event": "heartbeat"}); @@ -151,7 +151,7 @@ public function testDispatchingToDefaultRemoteMethod() returns Error? { test:assertEquals(resp2, {"event": "heartbeat"}); } -@test:Config {enable:false} +@test:Config {} public function testDispatchingToNone() returns Error? { Client cl = check new("ws://localhost:21401/noRemoteMethod"); check cl->writeMessage({"event": "heartbeat"}); @@ -160,7 +160,7 @@ public function testDispatchingToNone() returns Error? { test:assertEquals(resp2, {"event": "onMessages"}); } -@test:Config {enable:false} +@test:Config {} public function testDatabindingFailure() returns Error? { Client cl = check new("ws://localhost:21401/dataBindingFailure"); check cl->writeMessage({"event": "Messages"}); @@ -172,7 +172,7 @@ public function testDatabindingFailure() returns Error? { } } -@test:Config {enable: false} +@test:Config {} public function testUnderscore() returns Error? { Client cl = check new("ws://localhost:21401/underscore"); check cl->writeMessage({"type": "_ping"}); From 6f9e77ad24c5f6c1f1643bab0b52d8149faf17a8 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Mon, 6 Feb 2023 07:56:39 +0530 Subject: [PATCH 16/17] Update the specification --- docs/spec/spec.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 43c8f2fa5..bc2482f34 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -34,6 +34,7 @@ The conforming implementation of the specification is released and included in t * [onIdleTimeout](#onidletimeout) * [onClose](#onclose) * [onError](#onerror) + * 3.2.2. [Dispatching to custom remote methods](#322-dispatching-to-custom-remote-methods) 4. [Client](#4-client) * 4.1. [Client Configurations](#41-client-configurations) * 4.2. [Initialization](#42-initialization) @@ -350,6 +351,38 @@ remote function onError(websocket:Caller caller, error err) { } ``` +#### 3.2.2. [Dispatching to custom remote methods](#322-dispatching-to-custom-remote-methods) + +The WebSocket service also supports dispatching messages to custom remote functions based on the message type(declared by a field in the received message) with the end goal of generating meaningful Async APIs. + +For example, if the message is `{"event": "heartbeat"}` it will get dispatched to `onHeartbeat` remote function if the user has defined such a method. + +**Dispatching rules** + +1. The user can configure the field name(key) to identify the messages and the allowed values as message types. + +The `dispatcherKey` is used to identify the event type of the incoming message by its value. + +Ex: +incoming message = ` {"event": "heartbeat"}` +dispatcherKey = "event" +event/message type = "heartbeat" +dispatching to remote function = "onHeartbeat" + +```ballerina +@websocket:ServiceConfig { + dispatcherKey: "event" +} +service / on new websocket:Listener(9090) {} +``` + +2. Naming of the remote function. + +- If there are spaces and underscores between message types, those will be removed and made camel case("un subscribe" -> "onUnSubscribe"). +- The 'on' word is added as the predecessor and the remote function name is in the camel case("heartbeat" -> "onHeartbeat"). + +3. If an unmatching message type receives where a matching remote function is not implemented in the WebSocket service by the user, it gets dispatched to the default `onMessage` remote function if it is implemented. Or else it will get ignored. + ## 4. [Client](#4-client) `websocket:Client` can be used to send and receive data synchronously over WebSocket connection. The underlying implementation is non-blocking. From 853baa7b003a39c17b3b73bef61844f3155e5dc5 Mon Sep 17 00:00:00 2001 From: bhashinee Date: Mon, 6 Feb 2023 07:59:32 +0530 Subject: [PATCH 17/17] Update change log --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 6002e66f1..f5bf4b427 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added +- [Implement dispatching to custom remote functions](https://github.com/ballerina-platform/ballerina-standard-library/issues/3670) + ### Fixed - [Fix data binding failure when a union type is used](https://github.com/ballerina-platform/ballerina-standard-library/issues/3909) - [Fix data binding failure when a enum type is used](https://github.com/ballerina-platform/ballerina-standard-library/issues/3707)