Eclipse GlassFish と IntelliJ IDEAで Webアプリケーションの開発環境を構築するメモ

# 概要

Eclipse GlassFish 5.1.0を利用したJakarta EE 8 Webアプリケーションの開発環境の構築手順をまとめたメモです。
IDEにはIntelliJ IDEAを使用します。

**環境**

* Windows 10 Professional 1909
* GlassFish 5.1.0
* Oracle JDK 1.8.0_231
* IntelliJ IDEA 2019.3

**参考**

* [Eclipse EE4J] (https://projects.eclipse.org/projects/ee4j)
* [Jakarta® EE] (https://jakarta.ee/)
* [Eclipse GlassFish] (https://projects.eclipse.org/projects/ee4j.glassfish)
* [Java EE 8 の新機能概要のご紹介] (https://yoshio3.com/2014/12/19/java-ee-8-new-features/)

# 用語のおさらい

**Java EE**

> [JavaEE の概要] (https://www.oracle.com/technetwork/jp/java/javaee/overview/index.html)
>
> Java Platform, Enterprise Edition（Java EE）はコミュニティ主導のエンタープライズ・ソフトウェアの標準です。Java EEはJava Community Processを使って開発されており、業界の専門家、営利団体とオープンソース団体、Javaユーザー・グループ、さらに数え切れないほど多くの個人が貢献しています。

**EE4J**

> Eclipse Enterprise for Java (EE4J) is an open source initiative to create standard APIs, implementations of those APIs, and technology compatibility kits for Java runtimes that enable development, deployment, and management of server-side and cloud-native applications.
>\------------------------------------------------------------------------------------------------
> Eclipse Enterprise for Java（EE4J）は、標準API、それらのAPIの実装、およびサーバー側およびクラウドネイティブアプリケーションの開発、展開、管理を可能にするJavaランタイム用の技術互換性キットを作成するためのオープンソースイニシアチブです。

[EE4J (Eclipse Enterprise for Java)] (https://projects.eclipse.org/projects/ee4j)は、Eclipse Foundationのトップレベルプロジェクトで、プロジェクト管理委員会（PMC）がJakarta EEの仕様、API、TCK、およびリファレンス実装の開発・管理を行います。

**Jakarta EE**

Jakarta EEは、[Jakarta EE Working Group] (https://www.eclipse.org/org/workinggroups/jakarta_ee_charter.php) (2018年4月設立)が管理するプラットフォームです。
Java EEがOracleからEclipse Foundationへ移管されることが決まったのが2017年9月、それから約2年後の2019年9月に最初のリリースである[Jakarta EE 8] (https://projects.eclipse.org/jakartaee/releases/8)がリリースされました（機能的にはJava EE 8と同じです）。

2018年2月26日にJava EEに代わる名称がJakarta EEに決まったと[アナウンス] (https://eclipse-foundation.blog/2018/02/26/and-the-name-is/)がありましたが、そのときに下記の名称も新しくなると告知されました。

* Java EE → Jakarta EE
* Glassfish → Eclipse Glassfish
* Java Community Process (JCP) → Jakarta EE Working Group (Jakarta EE)
* Oracle development management → EE4J, PMC

**Eclipse GlassFish**

[Eclipse GlassFish] (https://projects.eclipse.org/projects/ee4j.glassfish) 5.1.0は、Java EE 8互換と認定されたアプリケーションサーバで、2019年1月にリリースされました。
このバージョンは、GlassFishがOracleからEclipse Foundationへ移管されてからリリースされた最初のバージョンです（ただしAPIや実装は、[Oracle GlassFish Open source Edition 5.0.1] (https://javaee.github.io/glassfish/)と基本的に変わりません）。
次のバージョン 5.2.0は、Jakarta EE 8互換と認定されるということです。

なお、Jakarta EE 8互換と認定されたアプリケーションサーバに[Payara® Server] (https://www.payara.fish/software/payara-server/)もあります。

# Eclipse GlassFishのインストールと起動

ここからGlassFishサーバのインストールから起動までの一連の手順を説明します。

※この記事では特に断りのない場合『Eclipse GlassFish』を『GlassFish』と記載します。

## ダウンロード

2020年1月現在、Full Profile (Full Platform)とそのサブセットであるWeb Profileが[ダウンロードページ](https://projects.eclipse.org/projects/ee4j.glassfish/downloads)よりダウンロードできます。
この2つのProfileの違いは[Java EE Web Profile vs Java EE Full Platform] (https://stackoverflow.com/questions/24239978/java-ee-web-profile-vs-java-ee-full-platform)と、[Java™ EE 8 のテクノロジー] (https://www.oracle.com/technetwork/jp/java/javaee/tech/index.html)、[Jakarta EE Web Profile 8] (https://jakarta.ee/specifications/webprofile/8/)などで確認できます。

この記事ではWeb Profile版を使用します。

**補足:Web Profileのコンポーネント**

|Component                            |Version|JSR     |Compatible Implementations |
|:------------------------------------|:-----:|:-------|:---|
|[Servlet] (https://jakarta.ee/specifications/servlet/4.0/) |4.0    |JSR-369 |Eclipse GlassFish 5.1|
|[Server Pages (JSP)] (https://jakarta.ee/specifications/pages/2.3/) |2.3    |JSR-245 |Eclipse GlassFish 5.1|
|[Expression Language (EL)] (https://jakarta.ee/specifications/expression-language/3.0/) |3.0    |JSR-341 |EE4J Implementation of Expression Language 3.0.3|
|[Debugging Support for Other Languages] (https://jakarta.ee/specifications/debugging/1.0/)|1.0    |JSR-45  |Eclipse GlassFish 5.1|
|[Standard Tag Library (JSTL)] (https://jakarta.ee/specifications/tags/1.2/) |1.2    |JSR-52  |Eclipse GlassFish 5.1|
|[Server Faces (JSF)] (https://jakarta.ee/specifications/faces/2.3/) |2.3    |JSR-372 |Mojarra 2.3.13|
|[RESTful Web Services (JAX-RS)] (https://jakarta.ee/specifications/restful-ws/2.1/) |2.1    |JSR-370 |Eclipse Jersey 2.28|
|[WebSocket] (https://jakarta.ee/specifications/websocket/1.1/) |1.1    |JSR-356 |Eclipse GlassFish 5.1|
|[JSON Processing (JSON-P)] (https://jakarta.ee/specifications/jsonp/1.1/) |1.1    |JSR-374 |Eclipse JSON Processing 1.1.5|
|[JSON Binding (JSON-B)] (https://jakarta.ee/specifications/jsonb/1.0/) |1.0    |JSR-367 |Eclipse Yasson 1.0.3|
|[Annotations] (https://jakarta.ee/specifications/annotations/1.3/) |1.3    |JSR-250 |Jakarta Annotations|
|[Enterprise Beans (EJB)] (https://jakarta.ee/specifications/enterprise-beans/3.2/) |3.2 Lite|JSR-345 |Eclipse Glassfish 5.1|
|[Transactions (JTA)] (https://jakarta.ee/specifications/transactions/1.3/) |1.3    |JSR-907 |Eclipse GlassFish 5.1.0|
|[Persistence (JPA)] (https://jakarta.ee/specifications/persistence/2.2/) |2.2    |JSR-338 |EclipseLink 2.7.4|
|[Bean Validation] (https://jakarta.ee/specifications/bean-validation/2.0/) |2.0    |JSR-380 |Hibernate Validator 6.0.17|
|[Managed Beans] (https://jakarta.ee/specifications/managedbeans/1.0/) |1.0    |        |Eclipse Glassfish 5.1 |
|[Interceptors] (https://jakarta.ee/specifications/interceptors/1.2/) |1.2    |JSR-318 |Eclipse GlassFish 5.1|
|[Contexts and Dependency Injection] (https://jakarta.ee/specifications/cdi/2.0/) |2.0    |JSR-365 |Weld 3.1.1.Final|
|[Dependency Injection] (https://jakarta.ee/specifications/dependency-injection/1.0/) |1.0    |JSR-330 |Weld 2.3.8.Final/Weld 3.1.1.Final|
|[Security] (https://jakarta.ee/specifications/security/1.0/) |1.0    |JSR-375 |Eclipse Soteria 1.0.1|
|[Authentication] (https://jakarta.ee/specifications/authentication/1.1/) |1.1    |JSR-196 |Eclipse GlassFish 5.1|

## インストール

インストールはダウンロードしたアーカイブファイルを展開するだけです。
この記事ではインストールディレクトリを下記にしました。以降はこのディレクトリを`%GLASSFISH_HOME%`と表記します。

```text:%GLASSFISH_HOME%
D:\dev\glassfish-web-5.1.0
```

環境変数pathにbinディレクトリを追加します。

```
%GLASSFISH_HOME%\bin
```

**JDKについて**

現時点ではJDK 9以降のバージョンでは正常に動作しないためJDK 1.8を利用します。なお、環境変数JAVA_HOMEがJDK 1.8を指していれば下記の対応は不要です。

`%GLASSFISH_HOME%\glassfish\config\asenv.bat`の最後に下記を追加します。

```
set AS_JAVA=C:\Program Files\Java\jdk1.8.0_231
```

## 起動と停止

起動と停止は`%GLASSFISH_HOME%\bin`ディレクトリにあるasadminコマンド (asadmin.bat)で行います。

**起動**

起動時に\<ドメイン名\>を指定しますが、作成されているドメインが1つだけの場合はドメイン名を省略できます。
インストール直後はデフォルトで`domain1`というドメインしか登録されていないので省略できます（ドメインについては後述します）。

```
> asadmin start-domain <ドメイン名>
```

下記はサーバ起動時のログです。起動に成功すると管理コンソールとサンプルのアプリケーションにアクセスできるようになります。`Admin Port: 4848`と表示されているように管理コンソールはポート4848でアクセスできます。

```
> asadmin start-domain
Waiting for domain1 to start ...............................
Successfully started the domain : domain1
domain  Location: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain1
Log File: D:\dev\glassfish-5.1.0\glassfish\domains\domain1\logs\server.log
Admin Port: 4848
Command start-domain executed successfully.
```

**管理コンソール**

GlassFishサーバの管理タスクを実行するブラウザベースのツールです。（管理コンソールの使い方はその都度説明します。）

```
http://localhost:4848
```

Web Profile版
![gg2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/60956fda-56bf-ca73-ad73-c6dc1f658736.png)
参考: Full Profile版 （管理できるリソースの種類が増えています）
![gg0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/dcaacdf1-ed83-af05-1162-47d5c2774d7c.png)

**サンプルのアプリケーション**

以下のURLにアクセスするとサンプルアプリケーション(Welcomeページのようなもの）にアクセスできます。
このアプリケーションは8080(HTTP)、8081(HTTPS)の2つのポートをリッスンしています。

```
http://localhost:8080
```

![gg1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/3d4be0c2-66e1-3004-3f3a-61a25cb4e74a.png)

**停止**

停止、再起動もドメイン名の指定は起動時と同じルールです。

```
> asadmin stop-domain <ドメイン名>
```

**再起動**

```
> asadmin restart-domain <ドメイン名>
```

以上がインストールから起動までの説明になります。


# 管理ツール

前述の『起動と停止』の項目で触れた通りGlassFishサーバの管理タスクを実行するツールに、CLIのasadminコマンドとGUIの管理コンソールがあります。どちらもほぼ同じ管理タスクを実行できます。

## asadminコマンド

```text:SYNOPSIS
> asadmin [asadmin options] [subcommand [options] [operands]] 
```

**asadmin options**

|long          |short  |default     |
|:-------------|:------|:-----------|
|--host        |-H     |localhost   |
|--port        |-p     |4848        |
|--user        |-u     |            |
|--passwordfile|-W     |            |
|--terse       |-t     |false       |
|--secure      |-s     |false       |
|--echo        |-e     |false       |
|--interactive |-I     |run from a console window = true / run without a console window = false |
|--detach      |       |false       |
|--help        |-?     |            |

### サブコマンド

`start-domain`や`stop-domain`などをサブコマンドと呼びます。サブコマンドの使い方は`--help`オプションで確認できます。
たとえば`start-domain`サブコマンドの使い方は下記の方法で調べられます。

```
> asadmin start-domain --help
```

#### ローカル／リモート

サブコマンドにはローカルサブコマンドとリモートサブコマンドがあります。その違いは以下に引用した通りです。

**ローカルサブコマンド**

>  A local subcommand can be run without a running domain administration server (DAS).
>  However, to run the subcommand and have access to the installation directory and the domain directory, the user must be logged in to the machine that hosts the domain.
> \---------------------------------------------------------------------------------------------
> ローカルサブコマンドは、実行中のドメイン管理サーバー（DAS）なしで実行できます。
> ただし、サブコマンドを実行してインストールディレクトリとドメインディレクトリにアクセスするには、ドメインをホストするマシンにログインする必要があります。

**リモートサブコマンド**

> A remote subcommand is always run by connecting to a DAS and running the subcommand there.
> A running DAS is required.
> \--------------------------------------------------------------------------------------------- 
> リモートサブコマンドは、DASに接続し、そこでサブコマンドを実行することにより常に実行されます。
> 実行中のDASが必要です。

### マルチコマンドモード

asadminコマンドにはマルチコマンドモードもあります。サブコマンドを指定せずにasadminとだけ実行するとマルチコマンドモードに入ります。

```
> asadmin
Use "exit" to exit and "help" for online help.
asadmin>
```

## 管理者ユーザのパスワードを設定

デフォルトの管理者ユーザは`admin`です。パスワードが設定されていないので管理コンソールには誰でもアクセスできますが、パスワードを設定することでログイン認証を行うことができます。

### 管理コンソールで設定

`Configurations` → `server-config` → `Security` → `Realms` → `admin-realm` → `Manage Users`をクリックします。
![au1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/e3ca6e90-56f8-90d4-5a4e-db2421596a71.png)

User IDの`admin`をクリックします。
![au2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/28485c2c-0a34-c118-9f7d-36b102ae6ed6.png)

新しいパスワードを入力して`SAVE`をクリックします。
![au3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/c62ad9fc-52c7-1707-5cca-bc7bdf3f7c85.png)

### asadminコマンドで設定

```text:change-admin-password
> asadmin change-admin-password
Enter admin user name [default: admin]> (enter)
Enter the admin password> (enter)
Enter the new admin password> ********* (new password)
Enter the new admin password again> ********* (new password)
Command change-admin-password executed successfully.
```

パスワードを設定すると未ログインで管理コンソールへアクセスするとログイン画面が表示されます。
![a1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/dc7f3c28-fe51-b52a-8942-62b8da0d8048.png)

またasadminコマンドの実行時にも同様にログインが求められます。

```
> asadmin list-commands
Enter admin user name>  admin
Enter admin password for user "admin"> **********
```

一度`login`サブコマンドでログインを行えば、以降はログインは不要になります。

```
> asadmin login
Enter admin user name [Enter to accept default]> admin
Enter admin password> **********
Login information relevant to admin user name [admin] for host [localhost] and admin port [4848] stored at [C:\Users\USERNAME\.gfclient\pass] successfully.
Make sure that this file remains protected. Information stored in this file will be used by administration commands to manage associated domain.
Command login executed successfully.
```

# ドメイン

## ドメインとは

GlassFishサーバインスタンスやそのコンフィグレーション、各種リソース、ライブラリ、アプリケーションのデプロイ領域のまとまりをドメインと呼びます。
インストールした1つのGlassFishサーバで複数のドメインを管理することができます。ドメインは互いに独立しているので、あるドメインの変更が他のドメインに影響を与えることはありません。

なお、README.txtに記載されている通りGlassFishサーバのインストール直後はデフォルトで`domain1`というドメインが作成されています。

> The default domain called 'domain1' is installed and preconfigured.
> \--------------------------------------------------------------------------
> 「domain1」というデフォルトのドメインがインストールされ、事前設定されています。

### ドメインの作成

`create-domain`サブコマンドで新しいドメインを作成できます。この例では`domain2`というドメインを作成します。
なお、開発用途であれば新しくドメインを作成する必要はありません。

```bat
> asadmin create-domain --adminport 4849 domain2
Enter admin user name [Enter to accept default "admin" / no password]>
Using port 4849 for Admin.
Default port 8080 for HTTP Instance is in use. Using 51549
Using default port 7676 for JMS.
Default port 3700 for IIOP is in use. Using 51550
Default port 8181 for HTTP_SSL is in use. Using 51551
Using default port 3820 for IIOP_SSL.
Using default port 3920 for IIOP_MUTUALAUTH.
Default port 8686 for JMX_ADMIN is in use. Using 51552
Using default port 6666 for OSGI_SHELL.
Using default port 9009 for JAVA_DEBUGGER.
Distinguished Name of the self-signed X.509 Server Certificate is:
[CN=home-dev-pc.mshome.net,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
Distinguished Name of the self-signed X.509 Server Certificate is:
[CN=home-dev-pc.mshome.net-instance,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US]
Domain domain2 created.
Domain domain2 admin port is 4849.
Domain domain2 allows admin login as user "admin" with no password.
Command create-domain executed successfully.
```

ドメインの一覧は`list-domains`サブコマンドで確認できます。この通り作成直後のドメインは起動していません。

```bat
> asadmin list-domains
domain1 running
domain2 not running
Command list-domains executed successfully.
```

ドメイン名を指定して`domain2`を起動します。

```bat
> asadmin start-domain domain2
Waiting for domain2 to start .......................
Successfully started the domain : domain2
domain  Location: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain2
Log File: D:\dev\glassfish-web-5.1.0\glassfish\domains\domain2\logs\server.log
Admin Port: 4849
Command start-domain executed successfully.
```

jpsで確認するとドメイン毎にJVMプロセスが起動していることがわかります。

```
> jps -l
17056 jdk.jcmd/sun.tools.jps.Jps
15112 com.sun.enterprise.glassfish.bootstrap.ASMain
5208 com.sun.enterprise.glassfish.bootstrap.ASMain
```

## ドメイン管理サーバ (DAS)

1つのドメインにデフォルトで`server`という名前のGlassFishサーバインスタンスが作成・実行されています。このサーバインスタンスでドメインの管理を行うので、ドメイン管理サーバ (DAS / Domain Administration Server)と呼びます。
下図はサーバインスタンスの情報を確認するページです。このページ上部のボタンでインスタンスの停止や再起動、ログの確認が行えます。
![das.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/e962dac5-25f3-d1b5-73b5-04518ce4dd55.png)
下線を引いたConfiguration:の`server-config`とは、このサーバインスタンスのコンフィグレーションで、下図のような設定項目があります。ちなみに`default-config`という名前のConfigurationはデフォルト設定値（テンプレート）で、新しくConfigurationを作成するときのコピー元として使えます。
![sc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/42e19803-cfc0-82a4-73c7-f1a2e24515a9.png)

## 仮想サーバ

> A virtual server, sometimes called a virtual host, is an object that allows the same physical server to host multiple Internet domain names. 
>\----------------------------------------------------------------------------
> 仮想サーバーは、仮想ホストとも呼ばれ、同じ物理サーバーが複数のインターネットドメイン名をホストできるようにするオブジェクトです。

GlassFishサーバインスタンス内に、さらに仮想サーバを作成することができます。
デフォルトでは`__asadmin`と`server`という名前の仮想サーバが作成されていて、管理コンソールは`__asadmin`で実行されています。
`server`は開発環境でアプリケーションの開発、テストで必要となる仮想サーバです。
![vs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/3bcfc8a7-230a-b6f9-02d0-eb4a48f5ad33.png)

### 仮想サーバの作成

`Configurations` → `server-config` → `Virtual Servers` → `New...`をクリックします。
![vs0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/9b86d9b1-3c75-6aeb-b78d-c4736c11c29a.png)
Idに`server2`と入力し、その他の項目はこのままで`OK`をクリックして登録します。
![vs1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/7ab3504a-e5ae-7af5-cdb3-539bed04c765.png)
下図は登録後です。
![vs2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/4a25be9b-177d-e4de-923b-3a416445fca8.png)
次にNetwork Listenerを追加します。
`Configurations` → `server-config` → `Network Config` → `Network Listeners` → `New...`をクリックします。
![nl0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/8b628a83-df0e-b9d0-eea9-24969fa961ae.png)
NameとProtocolに`http-listener-3`と入力、Default Virtual Serverに先ほど登録した`server2`を選択します。
Port、Address、Thread Poolは図の通りとして`OK`をクリックして登録します。
![nl1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/625d1a3f-3b1a-799d-289c-e30e19eaf044.png)
下図は登録後です。
![nl2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/4c5453c1-a89f-7d21-c7f0-f3b2273840ed.png)
登録した仮想サーバ`server2`の編集画面を開き、下線を引いた項目を編集して更新します。
![vs3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/158bf797-552f-9d39-dbc8-c9310b81835f.png)

ドキュメントルート(Docroot)に指定した`D:\var\www\server2`にindex.htmlを作成し、下記のアドレスにアクセスすると作成したページが表示されます。

```
http://localhost:8082
```

`list-virtual-servers`サブコマンドで仮想サーバの一覧を取得することができます。

```
> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.
```

デフォルトではポート4848のドメインの結果を返すので、他のドメインの仮想サーバを調べたいときは下記のように`--port`オプションでポートを指定します。

```
> asadmin --port 4849 list-virtual-servers
server
__asadmin
Command list-virtual-servers executed successfully.
```

## 現時点での状態

この時点でのドメインと仮想サーバは下記の通りです。

**ドメイン**

```
> asadmin list-domains -l
DOMAIN   ADMIN_HOST  ADMIN_PORT  RUNNING  RESTART_REQUIRED
domain1  localhost   4848        true     false
domain2  localhost   4849        true     false
Command list-domains executed successfully.
```

**仮想サーバ**

```
> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.

> asadmin --port 4849 list-virtual-servers
server
__asadmin
Command list-virtual-servers executed successfully.
```

## リソースの管理

GlassFishサーバが管理するリソースにJDBC Resourceがあります。

### JDBC Resourceの登録

JDBC Resourceを登録するには、Database Vendorが提供するJDBC Driverの登録と、JDBC Connection Poolの登録を行う必要があります。
以下は、MySQL Database用のJDBC Resourceの登録手順になります。

#### MySQL Connector/Jのダウンロードと配置

[MySQL Community Downloads] (https://dev.mysql.com/downloads/connector/j/)よりMySQL Connector/Jのアーカイブファイルをダウンロードします。2020年1月現在の最新バージョン8.0.18をダウンロードし、適当なディレクトリへ展開しておきます。
jarファイルを登録するには、エクスプローラなどで直接コピーする方法と`add-library`サブコマンドを使う方法があります。

直接コピーする場合は、サーバーを停止した状態で`mysql-connector-java-8.0.18.jar`を`%GLASSFISH_HOME%\glassfish\domains\domain1\lib`にコピーします。
（なお、`%GLASSFISH_HOME%\glassfish\lib`にコピーすると全ドメインで有効になります。）

`add-library`サブコマンドを使う場合はサーバーを停止する必要はありませんが、再起動が必要です。

```
> asadmin add-library .\mysql-connector-java-8.0.18.jar
Command add-library executed successfully.
```

`list-libraries`サブコマンドで登録したライブラリを確認できます。

```
> asadmin list-libraries
mysql-connector-java-8.0.18.jar
Command list-libraries executed successfully.
```

#### Databaseの準備

データベースにはMySQL 8.0.17を利用します。動作検証用に下記のデータベース、ユーザ、テーブルを作成します。

```sql
CREATE DATABASE IF NOT EXISTS test_db
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_general_ci;

CREATE USER IF NOT EXISTS 'test_user'@'localhost'
  IDENTIFIED BY 'test_user'
  PASSWORD EXPIRE NEVER;

GRANT ALL ON test_db.* TO 'test_user'@'localhost';
```

```sql
CREATE TABLE IF NOT EXISTS memo (
  id BIGINT AUTO_INCREMENT,
  title VARCHAR(255) NOT NULL,
  description TEXT NOT NULL,
  done BOOLEAN DEFAULT FALSE NOT NULL,
  updated TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
  PRIMARY KEY (id)
) CHARACTER SET = utf8mb4, COLLATE utf8mb4_general_ci;
```

#### JDBC Connection Poolsの登録

`Resources` → `JDBC` → `JDBC Connection Pools` → `New...`をクリックします。
![jp1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/71ef230c-b878-549b-cbfc-6a444240b4e4.png)
Pool Nameに`MySQLConnPool`と入力、Resource TypeとDatabase Driver Vendorは図の通り選択します。
この例ではResource Typeに`javax.sql.DataSource`を選択しましたが、`java.sql.Driver`を選択することもできます。
![jp2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/3484d07c-9553-e63a-d068-440dd16c4bb7.png)
Additional Propertiesは図の通り入力、`Finish`をクリックして登録します。なお黄色の下線のDatasource classnameはこの時点では変えられないので登録後に編集します。
![jp3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/106471da-be48-b964-1c0e-8a69f77bbf4f.png)

|Name                    |Value                               |
|:-----------------------|:-----------------------------------|
|user                    |test_user                           |
|password                |test_user                           |
|databaseName            |test_db                             |
|serverName              |localhost                           |
|portNumber              |3306                                |
|sslMode                 |disabled                            |
|allowPublicKeyRetrieval |true                                |
|serverTimezone          |Asia/Tokyo                          |

下図は登録後です。
![jp4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/f46dbf4b-74e5-cf61-5417-258103ed68b8.png)
編集画面を開きDatasource Classnameに`com.mysql.cj.jdbc.MysqlDataSource`と入力します。利用するJDBC Driverがのバージョンが8.0以降の場合はこのクラス名を指定する必要があるためです。
![jp5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/a94506d2-2f25-0e9f-e550-ecd69046a0a5.png)
次にAdvancedタブを開き図のConnection validationの`Required`にチェックを入れ、Table Nameに`dual`と入力して`Save`をクリックして登録します。
![jp6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/733a875d-a1d3-0e3b-ca0e-00a2423898c8.png)
Generalタブを開き図の`Ping`をクリックして通信できるか確認します。`Ping Succeeded`と表示されれば成功です。
![jp7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/95ae0459-5f6b-f043-f124-ab0bf967b70d.png)

#### JDBC Resourceの登録

`Resources` → `JDBC` → `JDBC Resources` → `New...`をクリックします。
![jr1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/39646dca-0d5b-da9d-ff1b-99844cc6ad90.png)
JNDI Nameに`jdbc/MySQLConnPool`と入力、Pool Nameに先ほど登録した`MySQLConnPool`を選択し`OK`をクリックして登録します。
![jr2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/0d9ba4cb-61e1-8753-9369-8764f3646469.png)
下図は登録後です。
![jr3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/1e89afd2-5e18-b440-5e02-98b42a1abcef.png)

#### 補足:Resource Typeにjavax.sql.Driverを選ぶ場合

Pool Nameに`MySQLConnPool2`と入力、Resource TypeとDatabase Driver Vendorは図の通り選択します。
![jp8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/36845973-5eb4-1e1d-a381-f21f901a2aa2.png)
黄色の下線のDriver Classnameはこの時点では変えられないので登録後に編集します。
![jp9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/7aed2a60-5172-ca7b-84ff-80b14db07008.png)
プロパティを更新します。この画面でプロパティを追加しようとするとエラーが起きて正常に登録できないので、図の3項目だけ登録します。残りは登録後に追加します。
![jp10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/9e28b73c-58ff-1c98-e636-a0127efa66f7.png)

|Name                    |Value                               |
|:-----------------------|:-----------------------------------|
|URL                     |jdbc:mysql://localhost:3306/test_db |
|user                    |test_user                           |
|password                |test_user                           |
|sslMode                 |DISABLED                            |
|allowPublicKeyRetrieval |true                                |
|serverTimezone          |Asia/Tokyo                          |

登録後に再度編集画面を開き、Driver Classsnameを`com.mysql.cj.jdbc.Driver`で更新します。
![jp11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/ce1d1710-ca69-7a4c-c88b-73540f391f1c.png)
残りのプロパティを追加します。
![jp12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/893e942b-60de-7cc3-c29f-0669ba7d419d.png)

以上でJDBC Resourceの登録は完了です。


# アプリケーションの開発とデプロイ

IDEにIntelliJ IDEAを利用します。
JetBrainsの公式ヘルプページ[Java EEアプリケーションの開発] (https://pleiades.io/help/idea/creating-and-running-your-first-java-ee-application.html)を参考にしました。

## Application Serverの登録

`＋`アイコンをクリックしてメニューを表示、その中から`Glassfish Server`を選択します。
![ap1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/24ab1939-7c1e-b446-e416-5bc97b77aa20.png)
GlassFishのインストールディレクトリを指定します。
![ap2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/af26c14e-968a-5485-ca2e-47be9d8503d7.png)
`OK`をクリックして登録します。
![ap3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/35b31a78-e3c3-f549-1e8f-47af59514c78.png)

## プロジェクトの作成

左側の選択項目から`Java Enterprise`を選択、右側のProject SDK、Java EE version、Application Serverはそれぞれ図の通り選択します。
![ip00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/d5faf411-f95c-5a3d-0936-3b6680a42e2d.png)
その下のAdditional Library And Frameworks:で、使用するLibraryとFrameworkにチェックを入れます。下図の下線を引いたものを使用しますが、黄色の下線を引いたJSON Processingだけは、この場でチェックを入れずにプロジェクト作成後に別途追加します。
![ip1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/1e4fc097-4fd7-ac88-d89c-dd7cf1763ff5.png)

**Web Application**
![web_application.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/239b71c9-6ebf-6bc6-8f47-e55035e6cf3d.png)

```text
web/WEB-INF/web.xml
```

```text:External_Libraries[GlassFish5.1.0]
jakarta.ejb-api.jar
jakarta.servlet.jsp-api.jar
jakarta.servlet-api.jar
```

**Bean Validation**
![bean_validation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/2599e285-b7c2-5f85-4cfb-92f15d3a66cc.png)

```text:External_Libraries[GlassFish5.1.0_Bean_Validation]
bean-validator.jar
bean-validator-cdi.jar
```

**CDI**
![cdi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/9070d174-93ca-587d-cf8d-fcc7fa7a9523.png)

```text
web/WEB-INF/beans.xml
```

```text:External_Libraries[GlassFish5.1.0_CDI]
cdi-api.jar
cdi-api-fragment.jar
weld-integration.jar
weld-integration-fragment.jar
weld-osgi-bundle.jar
```

**Hiberante**
![hibernate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/831abde5-834b-b44d-1503-789bbd5ac1d2.png)

`Configure...`をクリック、`Downloading Options`で`javax.persistence-api-2.2.jar`のチェックを外します。
![hibernate_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/6f9380c2-2438-3a39-e440-c0ad44541b32.png)
これは`JavaEE Persistence`で`javax.persistence.jar`が組み込まれるためです。

```text:Library
lib/antlr-2.7.7.jar
lib/byte-buddy-1.10.2.jar
lib/classmate-1.5.1.jar
lib/dom4j-2.1.1.jar
lib/FastInfoset-1.2.15.jar
lib/hibernate-commons-annotations-5.1.0.Final.jar
lib/hibernate-core-5.4.9.Final.jar
lib/istack-commons-runtime-3.0.7.jar
lib/jandex-2.1.1.Final.jar
lib/javassist-3.24.0-GA.jar
lib/javax.activation-api-1.2.0.jar
lib/jaxb-api-2.3.1.jar
lib/jaxb-runtime-2.3.1.jar
lib/jboss-logging-3.3.2.Final.jar
lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar
lib/stax-ex-1.8.jar
lib/txw2-2.3.1.jar
```

**JavaEE Persistence**
Providerに`Hibernate`を選択します。
![jpa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/825a1f32-82a8-5c6a-4cbf-53484bc1a553.png)

```text
src/META-INF/persistence.xml
```

```text:Library
lib/javax.persistence.jar
```

**JSON Binding**
![json_b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/cc2fedce-bb06-92f0-fc57-44e5d1e34d03.png)

```text:Library
lib/javax.json.bind-api.jar
```

**JSON Processing**
ここでは組み込まず、プロジェクト作成後に追加します。
![json_p.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/d198360a-2c06-237c-bb50-0e176a160478.png)

## プロジェクト作成後の設定

プロジェクト作成直後のディレクトリです。

```text
<PROJECT_ROOT>
  |
  +--- /lib
  |      |
  |      +--- antlr-2.7.7.jar
  |      +--- byte-buddy-1.10.2.jar
  |      +--- classmate-1.5.1.jar
  |      +--- dom4j-2.1.1.jar
  |      +--- FastInfoset-1.2.15.jar
  |      +--- hibernate-commons-annotations-5.1.0.Final.jar
  |      +--- hibernate-core-5.4.9.Final.jar
  |      +--- istack-commons-runtime-3.0.7.jar
  |      +--- jandex-2.1.1.Final.jar
  |      +--- javassist-3.24.0-GA.jar
  |      +--- javax.activation-api-1.2.0.jar
  |      +--- javax.json.bind-api.jar
  |      +--- javax.persistence.jar
  |      +--- jaxb-api-2.3.1.jar
  |      +--- jaxb-runtime-2.3.1.jar
  |      +--- jboss-logging-3.3.2.Final.jar
  |      +--- jboss-transaction-api_1.2_spec-1.1.1.Final.jar
  |      +--- stax-ex-1.8.jar
  |      +--- txw2-2.3.1.jar
  |
  +--- /src
  |      |
  |      +--- /META-INF
  |             |
  |             +--- persistence.xml
  |
  +--- /web
         |
         +--- /WEB-INF
         |      |
         |      +--- beans.xml
         |      |
         |      +--- web.xml
         |
         +--- index.jsp

<EXTERNAL LIBRARIES>
  |
  +--- GlassFish 5.1.0
  |     |
  |     +--- jakarta.ejb-api.jar
  |     +--- jakarta.servlet.jsp-api.jar
  |     +--- jakarta.servlet-api.jar
  |
  +--- GlassFish 5.1.0 - Bean Validation
  |     |
  |     +--- bean-validator.jar
  |     +--- bean-validator-cdi.jar
  |
  +--- GlassFish 5.1.0 - CDI : Contextes and Dependency Inejction
        |
        +--- cdi-api.jar
        +--- cdi-api-fragment.jar
        +--- weld-integration.jar
        +--- weld-integration-fragment.jar
        +--- weld-osgi-bundle.jar
```

### パッケージの作成

srcディレクトリにcom.example.demoというパッケージを作成、さらにその下にcontroller、dto、ejb、entity、serviceパッケージを作成します。

```text
/src
  |
  +--- /com
         |
         +--- /example
                |
                +--- /demo
                       |
                       +--- /controller
                       |
                       +--- /dto
                       |
                       +--- /ejb
                       |
                       +--- /entity
                       |
                       +--- /service
```

### index.jspの修正とJSTLの追加

下図のようにjspでJSTLを使おうとするとtaglibのuriが解決できないというエラーが表示されます。
![jstl0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/51727639-c71e-8782-9131-50292d6482e8.png)

```html:index.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <div>
        <h1>Welcome</h1>
        <div>
            <ul>
                <li><a href="${pageContext.request.contextPath}/memo">memo</a></li>
            </ul>
        </div>
    </div>
</body>
</html>
```

JSPでjstlを利用できるようにライブラリを追加します。メニューバー → `File` → `Project Structure` → `Project Settings` → `Libraries` → `＋`アイコン → `From Maven...`をクリックします。
![jstl1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/4481efec-72fd-192a-91e5-cdaa8abbd046.png)
検索欄に`javax.servlet:jstl`と入力して`虫眼鏡`アイコンをクリックして検索実行 → 検索結果から`javax.servlet:jstl:1.2`をクリック → `OK`ボタンをクリックしてライブラリに追加します。
![jstl2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/5566fcbd-ea9b-1287-641f-993522495020.png)

### JSON Processing

* 参考: [JSON Processing API documentation] (https://jakarta.ee/specifications/jsonp/1.1/apidocs/)

#### jakarta.json-apiの追加

JSON Processingが利用できるようにライブラリを追加します。メニューバー → `Project Structure` → `Project Settings` → `Libraries` → `＋`アイコン → `From Maven...`をクリックします。
![json-api1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/9b7abe1a-9ee0-d451-802f-be70b9a4746c.png)
検索欄に`jakarta.json:jakarta.json-api`と入力して`虫眼鏡`アイコンをクリックして検索実行 → 検査結果から`jakarta.json:jakarta.json-api:1.1.5`をクリック → `OK`ボタンをクリックしてライブラリに追加します。
![json-api2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/dcd4650f-ad3a-26aa-2e1e-3a1723a5e06c.png)

#### サンプルコード

```java
package com.example.demo.dto;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;

public class Phone {
  private String home;
  private String mobile;

  public Phone() {
  }

  public Phone(String home, String mobile) {
    this.home = home;
    this.mobile = mobile;
  }

  public String getHome() {
    return home;
  }

  public void setHome(String home) {
    this.home = home;
  }

  public String getMobile() {
    return mobile;
  }

  public void setMobile(String mobile) {
    this.mobile = mobile;
  }

  public JsonObject toJsonObject() {
    JsonObjectBuilder builder = Json.createObjectBuilder();
    if (home != null) {
      builder.add("home", home);
    }
    if (mobile != null) {
      builder.add("mobile", mobile);
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "Phone{" +
        "home='" + home + '\'' +
        ", mobile='" + mobile + '\'' +
        '}';
  }

}
```

```java
package com.example.demo.dto;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.bind.annotation.JsonbProperty;

public class Person {
  private String name;
  private String gender;
  private Integer age;
  @JsonbProperty("phones")
  private Phone phone;

  public Person() {
  }

  public Person(String name, String gender, Integer age, Phone phone) {
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.phone = phone;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGender() {
    return gender;
  }

  public void setGender(String gender) {
    this.gender = gender;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public Phone getPhone() {
    return phone;
  }

  public void setPhone(Phone phone) {
    this.phone = phone;
  }

  public JsonObject toJsonObject() {
    JsonObjectBuilder builder = Json.createObjectBuilder();
    builder
        .add("name", name)
        .add("gender", gender)
        .add("age", age);
    if (phone != null) {
      builder.add("phones", phone.toJsonObject());
    }
    return builder.build();
  }

  @Override
  public String toString() {
    return "Person{" +
        "name='" + name + '\'' +
        ", gender='" + gender + '\'' +
        ", age=" + age +
        ", phone=" + phone +
        '}';
  }

}
```

```java
package com.example.demo.ejb;

import com.example.demo.dto.Person;

import javax.ejb.Stateless;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonPointer;
import javax.json.JsonValue;
import javax.json.stream.JsonCollectors;
import java.util.List;

@Stateless
public class PersonProcessor {

  public JsonArray toJsonArray(List<Person> persons) {
    return persons.stream()
        .map(Person::toJsonObject)
        .collect(JsonCollectors.toJsonArray());
  }

  public JsonArray replace(JsonArray jsonArray, String pointer, String replaceValue) {
    JsonPointer p = Json.createPointer(pointer);
    JsonArray newArray = p.replace(jsonArray, Json.createValue(replaceValue));
    return newArray;
  }

  public JsonValue pickup(JsonArray jsonArray, String pointer) {
    JsonPointer p = Json.createPointer(pointer);
    if (p.containsValue(jsonArray)) {
      JsonValue value = p.getValue(jsonArray);
      return value;
    }
    return JsonValue.NULL;
  }

}
```

### JSON Binding

* 参考: [Jakarta JSON Binding 1.0.2 API Specification] (https://jakarta.ee/specifications/jsonb/1.0/apidocs/overview-summary.html)

#### サンプルコード

```Java
package com.example.demo.service;

import com.example.demo.dto.Person;

import javax.enterprise.context.Dependent;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

@Dependent
public class PersonService {

  public List<Person> toList(String json) {
    Jsonb jsonb = JsonbBuilder.create();
    List<Person> persons = jsonb.fromJson(json, new ArrayList<Person>(){}.getClass().getGenericSuperclass());
    return persons;
  }

  public List<Person> toList(Reader reader) {
    Jsonb jsonb = JsonbBuilder.create();
    List<Person> persons = jsonb.fromJson(reader, new ArrayList<Person>(){}.getClass().getGenericSuperclass());
    return persons;
  }

}
```

```Java
package com.example.demo.controller;

import com.example.demo.dto.Person;
import com.example.demo.service.PersonService;
import com.example.demo.ejb.PersonProcessor;

import javax.inject.Inject;
import javax.json.JsonArray;
import javax.json.JsonValue;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

@WebServlet(urlPatterns = {"/person"})
public class PersonController extends HttpServlet {
  private static final Logger log = Logger.getLogger(PersonController.class.getName());

  @Inject
  private PersonService personService;
  @Inject
  private PersonProcessor personProcessor;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.getRequestDispatcher("/WEB-INF/views/person/list.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    List<Person> persons;
    try (BufferedReader reader = req.getReader()) {
      // JSON Binding
      persons = personService.toList(reader);
      req.setAttribute("persons", persons);
    }

    // JSON Processing
    JsonArray jsonArray = personProcessor.toJsonArray(persons);
    JsonValue mobile = personProcessor.pickup(jsonArray, "/0/phones/mobile");
    JsonValue home = personProcessor.pickup(jsonArray, "/1/phones/home");
    req.setAttribute("mobile", mobile.toString());
    req.setAttribute("home", home.toString());

    req.getRequestDispatcher("/WEB-INF/views/person/list.jsp").forward(req, resp);
  }

}
```

### Inject

#### javax.injectの追加

`@Inect`アノテーションが利用できるようにライブラリを追加します。メニューバー → `Project Structure` → `Project Settings` → `Libraries` → `＋`アイコン → `From Maven...`をクリックします。
![ij0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/62cddf4d-8dfe-a4ac-2507-2649b34b3efe.png)
検索欄にjavax.inject:javax.injectと入力して虫眼鏡アイコンをクリックして検索実行 → 検査結果からjavax.inject:javax.inject:1をクリック → OKボタンをクリックしてライブラリに追加します。
![ij1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/3428605e-4420-38cf-a730-4246afa1e11f.png)

### web.xmlの修正とエラーページの追加

```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">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/views/errors/404.jsp</location>
    </error-page>
</web-app>
```

HTTPステータスコード404のときのエラーページを下記の場所に作成します。

```text
/web
  |
  +--- /WEB-INF
         |
         +--- /views
                |
                +--- /errors
                       |
                       +--- 404.jsp
```

```html:404.jsp
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <title>404 Not Found</title>
</head>
<body>
    <div>
        <h1>404 Not Found</h1>
    </div>
</body>
</html>
```

### 静的ファイルの追加

静的ファイルはwebディレクトリの直下に配置します。

```text
/web
  |
  +--- /public
         |
         +--- /css
                |
                +--- base.css
```

inex.jspのheadタグ内に下記の行を追加します。

```htmt:index.jsp
<link rel="stylesheet" href="public/css/base.css">
```

### アプリケーションの動作確認

この時点の状態でアプリケーションが起動するか確認します。
画面右上の`Run GlassFish 5.1.0`アイコンをクリックしてGlassFishサーバを起動します。
![run.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/c65271fc-3a0f-49fe-d27f-4f691c013c8a.png)

GlassFishサーバが起動すると自動的にブラウザが立ち上がり指定（デフォルトではトップページ、この例ではindex.jsp）のページが表示されます。

#### Application Serverのコンフィグレーション

`URL`で表示するページのURLを指定します。また任意のドメインや仮想サーバでアプリケーションを起動したい場合は`Server Domain`、`Virtual Server`で指定します。
![run_config.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/7fd61ca9-ee28-07aa-78ff-88682662d4a9.png)

## warファイルのビルド

ビルドの設定は初回だけです。

### ビルドの設定

メニューバーの`File` → `Project Structure` → `Project Settings` → `Artifacts` → `＋`アイコン → `Web Application: Archive` → `For 'demo-javaee8:war exploded`をクリックします。
![bw1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/67bf7d12-d7ad-29ce-c87e-ee79751e99f6.png)
画面右側`Output Layout`の`demo-javaee8_war.war`をクリックすると画面下に`Create Manifest...`ボタンが表示されるのでクリックします。
![bw2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/d86a0617-f5eb-0d72-03a8-88837a40f5a6.png)
Manifestファイルの場所を`/web/META-INF/MANIFEST.MF`に指定し、`OK`ボタンをクリックして画面を閉じます。
![bw3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/d997421d-4c42-4a91-b35c-a5e87f731122.png)

### ビルドの実行

メニューバーの`Build` → `Build Artifacts...`をクリックします。
![bw4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/e7fbc80e-1c23-e675-3c0e-31a610de34d3.png)
`demo-javaee8:war` → `Build`をクリックするとビルドが始まります。
![bw5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/052b5afa-6ff2-82e8-504b-ae035bb3e972.png)
下図の場所にwarファイルが出力されます。
![bw6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/a63418ca-df5a-6c31-9c37-3e989cf6204d.png)

### warファイルをデプロイする

IDE上で実行するGlassFishサーバではなく、ローカル上で実行中のGlassFishサーバへwarファイルをデプロイします。
管理コンソールにもデプロイ機能がありますが2020年1月現在、デプロイしようとするとエラーが発生してデプロイできないため（Issue : [Can´t deploy applications from Web Console [deploy origPath is NULL]] (https://github.com/eclipse-ee4j/glassfish/issues/22739)）、asadminコマンドでデプロイします。
なお、IntellJ IDEA上からもデプロイすることができますが、この記事では割愛します。

```
> asadmin deploy --contextroot=demo-javaee8 --name=demo-javaee8 /path/to/demo-javaee8_war.war
Application deployed with name demo-javaee8.
Command deploy executed successfully.
```

デプロイ時にドメインと仮想サーバを指定しなかった場合、ポート4848で実行中のドメインの仮想サーバすべてにアプリケーションがデプロイされます。
この記事の例でいえば`domain1`で仮想サーバ`server`、`server2`を実行しているのでこの2つにデプロイされます。

```
> asadmin list-virtual-servers
server
__asadmin
server2
Command list-virtual-servers executed successfully.
```

デプロイ後に下記のURLにアクセスすると同じアプリケーションが実行されていることが分かります。

```
# server
http://localhost:8080/demo-javaee8/
# server2
http://localhost:8082/demo-javaee8/
```

ポート4849で実行中の`domain2`に対してデプロイする場合は`--port`を指定します。

```
> asadmin --port 4849 deploy --contextroot=demo-javaee8 --name=demo-javaee8 /path/to/demo-javaee8_war.war
```

### 管理コンソールでデプロイされているアプリケーションを確認する

管理コンソールでデプロイされているアプリケーションを確認することができます。
この画面でUndeploy、Enable/Disableなどの操作が行えます。DisableにしたアプリケーションにアクセスするとHTTPステータスコード404が返ります。
![app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/1ddb6069-566b-edda-47d7-57c06db4135d.png)

### アプリケーションをアンデプロイする

アンデプロイは管理コンソールおよびasadminコマンドから行えます。アンデプロイではアプリケーション名を指定します。またデプロイ時と同様に、ポートと仮想サーバを指定しなければ仮想サーバにデプロイされているすべてのアプリケーションをアンデプロイします。

```
> asadmin undeploy demo-javaee8
Command undeploy executed successfully.
```

ポート4849で実行中の`domain2`に対するアンデプロイは`--port`を指定します。

```
> asadmin --port 4849 undeploy demo-javaee8
Command undeploy executed successfully.
```

## データベースアクセス処理の実装

次にJPAを使ったデータベースアクセス処理を実装します。

### persistence.xmlの修正

`jta-data-source`には管理コンソールで登録したJDBC Resourceの`jdbd/MySQLConnPool`を指定します。

```xml:persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<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="default" transaction-type="JTA">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>jdbc/MySQLConnPool</jta-data-source>
        <properties>
            <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform" />
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>
        </properties>
    </persistence-unit>
</persistence>
```

### Entity

```java
package com.example.demo.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;

@Table(name = "memo")
@Entity
public class Memo {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(name = "title", nullable = false)
  private String title;
  @Column(name = "description", nullable = false)
  private String description;
  @Column(name = "done", nullable = false)
  private Boolean done;
  @Column(name = "updated", nullable = false)
  private LocalDateTime updated;

  // ...アクセサ省略...

}
```

図の通りテーブル名やカラム名に赤い波線が表示されます。
![entity_warning.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/a5a39ef2-1a23-7987-0990-651b5cbc3e87.png)

アプリケーションのビルドや実行には影響はありませんが、これを解決する場合はメニューバー → `View` → `Tool Windows` → `Database`を開きDataSourceを追加します。
Database Tool Windowの`＋`アイコン → `Data Source` → `MySQL`を選択します。
![database_tool_window.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/a92bf3c9-66dc-5b3f-59ae-851afddb9887.png)
Databaseの接続に必要な項目を入力したら、`Test Connection`ボタンをクリックして確認します。
![database_tool_window_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/e54e7ab2-72a2-0221-7dba-0e1db3086a4c.png)
メニューバー → `View` → `Tool Windows` → `Persistence` → `demo-javaee8`を右クリック → `Assign Data Sources...` → 上記で追加したData Sourceを紐づけます。
![database_tool_window_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22772/608d5aef-0eec-89ac-1d75-44e10875ca62.png)

### Service

```Java
package com.example.demo.service;

import com.example.demo.entity.Memo;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import java.util.List;
import java.util.logging.Logger;

@Transactional
@Dependent
public class MemoService {
  private final static Logger log = Logger.getLogger(MemoService.class.getName());

  @Inject
  private EntityManager em;

  @PostConstruct
  public void init() {
    System.out.println("MemoService#init");
  }

  @PreDestroy
  public void destroy() {
    System.out.println("MemoService#destroy");
  }

  public Memo findById(Long id) {
    log.info("MemoService#findById(" + id + ")");
    TypedQuery<Memo> query = em.createQuery("SELECT m FROM Memo m WHERE m.id = :id", Memo.class)
        .setParameter("id", id);
    Memo memo = query.getSingleResult();
    return memo;
  }

  public List<Memo> findAll() {
    log.info("MemoService#findAll");
    TypedQuery<Memo> query = em.createQuery("SELECT m FROM Memo m", Memo.class);
    List<Memo> list = query.getResultList();
    return list;
  }

  public void save(Memo memo) {
    log.info("MemoService#save(" + memo.toString() + ")");
    em.persist(memo);
    em.flush();
   }

}
```

EntityManagerを`@Inject`アノテーションでインジェクトできるようにResourceクラスを作成します。

```Java
package com.example.demo;

import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;

public class Resource {
  @PersistenceUnit(unitName = "default")
  private EntityManagerFactory emf;

  @Produces
  public EntityManager getEntityManager() {
    return emf.createEntityManager();
  }

  public void closeEntityManager(@Disposes EntityManager em) {
    em.close();
  }
}
```

### Servlet

```Java
package com.example.demo.controller;

import com.example.demo.service.MemoService;
import com.example.demo.entity.Memo;

import javax.inject.Inject;
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;

@WebServlet(urlPatterns = {"/memo"})
public class MemoController extends HttpServlet {
  private static final Logger log = Logger.getLogger(MemoController.class.getName());

  @Inject
  private MemoService memoService;

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    List<Memo> memos = memoService.findAll();
    req.setAttribute("memos", memos);
    req.getRequestDispatcher("/WEB-INF/views/memo/list.jsp").forward(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try (BufferedReader reader = req.getReader()) {
      Memo memo = toMemo(reader);
      memoService.save(memo);
      req.setAttribute("memo", memo);
    }
    req.getRequestDispatcher("/WEB-INF/views/memo/list.jsp").forward(req, resp);
  }

  private static final JsonbConfig config = new JsonbConfig()
      .withEncoding("UTF-8")
      .withNullValues(Boolean.FALSE)
      .withDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());

  private Memo toMemo(BufferedReader reader) {
    Jsonb jsonb = JsonbBuilder.create(config);
    Memo memo = jsonb.fromJson(reader, Memo.class);
    return memo;
  }


```

#### JSP

```text
/web
  |
  +--- /WEB-INF
         |
         +--- /views
                |
                +--- /memo
                       |
                       +--- list.jsp
```

```html
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>メモ一覧</title>
    <link rel="stylesheet" href="public/css/base.css">
</head>
<body>
    <div>
        <h1>メモ一覧</h1>
        <div>
            <table>
                <thead>
                <tr>
                    <th>No</th>
                    <th>ID</th>
                    <th>タイトル</th>
                    <th>説明</th>
                    <th>完了</th>
                    <th>更新日</th>
                </tr>
                </thead>
                <tbody>
                <c:forEach items="${memos}" var="memo" varStatus="index">
                    <tr>
                        <td>${index}</td>
                        <td>${memo.id}</td>
                        <td>${memo.title}</td>
                        <td>${memo.description}</td>
                        <td>${memo.done}</td>
                        <td>${memo.updated}</td>
                    </tr>
                </c:forEach>
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>
```

# 補足

## [Specifications] (https://jakarta.ee/specifications/)

**Servlet**

- [Jakarta Servlet 4.0] (https://jakarta.ee/specifications/servlet/4.0/)
- Maven
    - `jakarta.servlet` : `jakarta.servlet-api` : `4.0.3`

**Server Pages (JSP)**

- [Jakarta Server Pages 2.3] (https://jakarta.ee/specifications/pages/2.3/)
- Maven
    - `jakarta.servlet.jsp` : `jakarta.servlet.jsp-api` : `2.3.6`

**Expression Language (EL)**

[Jakarta Expression Language 3.0] (https://jakarta.ee/specifications/expression-language/3.0/)
- Maven
    - `jakarta.el` : `jakarta.el-api` : `3.0.3`

**Debugging Support for Other Languages**

- [Jakarta Debugging Support for Other Languages 1.0] (https://jakarta.ee/specifications/debugging/1.0/)
- Maven


**Standard Tag Library (JSTL)**

- [Jakarta Standard Tag Library 1.2] (https://jakarta.ee/specifications/tags/1.2/)
- Maven
    - `jakarta.servlet.jsp.jstl` : `jakarta.servlet.jsp.jstl-api` : `1.2.7`

**Server Faces (JSF)**

- [Jakarta Server Faces 2.3] (https://jakarta.ee/specifications/faces/2.3/)
- Maven
    - `jakarta.faces` : `jakarta.faces-api` : `2.3.2`

**RESTful Web Services (JAX-RS)**

- [Jakarta RESTful Web Services 2.1] (https://jakarta.ee/specifications/restful-ws/2.1/)
- Maven
    - `jakarta.ws.rs` : `jakarta.ws.rs-api` : `2.1.6`

**WebSocket**

- [Jakarta WebSocket 1.1] (https://jakarta.ee/specifications/websocket/1.1/)
- Maven
    - `jakarta.websocket` : `jakarta.websocket-api` : `1.1.2`
    - `jakarta.websocket` : `jakarta.websocket-client-api` : `1.1.2`

**JSON Processing (JSON-P)**

- [Jakarta JSON Processing 1.1] (https://jakarta.ee/specifications/jsonp/1.1/)
- Maven
    - `jakarta.json` : `jakarta.json-api` : `1.1.6`

**JSON Binding (JSON-B)**

- [Jakarta JSON Binding Specification 1.0] (https://jakarta.ee/specifications/jsonb/1.0/)
- Maven
    - `jakarta.json.bind` : `jakarta.json.bind-api` : `1.0.2`

**Annotations**

- [Jakarta Annotations 1.3] (https://jakarta.ee/specifications/annotations/1.3/)
- Maven
    - `jakarta.annotation` : `jakarta.annotation-api` : `1.3.5`

**Enterprise Beans (EJB)**

- [Jakarta Enterprise Beans 3.2] (https://jakarta.ee/specifications/enterprise-beans/3.2/)
- Maven
    - `jakarta.ejb` : `jakarta.ejb-api` : `3.2.6`

**Transactions (JTA)**

- [Jakarta Transactions 1.3] (https://jakarta.ee/specifications/transactions/1.3/)
- Maven
    - `jakarta.transaction` : `jakarta.transaction-api` : `1.3.3`

**Persistence (JPA)**

- [Jakarta Persistence 2.2] (https://jakarta.ee/specifications/persistence/2.2/)
- Maven
    - `jakarta.persistence` : `jakarta.persistence-api` : `2.2.3`

**Bean Validation**

- [Jakarta Bean Validation 2.0] (https://jakarta.ee/specifications/bean-validation/2.0/)
- Maven
    - `jakarta.validation` : `jakarta.validation-api` : `2.0.2`

**Managed Beans**

- [Jakarta Managed Beans 1.0] (https://jakarta.ee/specifications/managedbeans/1.0/)
- Maven
    - `jakarta.annotation` : `jakarta.annotation-api` : `1.3.4`

**Interceptors**

- [Jakarta Interceptors 1.2] (https://jakarta.ee/specifications/interceptors/1.2/)
- Maven
    - `jakarta.interceptor` : `jakarta.interceptor-api` : `1.2.5`

**Contexts and Dependency Injection**

- [Jakarta Context Dependency Injection 2.0] (https://jakarta.ee/specifications/cdi/2.0/)
- Maven
    - `jakarta.enterprise` : `jakarta.enterprise.cdi-api` : `2.0.2`

**Dependency Injection**

- [Jakarta Dependency Injection 1.0] (https://jakarta.ee/specifications/dependency-injection/1.0/)
- Maven
    - `jakarta.inject` : `jakarta.inject-api` : `1.0`

**Security**

- [Jakarta Security 1.0] (https://jakarta.ee/specifications/security/1.0/)
- Maven
    - `jakarta.security.enterprise` : `jakarta.security.enterprise-api` : `1.0.2`

**Authentication**

- [Jakarta Authentication 1.1] (https://jakarta.ee/specifications/authentication/1.1/)
- Maven
    - `jakarta.security.auth.message` : `jakarta.security.auth.message-api` : `1.1.3`