Sunday, August 23, 2020

Developing Web & Enterprise Applications for the JVM - "This and That"

I am starting a new series of blog posts which will cover the ins and outs, tips and tricks of developing web and enterprise applications with Java.  Some people refer to enterprise meaning that an application requires interaction with a database and/or communication with other applications and services.  Therefore, this blog series will apply to "Web" or "Enterprise" application development for the Java Virtual Machine (JVM) using a number of different technologies.

In this blog series, I will not focus on any single strategy or API, but rather, I will cover a number of technical strategies and options that can be used for developing applications for deployment on the JVM.  The main goal is to provide readers with a broad overview of the ecosystem available for the JVM, ensuring that one knows various paths that can be followed to develop sophisticated applications.  I also hope to keep each post short and to the point, to allow one more time to develop and experiment, rather than reading my ramblings.  

There is no doubt that the JVM is one of the key technologies of our time, as it is installed on millions of devices worldwide, and tens of thousands of applications have been written for the JVM.  Even though it is one of the most popular platforms for application development, there tends to be questioning on which path to take when developing certain applications.  I like to think of the myriad of options as "tools" that are in the toolshed.  We can utilize a Phillips screwdriver to tighten an Allen wrench screw, but it certainly may not be the optimal choice and there are more suitable options available.  Much in the same manner, one may use Jakarta EE to develop a microservice just because he or she may know that tech, but there may be better options out there for the particular solution at hand.

Hopefully this blog series will provide you with a better understanding of which tool to choose for your future projects.  Just because you know one technology very well, it does not mean that you cannot pick up another technology easily enough and make your toolshed even more substantial for future endeavors.  One should continually learn new technologies so that they are not locked into using a single strategy.

To that end...I will start this series by showing how to develop a very simple web service which will be used to provide a temperature reading.  The web service end point can be harnessed by clients to obtain the current temperature by reading a file that has been written by a temperature sensor connected to a raspberry pi.  In the first post, I will utilize Jakarta EE 8 to build this service.  

Note:  As you read on in this series, you will see that version numbers of APIs mean the world.  As with any technology, web and enterprise development are ever-changing, and so moving from one version to the next usually means that there may be a number of changes in the way a service or application needs to be developed.  

The following index will be updated to include posts as they are written for inclusion in this series.


Developing a Simple Web Service with Jakarta EE 8

For context, Jakarta EE 8 is very much the same as Java EE 6 through Java EE 8.  The main differences between Java EE 6 and Jakarta EE 8 are the inclusion of newer specifications and updated technologies that had been added over the years.  Moreover, Jakarta EE 8 is an open source platform, whereas Java EE was not open source.  When Oracle open sourced Java EE to the Eclipse Foundation, it became known as Jakarta EE.

This example application (web service) will create a WAR file which can be deployed to any Jakarta EE 8-compliant application server container.  This includes micro-containers such as Payara Micro or WildFly.  To begin, create a Maven based WAR file project in your favorite Integrated Development Environment (IDE).  The most prevalent IDE's for developing Jakarta EE 8 applications include Eclipse, IntelliJ, or Apache NetBeans.  Another option is to download the Jakarta EE starter project.  In this post, I start with the Jakarta EE starter project and then I will open and develop within Apache NetBeans 12.  I will deploy to Payara Micro.

To get started, grab a copy of the Jakarta EE starter project simple-hello and copy it into a working directory on your machine.  Open the project in your IDE and rename it to PoolService-1.0-SNAPSHOT, as seen in Figure 1.
Figure 1:  Rename Project

Modify the class named org.demo.simple.ApplicationConfig, by adding a path. Once finished, it should look as follows:

package com.demo.simple;

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

/**
 * Configures the Jakarta REST application.
 * 
 * @author Ivar Grimstad (ivar.grimstad@eclipse-foundation.org)
 */
@ApplicationPath("resources")
public class ApplicationConfig extends Application {
    
}
This will create a root path for your Jakarta RESTful web services at the path "resources", meaning that in order to access any JAX-RS resources, preface the service name with this path.

Next, develop a controller class that can be utilized for reading the temperature file when invoked.  Create a new Java class named org.demo.simple.TemperatureController, which is responsible for reading the file and setting values within variables.  The temperature will be in the format (23.5, 74.3) with the first reading being in Celsius, and the second reading being in Fahrenheit. 

package com.demo.simple;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

/**
 *
 * @author juneau
 */
@Named
@RequestScoped
public class TemperatureController {
    
    private String currentTemperatureF;
    
    private String currentTemperatureC;
    
    public TemperatureController(){
        
    }
    
    protected String readTemperatureFile() {
        
        String temperatureFile = "<<path-to-file>>/temperature.txt";
        java.nio.file.Path path = Paths.get(temperatureFile);
        String currentTemperature = null;
        try (BufferedReader reader = Files.newBufferedReader(path, Charset.forName("UTF-8"))) {

            String currentLine = null;
            while ((currentLine = reader.readLine()) != null) {//while there is content on the current line
                currentTemperature = currentLine;
            }
        } catch (IOException ex) {
            ex.printStackTrace(); //handle an exception here
        }
        return currentTemperature;
    }

