GlassFish 5.1 + Servlet + JSP で Hello World

## 概要

- Java Servlet と JSP を含む WAR ファイルを Gradle で作成する
- GlassFish 5.1.0 をインストールする
- WAR ファイルを GlassFish 5.1.0 にデプロイする
- 動作確認環境: Java 8 (AdoptOpenJDK 1.8.0_265) + GlassFish 5.1.0 (Java Servlet 4.0 + JavaServer Pages 2.3) + macOS Catalina + Gradle 6.5.1

## GlassFish 5.1 とは

GlassFish 5.1 は Jakarta EE 8 と Java EE 8 に準拠した Web アプリケーションサーバ。Java Servlet 4.0 や JavaServer Pages 2.3 に対応している。

[Eclipse GlassFish](https://eclipse-ee4j.github.io/glassfish/)

> Eclipse GlassFish is a Jakarta EE compatible implementation sponsored by the Eclipse Foundation. Eclipse GlassFish 5.1 is also Java EE 8 Compatible.

## Java 8 をインストールする

Java 8 をインストールしていない場合は、Homebrew などでインストールしておき、 環境変数 JAVA_HOME と PATH を設定する。

```
$ brew tap AdoptOpenJDK/openjdk

$ brew cask install adoptopenjdk8

$ /usr/libexec/java_home -v 1.8
/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

$ export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home

$ export PATH=${JAVA_HOME}/bin:${PATH}
```

Java 11 や Java 14 では以下のようなエラーが発生し Glassfish 5.1.0 が起動しないので注意。

```
$ ./glassfish5/bin/asadmin start-domain
Exception in thread "main" java.lang.NullPointerException
	at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.initializeServiceLocator(AbstractModulesRegistryImpl.java:128)
	at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.newServiceLocator(AbstractModulesRegistryImpl.java:120)
	at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:194)
	at com.sun.enterprise.module.common_impl.AbstractModulesRegistryImpl.createServiceLocator(AbstractModulesRegistryImpl.java:200)
	at com.sun.enterprise.module.single.StaticModulesRegistry.createServiceLocator(StaticModulesRegistry.java:64)
	at com.sun.enterprise.admin.cli.CLIContainer.getServiceLocator(CLIContainer.java:193)
	at com.sun.enterprise.admin.cli.CLIContainer.getLocalCommand(CLIContainer.java:231)
	at com.sun.enterprise.admin.cli.CLICommand.getCommand(CLICommand.java:207)
	at com.sun.enterprise.admin.cli.AdminMain.executeCommand(AdminMain.java:347)
	at com.sun.enterprise.admin.cli.AdminMain.doMain(AdminMain.java:282)
	at org.glassfish.admin.cli.AsadminMain.main(AsadminMain.java:33)
```

## Java Servlet と JSP を含む WAR ファイルを Gradle で作成する

### ファイル一覧

```
├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── MyServlet.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            └── myjsp.jsp
```

### build.gradle

```groovy
plugins {
  id 'war'
}

repositories {
  mavenCentral()
}

dependencies {
  // Java Servlet 4.0 API
  // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
  providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
}

// Java 8
sourceCompatibility = 1.8

// Application
version = '1.0'
```

### src/main/java/com/example/MyServlet.java

```java
package com.example;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/myservlet")
public class MyServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    res.setContentType("text/html; charset=utf-8");
    try (PrintWriter out = res.getWriter()) {
      out.println("<html><body>");
      out.println("サーブレット: Hello Servlet World!<br>");
      out.println(getServletContext().getServerInfo());
      out.println("</body></html>");
    }
  }
}
```

### src/main/webapp/myjsp.jsp

```jsp
<%@ page contentType="text/html; charset=utf-8" %><html><body>
ジェイエスピー: Hello JSP World!<br>
<%= pageContext.getServletContext().getServerInfo() %><br>
java.vm.name: <%= System.getProperty("java.vm.name") %><br>
java.vm.vendor: <%= System.getProperty("java.vm.vendor") %><br>
java.vm.version: <%= System.getProperty("java.vm.version") %><br>
</body></html>
```

### src/main/webapp/WEB-INF/web.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>

<!-- Web Application Deployment Descriptor (Java Servlet 4.0) -->
<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>myjsp</servlet-name>
    <jsp-file>/myjsp.jsp</jsp-file>
  </servlet>

  <servlet-mapping>
    <servlet-name>myjsp</servlet-name>
    <url-pattern>/myjsp</url-pattern>
  </servlet-mapping>

</web-app>
```

### WAR ファイルを作成する

Gradle の build タスクで WAR ファイルを作成する。

```
$ gradle build
```

WAR ファイルが生成されていることを確認する。

```
$ file build/libs/mywebapp-1.0.war 
build/libs/mywebapp-1.0.war: Zip archive data, at least v1.0 to extract
```

## GlassFish 5.1.0 をインストールする

公式のマニュアル [Installing Eclipse GlassFish Server 5\.1](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/installation-guide/installing.html) に従ってインストールする。

[Eclipse GlassFish \| projects\.eclipse\.org](https://projects.eclipse.org/projects/ee4j.glassfish/downloads) から glassfish-5.1.0.zip (Eclipse GlassFish 5.1.0, Full Profile) をダウンロードする。

インストール先のディレクトリにダウンロードした glassfish-5.1.0.zip を展開する。

```
$ unzip glassfish-5.1.0.zip
```

zip ファイルをダウンロードして展開するのみでインストールが完了する。

## GlassFish を起動する

[Quick Start for Basic Features \- Starting and Stopping the Default Domain](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/quick-start-guide/basic-features.html#gglog) に従って起動する。

```
$ ./glassfish5/bin/asadmin start-domain
Waiting for domain1 to start ......
Successfully started the domain : domain1
domain  Location: /Users/foo/glassfish5/glassfish/domains/domain1
Log File: /Users/foo/glassfish5/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.
```

起動すると Administration Console http://localhost:4848/ で GlassFish の状態を管理できる。

## WAR ファイルを GlassFish 5.1.0 にデプロイする

asadmin deploy コマンドでデプロイできる。コマンドの詳細はリファレンスマニュアル [deploy](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/reference-manual/deploy.html) に載っている。

```
$ ./glassfish5/bin/asadmin deploy --contextroot=mywebappcr --name=mywebappname /Users/foo/mywebapp/build/libs/mywebapp-1.0.war
Application deployed with name mywebappname.
Command deploy executed successfully.
```

デプロイされた WAR ファイルはインストール先のディレクトリ以下にコピーされて展開される。

```
$ find . | grep mywebapp
./glassfish5/glassfish/domains/domain1/generated/xml/mywebappname
./glassfish5/glassfish/domains/domain1/generated/ejb/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname/mywebappname
./glassfish5/glassfish/domains/domain1/generated/policy/mywebappname/mywebappname/granted.policy
./glassfish5/glassfish/domains/domain1/generated/jsp/mywebappname
./glassfish5/glassfish/domains/domain1/applications/mywebappname
./glassfish5/glassfish/domains/domain1/applications/mywebappname/myjsp.jsp
./glassfish5/glassfish/domains/domain1/applications/mywebappname/META-INF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/META-INF/MANIFEST.MF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com/example
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/classes/com/example/MyServlet.class
./glassfish5/glassfish/domains/domain1/applications/mywebappname/WEB-INF/web.xml
./glassfish5/glassfish/domains/domain1/applications/__internal/mywebappname
./glassfish5/glassfish/domains/domain1/applications/__internal/mywebappname/mywebapp-1.0.war
```

## 動作確認

デプロイした Web アプリケーションの動作を確認する。

(Servlet 4.0 に対応しているはずなのに Servlet/3.1 と出力されるのはちょっと気になる)

```
$ curl --include http://localhost:8080/mywebappcr/myservlet
HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition  5.1.0 
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  5.1.0  Java/AdoptOpenJDK/1.8)
Content-Type: text/html;charset=utf-8
Content-Length: 118

