Ballerina Native Client Connectors and all you need to know :)

Ballerina is a general purpose programming language which is being developed by WSO2, More details here.

You can follow this and get to know all about Connectors, Endpoints related concepts in general. It also shows the steps needed to write Ballerina Client connectors.

Although this story is a bit more specific technical “how to” guide about creating “native” Client connectors in Ballerina, I will be explaining how all the internals fit together and work as well. :)

Note that even thought I’m always using the phrase “native Client connector”, only the actions inside the connector are native, not the connector.

Ballerina uses java SPI services to plug native stuff to Ballerina. So to write native stuff, what you have to do in general is as follows.

  1. Implement the Ballerina definitions by adding “native” keyword where applicable (For example provide action signature with “native” keyword at the beginning) and plug this to Ballerina using a java SPI service so that Ballerina can find the definitions. For this you need to provide an implementation for “org.ballerinalang.spi.ExtensionPackageRepositoryProvider” as an java SPI service.(note that you don’t need to use “native” keyword before connector)
  2. Implement the native implementation in java and plug that using a java SPI service to Ballerina runtime. For this you’ll need to implement “org.ballerinalang.spi.NativeElementProvider” interface and plug that as a java SPI service.
  3. Pack Ballerina packages(as resources) and native implementation within a jar file and put that into <BALLERINA_HOME>/bre/lib folder.

Although this seems too much to do, it’s not :) Ballerina provide so many facilities to do this easily :) for example, as the implementation for point (1), what you have to do is copy paste below class to your implementation and provide the location of Ballerina packages within the created jar. This is needed to find and load Ballerina packages from the jar.

@JavaSPIService("org.ballerinalang.spi.ExtensionPackageRepositoryProvider")
public class PackageRepositoryProvider implements ExtensionPackageRepositoryProvider {

private static final String JAR_BAL_LIB_LOCATION = "/META-INF/natives/";

@Override
public PackageRepository loadRepository() {
return new ClasspathPackageRepository(this.getClass(), JAR_BAL_LIB_LOCATION);
}
}

As you can see above, we use “JavaSPIService” annotation to generate the service. Also note the “JAR_BAL_LIB_LOCATIONpath where we will be copying *.bal files within the jar later in this tutorial. And that’s just it, point (1) done :)

Ballerina also provides facilities to create the java SPI service which load java native implementations. More on that later.

Let’s begin coding :)

Example connector definition will look like follows

package sample.con.print;public connector PrintConnector() {
native action printText(string text)(boolean);
}

As you can see above, we only write action signature with “native” keyword, action body will be a native implementation. (note the public keyword before the connector, because we need to use this connector in other packages)

What happens is Ballerina will use above definition to do semantic validations. And if the validations are successful, Ballerina will compile and run the program. And in runtime it will bind the real native implementation to above action signature and execute.

So how does Ballerina find relevant details to do the action and implementation matching?

answer is, Ballerina provides below functionalities for you to make the matching possible.

  1. You’ll need to extend “org.ballerinalang.connector.api.AbstractNativeAction” class to implement your native action. (execute method is the method which gets invoked as action)
  2. Ballerina provides “org.ballerinalang.natives.annotations.BallerinaAction” annotation which you need to use in the native implementation to provide required details for matching.(such as action name, connector name, package name and parameters etc)
  3. Ballerina provides an annotation processor which will process above annotations and create the java SPI service for you.

Example native action for the above connector will look like follows.

@BallerinaAction(
packageName = "sample.con.print",
actionName = "printText",
connectorName = "PrintConnector",
args = {
@Argument(name = "text",
type = TypeKind.STRING)
},
returnType = {
@ReturnType(type = TypeKind.BOOLEAN)
}
)
public class PrintText extends AbstractNativeAction {

@Override
public ConnectorFuture execute(Context context) {
ClientConnectorFuture future = new ClientConnectorFuture();
//Implement your logic here
System.out.println("**** Action printText called ****");
String text = getStringArgument(context, 0);
System.out.println("**** " + text + " ****");
BBoolean result = new BBoolean(true);
future.notifyReply(result);
return future;

}
}

There are some important stuff for you to note. Ballerina treat every native action as asynchronous actions. So in every action, you can do the task asynchronously without keeping the Ballerina thread busy. What you need to do is let Ballerina know when you are done with the task. To achieve this we are using future pattern. So the action implementation should return a future (you can use “org.ballerinalang.nativeimpl.actions.ClientConnectorFuture” implementation provided by Ballerina) which Ballerina will use to register an event listener. So when you are done with your asynchronous task, you can let Ballerina know that through the future object(you can pass that around your threads and use that for notifications). Since we are treating all the actions as asynchronous actions, you’ll need to do the notification part even your action is synchronous one, that’s why I have done the notify part before returning the future in above example. That will allow Ballerina to continue the program execution.

