Writing a gRPC Based Event Handler using the WSO2 Identity Server Eventing Framework

In this article, I am going to walk you through the implementation of a gRPC based event handler using the WSO2 Identity Server Eventing Framework that I implemented as a part of my internship project in WSO2. The main objective of this implementation is to introduce gRPC services to a WSO2 identity server event handler.

Spend little time to read mentioned blogs as this implementation follows similar approaches as them.

Overview

What we are trying to build here is a Event Handler which implements its methods on a remote gRPC server rather than implements in its own. Using this event handler, users can be able to handle events only by configuring the deployment.toml file of the WSO2 Identity Server and gRPC server. That’s mean there will not need to re-write the Event Handler again and again for different use cases.

We have mainly 3 artifacts,

1. the jar file of the gRPC Based Event Handler

2. .proto file to implement gRPC Server

3. Instructions to configure the deployment.toml of the Identity Server

to users to customize the event handler.

High-Level Architecture

Let’s have a look at how the gRPC Service Stub places in the gRPC Based Event Handler and how it connects with remote gRPC Server.

Defining gRPC Services and Messages in a .proto file

We define the gRPC services and messages in a text file with .proto extension. As we are going to define gRPC service methods to override existing methods of the AbstractEventHandler class, here we define 3 service methods getName(), getPriority(), handleEvent() and request, response messages for those service methods. The .proto file can be compiled using Protocol Buffer to generate data access classes in any preferred language.

  • We should use this .proto file to generate data access class for both client and for the server.
syntax = "proto3";
option java_package = "org.wso2.grpc.event.handler.grpc";

service service{
rpc getName(Empty) returns (HandlerName){};
rpc getPriority(MessageContext) returns (Priority){};
rpc handleEvent(Event) returns (Log){};
}

message Empty{

}
message HandlerName{
string name = 1;
}
message MessageContext{

}
message Priority{
int32 priority = 1;
}
message Event{
string event = 1;
map<string,string> eventProperties = 3;
}
message Log{
string log = 1;
}
  • option java_package defines the target package that we are willing to generate data access classes. It should be defined by us according to the requirements.
  • When we generate gRPC data access classes for server implementations, the target package should be changed.

Implementing a gRPC based Event Handler

You can find the git repository of the implemented gRPC based event handler here, https://github.com/NuwangaHerath/gRPC-Based-Event-Handler

Follow these steps to create your own gRPC based custom event handler.

Configuring the pom.xml

  • We have to add all the necessary dependencies and plugins to the pom.xml. Here is the link for the pom.xml.
https://github.com/NuwangaHerath/gRPC-Based-Event-Handler/blob/main/pom.xml

Compiling the .proto file

  • Place or create the .proto file inside the resources directory and run maven build to compile the .proto file to generate data access classes. You will be asked to add the protocol buffer plugin to your IDE if it has not added already.
  • Make sure to change the option java_package of the .proto file according to your project package structure.

Implementing the event handler class

  • Create a class GrpcEventHandler by extending the AbstractEventHandler class inside the org.wso2.grpc.event.handler package.
  • Inside the GrpcEventHandler class constructor, Obtain host and port for gRPC remote server from identity-event.properties file of the WSO2 Identity Server. We configure the identity-event.properties file with host and port values later.
{
try {
this.grpcEventHandlerConfiguration = IdentityEventConfigBuilder.getInstance().getModuleConfigurations
("grpcBasedEventHandler");
} catch (IdentityEventException e) {
log.info("IdentityEventException: ", e);
}
}

// Obtain grpcServerHost and grpcServerPort from identity-event properties.
this.grpcServerHost = grpcEventHandlerConfiguration.getModuleProperties()
.getProperty("grpcBasedEventHandler.host");
this.grpcServerPort = grpcEventHandlerConfiguration.getModuleProperties()
.getProperty("grpcBasedEventHandler.port");
  • Inside the GrpcEventHandler class constructor, Create the channel and the gRPC Client Stub for access gRPC service methods of the remote gRPC server.
// Create the channel for gRPC server.
this.channel = NettyChannelBuilder.forAddress(grpcServerHost, Integer.parseInt(grpcServerPort))
.usePlaintext().build();

// Create the gRPC client stub.
this.clientStub = serviceGrpc.newBlockingStub(channel);
  • Inside the GrpcEventHandler class override getName(), getPriority(), handleEvent() methods using gRPC service methods of the client stub.
@Override
public String getName() {

// Obtain handlerName from remote gRPC server
Service.HandlerName handlerName = clientStub.getName(Service.Empty.newBuilder().build());
return handlerName.getName();
}

@Override
public int getPriority(MessageContext messageContext) {

// Obtain priority from remote gRPC server
Service.Priority priority = clientStub.getPriority(Service.MessageContext.newBuilder().build());
return priority.getPriority();
}

