[JAVA] Try RESTful web services with Nablarch's container archetype

What's?

I wanted to pretend to be Nablarch for a moment.

I would like to set a little theme and try it from the beginning.

Try it

Reference) Nablarch 5u18 has been released!

environment

This environment is here.

$ java --version
openjdk 11.0.9 2020-10-20
OpenJDK Runtime Environment (build 11.0.9+11-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9+11-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.9, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-52-generic", arch: "amd64", family: "unix"


$ docker version
Client: Docker Engine - Community
 Version:           19.03.13
 API version:       1.40
 Go version:        go1.13.15
 Git commit:        4484c46d9d
 Built:             Wed Sep 16 17:02:52 2020
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.13
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       4484c46d9d
  Built:            Wed Sep 16 17:01:20 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.7
  GitCommit:        8fba4e9a7d01810a393d5d25a3621dc101981175
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

Create a project

Take a look at Getting Started.

Getting Started

If you want to create a full-fledged application, create it from a blank project.

It seems better to start with a blank project, so let's make it a blank project.

Blank Project

Starting with Nablarch 5u18, support for containers is included, so use this. You can create a Docker image with Jib.

Docker containerization

Setup procedure.

Initial setup of RESTful web service project for container

I have a batch file for Windows, but since this environment is Linux, I execute maven-archetype-plugin: 2.4: generate directly.

$ mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate \
  -DinteractiveMode=false \
  -DarchetypeGroupId=com.nablarch.archetype \
  -DarchetypeArtifactId=nablarch-container-jaxrs-archetype \
  -DarchetypeVersion=5u18 \
  -DgroupId=com.example \
  -DartifactId=hello-nablarch-restful \
  -Dversion=0.0.1 \
  -Dpackage=com.example

It's done.

[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: nablarch-container-jaxrs-archetype:5u18
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: hello-nablarch-restful
[INFO] Parameter: version, Value: 0.0.1
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: packageInPathFormat, Value: com/example
[INFO] Parameter: package, Value: com.example
[INFO] Parameter: groupId, Value: com.example
[INFO] Parameter: artifactId, Value: hello-nablarch-restful
[INFO] Parameter: version, Value: 0.0.1
[INFO] project created from Archetype in dir: /path/to/hello-nablarch-restful
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.583 s
[INFO] Finished at: 2020-11-11T11:10:34+09:00
[INFO] ------------------------------------------------------------------------

Moved into the project.

$ cd hello-nablarch-restful

Let's take a quick look at the file.

$ find -type f
./README.md
./pom.xml
./.gitignore
./h2/bin/h2-1.3.176.jar
./h2/bin/h2w.bat
./h2/bin/h2.sh
./h2/bin/h2.bat
./h2/db/SAMPLE.h2.db
./h2/db/SAMPLE.h2.db.org
./db/ddl/db2/create.sql
./db/ddl/db2/drop.sql
./db/ddl/sqlserver/create.sql
./db/ddl/sqlserver/drop.sql
./db/ddl/oracle/create.sql
./db/ddl/oracle/drop.sql
./db/ddl/postgresql/create.sql
./db/ddl/postgresql/drop.sql
./db/ddl/h2/create.sql
./db/ddl/h2/drop.sql
./db/data/db2/data.sql
./db/data/sqlserver/data.sql
./db/data/oracle/data.sql
./db/data/postgresql/data.sql
./db/data/h2/data.sql
./src/main/webapp/WEB-INF/web.xml
./src/main/resources/entity/data-model_sqlserver.edm
./src/main/resources/entity/data-model.edm
./src/main/resources/entity/data-model_postgresql.edm
./src/main/resources/entity/data-model_db2.edm
./src/main/resources/entity/data-model_oracle.edm
./src/main/resources/rest-boot.xml
./src/main/resources/env.config
./src/main/resources/META-INF/services/nablarch.core.repository.di.config.externalize.ExternalizedComponentDefinitionLoader
./src/main/resources/rest-component-configuration.xml
./src/main/resources/messages.properties
./src/main/resources/data-source.xml
./src/main/resources/log.properties
./src/main/resources/app-log.properties
./src/main/resources/common.config
./src/main/jib/usr/local/tomcat/conf/server.xml
./src/main/jib/usr/local/tomcat/conf/logging.properties
./src/main/java/com/example/entity/package-info.java
./src/main/java/com/example/entity/SampleUser.java
./src/main/java/com/example/package-info.java
./src/main/java/com/example/domain/package-info.java
./src/main/java/com/example/domain/SampleDomainBean.java
./src/main/java/com/example/domain/SampleDomainManager.java
./src/main/java/com/example/SampleAction.java
./src/main/java/com/example/dto/SampleUserListDto.java
./src/test/resources/log.properties
./src/test/resources/unit-test.xml
./src/test/resources/data/SAMPLE_USER.csv
./src/test/java/com/example/SampleApiTest.java
./src/test/java/com/example/SampleActionTest.java

The configuration is explained here.

Maven Archetype Configuration (https://nablarch.github.io/docs/5u18/doc/application_framework/application_framework/blank_project/MavenModuleStructures/index.html)

Let's take a look at the profile.

$ mvn help:all-profiles
[INFO] Scanning for projects...
[WARNING] The POM for org.eclipse.m2e:lifecycle-mapping:jar:1.0.0 is missing, no dependency information available
[WARNING] Failed to retrieve plugin descriptor for org.eclipse.m2e:lifecycle-mapping:1.0.0: Plugin org.eclipse.m2e:lifecycle-mapping:1.0.0 or one of its dependencies could not be resolved: Failure to find org.eclipse.m2e:lifecycle-mapping:jar:1.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced
[INFO] 
[INFO] -----------------< com.example:hello-nablarch-restful >-----------------
[INFO] Building hello-nablarch-restful 0.0.1
[INFO] --------------------------------[ war ]---------------------------------
[WARNING] The POM for org.eclipse.m2e:lifecycle-mapping:jar:1.0.0 is missing, no dependency information available
[WARNING] Failed to retrieve plugin descriptor for org.eclipse.m2e:lifecycle-mapping:1.0.0: Plugin org.eclipse.m2e:lifecycle-mapping:1.0.0 or one of its dependencies could not be resolved: Failure to find org.eclipse.m2e:lifecycle-mapping:jar:1.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced
[INFO] 
[INFO] --- maven-help-plugin:3.2.0:all-profiles (default-cli) @ hello-nablarch-restful ---
[INFO] Listing Profiles for Project: com.example:hello-nablarch-restful:war:0.0.1
  Profile Id: BACKUP (Active: false , Source: pom)
  Profile Id: gsp (Active: false , Source: pom)
  Profile Id: ossrh (Active: false , Source: pom)

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.106 s
[INFO] Finished at: 2020-11-11T11:06:10+09:00
[INFO] ------------------------------------------------------------------------

Archetypes for containers don't seem to have the concept of profiles for the environment.

Container production environment settings

It seems that the value written in env.config is set to be overwritten by the environment variable.

When creating a project, it looks like this.

$ grep -vE '^$|^#' src/main/resources/env.config 
nablarch.db.jdbcDriver=org.h2.Driver
nablarch.db.url=jdbc:h2:./h2/db/SAMPLE
nablarch.db.user=SAMPLE
nablarch.db.password=SAMPLE
nablarch.db.schema=PUBLIC
nablarch.db.maxPoolSize=5
nablarch.db.minimumIdle=5
nablarch.db.connectionTimeout=30000
nablarch.db.idleTimeout=600000
nablarch.db.maxLifetime=1800000
nablarch.db.validationTimeout=5000
nablarch.codeCache.loadOnStartUp=false

There seems to be a sample Action class as well

src/main/java/com/example/SampleAction.java

package com.example;

import com.example.dto.SampleUserListDto;
import com.example.entity.SampleUser;
import nablarch.common.dao.EntityList;
import nablarch.common.dao.UniversalDao;
import nablarch.fw.web.HttpRequest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 *Action class for communication confirmation.
 *
 * @deprecated TODO This is a class for checking communication. Please delete it after the confirmation is completed.
 */
@Path("/find")
public class SampleAction {

    /**
     *Search process.
     * <p>
     *Use JSON for the response.
     * </p>
     *
     * @param req HTTP request
     * @return user information(JSON)
     */
    @GET
    @Path("/json")
    @Produces(MediaType.APPLICATION_JSON)
    public EntityList<SampleUser> findProducesJson(HttpRequest req) {
        return UniversalDao.findAll(SampleUser.class);
    }

    /**
     *Search process.
     * <p>
     *Use XML for the response.
     * </p>
     *
     * @param req HTTP request
     * @return user information(XML)
     */
    @GET
    @Path("/xml")
    @Produces(MediaType.APPLICATION_XML)
    public SampleUserListDto findProducesXml(HttpRequest req) {
        EntityList<SampleUser> sampleUserList = UniversalDao.findAll(SampleUser.class);
        return new SampleUserListDto(sampleUserList);
    }

}

For the time being, let's follow the method of communication confirmation.

Communication confirmation

First, create a Docker image.

$ mvn package jib:dockerBuild -DskipTests=true

I was able to do it.

$ docker image ls | grep hello
hello-nablarch-restful             0.0.1               a4b772a979de        50 years ago        448MB
hello-nablarch-restful             latest              a4b772a979de        50 years ago        448MB

The default base image is that of Tomcat.

Create a container image

I will start it.

$ docker container run -it --rm -p 8080:8080 -v `pwd`/h2:/usr/local/tomcat/h2 hello-nablarch-restful:0.0.1

Confirmation. It seems OK for the time being.

$ curl localhost:8080/find/json
[{"userId":1,"kanjiName":"Rakutaro Nabe","kanaName":"Naburakutaro"},{"userId":2,"kanjiName":"Rakujiro Nabe","kanaName":"Naburakujiro"}]

By the way, if you forget to specify Volume in this procedure, the database will disappear and you will fail (I was addicted to overlooking it).

-v `pwd`/h2:/usr/local/tomcat/h2

Run container image

XML-Less DI Configuration

Next, let's use DI lightly. It seems that DI using annotations can be done, so I will use this.

Build an object of annotated class

It seems to create a subclass of the AnnotationComponentDefinitionLoader class.

What's the place to put it? I thought, so refer to the following.

Service Development Reference for SPA + REST API Configuration

https://github.com/Fintan-contents/example-chat/blob/master/backend/src/main/java/com/example/system/nablarch/di/ExampleChatComponentDefinitionLoader.java

Well, as a result I kept it simple.

src/main/java/com/example/system/HelloRestComponentDefinitionLoader.java

package com.example.system;

import nablarch.core.repository.di.config.externalize.AnnotationComponentDefinitionLoader;

public class HelloRestComponentDefinitionLoader extends AnnotationComponentDefinitionLoader {
    @Override
    protected String getBasePackage() {
        return "com.example";
    }
}

The getBasePackage method returns the package to be scanned.

I'm incorporating this into the ServiceLoader gimmick, but it seems that the target file already exists.

src/main/resources/META-INF/services/nablarch.core.repository.di.config.externalize.ExternalizedComponentDefinitionLoader

nablarch.core.repository.di.config.externalize.OsEnvironmentVariableExternalizedLoader
nablarch.core.repository.di.config.externalize.SystemPropertyExternalizedLoader

Added here.

nablarch.core.repository.di.config.externalize.OsEnvironmentVariableExternalizedLoader
nablarch.core.repository.di.config.externalize.SystemPropertyExternalizedLoader
com.example.system.HelloRestComponentDefinitionLoader

Create a class that was in the sample and transferred the data acquisition process. Add the @SystemRepositoryComponent annotation.

src/main/java/com/example/service/UserService.java

package com.example.service;

import com.example.entity.SampleUser;
import nablarch.common.dao.EntityList;
import nablarch.common.dao.UniversalDao;
import nablarch.core.repository.di.config.externalize.annotation.SystemRepositoryComponent;

@SystemRepositoryComponent
public class UserService {
    public EntityList<SampleUser> findUsers() {
        return UniversalDao.findAll(SampleUser.class);
    }
}

So, create an Action class that uses this.

src/main/java/com/example/HelloAction.java

package com.example;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.example.entity.SampleUser;
import com.example.service.UserService;
import nablarch.common.dao.EntityList;
import nablarch.core.repository.di.config.externalize.annotation.SystemRepositoryComponent;

@SystemRepositoryComponent
@Path("/hello")
public class HelloAction {
    UserService userService;

    public HelloAction(UserService userService) {
        this.userService = userService;
    }

    @GET
    @Path("/json")
    @Produces(MediaType.APPLICATION_JSON)
    public EntityList<SampleUser> users() {
        return userService.findUsers();
    }
}

In the constructor, let's receive the component.

    public HelloAction(UserService userService) {
        this.userService = userService;
    }

It seems that additional settings are required to manage the Action class in a DI container.

Manage Action class with DI container

This is an excerpt.

src/main/resources/rest-component-configuration.xml

  <!--Package mapping settings-->
  <component name="packageMapping" class="nablarch.integration.router.PathOptionsProviderRoutesMapping">
    <property name="pathOptionsProvider">
      <component class="nablarch.integration.router.jaxrs.JaxRsPathOptionsProvider">
        <property name="applicationPath" value="${nablarch.webApi.applicationPath}"/>
        <property name="basePackage" value="${nablarch.commonProperty.basePackage}"/>
      </component>
    </property>
    <property name="methodBinderFactory">
      <component class="nablarch.fw.jaxrs.JaxRsMethodBinderFactory">
        <property name="handlerList">
          <component class="nablarch.integration.jaxrs.jersey.JerseyJaxRsHandlerListFactory"/>
        </property>
      </component>
    </property>
  </component>

Add SystemRepositoryDelegateFactory as delegateFactory.

  <!--Package mapping settings-->
  <component name="packageMapping" class="nablarch.integration.router.PathOptionsProviderRoutesMapping">
    <property name="pathOptionsProvider">
      <component class="nablarch.integration.router.jaxrs.JaxRsPathOptionsProvider">
        <property name="applicationPath" value="${nablarch.webApi.applicationPath}"/>
        <property name="basePackage" value="${nablarch.commonProperty.basePackage}"/>
      </component>
    </property>
    <property name="methodBinderFactory">
      <component class="nablarch.fw.jaxrs.JaxRsMethodBinderFactory">
        <property name="handlerList">
          <component class="nablarch.integration.jaxrs.jersey.JerseyJaxRsHandlerListFactory"/>
        </property>
      </component>
    </property>
    <property name="delegateFactory">
        <component class="nablarch.fw.handler.SystemRepositoryDelegateFactory"/>
    </property>
  </component>

After doing so, build the Docker image again and start it.

$ mvn package jib:dockerBuild -DskipTests=true
$ docker container run -it --rm -p 8080:8080 -v `pwd`/h2:/usr/local/tomcat/h2 hello-nablarch-restful:0.0.1 

Confirmation.

$ curl localhost:8080/hello/json
[{"userId":1,"kanjiName":"Rakutaro Nabe","kanaName":"Naburakutaro"},{"userId":2,"kanjiName":"Rakujiro Nabe","kanaName":"Naburakujiro"}]

It's OK.

Change database to PostgreSQL

Finally, let's change the database used from H2 to PostgreSQL.

Procedure for changing RDBMS to be used

The user and schema used for connection must be created in the RDBMS.

Because it means that.

PostgreSQL decides to use the Docker image.

postgres

Even if it is 12.2 or higher, it will be okay ...

Operating environment

Looking at the structure of the project, it seems that it contains SQL files

Maven Archetype Configuration (https://nablarch.github.io/docs/5u18/doc/application_framework/application_framework/blank_project/MavenModuleStructures/index.html)

Convert to UTF-8 (written in Shift_JIS ...).

$ iconv -f sjis -t utf-8 db/ddl/postgresql/create.sql -o db/ddl/postgresql/create.sql.utf8
$ iconv -f sjis -t utf-8 db/data/postgresql/data.sql -o db/data/postgresql/data.sql.utf8

Start PostgreSQL.

$ docker container run -it --rm --name postgres \
  -v `pwd`/db:/data/db \
  -e POSTGRES_USER=postgres_user \
  -e POSTGRES_PASSWORD=postgres_password \
  -e POSTGRES_DB=mydatabase \
  postgres:13.0

Enter the container, execute DDL and register data.

$ docker container exec -it postgres bash
$ psql -U postgres_user -f /data/db/ddl/postgresql/create.sql.utf8 mydatabase
$ psql -U postgres_user -f /data/db/data/postgresql/data.sql.utf8 mydatabase

This time, let's treat it like another host without binding it to the port on the host side. Check the IP address of the container.

$ docker container inspect postgres | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

Replaced JDBC driver from H2 to PostgreSQL

    <!-- TODO:Modify the JDBC driver according to the DB product used in the project.-->
    <!--
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.3.176</version>
      <scope>runtime</scope>
    </dependency>
    -->
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <version>42.2.18</version>
      <scope>runtime</scope>
    </dependency>

Fixed connection settings. You can overwrite it with an environment variable, but this time.

src/main/resources/env.config

$ grep nablarch.db src/main/resources/env.config 
#nablarch.db.jdbcDriver=org.h2.Driver
nablarch.db.jdbcDriver=org.postgresql.Driver

#nablarch.db.url=jdbc:h2:./h2/db/SAMPLE
nablarch.db.url=jdbc:postgresql://172.17.0.2:5432/mydatabase

#nablarch.db.user=SAMPLE
nablarch.db.user=postgres_user

#nablarch.db.password=SAMPLE
nablarch.db.password=postgres_password

#nablarch.db.schema=PUBLIC
nablarch.db.schema=public

Creating a container image.

$ mvn clean
$ mvn package jib:dockerBuild -DskipTests=true

Start-up.

$ docker container run -it --rm -p 8080:8080 hello-nablarch-restful:0.0.1

Confirmation.

$ curl localhost:8080/hello/json
][{"userId":1,"kanjiName":"Rakutaro Nabe","kanaName":"Naburakutaro"},{"userId":2,"kanjiName":"Rakujiro Nabe","kanaName":"Naburakujiro"}]

I was able to do it.

Recommended Posts

Try RESTful web services with Nablarch's container archetype
Try with resources statement in web app
Try using another Servlet container Jetty with Docker
Try using DI container with Laravel and Spring Boot