If I explain bit more on how Ballerina handles asynchronous actions. It’s like this, when asynchronous action is invoked, what we do is,

  1. Create a listener with the context and save the current execution point in the context.
  2. Execute the action and get the future.
  3. Register above created listener to the future given by the action.
  4. Release the Ballerina thread to the pool.

So when we receive a notification that action invocation is finished, then we start executing the program from the last saved location(We have those details in our listener). As you can imagine, this was done to avoid keeping Ballerina threads busy unnecessarily, so that those threads can be used to process other requests.

So how do we access action parameters inside the native implementation?

For this Ballerina provides some helper methods in the “AbstractNativeAction” implementation.

public int getIntArgument(Context context, int index)
public String getStringArgument(Context context, int index)
public BValue getRefArgument(Context context, int index)
...
..

There is a bit tricky part though, that is, the index value above is not the index value of the parameter. It is a separate index value for each type, that is string index, int index, refType index etc.

For example, if your action signature is as follows

native action test(string a, int b, string c, int d)

Then to get value “a”, “b”, “c”, “d” what you need to do is as follows. (note the index values)

String a = getStringArgument(context, 0);
String c = getStringArgument(context, 1);
int b = getIntArgument(context, 0);
int d = getIntArgument(context, 1);

Apart from that, for the asynchronous notifications, you have the following APIs in the future object.

void notifySuccess();
void notifyReply(BValue... value);
void notifyFailure(BallerinaConnectorException ex);

“notifySuccess” can be used to notify Ballerina that action invocation is finished when action has no return values.

“notifyReply” is to return the values of action invocation.

“notifyFailure” can be used to notify any failures to the Ballerina side.

Next step is to generate the java SPI service and bundle the whole thing to a jar file. As we are using maven, we can use maven processor plug-in to generate the SPI service. Plug-in configuration looks like follows. (note annotation processor “org.ballerinalang.codegen.BallerinaAnnotationProcessor” provided by Ballerina.)

<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>${mvn.processor.plugin.version}</version>
<configuration>
<processors>
<processor>org.ballerinalang.codegen.BallerinaAnnotationProcessor</processor>
</processors>
<options>
<nativeEntityProviderPackage>org.sample.con.print.generated</nativeEntityProviderPackage>
<nativeEntityProviderClass>StandardNativeElementProvider</nativeEntityProviderClass>
</options>
</configuration>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
</execution>
</executions>
</plugin>

As you can see in highlighted lines, what you have to do is provide a class name and package name.

Apart from that, you’ll need to copy Ballerina definitions(*.bal files) to the jar, for that you can use below maven resource configuration.

<resources>
<!-- copy built-in ballerina sources to the jar -->
<resource>
<directory>${project.build.directory}/../src/main/ballerina</directory>
<targetPath>META-INF/natives</targetPath>
</resource>
</resources>

Finally, that’s it :) you can build the project, put the jar file to the lib folder(location mentioned at the begining) and start using your client connector. For your reference this will point you to the full implementation in github.

So now we have implemented our connector, how to use that in Ballerina?

Below example code illustrates how you can use the client connector you just created, in Ballerina.

import sample.con.print;

function main(string[] args) {
endpoint<print:PrintConnector> ep {
create print:PrintConnector();
}
boolean result = ep.printText("sample text");
println("result - " + result);
}

There is a small P.S part though :)

Say if you need a way to do something when you create the connector(for example if the connector is a SQL connector, then initializing connection pools, health checking etc needs to be done when the connector is getting initialized), for that you can use connector “init” action.

For this you’ll need only to provide native implementation(no need to provide Ballerina definition) with action name as “<init>”. You can find a sample init action class below. For your reference this is also available in the github source as well.

@BallerinaAction(packageName = "sample.con.print",
actionName = "<init>",
connectorName = "PrintConnector",
args = {
@Argument(name = "c",
type = TypeKind.CONNECTOR)
})
public class Init extends AbstractNativeAction {

@Override
public ConnectorFuture execute(Context context) {
System.out.println("** Action init called **");
ClientConnectorFuture future = new ClientConnectorFuture();
future.notifySuccess();
return future;

}
}

(Note that this was tested in Ballerina version 0.95.0)

Hope this has provided you a comprehensive guide about creating native connectors in Ballerina and how Ballerina internals handles them. :)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store