June 25, 2019

Getting Started with NetBeans and Java EE 8 and JBoss EAP 7.2

Introduction

NetBeans has very good Java EE integration both for code completion support, but also deploying, running and debugging Java EE application in a Java EE container.

Currently is NetBeans moving to Apache NetBeans, but this is still work in progress and involves licensing. See e.g. https://developer.jboss.org/thread/279950.

Current Apache NetBeans releases have limitations, so I recommend sticking with the latest Oracle/Sun version 8.2, until the migration process is more done. The 8.2 version is running on Java 8.

  • Apache NetBeans 10 has no Java EE support.
  • Apache NetBeans 11 has Java EE support, but not WildFly or JBoss EAP. Supports Java 11.

Installation

There is also a ready to use installation script, but I prefer downloading the zip version. The zip version is also good when working on offline computers.

  1. Download netbeans-8.2-201609300101-javaee.zip from https://netbeans.org/downloads/zip.html.
  2. Unzip to e.g. /home/magnuskkarlsson/bin/netbeans-8.2-201609300101-javaee
  3. Run it with bin/netbeans

Then you need to download and extract JBoss EAP 7.2 from https://developers.redhat.com/products/eap/download/ to e.g. /home/magnuskkarlsson/bin/jboss-eap-7.2.0

Before we set up our Java EE Web project inside NetBeans i prefer to change the keyboard shortcut to Eclipse. Open Tools -> Options.

Now we are ready to create Java EE 8 project. Open File -> New Project...

Here we need to add JBoss EAP. To add the version 7.2 as a JBoss EAP does not work, but you can add it as a WildFly application server.

Then click finish. Now your maven Java EE Web project is created, but we need to clean it up and modify the pom.xml to use Java EE 8 instead. I also added some testing dependencies, which are handy when doing JPA integration testing. For generating random test data I use javafaker.


<?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>example-javaee8</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <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</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>
        <!-- generate random test data -->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>0.18</version>
            <scope>test</scope>
        </dependency>
        <!-- in-memory db for jpa it -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
            <scope>test</scope>
        </dependency>
        <!-- production db -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
            <scope>test</scope>
        </dependency>
        <!-- hibernate 5 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.3.7.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
            <version>5.3.7.Final</version>
            <scope>test</scope>
        </dependency>
        <!-- bean validation -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.14.Final</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Then modify web.xml to use Java EE 8 Servlet 4 and add JSF support - 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">
     
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    
    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

Then add Java EE 8 CDI 2.0 - 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"
          bean-discovery-mode="all"
          version="2.0">
 
</beans>

Now lets add a simple JSF index page - src/main/webapp/index.xhtml.


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:core="http://xmlns.jcp.org/jsf/core">

    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        Hello from Facelets
    </h:body>
</html>

Now lets test it, by first right click on project and select Clean and Build and then Run. JBoss EAP 7.2 is now started from NetBeans and you default browser should now open and display index.xhtml.

You are now ready to develop your Java EE 8 application and whenever your code is out of sync simply Clean and Build again and whenever neccessary select Run and the application will be redeployed.

June 9, 2019

https://stackoverflow.com/questions/52431764/difference-between-openjdk-and-adoptopenjdk


https://stackoverflow.com/questions/52431764/difference-between-openjdk-and-adoptopenjdk

Implement Metrics with Java EE Interceptors and Exposing with MBean

Background

A crucial capability when deploying an application to production is to be able to monitor it and set alerts if application is running slow. This is the background for Eclipse Micropofile Metrics project, which most Java EE container does not yet support, but will probably in near future.

The other thing you need after added monitoring is to expose that data and many monitoring tools use MBean for that.

So here we are going to show how to add Timer Metrics in a Java EE way with Interceptors (previously called Aspect Oriented Programming, AOP) and to expose that data with MBean.

Reference:

Java EE Interceptors

The interceptor consist of one class, which is annotated with @javax.interceptor.Interceptor and have one method annotated with @javax.interceptor.AroundInvoke.


package se.magnuskkarlsson.metrics.control;

import java.util.logging.Logger;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import se.magnuskkarlsson.metrics.boundary.TimerResource;

@Interceptor
public class TimerInterceptor {

    private final Logger log = Logger.getLogger(TimerInterceptor.class.getName());
    @Inject
    protected TimerResource resource;

    // ----------------------- Logic Methods -----------------------

    @AroundInvoke
    public Object intercept(InvocationContext ctx) throws Exception {
        String className = ctx.getTarget().getClass().getName();
        String methodName = ctx.getMethod().getName();
        long start = System.currentTimeMillis();
        try {
            // call the target code
            return ctx.proceed();
        } finally {
            // target code can fail so we measure the duration with a finally block
            long duration = System.currentTimeMillis() - start;
            log.info(className + "#" + methodName + " " + duration + " ms");
            resource.getStatisticsMBean(className).add(methodName, duration);
        }
    }

    // ----------------------- Helper Methods -----------------------

    // ----------------------- Get and Set Methods -----------------------

}

Reference:

MBean

A MBean is a POJO, but with naming requirements:

  • First it needs an interface.
  • Second the interface name must end with MBean.
  • And finally the implementing class must be named as same as the interface, but without the MBean.

package se.magnuskkarlsson.metrics.control;

import java.util.Map;

public interface TimerMBean {

    public void add(String key, long value);

    public Map<String, String> getTimers();

}

package se.magnuskkarlsson.metrics.control;

