Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ballerina/annotation.bal
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ public annotation WSServiceConfig ServiceConfig on service;

# Configurations used to define dispatching rules for remote functions.
#
# + value - The value which is going to be used for dispatching to custom remote functions.
public type WsDispatcherMapping record {|
string value;
# + dispatcherValue - The dispatcher value which is going to be used for dispatching to custom remote functions.
public type WsDispatcherConfig record {|
string dispatcherValue;
|};

# The annotation which is used to configure the dispatching rules for WebSocket remote functions.
public const annotation WsDispatcherMapping DispatcherMapping on function;
public const annotation WsDispatcherConfig DispatcherConfig on function;
12 changes: 6 additions & 6 deletions ballerina/tests/test_dispatcher_mapping_annotation.bal
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ service / on new Listener(22103) {
service class WsService22103 {
*Service;

@DispatcherMapping {
value: "subscribe"
@DispatcherConfig {
dispatcherValue: "subscribe"
}
remote function onSubscribeMessage(Subscribe message) returns string {
return "onSubscribeMessage";
Expand All @@ -46,19 +46,19 @@ service class WsService22103 {
}

@test:Config {
groups: ["dispatcherMappingAnnotation"]
groups: ["dispatcherConfigAnnotation"]
}
public function testDispatcherMappingAnnotation() returns error? {
public function testDispatcherConfigAnnotation() returns error? {
Client wsClient = check new ("ws://localhost:22103/");
check wsClient->writeMessage({event: "subscribe", data: "test"});
string res = check wsClient->readMessage();
test:assertEquals(res, "onSubscribeMessage");
}

@test:Config {
groups: ["dispatcherMappingAnnotation"]
groups: ["dispatcherConfigAnnotation"]
}
public function testDispatcherMappingAnnotationWithCustomOnError() returns error? {
public function testDispatcherConfigAnnotationWithCustomOnError() returns error? {
Client wsClient = check new ("ws://localhost:22103/");
check wsClient->writeMessage({event: "subscribe", invalidField: "test"});
string res = check wsClient->readMessage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,15 +573,16 @@ public void testRemoteFunctionWithStreamAndCloseFrameReturnTypes() {
}

@Test
public void testDispatcherMappingAnnotation() {
public void testDispatcherConfigAnnotation() {
Package currentPackage = loadPackage("sample_package_63");
PackageCompilation compilation = currentPackage.getCompilation();
DiagnosticResult diagnosticResult = compilation.diagnosticResult();
Assert.assertEquals(diagnosticResult.errorCount(), 3);
Diagnostic firstDiagnostic = (Diagnostic) diagnosticResult.errors().toArray()[0];
assertDiagnostic(firstDiagnostic, PluginConstants.CompilationErrors.RE_DECLARED_REMOTE_FUNCTIONS);
Diagnostic secondDiagnostic = (Diagnostic) diagnosticResult.errors().toArray()[1];
assertDiagnostic(secondDiagnostic, PluginConstants.CompilationErrors.DUPLICATED_DISPATCHER_MAPPING_VALUE);
assertDiagnostic(secondDiagnostic,
PluginConstants.CompilationErrors.DUPLICATED_DISPATCHER_MAPPING_DISPATCHER_VALUE);
Diagnostic thirdDiagnostic = (Diagnostic) diagnosticResult.errors().toArray()[2];
assertDiagnostic(thirdDiagnostic, PluginConstants.CompilationErrors.INVALID_FUNCTION_ANNOTATION);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ service class WsService {
return "onSubscribe";
}

@ws:DispatcherMapping {
value: "subscribe"
@ws:DispatcherConfig {
dispatcherValue: "subscribe"
}
remote function onSubscribeMessage(Subscribe message) returns string {
return "onSubscribeMessage";
}

@ws:DispatcherMapping {
value: "subscribe"
@ws:DispatcherConfig {
dispatcherValue: "subscribe"
}
remote function onSubscribeText(Subscribe message) returns string {
return "onSubscribeText";
}

@ws:DispatcherMapping {
value: "ping"
@ws:DispatcherConfig {
dispatcherValue: "ping"
}
remote function onPing(Subscribe message) returns string {
return "onPing";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ public enum CompilationErrors {
"WEBSOCKET_215"),
INVALID_REMOTE_FUNCTIONS("Cannot have `{0}` with `onMessage` remote function",
"WEBSOCKET_216"),
RE_DECLARED_REMOTE_FUNCTIONS("Cannot have `{0}` because the message type `{1}` is already " +
RE_DECLARED_REMOTE_FUNCTIONS("Cannot have `{0}` because the dispatcher value `{1}` is already " +
"associated with `{2}` remote function", "WEBSOCKET_217"),
DUPLICATED_DISPATCHER_MAPPING_VALUE("DispatcherMapping annotation value `{0}` is already " +
DUPLICATED_DISPATCHER_MAPPING_DISPATCHER_VALUE("DispatcherConfig annotation dispatcherValue `{0}` is already " +
"exists", "WEBSOCKET_218"),
INVALID_FUNCTION_ANNOTATION("Invalid annotation provided for `{0}` remote function. "
+ "This annotation can only be used with the custom dispatcher functions", "WEBSOCKET_219"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

import static io.ballerina.stdlib.websocket.WebSocketConstants.ANNOTATION_ATTR_DISPATCHER_VALUE;
import static io.ballerina.stdlib.websocket.WebSocketResourceDispatcher.createCustomRemoteFunction;
import static io.ballerina.stdlib.websocket.plugin.PluginConstants.CompilationErrors.DUPLICATED_DISPATCHER_MAPPING_VALUE;
import static io.ballerina.stdlib.websocket.plugin.PluginConstants.CompilationErrors.DUPLICATED_DISPATCHER_MAPPING_DISPATCHER_VALUE;
import static io.ballerina.stdlib.websocket.plugin.PluginConstants.CompilationErrors.INVALID_FUNCTION_ANNOTATION;
import static io.ballerina.stdlib.websocket.plugin.PluginConstants.CompilationErrors.RE_DECLARED_REMOTE_FUNCTIONS;

Expand All @@ -64,7 +64,7 @@ public class WebSocketServiceValidator {
this.ctx = syntaxNodeAnalysisContext;
}

private static Optional<String> getDispatcherMappingAnnotatedFunctionName(FunctionDefinitionNode node,
private static Optional<String> getDispatcherConfigAnnotatedFunctionName(FunctionDefinitionNode node,
SyntaxNodeAnalysisContext ctx) {
if (node.metadata().isEmpty()) {
return Optional.empty();
Expand All @@ -77,7 +77,7 @@ private static Optional<String> getDispatcherMappingAnnotatedFunctionName(Functi
if (!annotationType.get().getModule().flatMap(Symbol::getName)
.orElse("").equals(WebSocketConstants.PACKAGE_WEBSOCKET) ||
!annotationType.get().getName().orElse("")
.equals(WebSocketConstants.WEBSOCKET_DISPATCHER_MAPPING_ANNOTATION)) {
.equals(WebSocketConstants.WEBSOCKET_DISPATCHER_CONFIG_ANNOTATION)) {
continue;
}
if (annotationNode.annotValue().isEmpty()) {
Expand Down Expand Up @@ -160,10 +160,10 @@ public void validate() {
!functionSet.containsKey(PluginConstants.ON_BINARY_MESSAGE)) {
reportDiagnostic(classDefNode, PluginConstants.CompilationErrors.ON_MESSAGE_GENERATION_HINT);
}
validateDispatcherMappingAnnotations(classDefNode, functionSet);
validateDispatcherConfigAnnotations(classDefNode, functionSet);
}

private void validateDispatcherMappingAnnotations(ClassDefinitionNode classDefNode,
private void validateDispatcherConfigAnnotations(ClassDefinitionNode classDefNode,
Map<String, Boolean> functionSet) {
Set<String> seenAnnotationValues = new HashSet<>();
for (Node node : classDefNode.members()) {
Expand All @@ -176,12 +176,12 @@ private void validateDispatcherMappingAnnotations(ClassDefinitionNode classDefNo
continue;
}
Optional<String> funcName = ctx.semanticModel().symbol(funcDefinitionNode).flatMap(Symbol::getName);
Optional<String> annoDispatchingValue = getDispatcherMappingAnnotatedFunctionName(funcDefinitionNode, ctx);
Optional<String> annoDispatchingValue = getDispatcherConfigAnnotatedFunctionName(funcDefinitionNode, ctx);
if (funcName.isEmpty() || annoDispatchingValue.isEmpty()) {
continue;
}
if (seenAnnotationValues.contains(annoDispatchingValue.get())) {
Utils.reportDiagnostics(ctx, DUPLICATED_DISPATCHER_MAPPING_VALUE,
Utils.reportDiagnostics(ctx, DUPLICATED_DISPATCHER_MAPPING_DISPATCHER_VALUE,
funcDefinitionNode.location(), annoDispatchingValue.get());
continue;
}
Expand Down
12 changes: 6 additions & 6 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,13 +389,13 @@ For example, if the message is `{"event": "heartbeat"}` it will get dispatched t
* 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. Custom Dispatching with `@DispatcherMapping` annotation
3. Custom Dispatching with `@websocket:DispatcherConfig` annotation

The `@DispatcherMapping` annotation allows users to explicitly define the dispatching behavior for remote functions. If an incoming message type matches the value in the annotation, the respective remote function will be invoked.
The `@websocket:DispatcherConfig` annotation allows users to explicitly define the dispatching behavior for remote functions. If an incoming message type matches the dispatcherValue in the annotation, the respective remote function will be invoked.

```ballerina
@DispatcherMapping {
value: "subscribe"
@websocket:DispatcherConfig {
dispatcherValue: "subscribe"
}
remote function onSubscribeMessage(Subscribe message) returns string {
return "onSubscribeMessage";
Expand Down Expand Up @@ -427,8 +427,8 @@ dispatching error remote function = "onHeartbeatError"
* Example 2

```ballerina
@websocket:DispatcherMapping {
value: "subscribe"
@websocket:DispatcherConfig {
dispatcherValue: "subscribe"
}
remote function onSubscribeMessage(Subscribe message) returns error? {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public class WebSocketConstants {
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 String WEBSOCKET_DISPATCHER_MAPPING_ANNOTATION = "DispatcherMapping";
public static final String ANNOTATION_ATTR_DISPATCHER_VALUE = "value";
public static final String WEBSOCKET_DISPATCHER_CONFIG_ANNOTATION = "DispatcherConfig";
public static final String ANNOTATION_ATTR_DISPATCHER_VALUE = "dispatcherValue";

public static final BString RETRY_CONFIG = StringUtils.fromString("retryConfig");
public static final String LOG_MESSAGE = "{} {}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private Map<String, RemoteMethodType> getDispatchingFunctionMap(ServiceType disp
@SuppressWarnings(UNCHECKED)
public static Optional<String> getAnnotationDispatchingValue(RemoteMethodType remoteFunc) {
BMap<BString, Object> annotations = (BMap<BString, Object>) remoteFunc.getAnnotation(fromString(
ModuleUtils.getPackageIdentifier() + ":" + WebSocketConstants.WEBSOCKET_DISPATCHER_MAPPING_ANNOTATION));
ModuleUtils.getPackageIdentifier() + ":" + WebSocketConstants.WEBSOCKET_DISPATCHER_CONFIG_ANNOTATION));
if (annotations != null && annotations.containsKey(fromString(ANNOTATION_ATTR_DISPATCHER_VALUE))) {
String dispatchingValue = annotations.
getStringValue(fromString(ANNOTATION_ATTR_DISPATCHER_VALUE)).getValue();
Expand Down