Java Posts

Creating a Red Hat AMQ Broker or ActiveMQ Artemis Stress Testing using JMeter

Red Hat AMQ Broker (or its Opensource version, Apache ActiveMQ), is a flexible, high-performance messaging platform that delivers information reliably, enabling real-time integration. It can handle lot of messages at the same time, up to 21-22thousands messages/seconds, as per below link.

https://activemq.apache.org/performance.html

In this article, im trying to try and see how many transaction can one AMQ handle by using a JMeter script. Lets start by installing ActiveMQ Artemis from docker,

docker run -it --rm \
  -p 8161:8161 \
  -p 61616:61616 \
  -e ARTEMIS_USERNAME=admin \
  -e ARTEMIS_PASSWORD=password \
  vromero/activemq-artemis

Next step is downloading JMeter, and creating a test plan. But first, we need to download artemis-jms-client first and put it on our JMeter_FOLDER/lib/ext folder.

Next is opening our JMeter and create a thread group, for this example im creating 2 loops , with 2 threads each.

Create JMS Publisher, with below parameter

Initial Context Factory : org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
Provider URL : tcp://localhost:61616
User Authorization : admin
Password Authorization : password
Connection Factory : ConnectionFactory
Destination : dynamicQueues/queue01

JMSCorrelationID : ${__counter(FALSE,)} with class of value java.lang.String


Create JMS Subscriber, with below parameter

Initial Context Factory : org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory
Provider URL : tcp://localhost:61616
User Authorization : admin
Password Authorization : password
Connection Factory : ConnectionFactory
Destination : dynamicQueues/queue01


Run JMeter and we can see the result on both Result Tree and Summary Report,

Have fun stresstesting AMQ 🙂

Deploying Apache Tomcat 8 on Top of Red Hat UBI 8 and Java 11

UBI (Universal Base Image) 8 is an OCI-compliant container base operating system images with complementary runtime languages and packages that are freely redistributable. Like previous base images, they are built from portions of Red Hat Enterprise Linux. UBI images can be obtained from the Red Hat container catalog, and be built and deployed anywhere.

On this sample, im trying to create a Apache Tomcat 8 (version 8.0.5 to be precise), and deploy it on top on a UBI8 base image, with JDK 11 installed on it. And after that, i’ll deploy a simple hello world java application on top of Tomcat 8.

So, lets start with a simple java application,

git clone https://github.com/edwin/hello-world-jboss-eap.git

and build it into a war file

mvn clean package

create a Dockerfile to install Apache Tomcat 8 and copy our created war file into Tomcat’s webapps folder

FROM registry.access.redhat.com/ubi8/openjdk-11

RUN curl -k https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.5/bin/apache-tomcat-8.0.5.tar.gz -L -o "/tmp/apache-tomcat-8.0.5.tar.gz" \
    && tar -xf "/tmp/apache-tomcat-8.0.5.tar.gz" -C /deployments/ ;

# remove it manually, otherwise it will create an error "Endorsed standards and standalone APIs in modular form will be supported via the concept of upgradeable modules"
RUN sed -i 's/endorsed/e/g' /deployments/apache-tomcat-8.0.5/bin/catalina.sh

COPY HelloWorld.war /deployments/apache-tomcat-8.0.5/webapps/HelloWorld.war

EXPOSE 8080 
CMD ["/deployments/apache-tomcat-8.0.5/bin/catalina.sh", "run"]

Build it,

docker build -t ubi8-with-tomcat8 .

And run,

docker run -p 8080:8080 ubi8-with-tomcat8

We can see the result on browser,


If we want to create an HTTPS application, then there will be some modification needed. First we need to create a keystore, and give changeit as its password.

keytool -genkey -alias tomcat -keyalg RSA -keystore my-release-key.keystore

Edit Tomcat’s server.xml, adding below line

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               keystoreFile="/deployments/my-release-key.keystore" keystorePass="changeit"
               clientAuth="false" sslProtocol="TLS" />

Do some minor modification on Dockerfile,

FROM registry.access.redhat.com/ubi8/openjdk-11

RUN curl -k https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.5/bin/apache-tomcat-8.0.5.tar.gz -L -o "/tmp/apache-tomcat-8.0.5.tar.gz" \
    && tar -xf "/tmp/apache-tomcat-8.0.5.tar.gz" -C /deployments/ ;

# remove it manually, otherwise it will create an error "Endorsed standards and standalone APIs in modular form will be supported via the concept of upgradeable modules"
RUN sed -i 's/endorsed/e/g' /deployments/apache-tomcat-8.0.5/bin/catalina.sh

COPY server.xml /deployments/apache-tomcat-8.0.5/conf/server.xml
COPY my-release-key.keystore /deployments/my-release-key.keystore
COPY HelloWorld.war /deployments/apache-tomcat-8.0.5/webapps/HelloWorld.war

EXPOSE 8080 8443
CMD ["/deployments/apache-tomcat-8.0.5/bin/catalina.sh", "run"]

Open browser to see the result,

Have fun with UBI8

Intercepting and Read JMS Messages on Red Hat AMQ

Red Hat AMQ, or its OpenSource version which is ActiveMQ, is a JMS 1.1-compliant messaging system. It consists of a broker and client-side libraries that enable remote communication among distributed client applications. Red Hat AMQ provides numerous connectivity options and can communicate with a wide variety of non-JMS clients through its support of the OpenWire and STOMP wire protocols.

