Java Posts

Deploying Fuse 7 on Top of Spring Boot to Openshift 4

Red Hat Fuse is an Open Source Integration platform which provide a very agile and lightweight artifact, which make it very suitable for a microservice deployment. And in this sample, im going to deploy Fuse on top of Red Hat OpenShift Container Platform.

First as always, we need to create a simple pom file. In here im using the latest version of Fuse. And that is 7.9.

<?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>hello-world-fuse-on-ocp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <fuse.version>7.9.0.fuse-sb2-790065-redhat-00001</fuse.version>
        <spring-boot.version>2.1.4.RELEASE-redhat-00001</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.redhat-fuse</groupId>
                <artifactId>fuse-springboot-bom</artifactId>
                <version>${fuse.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-http-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-servlet-starter</artifactId>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.jboss.redhat-fuse</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${fuse.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Create a main class,

package com.edw;

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

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Create its route,

package com.edw.routes;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldRoute extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        rest()
                .get("hello")
                .route()
                .setHeader(Exchange.HTTP_RESPONSE_CODE, simple("200"))
                .setHeader(Exchange.CONTENT_TYPE, simple("application/json"))
                .setBody(constant("{\"hello\":\"world\"}"))
                .endRest()
        ;
    }
}

Set application.properties for our application’s configuration. One of the most important is settingup Camel’s context path for serving API endpoints.

# The Camel context name
camel.springboot.name=hello-world-fuse-on-ocp

# enable all management endpoints
endpoints.enabled=true
management.security.enabled=false

camel.component.servlet.mapping.contextPath=/api/*
logging.level.root=info

And a settings.xml file for providing Red Hat repository location,

<?xml version="1.0"?>
<settings>

    <profiles>
        <profile>
            <id>extra-repos</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <repositories>
                <repository>
                    <id>central</id>
                    <url>https://repo1.maven.org/maven2</url>
                    <releases>
                        <enabled>true</enabled>
                    </releases>
                    <snapshots>
                        <enabled>false</enabled>
                    </snapshots>
                </repository>
                <repository>
                    <id>redhatga</id>
                    <name>Enterprise Releases</name>
                    <url>https://maven.repository.redhat.com/ga</url>
                </repository>
                <repository>
                    <id>redhatearly</id>
                    <name>Enterprise Releases</name>
                    <url>https://maven.repository.redhat.com/earlyaccess/all</url>
                </repository>
            </repositories>

            <pluginRepositories>
                <pluginRepository>
                    <id>central</id>
                    <url>https://repo1.maven.org/maven2</url>
                    <releases>
                        <enabled>true</enabled>
                    </releases>
                    <snapshots>
                        <enabled>false</enabled>
                    </snapshots>
                </pluginRepository>
                <pluginRepository>
                    <id>redhatga</id>
                    <name>Enterprise Releases</name>
                    <url>https://maven.repository.redhat.com/ga</url>
                </pluginRepository>
                <pluginRepository>
                    <id>redhatearly</id>
                    <name>Enterprise Releases</name>
                    <url>https://maven.repository.redhat.com/earlyaccess/all</url>
                </pluginRepository>
            </pluginRepositories>
        </profile>
    </profiles>

</settings>

Execute below command to run on our local,

$ mvn spring-boot:run -s settings.xml

And run curl to see whether our application’s endpoint is ready to accept request or not,

$ curl -kv http://localhost:8080/api/hello

*   Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /api/hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< accept: */*
< breadcrumbId: 123
< user-agent: curl/7.65.0
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Tue, 17 Aug 2021 14:21:05 GMT
<
* Connection #0 to host localhost left intact
{"hello":"world"} 

Once we are confident that the code is working, we can deploy it to Openshift Container Platform by using below command.

oc new-app registry.access.redhat.com/ubi8/openjdk-8~https://github.com/edwin/hello-world-fuse-on-ocp

Full code for this sample can be downloaded on below link.

https://github.com/edwin/hello-world-fuse-on-ocp

Thanks for reading and dont forget to have fun using Fuse.

Create a Simple CRUD REST API using Quarkus

Just several days ago, Quarkus release version 2.0, providing more features and some more improvements in this release. In this tutorial, we’ll try to see how can we use the latest Quarkus version to create a simple rest api, which is connect to an existing MySQL database by using hibernate.

