October 29, 2019

How To Install VisualVM on Fedora 30 and OpenJDK 11

Installation

VisualVM is not bundle with OpenJDK 11, but can be easily downloaded, unzipped and ran.

https://visualvm.github.io/download.html

$ wget https://github.com/visualvm/visualvm.src/releases/download/1.4.4/visualvm_144.zip

$ unzip visualvm_144.zip

$ visualvm_144/bin/visualvm

Feature

https://visualvm.github.io/features.html

October 22, 2019

How to Read MSUPN in X509Certificate

Maven dependency

        <!-- The prov module provides all the JCA/JCE provider functionality. -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>
        <!-- The pkix module is the home for code for X.509 certificate generation 
            and the APIs for standards that rely on ASN.1 such as CMS, TSP, PKCS#12, OCSP, CRMF, 
            and CMP. -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>${bouncycastle.version}</version>
        </dependency>

The source code for The Bouncy Castle Crypto Package For Java.

The Java code

package se.magnuskkarlsson.example.bouncycastle;

import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.asn1.x509.GeneralName;

public class X509CertificateUtils {

    public static final String MSUPN_OID = "1.3.6.1.4.1.311.20.2.3";
    private final Logger log = Logger.getLogger(X509CertificateUtils.class.getName());

    public String getMSUPNFromX509Certificate(X509Certificate cert) throws CertificateParsingException {
        Collection<List<?>> sans = JcaX509ExtensionUtils.getSubjectAlternativeNames(cert);
        for (List<?> san : sans) {

            log.info("Read X509 SAN " + sans);
            int sanType = (int) san.get(0);
            if (sanType == GeneralName.otherName) {

                ASN1Sequence sanASN1Sequence = (ASN1Sequence) san.get(1);
                String msupn = getSANFromASN1Sequence(sanASN1Sequence);
                if (msupn != null) {
                    return msupn;
                }
            }
        }
        return null;
    }

    private String getSANFromASN1Sequence(ASN1Sequence sanASN1Sequence) {
        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) sanASN1Sequence.getObjectAt(0);
        if (!MSUPN_OID.equals(oid.getId())) {
            log.warning("Invalid MSUPN OID, expected '" + MSUPN_OID + "' got '" + oid.getId() + "'.");
            return null;
        }

        ASN1TaggedObject sanASN1TaggedObject = (ASN1TaggedObject) sanASN1Sequence.getObjectAt(1);
        ASN1Primitive sanASN1Primitive = sanASN1TaggedObject.getObject();

        if (sanASN1Primitive instanceof ASN1String) {
            return ((ASN1String) sanASN1Primitive).getString();
        }
        log.warning("Invalid ASN.1 Primitive class, expected ASN1String, got " + sanASN1Primitive.getClass());
        return null;
    }

}

October 14, 2019

Eclipse Microprofile Metrics and Application Metrics with Wildfly 18

Introduction

In my previous blog Eclipse Microprofile Metrics with Wildfly 18 and Prometheus, we were getting started with Eclipse Microprofile Metrics and Wildfly 18.

All Metrics: http://127.0.0.1:9990/metrics

Basic Metrics: http://127.0.0.1:9990/metrics/base

Vendor Specific Metrics: http://127.0.0.1:9990/metrics/vendor

Application Specific Metrics: http://127.0.0.1:9990/metrics/application

And in this blog we will focus how to write application specific metrics. Eclipse Microprofile Metrics is by default active in Wildfly 18, so no activation or configurations are needed. The application is a standard Java EE 8 web application with JAX-RS.

For documentation see Metrics Specification https://github.com/eclipse/microprofile-metrics/releases

@Counted

The @Counted annotation which counts how many time a request has been made.

    @GET
    @Path("/firstName")
    @Counted
    public Response getFirstName() {
        JsonObject json = Json.createObjectBuilder().add("@Counted", "HELLO " + System.currentTimeMillis()).build();
        return Response.ok(json.toString()).build();
    }
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getFirstName_total counter
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getFirstName_total 1.0

@Gauge

The @Gauge is the most basic metric type that you can use as it just returns a value.

    @GET
    @Path("/time")
    @Produces(MediaType.TEXT_PLAIN)
    @Gauge(unit = "time")
    public Long getTime() {
        return System.currentTimeMillis();
    }
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTime_time gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTime_time 1.571012533582E12