<html><body>
サーブレット: Hello Servlet World!<br>
GlassFish Server Open Source Edition  5.1.0 
</body></html>
```

```
$ curl --include http://localhost:8080/mywebappcr/myjsp
HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition  5.1.0 
X-Powered-By: JSP/2.3
Set-Cookie: JSESSIONID=0123456789abcdef0123456789ab; Path=/mywebappcr; HttpOnly
Content-Type: text/html;charset=utf-8
Content-Length: 230

<html><body>
ジェイエスピー: Hello JSP World!<br>
GlassFish Server Open Source Edition  5.1.0 <br>
java.vm.name: OpenJDK 64-Bit Server VM<br>
java.vm.vendor: AdoptOpenJDK<br>
java.vm.version: 25.265-b01<br>
</body></html>
```

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

asadmin undeploy コマンドでデプロイした Web アプリケーションを取り除くことができる。

```
$ ./glassfish5/bin/asadmin undeploy mywebappname
Command undeploy executed successfully.
```

## GlassFish を停止する

asadmin stop-domain コマンドで停止できる。

```
$ ./glassfish5/bin/asadmin stop-domain
Waiting for the domain to stop .
Command stop-domain executed successfully.
```

## 参考資料

- [Eclipse GlassFish Documentation and User Guides](https://eclipse-ee4j.github.io/glassfish/docs/)
- [Eclipse GlassFish Server Release Notes, Release 5\.1](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/release-notes/toc.html)
- [Eclipse GlassFish Server Installation Guide, Release 5\.1](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/installation-guide/toc.html)
- [Eclipse GlassFish Server Quick Start Guide, Release 5\.1](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/quick-start-guide/toc.html)
- [Eclipse GlassFish Server Reference Manual, Release 5\.1](https://eclipse-ee4j.github.io/glassfish/docs/5.1.0/reference-manual/toc.html)
- [Eclipse GlassFish と IntelliJ IDEAで Webアプリケーションの開発環境を構築するメモ \- Qiita](https://qiita.com/rubytomato@github/items/c97e00a0094a5dc25f92)