Lets start with a simple pom.xml 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>hello-world-quarkus</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <compiler-plugin.version>3.8.1</compiler-plugin.version>
        <maven.compiler.parameters>true</maven.compiler.parameters>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
        <quarkus.platform.version>2.0.0.Final</quarkus.platform.version>
        <surefire-plugin.version>3.0.0-M5</surefire-plugin.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-orm</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-jsonb</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-mysql</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                            <goal>generate-code</goal>
                            <goal>generate-code-tests</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <parameters>${maven.compiler.parameters}</parameters>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <configuration>
                    <systemPropertyVariables>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${surefire-plugin.version}</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                                <configuration>
                                    <systemPropertyVariables>
                                        <native.image.path>
                                            ${project.build.directory}/${project.build.finalName}-runner
                                        </native.image.path>
                                        <java.util.logging.manager>org.jboss.logmanager.LogManager
                                        </java.util.logging.manager>
                                        <maven.home>${maven.home}</maven.home>
                                    </systemPropertyVariables>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <properties>
                <quarkus.package.type>native</quarkus.package.type>
            </properties>
        </profile>
    </profiles>
</project>

Next is creating a simple java model, as table representation

package com.edw.model;

import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;

@Entity
@Table(name = "T_TRANSFER")
public class Transfer {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    private String accountFrom;
    private String accountTo;
    private Date transferDate;
    private BigDecimal amount;

    public Transfer() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAccountFrom() {
        return accountFrom;
    }

    public void setAccountFrom(String accountFrom) {
        this.accountFrom = accountFrom;
    }

    public String getAccountTo() {
        return accountTo;
    }

    public void setAccountTo(String accountTo) {
        this.accountTo = accountTo;
    }

    public Date getTransferDate() {
        return transferDate;
    }

    public void setTransferDate(Date transferDate) {
        this.transferDate = transferDate;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
}

After that we can create a simple class for querying using EntityManager,

package com.edw.service;

import com.edw.dto.TransferDto;
import com.edw.model.Transfer;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

@Transactional
@ApplicationScoped
public class TransferService {
    @Inject
    EntityManager em;

    public List<Transfer> findAll() {
        return em.createQuery("select t from Transfer t", Transfer.class).getResultList();
    }

    public Long create(TransferDto transferDto) {
        Transfer transfer = new Transfer();
        transfer.setAccountFrom(transferDto.getAccountFrom());
        transfer.setAccountTo(transferDto.getAccountTo());
        transfer.setAmount(transferDto.getAmount());
        transfer.setTransferDate(new Date());
        em.persist(transfer);

        return transfer.getId();
    }

}

And a controller to handle rest api request,

package com.edw.controller;

import com.edw.dto.TransferDto;
import com.edw.service.TransferService;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.HashMap;

@Path("/transfer")
public class TransferController {

    @Inject
    TransferService transferService;

    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public Response findAll() {
        return Response
                .ok(transferService.findAll())
                .build();
    }

    @POST
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(TransferDto transferDto) {
        try {
            Long id = transferService.create(transferDto);
            return Response // 200
                    .ok(
                            new HashMap() {{
                                put("id", id);
                            }})
                    .build();
        } catch (Exception ex) {
            return Response // error 400
                    .status(400).entity(
                            new HashMap() {{
                                put("error", "failed to insert");
                            }})
                    .build();
        }
    }

}

Last step is configuring our database connection in application.properties file

quarkus.datasource.jdbc.url=jdbc:mysql://192.168.1.1:3306/db_test
quarkus.datasource.jdbc.driver=com.mysql.cj.jdbc.Driver
quarkus.datasource.jdbc.max-size=20
quarkus.datasource.jdbc.min-size=2

quarkus.datasource.username=root
quarkus.datasource.password=password

quarkus.hibernate-orm.database.generation=update

Finally, we can start our Quarkus app by running below command,

$ mvn compile quarkus:dev

And use curl to check whether API is accessible or not.

$ curl -L -X POST 'http://localhost:8080/transfer/' \
-H 'Content-Type: application/json' \
--data-raw '{
    "accountFrom":"4444",
    "accountTo": "55555",
    "amount" : 50000
}'

The full code for this can be accessed on below github link

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

Have fun with Quarkus 🙂

User Lifecycle Management with Keycloak

Created a presentation for JVM Indonesia Meetup, talking about how Keycloak handling a user lifecycle management. From first time activating, to deactivating.

The presentation can be found on slideshare.

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 🙂

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