@Metered

The @Metered annotation measures the rate at which a set of events occur.

    @GET
    @Path("/title")
    @Metered(unit = MetricUnits.MILLISECONDS)
    public Response getTitle() {
        JsonObject json = Json.createObjectBuilder().add("@Metered", "HELLO " + System.currentTimeMillis()).build();
        return Response.ok(json.toString()).build();
    }
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_total counter
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_total 2.0
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_rate_per_second 0.10850140606928724
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_one_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_one_min_rate_per_second 0.030703655021877174
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_five_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_five_min_rate_per_second 0.0065567799035988195
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_fifteen_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getTitle_fifteen_min_rate_per_second 0.002209922141215539

@Timed

The @Timed annotatoin measures the duration of an event.

    @GET
    @Path("/occupation")
    @Timed(unit = MetricUnits.MILLISECONDS)
    public Response getOccupation() {
        try {
            Thread.sleep((long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        JsonObject json = Json.createObjectBuilder().add("@Timed", "HELLO " + System.currentTimeMillis()).build();
        return Response.ok(json.toString()).build();
    }
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_rate_per_second 0.10850160127626114
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_one_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_one_min_rate_per_second 0.031982234148270686
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_five_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_five_min_rate_per_second 0.0066114184713530035
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_fifteen_min_rate_per_second gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_fifteen_min_rate_per_second 0.0022160607980413085
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_min_seconds gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_min_seconds 0.326822982
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_max_seconds gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_max_seconds 0.492277962
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_mean_seconds gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_mean_seconds 0.4076894175173451
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_stddev_seconds gauge
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_stddev_seconds 0.08270655402029932
# TYPE application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds summary
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds_count 2.0
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.5"} 0.326822982
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.75"} 0.492277962
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.95"} 0.492277962
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.98"} 0.492277962
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.99"} 0.492277962
application_se_magnuskkarlsson_example_microprofile_boundary_PersonResource_getOccupation_seconds{quantile="0.999"} 0.492277962

JSON Output

You can also query the application metrics with accept type json.

$ curl -H "Accept: application/json" http://127.0.0.1:9990/metrics/application

{
    "se.magnuskkarlsson.example.microprofile.boundary.PersonResource.getTitle": {
        "fiveMinRate": 0.004020212971829918,
        "fifteenMinRate": 0.0021635871943593244,
        "meanRate": 0.00440791116923509,
        "count": 3,
        "oneMinRate": 0.015991588875909905
    },
    "se.magnuskkarlsson.example.microprofile.boundary.PersonResource.getMiddleName": {
        "current": 0,
        "min": 0,
        "max": 0
    },
    "se.magnuskkarlsson.example.microprofile.boundary.PersonResource.getOccupation": {
        "p99": 928.815422,
        "min": 326.822982,
        "max": 928.815422,
        "mean": 928.7665329420207,
        "p50": 928.815422,
        "p999": 928.815422,
        "stddev": 5.110449494753552,
        "p95": 928.815422,
        "p98": 928.815422,
        "p75": 928.815422,
        "fiveMinRate": 0.0040261670318063715,
        "fifteenMinRate": 0.0021665192890272054,
        "meanRate": 0.004407906126221853,
        "count": 3,
        "oneMinRate": 0.01599160852294868
    },
    "se.magnuskkarlsson.example.microprofile.boundary.PersonResource.getFirstName": 3
}

Eclipse Microprofile Metrics with Wildfly 18 and Prometheus

Eclipse Microprofile Metrics

"This specification aims at providing a unified way for Microprofile servers to export Monitoring data ("Telemetry") to management agents and also a unified Java API, that all (application) programmers can use to expose their telemetry data." [https://microprofile.io/project/eclipse/microprofile-metrics]

Source code https://github.com/eclipse/microprofile-metrics/

Eclipse Microprofile Metrics Specification https://github.com/eclipse/microprofile-metrics/releases

JBoss EAP 7.2

Does not support Eclipse Microprofile Metrics.

Wildfly 18.0.0.Final

Wildfly 18.0.0.Final supports Eclipse Microprofile Metrics 2.0.0 [1], which is part of Eclipse Microprofile 3.0.

[1] $JBOSS_HOME/modules/system/layers/base/org/eclipse/microprofile/metrics/api/main/microprofile-metrics-api-2.0.2.jar

Documentation https://docs.wildfly.org/18/Admin_Guide.html#MicroProfile_Metrics_SmallRye

Configuration

[standalone@localhost:9990 /] /subsystem=microprofile-metrics-smallrye:read-resource(recursive=true, include-defaults=true)
{
    "outcome" => "success",
    "result" => {
        "exposed-subsystems" => ["*"],
        "prefix" => expression "${wildfly.metrics.prefix:wildfly}",
        "security-enabled" => false
    }
}

Wildfly exposes Metrics via HTTP Management Interface, i.e. http://127.0.0.1:9990/metrics.

Prometheus

Prometheus is an open-source monitoring and alerting platform. Its main features are:

  • "Prometheus implements a highly dimensional data model. Time series are identified by a metric name and a set of key-value pairs."
  • "PromQL allows slicing and dicing of collected time series data in order to generate ad-hoc graphs, tables, and alerts."
  • "Prometheus has multiple modes for visualizing data: a built-in expression browser, Grafana integration, and a console template language."
  • "Prometheus stores time series in memory and on local disk in an efficient custom format. Scaling is achieved by functional sharding and federation."
  • "Each server is independent for reliability, relying only on local storage. Written in Go, all binaries are statically linked and easy to deploy."
  • "Alerts are defined based on Prometheus's flexible PromQL and maintain dimensional information. An alertmanager handles notifications and silencing."
  • "Client libraries allow easy instrumentation of services. Over ten languages are supported already and custom libraries are easy to implement."
  • "Existing exporters allow bridging of third-party data into Prometheus. Examples: system statistics, as well as Docker, HAProxy, StatsD, and JMX metrics."

[https://prometheus.io/]

Prometheus can either be locally installed or via Docker.

For local installation, download latest Prometheus version unpack it and run './prometheus'.

To use Docker, use Prometheus Image at https://hub.docker.com/r/prom/prometheus.

Prometheus Docker Image source (Dockerfile) https://github.com/prometheus/prometheus/blob/master/Dockerfile.

Prometheus Docker Image documentation https://prometheus.io/docs/prometheus/latest/installation/.

Before we can use Prometheus for Wildfly we need to add Wildfly metrics endpoint to Prometheus configuration. First download Prometheus and edit prometheus.yml in the root of the zipped installation.

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

  # this is the configuration to poll metrics from WildFly 18
  # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
  - job_name: 'wildfly18'
    scrape_interval: 2s
    metrics_path: '/metrics'
    scheme: 'http'
    static_configs:
    - targets: ['127.0.0.1:9990']

Then start Prometheus with './prometheus', then open http://127.0.0.1:9090/. To test it check which metrics wildfly is exposing, by calling it metrics endpoint.

$ curl http://127.0.0.1:9990/metrics

# HELP base_cpu_processCpuLoad Displays the "recent cpu usage" for the Java Virtual Machine process.
# TYPE base_cpu_processCpuLoad gauge
base_cpu_processCpuLoad 1.5940700593791097E-4

Go back to Prometheus and enter base_cpu_processCpuLoad.

October 13, 2019

Eclipse Microprofile OpenTracing with Java EE 8 and JBoss EAP 7.2

MicroProfile OpenTracing

"The MicroProfile OpenTracing specification defines behaviors and an API for accessing an OpenTracing compliant Tracer object within your JAX-RS application. The behaviors specify how incoming and outgoing requests will have OpenTracing Spans automatically created." [https://microprofile.io/project/eclipse/microprofile-opentracing]

JBoss EAP 7.2

"SmallRye OpenTracing component and is provided by the microprofile-opentracing-smallrye subsystem."

"This subsystem implements Microprofile 1.1, which includes support for tracing requests to JAX-RS endpoints and CDI beans and is included in the default JBoss EAP 7.2 configuration."

"The microprofile-opentracing-smallrye subsystem ships with the Jaeger Java Client as the default tracer"

"Each individual WAR deployed to the JBoss EAP server automatically has its own Tracer instance."

"Instead, you configure the Jaeger Client by setting system properties or environment variables. See the Jaeger documentation [1] for information about how to configure the Jaeger Client. See Configuration via Environment [2] in the Jaeger documentation for the list of valid system properties."

"Because this feature is provided as Technology Preview, the current configuration options, particularly those related to configuring the Jaeger Java Client tracer using system properties and environment variables, might change in incompatible ways in future releases."

[JBoss EAP 7.2 Configuration Guide Eclipse Microprofile OpenTracing]

The microprofile-opentracing-smallrye subsystem does not offer any configuration in JBoss EAP 7.2

/subsystem=microprofile-opentracing-smallrye:read-resource-description(recursive=true)
{
    "outcome" => "success",
    "result" => {
        "description" => "Wildfly Extension for Eclipse MicroProfile OpenTracing With SmallRye",
        "attributes" => {},
        "operations" => undefined,
        "notifications" => undefined,
        "children" => {}
    }
}

Jaeger

See https://www.jaegertracing.io/.

Jaeger Openshift Template

See https://github.com/jaegertracing/jaeger-openshift. To run, run below command.

$ oc process -f https://raw.githubusercontent.com/jaegertracing/jaeger-openshift/master/all-in-one/jaeger-all-in-one-template.yml | oc create -f -

Jaeger Docker Image

But we are going to run the all-in-one docker image, see https://hub.docker.com/r/jaegertracing/all-in-one

To install Docker, see http://magnus-k-karlsson.blogspot.com/2019/08/install-docker-community-edition-ce-on.html.

Source code (Dockerfile) https://github.com/jaegertracing/jaeger/blob/master/cmd/all-in-one/Dockerfile

To run

$ docker run -d -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 14268:14268 -p 14250:14250 -p 16686:16686 jaegertracing/all-in-one

Web Application

Now we are going to write our application, use Java EE 8 and Eclipse Microprofile 1.4 from http://magnus-k-karlsson.blogspot.com/2019/10/eclipse-microprofile-and-java-ee-8-with.html.

Now lets write a @Traced JAX-RS endpoint and a CDI bean.

package se.magnuskkarlsson.example.microprofile.boundary;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.opentracing.Traced;

import se.magnuskkarlsson.example.microprofile.control.PersonService;

@Traced
@Path("/persons")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    @Inject
    protected PersonService personService;

    @GET
    public Response hello() {
        JsonObject json = Json.createObjectBuilder().add("message", personService.getMessage()).build();
        return Response.ok(json.toString()).build();
    }

}
package se.magnuskkarlsson.example.microprofile.control;

import org.eclipse.microprofile.opentracing.Traced;

@Traced
public class PersonService {

    public String getMessage() {
        return "HELLO " + System.currentTimeMillis();
    }

}

Also see JBoss EAP 7.2 Development Guide Eclipse Microprofile OpenTracing

Now we need Jaeger client configuration, configuration.properties

JAEGER_SERVICE_NAME=magnuskkarlsson
JAEGER_ENDPOINT=http://127.0.0.1:14268/api/traces
JAEGER_SAMPLER_TYPE=const
JAEGER_SAMPLER_PARAM=1

Build, deploy and start JBoss EAP 7.2 with above configuration file.

$ ./standalone.sh -P /home/magnuskkarlsson/eclipse-workspace/javaee8-microprofile1.4-example/configuration.properties
...
20:06:17,308 DEBUG [io.jaegertracing.thrift.internal.senders.ThriftSenderFactory] (ServerService Thread Pool -- 72) Using the HTTP Sender to send spans directly to the endpoint.
20:06:17,409 DEBUG [io.jaegertracing.internal.senders.SenderResolver] (ServerService Thread Pool -- 72) Using sender HttpSender()
20:06:17,417 INFO  [io.jaegertracing.Configuration] (ServerService Thread Pool -- 72) Initialized tracer=JaegerTracer(version=Java-0.30.6.redhat-00001, serviceName=magnuskkarlsson, reporter=RemoteReporter(sender=HttpSender(), closeEnqueueTimeout=1000), sampler=ConstSampler(decision=true, tags={sampler.type=const, sampler.param=true}), tags={hostname=localhost.localdomain, jaeger.version=Java-0.30.6.redhat-00001, ip=127.0.0.1}, zipkinSharedRpcSpan=false, expandExceptionLogs=false)
...

Now open web application and make a few requests http://localhost:8080/javaee8-microprofile1.4-example/rest/persons.

Now lets see if those traces are sent to Jaeger, open http://127.0.0.1:16686/search

Eclipse Microprofile Health with Java EE 8 and JBoss EAP 7.2

In my previous blogs we have layed out a maven project for Eclipse Microprofile and tested Eclipse Microprofile Configuration.

In this blog we will test Eclipse Microprofile Health with JBoss EAP 7.2. First all Eclipse Microprofile in JBoss EAP 7.2 are "Technology Previews only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs)". [https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.2/html-single/configuration_guide/index#microprofile_health_check]

"By default, the MicroProfile Health SmallRye subsystem only examines if the server is running."

You can invoke/call Health in two ways. Through CLI or HTTP.

[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:check
{
    "outcome" => "success",
    "result" => {
        "outcome" => "UP",
        "checks" => []
    }
}

The HTTP endpoint is only exposed through the management interface. "The default address for the /health endpoint, accessible from the management interfaces, is http://127.0.0.1:9990/health."

And also there is not much of configuration you can do.

[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:read-resource-description(recursive=true, inherited=true)
{
    "outcome" => "success",
    "result" => {
        "description" => "WildFly Extension for Eclipse MicroProfile Health With SmallRye",
        "capabilities" => [
            {
                "name" => "org.wildfly.extension.microprofile.health.smallrye",
                "dynamic" => false
            },
            {
                "name" => "org.wildlfy.microprofile.health.reporter",
                "dynamic" => false
            }
        ],
        "attributes" => {"security-enabled" => {
            "type" => BOOLEAN,
            "description" => "True if authentication is required to access the HTTP endpoint on the HTTP management interface.",
            "expressions-allowed" => true,
            "required" => false,
            "nillable" => true,
            "default" => true,
            "access-type" => "read-write",
            "storage" => "configuration",
            "restart-required" => "all-services"
        }},
        "operations" => undefined,
        "notifications" => undefined,
        "children" => {}
    }
}

This is all not that exciting. But what is when you start to write application specific health check. You do that by implementing org.eclipse.microprofile.health.HealthCheck and annotating class with org.eclipse.microprofile.health.Health.

The best practice is to implement a specific health check in a separate class and then will jboss global health check aggregates all the result from each health check to the overall health check result. So lets start to write a JPA health check.

package se.magnuskkarlsson.example.microprofile;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

@Health
@ApplicationScoped
public class JPAHealthCheck implements HealthCheck {

    private final Logger log = Logger.getLogger(JPAHealthCheck.class.getName());

    @PersistenceContext
    protected EntityManager em;

    @Override
    public HealthCheckResponse call() {
        Integer result = null;
        try {
            result = (Integer) em.createNativeQuery("SELECT 1").getSingleResult();
        } catch (Exception e) {
            log.log(Level.SEVERE, "Failed to perform JPA health check.", e);
        }
        boolean state = (result == 1) ? true : false;
        return HealthCheckResponse.named("jpa-health-check").withData("SELECT 1", result).state(state).build();
    }

}

Build, deploy and test.

[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:check
{
    "outcome" => "success",
    "result" => {
        "outcome" => "UP",
        "checks" => [{
            "name" => "health-test",
            "state" => "UP",
            "data" => {"JPA 'SELECT 1'" => "1"}
        }]
    }
}

Now build a second health check for disk space, but here we will deliberate hard code the output state to down, to test jboss aggregated state value.

package se.magnuskkarlsson.example.microprofile;

import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.context.ApplicationScoped;

import org.eclipse.microprofile.health.Health;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;

@Health
@ApplicationScoped
public class DiskSpaceHealthCheck implements HealthCheck {

    private final Logger log = Logger.getLogger(DiskSpaceHealthCheck.class.getName());

    @Override
    public HealthCheckResponse call() {
        File path = new File("/");
        long diskFreeInBytes = 0L;
        try {
            diskFreeInBytes = path.getUsableSpace();
        } catch (Exception e) {
            log.log(Level.SEVERE, "Failed to perform disk space health check.", e);
        }
        boolean state = false;
        return HealthCheckResponse.named("disk-space-health-check").withData(path.getAbsolutePath(), diskFreeInBytes)
                .state(state).build();
    }

}

Build, deploy and test.

[standalone@localhost:9990 /] /subsystem=microprofile-health-smallrye:check
{
    "outcome" => "success",
    "result" => {
        "outcome" => "DOWN",
        "checks" => [
            {
                "name" => "jpa-health-check",
                "state" => "UP",
                "data" => {"SELECT 1" => 1}
            },
            {
                "name" => "disk-space-health-check",
                "state" => "DOWN",
                "data" => {"/" => 409898860544L}
            }
        ]
    }
}

As you can see the overall state is DOWN.

To get more inspiration for others health check, see spring boots health indicators - https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/actuate/health/AbstractHealthIndicator.html

And finally it is of interest to read the Eclipse Microprofile Health Specification.

October 12, 2019

Eclipse Microprofile Configuration with Java EE 8 and JBoss EAP 7.2

Here we are going to look at Eclipse Microprofile Configuration with Java EE 8 and JBoss EAP 7.2.

The configuration project is a convenient project to easily load configuration from:

  • System Property, e.g. -Dnum.size=20
  • Properties file, e.g. configuration.properties

To get started we use the previous blog project setup, with pom.xml, web.xml, beans.xml and persistence.xml. Please see https://magnus-k-karlsson.blogspot.com/2019/10/eclipse-microprofile-and-java-ee-8-with.html.

And the injection configuration:

package se.magnuskkarlsson.example.microprofile.boundary;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.config.inject.ConfigProperty;

@Path("/persons")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PersonResource {

    @Inject
    @ConfigProperty(name = "num.size", defaultValue = "12")
    int numSize;

    @GET
    public Response hello() {
        JsonObject json = Json.createObjectBuilder().add("message", "HELLO " + numSize).build();
        return Response.ok(json.toString()).build();
    }

}

And to make JAX-RS complete we also need

package se.magnuskkarlsson.example.microprofile;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest")
public class JAXRSApplication extends Application {

}

Now we can build and deploy the web application. To configure we have now two options

  • System Properties, e.g. ./standalone.sh -Dnum.size=20
  • Property File, e.g. ./standalone.sh -P ../standalone/configuration/configuration.properties

Test both ways, but for production, property file is the recommended way.

Eclipse Microprofile and Java EE 8 with JBoss EAP 7.2

The Eclipse Microprofile is a spin of from Java EE and is a fast pace standardization community for building standardized Microservices. There are several subprojects within Microprofile and there are constantly more added all the time. To see all full list visit https://microprofile.io/projects/.

To get started to use Eclipse Microprofile you simple add the maven dependency org.eclipse.microprofile:microprofile.

Before using Eclipse Microprofile, you must first check which version your Java Container supports, alternative you can packages the binaries inside your web application.

For JBoss EAP 7.2 it does not support the entire Eclipse Microprofile version 1.4, but a subset of it. To get started use the following pom.xml.

<?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>se.magnuskkarlsson.examples</groupId>
    <artifactId>javaee8-microprofile1.4-example</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.microprofile</groupId>
            <artifactId>microprofile</artifactId>
            <version>1.4</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>

        <!-- Test Support -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                    <showDeprecation>true</showDeprecation>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

And the src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

</web-app>

And the src/main/webapp/WEB-INF/beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
    version="2.0" bean-discovery-mode="all">

</beans>

And the src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
    version="2.2">

    <persistence-unit name="PROD"></persistence-unit>
</persistence>

October 6, 2019

Getting Started with RH SSO 7.3 (Keycloak)

Introduction

In this blog we are going to install and configure RH SSO 7.3 (upstream Keycloak) from scratch and then develop a simple Java Web application, protected with RH SSO (Keycloak).

We are going to run the RH SSO 7.3 and JBoss EAP 7.2 on the same machine and locally, but before that we need to install Java 11 (OpenJDK 11). Since this is a developing machine we are going to install every OpenJDK 11 packages.

$ sudo dnf install -y java-11-openjdk java-11-openjdk-src java-11-openjdk-demo java-11-openjdk-devel java-11-openjdk-jmods java-11-openjdk-javadoc java-11-openjdk-headless java-11-openjdk-javadoc-zip

If you have multiple of Java versions installed, please set Java 11 as default

$ sudo update-alternatives --config java

$ java -version
openjdk version "11.0.4" 2019-07-16
OpenJDK Runtime Environment 18.9 (build 11.0.4+11)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.4+11, mixed mode, sharing)

RH SSO 7.3 (Keycloak)

Now install RH SSO from ZIP (in production you would install it via RPM).

$ unzip rh-sso-7.3.0.GA.zip
$ mv rh-sso-7.3 rh-sso-7.3.0
$ cd rh-sso-7.3.0/bin/

$ ./standalone.sh

Now configure Admin account, by open http://localhost:8080/auth/ and then open http://localhost:8080/auth/admin/ and login as Admin.

  1. In the left menu create a new Realm 'demo'.
  2. In the demo realm create a new User with username 'joe'
  3. After created User, click on User Credential tab.
  4. And reset User 'joe' password and don't forget to set Temporary to false.
  5. Then create a new Role, by clicking on left menu Role and then Add role.
  6. Lets name the Role to 'OIDCDEMO_USER'.
  7. Go back to User by clicking on User in lefter meny and find user 'joe'.
  8. After clicking on User 'joe', then click on tab Role Mappings.
  9. Select our newly created Role 'OIDCDEMO_USER' under Available Roles and click Add selected.

Web Application Server - JBoss EAP 7.2

Install via zip (in production you would install via RPM). After extracting the zip files, we install OpenID Connector to EAP via adapter-elytron-install-offline.cli and then starts EAP with a port offset of 100. To test it open http://localhost:8180/

$ unzip jboss-eap-7.2.0.zip
$ mv jboss-eap-7.2 jboss-eap-7.2.0-oidc
$ unzip rh-sso-7.3.0.GA-eap7-adapter.zip -d jboss-eap-7.2.0-oidc
$ cp jboss-eap-7.2.0-oidc/standalone/configuration/standalone.xml jboss-eap-7.2.0-oidc/standalone/configuration/standalone.xml.ORIG

$ cd jboss-eap-7.2.0-oidc/bin
$ ./jboss-cli.sh --file=adapter-elytron-install-offline.cli

$ ./standalone.sh -Djboss.socket.binding.port-offset=100

The Java EE 8 Web Application

Now lets create a simple Java EE 8 Web Application, with a single index.jsp and a logout.jsp.

$ mkdir rh-sso-7.3-demo
$ cd rh-sso-7.3-demo 
$ mkdir -p src/main/java/se/magnuskkarlsson/example/oidc
$ mkdir -p src/main/resources/META-INF
$ mkdir -p src/main/webapp/WEB-INF
$ touch src/main/webapp/WEB-INF/web.xml
$ mkdir -p src/test/java/se/magnuskkarlsson/example/oidc
$ mkdir -p src/test/resources
$ vi pom.xml
<?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>se.magnuskkarlsson.examples</groupId>
    <artifactId>rh-sso-7.3-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Test Support -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                    <showDeprecation>true</showDeprecation>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Now the important part of web.xml, where we configure our security.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Unprotected pages</web-resource-name>
            <url-pattern>/logout.jsp</url-pattern>
        </web-resource-collection>
        <!-- <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> -->
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Protected pages</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>OIDCDEMO_USER</role-name>
        </auth-constraint>
        <!-- <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> -->
    </security-constraint>

    <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <!-- <realm-name>demo</realm-name> -->
    </login-config>

    <security-role>
        <role-name>OIDCDEMO_USER</role-name>
    </security-role>

    <session-config>
        <session-timeout>15</session-timeout>
        <cookie-config>
            <http-only>true</http-only>
            <!-- <secure>true</secure> -->
        </cookie-config>
        <tracking-mode>COOKIE</tracking-mode>
    </session-config>
</web-app>

And our index.jsp and logout.jsp

<%@ page import="java.util.*"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Secure Web App</title>
<style>
table, th, td {
    border: 1px solid black;
    border-collapse: collapse;
}

th, td {
    padding: 10px;
}
</style>
</head>
<body>
    <table border="1">
        <!-- Security -->
        <tr>
            <td>getAuthType:</td>
            <td><%=request.getAuthType()%></td>
        </tr>
        <tr>
            <td>isSecure:</td>
            <td><%=request.isSecure()%></td>
        </tr>
        <tr>
            <td>getRemoteUser:</td>
            <td><%=request.getRemoteUser()%></td>
        </tr>
        <tr>
            <td>getUserPrincipal:</td>
            <td><%=request.getUserPrincipal()%></td>
        </tr>
        <tr>
            <td>getSession(false):</td>
            <td><%=request.getSession(false)%></td>
        </tr>
        <tr>
            <td>isUserInRole(<%=request.getParameter("UserInRole")%>):
            </td>
            <td>
                <div>
                    <form action="index.jsp" method="get">
                        <input type="text" id="UserInRole" name="UserInRole" /> <input type="submit" name="Submit" />
                    </form>
                </div>
                <div>
                    <%=request.isUserInRole(request.getParameter("UserInRole"))%>
                </div>
            </td>
        </tr>
        <!-- Cookies and Headers -->
        <tr>
            <td>Cookies:</td>
            <td>
                <%
                    for (int i = 0; request.getCookies() != null && i < request.getCookies().length; ++i) {
                        Cookie cookie = request.getCookies()[i];
                        out.println(cookie.getPath() + " " + cookie.getDomain() + " " + cookie.getName() + "="
                                + cookie.getValue() + " " + cookie.isHttpOnly() + " " + cookie.getSecure() + "<br/>");
                    }
                %>
            </td>
        </tr>
        <tr>
            <td>Header:</td>
            <td>
                <%
                    for (Enumeration<String> headers = request.getHeaderNames(); headers.hasMoreElements();) {
                        String header = headers.nextElement();
                        out.println(header + ": " + request.getHeader(header) + "<br/>");
                    }
                %>
            </td>
        </tr>
        <!-- Locale Address -->
        <tr>
            <td>getLocalAddr:</td>
            <td><%=request.getLocalAddr()%></td>
        </tr>
        <tr>
            <td>getLocalName:</td>
            <td><%=request.getLocalName()%></td>
        </tr>
        <tr>
            <td>getLocalPort:</td>
            <td><%=request.getLocalPort()%></td>
        </tr>
        <!-- Remote Address -->
        <tr>
            <td>getRemoteAddr:</td>
            <td><%=request.getRemoteAddr()%></td>
        </tr>
        <tr>
            <td>getLocalName:</td>
            <td><%=request.getRemoteHost()%></td>
        </tr>
        <tr>
            <td>getRemotePort:</td>
            <td><%=request.getRemotePort()%></td>
        </tr>
        <!-- Different Paths -->
        <tr>
            <td>getContextPath:</td>
            <td><%=request.getContextPath()%></td>
        </tr>
        <tr>
            <td>getPathInfo:</td>
            <td><%=request.getPathInfo()%></td>
        </tr>
        <tr>
            <td>getQueryString:</td>
            <td><%=request.getQueryString()%></td>
        </tr>
        <tr>
            <td>getRequestURI:</td>
            <td><%=request.getRequestURI()%></td>
        </tr>
        <tr>
            <td>getRequestURL:</td>
            <td><%=request.getRequestURL()%></td>
        </tr>
        <tr>
            <td>getServletPath:</td>
            <td><%=request.getServletPath()%></td>
        </tr>
    </table>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Logout</title>
</head>
<body>
    <%
        if (request.getSession(false) != null) {
            request.getSession(false).invalidate();
        }
        request.logout();
        /*
        "You can log out of a web application in multiple ways. For Java EE servlet containers, you 
        can call HttpServletRequest.logout(). For other browser applications, you can redirect 
        the browser to 
        http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri, 
        which logs you out if you have an SSO session with your browser."
        [https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.3/html/securing_applications_and_services_guide/openid_connect_3#logout]
        */
    %>
    <h3>You are logged out</h3>
</body>
</html>

Configure OpenID Connect for Web Application

Now we need to configure OpenID Connect for our Web Application in RH SSO

And finally add OpenID Connect configuration to our web application src/main/webapp/WEB-INF/keycloak.json.

{
 "realm": "demo",
 "auth-server-url": "http://localhost:8080/auth",
 "ssl-required": "external",
 "resource": "rh-sso-7.3-demo",
 "public-client": true,
 "enable-cors": true
}

References

  1. https://www.keycloak.org/docs/latest/getting_started/index.html
  2. https://github.com/keycloak/keycloak-quickstarts/tree/latest/app-profile-jee-vanilla
  3. https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.3/html/securing_applications_and_services_guide/openid_connect_3#java_adapters