New Custom Capability in Kie Server
By default, Kie Server extensions are exposed through REST or JMS data transports. We can extend Kie Server to support a custom data transport adding a new capability.
As an example, this procedure adds a custom data transport to the Kie Server 7.7.1 image that uses the Drools extension and that is based on Apache MINA, an open-source Java network-application framework. The example custom MINA transport exchanges string-based data that relies on existing marshalling operations and supports only JSON format.
1.- Create Project
mvn archetype:generate -DgroupId=org.sgitario.jbpm -DartifactId=new-capability -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
2.- Add kie server dependencies into the pom.xml
Depending on the Kie Server image, you might need to change the kie server dependencies. For Kie Server 7.7.1 image, the dependencies version is 7.38.0.Final.
<packaging>jar</packaging>
<properties>
<version.org.kie>7.38.0.Final</version.org.kie>
</properties>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie.server</groupId>
<artifactId>kie-server-api</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie.server</groupId>
<artifactId>kie-server-services-common</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kie.server</groupId>
<artifactId>kie-server-services-drools</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${version.org.kie}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Note that we need to create a fat jar, so the Mina dependency is added (as it’s not included in Kie Server by default) and also we need to exclude the others (using scope provided). Another alternative is to include the Mina dependency in the Kie Server classpath manually. |
3.- Create Text Based Handler for Mina Transport
The handler expects to receive something like “containerId | argument” in order to invoke the “containerId” with the specified “argument”. The flow will end when it receives either “exit”. |
public class TextBasedIoHandlerAdapter extends IoHandlerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(TextBasedIoHandlerAdapter.class);
private final KieContainerCommandService batchCommandService;
public TextBasedIoHandlerAdapter(KieContainerCommandService batchCommandService) {
this.batchCommandService = batchCommandService;
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String completeMessage = message.toString();
LOG.debug("Received message '{}'", completeMessage);
if (completeMessage.trim().equalsIgnoreCase("exit")) {
session.closeOnFlush();
return;
}
String[] elements = completeMessage.split("\\|");
LOG.debug("Container id {}", elements[0]);
ServiceResponse<String> result = batchCommandService.callContainer(elements[0], elements[1],
MarshallingFormat.JSON, null);
if (result.getType().equals(ServiceResponse.ResponseType.SUCCESS)) {
session.write(result.getResult());
LOG.debug("Successful message written with content '{}'", result.getResult());
} else {
session.write(result.getMsg());
LOG.debug("Failure message written with content '{}'", result.getMsg());
}
}
}
4.- Implement KieServerExtension interface in order to filter out application resources depending on the caller extensions and transports
public class MinaDroolsKieServerExtension implements KieServerExtension {
public static final String DROOLS_EXTENSION = DroolsKieServerExtension.EXTENSION_NAME;
public static final String EXTENSION_NAME = "Drools-Mina";
public static final String MINA_CAPABILITY = "BRM-Mina";
private static final Logger LOG = LoggerFactory.getLogger(MinaDroolsKieServerExtension.class);
private static final String MINA_HOST = System.getProperty("org.kie.server.drools-mina.ext.port", "localhost");
private static final String MINA_PORT = System.getProperty("org.kie.server.drools-mina.ext.port", "9123");
private IoAcceptor acceptor;
private boolean initialized = false;
@Override
public boolean isActive() {
return true;
}
@Override
public void init(KieServerImpl kieServer, KieServerRegistry registry) {
KieServerExtension droolsExtension = registry.getServerExtension(DROOLS_EXTENSION);
if (droolsExtension == null) {
LOG.warn("No Drools extension available, quiting...");
return;
}
KieContainerCommandService batchCommandService = findByType(droolsExtension.getServices(),
KieContainerCommandService.class);
if (batchCommandService != null) {
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
acceptor.setHandler(new TextBasedIoHandlerAdapter(batchCommandService));
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
try {
acceptor.bind(new InetSocketAddress(MINA_HOST, Integer.parseInt(MINA_PORT)));
LOG.info("{} -- Mina server started at {} and port {}", toString(), MINA_HOST, MINA_PORT);
} catch (IOException e) {
LOG.error("Unable to start Mina acceptor due to {}", e.getMessage(), e);
}
initialized = true;
}
}
@Override
public void destroy(KieServerImpl kieServer, KieServerRegistry registry) {
if (acceptor != null) {
acceptor.dispose();
acceptor = null;
}
LOG.info("{} -- Mina server stopped", toString());
}
@Override
public List<Object> getAppComponents(SupportedTransports type) {
// Nothing for supported transports (REST or JMS)
return Collections.emptyList();
}
@Override
public <T> T getAppComponents(Class<T> serviceType) {
return null;
}
@Override
public String getImplementedCapability() {
return MINA_CAPABILITY;
}
@Override
public List<Object> getServices() {
return Collections.emptyList();
}
@Override
public String getExtensionName() {
return EXTENSION_NAME;
}
@Override
public Integer getStartOrder() {
return 20;
}
@Override
public String toString() {
return EXTENSION_NAME + " KIE Server extension";
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void createContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters) {
// Empty, already handled by the `Drools` extension
}
@Override
public void updateContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters) {
// Empty, already handled by the `Drools` extension
}
@Override
public boolean isUpdateContainerAllowed(String id, KieContainerInstance kieContainerInstance,
Map<String, Object> parameters) {
// Empty, already handled by the `Drools` extension
return false;
}
@Override
public void disposeContainer(String id, KieContainerInstance kieContainerInstance, Map<String, Object> parameters) {
// Empty, already handled by the `Drools` extension
}
@SuppressWarnings("unchecked")
private <T> T findByType(List<Object> services, Class<T> clazz) {
return services.stream().filter(svc -> clazz.isAssignableFrom(svc.getClass())).map(svc -> (T) svc).findFirst()
.get();
}
}
5.- Make the service discoverable
To make the new endpoint discoverable, create a src/main/resources/META-INF/services/org.kie.server.services.api.KieServerExtension file and add the fully qualified class name of the KieServerApplicationComponentsService implementation class within the file:
org.sgitario.jbpm.CustomKieServerApplicationComponentsService
6.- Build the project
mvn clean package
It will generate the file target/new-capability-1.0-SNAPSHOT-jar-with-dependencies.jar.
7.- Create our own image of Kie Server
Create a file called Dockerfile:
FROM registry.redhat.io/rhdm-7/rhdm-kieserver-rhel8:7.7.1
COPY target/new-capability-1.0-SNAPSHOT-jar-with-dependencies.jar /opt/eap/standalone/deployments/ROOT.war/WEB-INF/lib/
Note that we need to sign up in the Red Hat registry. |
Then, build the image:
docker build . --tag quay.io/jcarvaja/rhdm-kieserver-rhel8-new-capability
8.- Prepare the KJAR example
We’re going to create a pretty simple example in Drools (more about this topic in this post). This example will only say hello to persons.
First, we need a Maven repository to be accessible to push the KJar module and our Kie Server:
docker run -d -p 8081:8081 --name nexus sonatype/nexus
The default credentials is admin/admin123.
Create the pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.jbpm.test</groupId>
<artifactId>say-hello-kjar-example</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>kjar</packaging>
<name>say-hello-kjar-example</name>
<build>
<plugins>
<plugin>
<groupId>org.kie</groupId>
<artifactId>kie-maven-plugin</artifactId>
<version>7.38.0.Final</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
<distributionManagement>
<snapshotRepository>
<id>nexus-snapshots</id>
<url>http://localhost:8081/nexus/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>
Then, the *Person.java domain model:
package org.jbpm.test;
public class Person implements java.io.Serializable {
private Integer age;
private String name;
// constructors, getters and setters
}
The say-hello-person.drl rule:
import org.jbpm.test.Person;
rule SayHello
when
$p : Person(age >= 21)
then
System.out.println("Hello " + $p.getName());
end
And finally, we build and publish the artifact into our Nexus instance:
mvn --settings settings.xml clean install deploy
9.- Test it
We first run our Kie Server image:
docker run -it -p 8080:8080 -p 9123:9123 --rm --env KIE_ADMIN_USER=admin --env KIE_ADMIN_PWD=admin --env MAVEN_MIRROR_URL=http://nexus:8081/nexus/content/groups/public/ --link nexus quay.io/jcarvaja/rhdm-kieserver-rhel8-new-capability
We should see this log in the server logs:
INFO [org.sgitario.jbpm.MinaDroolsKieServerExtension] (ServerService Thread Pool -- 77) Drools-Mina KIE Server extension -- Mina server started at localhost and port 9123
INFO [org.kie.server.services.impl.KieServerImpl] (ServerService Thread Pool -- 77) Drools-Mina KIE Server extension has been successfully registered as server extension
We’re going to run the same KJAR example as in this post (step 7).
curl -X PUT "http://admin:admin@localhost:8080/services/rest/server/containers/sayHello" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"container-id\" : \"sayHello\", \"release-id\" : { \"group-id\" : \"org.jbpm.test\", \"artifact-id\" : \"say-hello-kjar-example\", \"version\" : \"1.0-SNAPSHOT\" }}"
So, let’s connect with the Mina server:
telnet 127.0.0.1 9123
And then type:
sayHello|{"lookup":"ksession","commands":[{"insert":{"object":{"org.jbpm.test.Person":{"name":"john","age":20}}}},{"fire-all-rules":""}]}
And, as John is older than 21, we should see the greetings in the server logs:
INFO [stdout] (NioProcessor-2) Hello john
Finally, we need to type “exit” to quit the connection with the Mina server.
Conclusion
All the source code can be found in this repository.