import java.util.LongSummaryStatistics;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class Timer implements TimerMBean {

    private final ConcurrentHashMap<String, LongSummaryStatistics> timers = new ConcurrentHashMap<>();

    // ----------------------- Logic Methods -----------------------

    @Override
    public void add(String key, long value) {
        // "The entire method invocation is performed atomically"
        LongSummaryStatistics summary = timers.computeIfAbsent(key, k -> new LongSummaryStatistics());
        summary.accept(value);
    }

    @Override
    public Map<String, String> getTimers() {
        return timers.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().toString()));
    }

    // ----------------------- Helper Methods -----------------------

    // ----------------------- Get and Set Methods -----------------------

}

Reference:

Java EE Singleton

Finally we need to register these MBeans to you Java EE container and also unregister those when your application is undeployed. To keep track of these MBeans I use a Java EE singleton. The singleton pattern is generally not recommended, but for specific purpose it is good.

A Java EE Singleton is an EJB, which means it is transaction aware, available via dependency injection, but also it is default thread safe by the Java EE container, this is both good and bad. Good because it shields the developer from writing thread safe code, but also bad since locking is not good for performance.

You can choose which looking mode you will have with @javax.ejb.ConcurrencyManagement, the default value for it is javax.ejb.ConcurrencyManagementType.CONTAINER, which means the container will do the locking. But you can also control locking with @javax.ejb.Lock(javax.ejb.LockType.READ) and @javax.ejb.Lock(javax.ejb.LockType.WRITE) and locking timeouts with @javax.ejb.AccessTimeout.

See for example https://www.byteslounge.com/tutorials/java-ee-ejb-concurrency-concurrencymanagement-lock-and-locktype for examples.

But here we will manage the concurrency by yourself, with the help of java.util.concurrent.ConcurrentHashMap and it's method computeIfAbsent which is thread safe. The second argument in computeIfAbsent is a construction for new values if the key is not existing.


package se.magnuskkarlsson.metrics.boundary;

import java.lang.management.ManagementFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PreDestroy;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import se.magnuskkarlsson.metrics.control.Timer;
import se.magnuskkarlsson.metrics.control.TimerMBean;

@Singleton
@Startup
// "Bean developer is responsible for managing concurrent access to the bean instance."
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class TimerResource {

    private final Logger log = Logger.getLogger(TimerResource.class.getName());
    private ConcurrentHashMap<String, TimerMBean> mbeans = new ConcurrentHashMap<>();

    // ----------------------- Logic Methods -----------------------

    public TimerMBean getStatisticsMBean(final String className) {
        // "The entire method invocation is performed atomically"
        return mbeans.computeIfAbsent(className, k -> registerMBean(className));
    }

    @PreDestroy
    public void preDestroy() {
        for (String key : mbeans.keySet()) {
            try {
                MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
                ObjectName objectName = new ObjectName("metrics.timer:type=" + key);
                mBeanServer.unregisterMBean(objectName);
                log.info("Successfully unregistered " + objectName);
            } catch (Exception e) {
                log.log(Level.WARNING, "Failed to unregister MBean.", e);
            }
        }
    }

    // ----------------------- Helper Methods -----------------------

    protected TimerMBean registerMBean(String className) {
        try {
            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName("metrics.timer:type=" + className);
            Timer statistics = new Timer();
            mBeanServer.registerMBean(statistics, objectName);
            log.info("Successfully registered " + objectName);
            return statistics;
        } catch (Exception e) {
            throw new IllegalStateException("Failed to register MBean.", e);
        }
    }

    // ----------------------- Get and Set Methods -----------------------

}

Reference:

Usage

Now you can simply add the @javax.interceptor.Interceptors to your methods you want to get timer metrics from.


    @GET
    @Interceptors(TimerInterceptor.class)
    public List<Person> getAll() { }

Test

To verify the final result open JConsole or VisualVM and get MBean.

June 5, 2019

How to enable SameSite for WildFly and JBoss EAP

Background

A good cookie header should look like:


Set-Cookie: a=b; HttpOnly; secure; SameSite=strict

(HttpOnly = No JavaScript; secure = SSL only; SameSite = no cross-origin cookie sharing)

https://www.owasp.org/index.php/SameSite

Java Servlet 4.0 (Java EE 8)

The sad thing about SameSite is that is not supported in Servlet 4.0.

Java(TM) EE 8 Specification APIs: https://javaee.github.io/javaee-spec/javadocs/javax/servlet/http/Cookie

Wildfly 16

Since Wildfly is built in java, the Wildfly server does not support SameSite.

9.7.3. Servlet container configuration
Session Cookie Configuration
https://docs.wildfly.org/16/Admin_Guide.html#Undertow

JSR 369: JavaTM Servlet 4.0 Specification
https://jcp.org/en/jsr/detail?id=369

JSR 366: Java Platform, Enterprise Edition 8 (Java EE 8) Specification
https://jcp.org/en/jsr/detail?id=366

Example


<servlet-container name="default">
    <session-cookie http-only="true" secure="true"/>
    <jsp-config/>
</servlet-container>

Solution

Best solution I have found is to build a custom Filter, which add SameSite=strict.

https://stackoverflow.com/questions/49697449/how-to-enable-samesite-for-jsessionid-cookie

Best Cyber Security Certifications 2019

Best Cyber Security Certifications 2019
https://www.hackread.com/best-cyber-security-certifications-2019/