    /**
     * @return the currentTemperatureF
     */
    public String getCurrentTemperatureF() {
        String temp = readTemperatureFile();
        currentTemperatureF = temp.substring(temp.indexOf(",") + 1, temp.lastIndexOf(")"));
        return currentTemperatureF;
    }

    /**
     * @param currentTemperatureF the currentTemperatureF to set
     */
    public void setCurrentTemperatureF(String currentTemperatureF) {
        this.currentTemperatureF = currentTemperatureF;
    }

    /**
     * @return the currentTemperatureC
     */
    public String getCurrentTemperatureC() {
        String temp = readTemperatureFile();
        currentTemperatureC = temp.substring(temp.indexOf("(") + 1, temp.lastIndexOf(","));
        return currentTemperatureC;
    }

    /**
     * @param currentTemperatureC the currentTemperatureC to set
     */
    public void setCurrentTemperatureC(String currentTemperatureC) {
        this.currentTemperatureC = currentTemperatureC;
    }
    
}

An interested service can now inject TemperatureController using Contexts and Dependency Injection (CDI), and call upon getCurrentTemperatureF() or getCurrentTemperatureC() in order to obtain the requested temperature format.  As such, lastly create a Jakarta Restful Web Services file named TemperatureResource, and enter the following code:

package com.demo.simple;

import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PUT;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.core.MediaType;

/**
 * JAX-RS Web Service
 *
 * @author juneau
 */
@Path("temperature")
@RequestScoped
public class TemperatureResource {
    
    @Inject
    private TemperatureController temperatureController;

    /**
     * Creates a new instance of TemperatureResource
     */
    public TemperatureResource() {
    }

    /**
     * Calls upon the TemperatureController and obtains the current temperature
     * in Fahrenheit.
     *
     * @return an instance of java.lang.String
     */
    @GET
    @Produces({MediaType.TEXT_PLAIN})
    public String getXml() {
        String currentTemperature = temperatureController.getCurrentTemperatureF();
        return currentTemperature;
    }

    /**
     * PUT method for updating or creating an instance of TemperatureResource
     *
     * @param content representation for the resource
     */
    @PUT
    @Consumes(MediaType.APPLICATION_XML)
    public void putXml(String content) {
    }
    
    /**
     * Calls upon the TemperatureController and retrieves the current temperature
     * in Fahrenheit.  Same as getXml();
     * @return 
     */
    @GET
    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
    @Path("f")
    public String getF() {
        String currentTemperature = temperatureController.getCurrentTemperatureF();
        return currentTemperature;
    }

    /**
     * Calls upon the TemperatureController and retrieves the current temperature
     * in Celsius.
     * @return 
     */
    @GET
    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
    @Path("c")
    public String getC() {
        String currentTemperature = temperatureController.getCurrentTemperatureC();
        return currentTemperature;
    }
    
}

This is a web service that will be made available at the URI /resources/temperature, so when the URL http://localhost:8080/poolService/resources/temperature is entered, then the temperature in Fahrenheit should be displayed. This happens because the method named getXml() is called by default since it is annotated with @GET and does not contain a path. It will return plain text since it is annotated with @Produces({MediaType.TEXT_PLAIN}). If I wish to change the default format to something else, say XML, then I could modify the @Produces annotation to: @Produces({MediaType.APPLICATION_JSON}).  The getXml() method invokes the TemperatureController getCurrentTemperatureF() method to read the temperature and return the result.

Use your favorite IDE or the command line to compile and build the project to create a WAR (Web Archive) file named simple-hello.war.
 

Deployment

I am going to use a micro container to deploy the application.  However, it could also be deployed into any other Jakarta EE compatible container such as GlassFish, Payara, or Open Liberty.  See the following link for compatible implementations:  https://jakarta.ee/compatibility/

In this case, download Payara Micro 5.2020.4 and place the downloaded JAR file into a directory.  Next, open a terminal and type in the following to start Payara Micro and deploy the WAR file:

java -jar payara-micro-5.2020.4.jar --deploy <path-to-war-file>/simple-hello.war

This will start the server and output something similar to the following in the terminal:
Payara Micro URLs:
http://<your-machine>:8080/simple-hello

'simple-hello' REST Endpoints:
GET	/simple-hello/resources/application.wadl
GET	/simple-hello/resources/hello
GET	/simple-hello/resources/temperature
PUT	/simple-hello/resources/temperature
GET	/simple-hello/resources/temperature/c
GET	/simple-hello/resources/temperature/f

]]
Input the following URL into the browser to run the service:  http://<your-machine>:8080/simple-hello/resources/temperature