@Override
public void handleEvent(Event event) throws IdentityEventException {

Map<String, Object> eventProperties = event.getEventProperties();
String userName = (String) eventProperties.get(IdentityEventConstants.EventProperty.USER_NAME);
String tenantDomain = (String) eventProperties.get(IdentityEventConstants.EventProperty.TENANT_DOMAIN);
String eventName = event.getEventName();

// Define event properties for create gRPC event message
Map<String, String> grpcMap = new HashMap<>();
grpcMap.put("user-name", userName);
grpcMap.put("tenant-domain", tenantDomain);

// Define the gRPC event message
Service.Event event1 = Service.Event.newBuilder().setEvent(eventName).putAllEventProperties(grpcMap).build();

// Obtain log message from remote gRPC server
Service.Log remoteLog = clientStub.handleEvent(event1);
log.info(remoteLog.getLog());

}

Register the Event Handler

  • We have to register the event handler in the service component as follows.
  • Create a class named GrpcEventHandlerComponent inside the package org.wso2.grpc.event.handler.internal create active and deactivate methods.
/**
* @scr.component name="org.wso2.grpc.event.handler.internal.GrpcEventHandlerComponent" immediate="true"
*/
public class GrpcEventHandlerComponent {

private static Log log = LogFactory.getLog(GrpcEventHandlerComponent.class);

@Activate
protected void activate(ComponentContext context) {

GrpcEventHandler eventHandler = new GrpcEventHandler();
// Register the custom listener as an OSGI service.
context.getBundleContext().registerService(
AbstractEventHandler.class.getName(), eventHandler, null);
log.info("gRPC event handler is activated successfully.");
}

@Deactivate
protected void deactivate(ComponentContext context) {

if (log.isDebugEnabled()) {
log.debug("gRPC event handler is deactivated ");
}
}

}

Project folder structure will look like this,

  • Build the project using maven build mvn clean install and obtain the jar file from the target directory.
  • Copy the created jar file to {wso2is-home}/repository/component/dropins directory.

Configuring the Event Handler

  • The custom event configuration can be added as follows to {wso2is-home}/repository/conf/deployment.toml file. The events which need to subscribe to the handler can be listed in subscriptions.
  • In this example, we subscribe PRE_ADD_USER and POST_ADD_USER events to the handler.
  • We add host and port of the remote gRPC server as properties under the event handler configuration.
[[event_handler]]
name="grpcBasedEventHandler"
subscriptions=["POST_ADD_USER"]
enable=true
properties.host="localhost"
properties.port="8010"
  • If you don’t use the deployment.toml for configuration, add the following configs to {wso2is-home}/repository/conf/identity/identity-event.properties file.
# Custom event configuration.module.name.27=grpcBasedEventHandler
grpcBasedEventHandler.subscription.1=POST_ADD_USER
grpcBasedEventHandler.host=localhost
grpcBasedEventHandler.port=8010

Implementing gRPC Servers

  • Using the defined .proto file, we can generate data access classes in any gRPC supported languages and can implement servers using the generated data access classes as well.
  • Make sure to run the servers on the same host and port that we configured in the deployment.toml.
  • Override the gRPC service methods getName(), getPriority(), handleEvent() in the server-side to return required values
  • As an example, getName() method should return event handler name. In our case return value should be grpcBasedEventHandler.
  • getPriority() method should return priority value as integer.
  • In our example, handleEvent() method should return a log.
  • Here, you can find Java and Python gRPC servers that I created for this example.

Java gRPC Server

https://github.com/NuwangaHerath/gRPC-event-handler-server-Java
  • Run HandlerServer.java file to start the server.

Python gRPC Server

https://github.com/NuwangaHerath/gRPC-event-handler-server-python
  • Run HandlerService.py file to start the server

Testing the Event Handler

  • Start the Java/Python gRPC Server that I mentioned above.
  • If you have already implemented a server, start it.
  • Then open the command prompt in {wso2is-home}/repository directory and execute of the following commands to start the server.
For Windows:
$ wso2server.bat --run
For Linux:
$ sh wso2server.sh
  • Now we can see the startup logs in the terminal. Let's check whether the activation log message of the event handler prints in the terminal. It should be print as follows.
INFO {org.wso2.grpc.event.handler.internal.GrpcEventHandlerComponent} - gRPC event handler activated successfully.

Add a user to WSO2 Identity Server

  • As we subscribed POST_ADD_USER event to the event handler, we can check the working of the event handler by adding a user to WSO2 Identity Server.
  • If a user successfully added to the server, you can see the log messages which are returned by the gRPC remote server in the terminal.

Finally we implemented the gRPC Based Event Handler successfully. I hope that you all got broader knowledge and practice on the implementation.

Time to try your own gRPC based event handler and to write your own gRPC server!

If you have faced any problem, put it in the comment section.

Software Engineering Intern @ WSO2, Computer Science and Engineering Undergraduate @ University of Moratuwa, Sri Lanka