So basically it’s a very reliable messaging system, but there are times where we need to capture whatever messages that comes thru AMQ for some debugging purpose. And I can see that AMQ have an interceptor feature that we can leverage to capturing JMS messages that goes thru AMQ.

https://access.redhat.com/documentation/en-us/red_hat_amq/7.2/html/using_amq_broker/interceptors

Creating an interceptor class is actually quite straight forward, lets start with a simple POM file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>AmqInterceptor</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>artemis-jms-client-all</artifactId>
            <version>2.17.0</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

And a simple java file,

package com.edw;

import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.core.protocol.core.Packet;
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionReceiveMessage;
import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionSendMessage;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;

public class SimpleInterceptor implements Interceptor {
    public boolean intercept(final Packet packet, final RemotingConnection connection) throws ActiveMQException {

        try {
            if (packet instanceof SessionSendMessage) {
                SessionSendMessage realPacket = (SessionSendMessage) packet;
                Message msg = realPacket.getMessage();

                if((msg.getTimestamp()>0) && msg.getUserID()!=null) {
                    ActiveMQBuffer activeMQBuffer = realPacket.getMessage().getBodyBuffer();

		    // it will write the log to a file. Dont use this, use a real logging mechanism such as slf4j
                    List<String> lines = Arrays.asList( "***** msg in *****", msg.toString(), activeMQBuffer.readNullableSimpleString().toString(), "*****");
                    Files.write(Paths.get("amq.out"), lines, 
						StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
                }
            }
            else if (packet instanceof SessionReceiveMessage) {
                SessionReceiveMessage realPacket = (SessionReceiveMessage) packet;
                Message msg = realPacket.getMessage();

                if((msg.getTimestamp()>0) && msg.getUserID()!=null) {

		    // it will write the log to a file. Dont use this, use a real logging mechanism such as slf4j
                    List<String> lines = Arrays.asList( "***** msg out *****", msg.toString(), "*****");
                    Files.write(Paths.get("amq.out"), lines, 
						StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return true;
    }
}

Build by using below command,

mvn clean package

And put the created jar file into amq_broker_home/lib folder. Restart AMQ and we can see that messages that goes in and out of AMQ is now can be logged.

Code for this can be found here,

https://github.com/edwin/amq-message-interceptor

Simple Hello World App by Using Snowdrop BOM Dependency Management

According to its website (snowdrop.dev), snowdrop is a cloud native spring boot app which is a project within the Red Hat community. From syntax perspective, it’s pretty much the same as ordinary Spring Boot and we can see on below code as an example.

Let’s start by creating a new java project with pom.xml, the highlighted part here is the bom configuration provided by Snowdrop.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>HelloWorldSnowdrop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>redhat-early-access</id>
            <name>Red Hat Early Access Repository</name>
            <url>https://maven.repository.redhat.com/earlyaccess/all/</url>
        </repository>
        <repository>
            <id>redhat-ga</id>
            <name>Red Hat GA Repository</name>
            <url>https://maven.repository.redhat.com/ga/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>redhat-early-access</id>
            <name>Red Hat Early Access Repository</name>
            <url>https://maven.repository.redhat.com/earlyaccess/all/</url>
        </pluginRepository>
        <pluginRepository>
            <id>redhat-ga</id>
            <name>Red Hat GA Repository</name>
            <url>https://maven.repository.redhat.com/ga/</url>
        </pluginRepository>
    </pluginRepositories>

    <properties>
        <snowdrop-bom.version>2.3.6.Final-redhat-00001</snowdrop-bom.version>
        <spring-boot.version>2.1.4.RELEASE-redhat-00001</spring-boot.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.snowdrop</groupId>
                <artifactId>snowdrop-dependencies</artifactId>
                <version>${snowdrop-bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>


    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

Next is creating an ordinary rest api, like a regular spring boot.

package com.edw;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
package com.edw.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class HelloWorldController {

    @GetMapping("/")
    public Map index() {
        return new HashMap() {{
            put("hello", "world");
        }};
    }
}

Build, and run it thru IDE. And can test by using a curl command,

curl -kv http://localhost:8080/

Code can be accesses on below github link,

https://github.com/edwin/hello-world-snowdrop

How to Solve Keycloak Error, Uncaught server error: org.keycloak.authentication.AuthenticationFlowException

Had this error today when creating a custom authentication SPI for Keycloak

16:17:13,588 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-1) Uncaught server error: 
org.keycloak.authentication.AuthenticationFlowException

        at org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:913)
        at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.resourceOwnerPasswordCredentialsGrant(TokenEndpoint.java:554)
        at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:187)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:140)
        at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:509)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:399)
        at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:363)
        at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:365)
        at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:337)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:137)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:106)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:132)
        at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:100)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:443)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:233)
        at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:139)
        at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
        at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:142)
        at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:219)

This error happens because im not putting any userModel into Keycloak’s flowContext and adding a userModel solve this problem. Code can be seen here,

https://github.com/edwin/keycloak-password-encryptor/blob/master/src/main/java/com/edw/keycloak/spi/CustomKeycloakPasswordEncryptor.java#L93