3

I am trying to implement a simple annotation processor with ballerina (jBallerina-1.0.5 on MacOS Catalina). In this example, I need to:

  1. Iterate through available resource functions of a given service
  2. For each iteration, get values of a given @annotation as annotated in the resource

For an example:

...
service my_service on new http:Listener(8080) {
    @my_annonatation {
        value: "my-value-for-resource-1"
    }
    resource function my_resource_1() {}

    @my_annonatation {
        value: "my-value-for-resource-2"
    }
    resource function my_resource_2() {}
}

According to the above service code snippet, I need get resource names as ["my_resource_1", "my_resource_2"] and @annotation value as my-value-for-resource-1,my-value-for-resource-2 for my_resource_1 and my_resource_2 accordingly.

My questions are:

  1. How can I get the resources defined in a given service with ballerina
  2. I have tried with function getServiceAnnotations(service serviceType, string annotName, string? moduleName = ()) returns any of ballerina/reflect module to get the @annotation value. But it doesn't give anything in return (Apparently it returns an empty string or could be null).

I don't see any good article for ballerina annotation processing, except for old ballerina 0.x pre-releases 1. I found that the ballerina/reflect module is now updated 2 3 since 0.x pre-releases as I compared with the ballerina source code (but it has not mentioned in ballerina release notes 4).

And also the article "Extending Ballerina" 5 has mentioned an alternative way for annotation processing through compiler extensions which delegates the operation to Java. However we have a limitation to push Java dependancies into the ballerina central.

Please suggest me a workaround to get the expected result and it is highly appreciated the code in pure ballerina language.

References

  1. Ballerina Annotation Processing example for 0.9.x releases
  2. ballerina/reflect of 0.x
  3. ballerina/reflect of 1.0.5
  4. Ballerina release notes
  5. Extending Ballerina
Channa Jayamuni
  • 1,876
  • 1
  • 18
  • 29
  • 1
    Please refer here how we can read resource and service annotations, and the cast the annotations to it record type. Then using the recored type you can get the value of the annotations https://github.com/wso2/product-microgateway/blob/jballerina/components/micro-gateway-core/src/main/ballerina/src/gateway/utils/utils.bal#L39-L47 – Rajith Roshan Dec 15 '19 at 04:43
  • @RajithRoshan you're on spot!, I was referring to the micro-gateway source code and successfully compiled locally including micro-gateway-core which contains annotation processing operations. As I find micro-gateway uses a different version of Ballerina and the `ballerina/reflect` module is now updated. I am testing on jballerina-1.0.5. – Channa Jayamuni Dec 15 '19 at 05:11

2 Answers2

3

Annotation processing can be done at either compile-time or runtime. Right now (with Ballerina 1.0.5 and earlier versions), compile-time annotation processing can only be done via the compiler extensions as described in the "Extending Ballerina" doc, whereas runtime annotation access can be done using the annotation access expression or via the ballerina/reflect module (only those that are not possible via the annotation access expression are now available in the ballerina/reflect module - while this addition is mentioned in the release notes "A binary operator .@ has been introduced to access annotation values at runtime.", the fact that this removes some functions from the ballerina/reflect module seems to have been missed).

https://github.com/ballerina-platform/ballerina-lang/issues/19325 describes the proposed Ballerina compiler plugin architecture, which would allow writing the extensions in Ballerina.

At runtime:

  1. Retrieving the names of the resources defined in the service - IINM this is currently not possible without writing an external method (using Java interop). Are the names of the resources not known at compile time? Can you please create an issue in the ballerina-lang repository with the requirement?

  2. Accessing the resource annotations - this can be done using the reflect:getResourceAnnotations method

import ballerina/http;
import ballerina/io;
import ballerina/reflect;

type AnnotRecord record {| 
    string value; 
|};

annotation AnnotRecord my_annonatation on resource function;

string[] resourceNames = ["my_resource_1", "my_resource_2"];

service my_service on new http:Listener(8080) {
    @my_annonatation {
        value: "my-value-for-resource-1"
    }
    resource function my_resource_1(http:Caller caller, http:Request request) {}

    @my_annonatation {
        value: "my-value-for-resource-2"
    }
    resource function my_resource_2(http:Caller caller, http:Request request) {}
}

public function main() {
    foreach var resourceName in resourceNames {
        any annot = reflect:getResourceAnnotations(my_service, resourceName, "my_annonatation");
        if annot is AnnotRecord {
            io:println(annot.value);
        }
    }
}
MaryamZi
  • 871
  • 5
  • 5
  • thanks for the response. And it is a good idea to submit an issue to request the resource-listing feature in a future release. I have tried with `reflect:getResourceAnnotations` function in the exact similar way. Unfortunately, it doesn't work for me, and it just gives me an empty value. Could you get the expected result from the above code snippet? And which ballerina version you're currently using? Jballerina-1.0.5 doesn't treat me well even for the above code. – Channa Jayamuni Dec 15 '19 at 05:21
  • @ChannaJayamuni I tried this out with 1.0.5. ``` $ ballerina -v Ballerina 1.0.5 Language specification 2019R3 ``` The following was the output ``` $ ballerina run xyz.bal Compiling source xyz.bal Generating executables Running executables my-value-for-resource-1 my-value-for-resource-2 [ballerina/http] started HTTP/WS listener 0.0.0.0:8080 ``` Any possibility of sharing the code you are running? Can you also confirm if the annotation is defined in the same file/module or a different one? Are you getting an empty value for this sample too? – MaryamZi Dec 15 '19 at 05:42
  • that's strange! it works for individual ballerina files, but not for ballerina projects. I have placed the same code in a ballerina project, So I don't get the annotation value. Do you think if there's any specific reason for this behavior? Or probably do I need to add some configs to the toml file? – Channa Jayamuni Dec 15 '19 at 05:53
  • Can you try passing `/:` as the final argument to the `reflect:getResourceAnnotations` function? For example `any annot = reflect:getResourceAnnotations(my_service, resourceName, "my_annonatation", "userone/foo:0.1.0");` Ideally this should be handled by the `ballerina/reflect` module itself. I've created https://github.com/ballerina-platform/ballerina-lang/issues/20396 to track this. – MaryamZi Dec 15 '19 at 06:23
  • Boom !!! It worked like magic! Thanks @MaryamZi. I have added /: into the function. And I appreciate you've created an issue for this. – Channa Jayamuni Dec 15 '19 at 07:58
0

UPDATE:

@MaryamZis' solution is nicely worked for single ballerina files but not for ballerina projects. As she suggested, I have passed the <org-name>/<module>:<version> into the module parameter of the reflect:getResourceAnnotations function to get it working.

...
any annot = reflect:getResourceAnnotations(
    my_service, 
    resourceName, 
    "my_annonatation", 
    "my_org/my_module:0.1.0"
);
...

//NOTE: org-name and version are defined in Ballerina.toml of the project

However, it is expected to detect current projects' annotations by reflect functions without specifying the module. This issue is logged at the ballerina git repository by @MaryamZi.

Channa Jayamuni
  • 1,876
  • 1
  • 18
  • 29