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.
- To get better idea and knowledge on implementation of a Custom Event Handler using the WSO2 Identity Server Eventing Framework, follow the blog Writing a Custom event handler using the WSO2 Identity Server Eventing Framework by Gangani Chamika.
- To have a clear idea and knowledge on the introduction of gRPC services to a WSO2 Identity Server Extension, follow the blog Implementing a WSO2 Identity Server Extension Using gRPC Services.
- To get better knowledge on the writing of a WSO2 custom OSGI component, follow the blog How to write a WSO2 custom OSGi component? by Dewni Weeraman.
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 Handler2.
.proto
file to implement gRPC Server3. 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 theAbstractEventHandler
class, here we define 3 service methodsgetName()
,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.
- First of all, Start a maven project on IntelliJ Idea IDE. For all who are not aware of it, here is the way to do it http://extspeeder.com/userguide/intellij/
Configuring the pom.xml
- We have to add all the necessary dependencies and plugins to the
pom.xml
. Here is the link for thepom.xml
.
https://github.com/NuwangaHerath/gRPC-Based-Event-Handler/blob/main/pom.xml
Compiling the .proto
file
- Place or create the
.proto
file inside theresources
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 theAbstractEventHandler
class inside theorg.wso2.grpc.event.handler
package. - Inside the
GrpcEventHandler
class constructor, Obtainhost
andport
for gRPC remote server fromidentity-event.properties
file of the WSO2 Identity Server. We configure theidentity-event.properties
file withhost
andport
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 thechannel
and thegRPC 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 overridegetName()
,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 packageorg.wso2.grpc.event.handler.internal
createactive
anddeactivate
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 thejar
file from thetarget
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
andPOST_ADD_USER
events to the handler. - We add
host
andport
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
andport
that we configured in thedeployment.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 begrpcBasedEventHandler
. 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.
References
[3] https://medium.com/@dewni.matheesha/how-to-write-a-wso2-custom-osgi-component-2fd90de7eb1a
[4] https://github.com/NuwangaHerath/gRPC-Based-Event-Handler
[5] https://github.com/NuwangaHerath/gRPC--event-handler-server-Java
[6] https://github.com/NuwangaHerath/gRPC-event-handler-server-python