LoginSignup
5
0

【ハンズオン】Visual Studio CodeとLiberty ToolsではじめるOpen Liberty開発 さいしょの一歩

Last updated at Posted at 2023-10-31

はじめに

このハンズオンの目的

このハンズオンの目的は,広く使われているIDEであるVisual Studio Codeを用いて,Open Liberty(やWebSphere Liberty)の開発を実施する方法の基礎を学ぶことです。前提知識として,以下のものを想定しています。

  • Visual Studio Codeを利用して,基本的なファイルの編集作業ができること
  • OSのターミナルからコマンドの実行ができること

実際の開発を行うためにはJavaやJava EEについての知識が必要になりますが,このハンズオンでは必要なソースコードについては全て掲載してあります。全てコピーできるようになっていますので,取りあえずは必要ありません。

このハンズオンを実施することで,以下のことを学ぶことができます。

  • Libertyの環境を作成するMavenのプロジェクトの基本
  • Visual Studio Codeを使用したLibertyの開発・テスト方法

このハンズオンで作成するアプリケーションはあくまでサンプルですので,本来必要なエラー処理や脆弱性対応が全くとられていません。実際のサーバーでこのまま利用することは絶対にしないでください。

Libertyについて

Open Libertyは,OSSで開発されているエンタープライズJavaアプリケーションの実行環境です。以下のような特徴を持っています

  • Jakarta EE 9.1 / 10MicroProfile 6.0など,最新の業界標準仕様に準拠
  • ゼロマイグレーション・ポリシーにより,Java EE 7 / 8やMicroProfile 1.4 / 2.2 / 3.3 / 4.1など,従来の仕様もサポートを継続
  • 最新のJava 21をサポート,Java 8のサポートも2026年9月まで継続予定
  • Eclipse Ppublic Licenseで提供,無料で利用可能
  • Eclipse IDE / VScode / IDEA IntelliJに開発者ツールを無償提供
  • DevOps,CI/CD,各種自動化ツールとの高い親和性
  • コンテナにも最適,公式イメージを4週間ごとに提供,InstantOnで1秒以内の高速起動

また,Open Libertyをベースとした商用アプリケーションサーバーであるWebSphere Libertyは,Open Libertyと完全にバージョンが同期されています。Open Libertyで動いているアプリケーションや構成ファイルは,同じ時期に出る同じバージョンのWebSphere Libertyで完全に動作します。サポートが必要であれば,OSSから商用製品に簡単に移行してサポートを受けることができます。

Visual Studio Codeによる開発について

従来は,WebSphere上で動くアプリケーションを開発するためには,Eclipse IDEを利用することが事実上,必須となっていました。ですが,昨今ではVisual Studio CodeやIntelliJなども,人気のJava開発環境としてユーザーが増えてきています。現在のLibertyは,Mavenプロジェクトによる開発,テスト環境の構築ができるようになっており,多くのJava開発環境で開発をおこなうことができるようになっています。また,Eclipse IDE / VScode / IDEA IntelliJに開発者ツールであるLiberty Toolsが無償で提供されており,設定ファイルの構文チェックや,Libertyテスト環境の起動などがIDEからできるようになっています。

今回のハンズオンでは,無償の統合IDE環境として人気の出てきたVisual Studio Codeを利用して,Liberty上のアプリケーションの開発を実践します。

ハンズオンの実行に必要な事前準備

このハンズオンの実行にあたっては,以下の準備が必要です。

JDK: Javaの開発環境
今回のハンズオンではJava SE 8ないし11/17に準拠したJDKを利用することを想定しています。これよりも新しいバージョンであっても,基本的には大丈夫なはずです。まだ導入していない場合には,Eclipse TemurinIBM Semeru Runtimesなどを導入しておいてください。
Visual Studio Code
従来型のWebSphere Application Server,WAS Traditionalランタイムを対象としたアプリケーションを実装するには,EclipseやそれをベースとしたRational Application Developerなどが利用されていました。もちろんLibertyでもEclipseが利用しての開発が可能ですが,Libertyの開発者ツールはEclipseだけでなく,Visual Studio CodeIDEA IntelliJにも提供されています。今回のハンズオンでは,Visual Studio Codeを利用して開発を行います。

こちらなどからダウンロードして,導入しておいてください。

Extension Pack for Java
Visual Studio Codeは,拡張によって様々な機能を追加することができます。Java言語でアプリケーションを開発するための拡張をいくつかまとめたものがMicrosoftから「Extension Pack for Java」として提供されていますので,これを導入しておいてください。

また,この資料では「Japanese Language Pack for Visual Studio Code」拡張も導入し,画面のメニューなどを日本語化しています。

  1. Visual Studio Codeで画面左側のアクティビティ・バーから以下のような「拡張機能」マークをクリック
  2. 「Marketplaceで拡張機能を検索する」にJavaと入力して検索する。
  3. 検索結果から出てきた「Extension Pack for Java」を選択し,発行者がMicrosoftであることを確認してから,「インストール」をクリックする。

ハンズオン手順

Liberty Toolsの導入

Visual Studio CodeのMarketplaceから,Liberty Toolsを検索して導入します。Liberty Toolsは,OSSで開発されているLibertyの次世代開発者ツールです。Visual Studio Code上で開発モードでLibertyを稼働してデバッグをすることができます。また,サーバー構成ファイルなどをコンテンツアシストの補助によって編集することもできます。

  1. Visual Studio Codeで画面左側の画面左側のアクティビティ・バーから「拡張機能」マークをクリック
  2. 「Marketplaceで拡張機能を検索する」にLibertyと入力して検索する。
  3. 検索結果から出てきた「Liberty Tools」を選択し,発行者がOpen Liberty(openliberty.io)であることを確認してから,「インストール」をクリックする。

image.png

プロジェクトの作成

Libertyでは,アプリケーションを実行するサーバーをあらかじめ導入しておく必要はありません。Maven(やGradle)のプロジェクト構成ファイル(pom.xml)にLibertyプラグインを組み込むと,Mavenのビルド時に環境を自動構築したり,パッケージ作成時にアプリ+Libertyランタイム+構成ファイルをまとめて導入することができる単一のZIPを作成したりすることができます。
image.png

LibertyのMavenプロジェクトを作成するには,OpenLiberty.ioのスターターページを利用するのが便利です。こちらのページにアクセスして,アプリケーションで使用するJava SEやJava EEのバージョンを指定すると,Maven/Gradleのプロジェクトを作成することが可能です。
image.png

こちらのページにアクセスし,以下の内容を入力して「Generate Project」をクリックしてください。

Group
Mavenの<groupId>に指定する文字列を入力します。ピリオドで区切られた,英数字およびアンダースコア(_)の文字列を指定します。これは実装するアプリケーションのJavaのpackage名などに使用されます。今回のハンズオンでは「com.ibm.jp.techexchange」と入力します。
Artifact
Mavenの<artifactId>に指定する文字列を入力します。英数字およびアンダースコア(_)が利用できます。これは作成したアプリケーションの名前となり,呼び出すURLなどに利用されます。単語の区切りとしてハイフン(-)が利用できます。今回のハンズオンでは「sample-app」と入力します。
Build Tool
このハンズオンではMavenを選択します。
Java SE Version
このハンズオンでは8を選択します。
Java EE/Jakarta EE Version
このハンズオンでは8.0を選択します。
MicroProfile Version
このハンズオンではNoneを選択します。

プロジェクトを格納したZIPファイルがダウンロードされます。

VScodeでの開発の基本

適当なフォルダを作成して(たとえばsample-app)ダウンロードしたZIPファイルを展開します。このフォルダをVisual Studio Codeで開きます。メニューの「ファイル」→「フォルダを開く...」で展開したフォルダを指定して開きます。
image.png

フォルダ内のファイルの作成者を信頼するかのダイアログボックスが出た場合には,「はい、作成者を信頼します」を選択します。プロジェクトに含まれるファイルには実行可能ファイルやコンパイルすれば実行可能になるファイルも含まれるため,このような確認が出ます。
image.png

プロジェクトに含まれるファイルを確認します。

Mavenの標準であるsrc/main/javaディレクトリ以下にJavaのソスコードファイルが格納されます。Libertyのプラグインを入れると,さらにsrc/main/liberty/configディレクトリが作成され,Libertyの構成ファイルなどが格納できるようになります。server.xmlがLibertyの構成ファイルです。
image.png

プロジェクト構成ファイルであるpom.xmlにLibertyのプラグインが構成されています。<pluginManagement>要素には,このプロジェクトや子プロジェクトに有効なプラグインの構成が定義され,その下の<plugins>要素のしたで実際にliberty-maven-pluginを使用するプラグインとして記述しています。

pom.xml
<build>
    <finalName>sample-app</finalName>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
            <plugin>
                <groupId>io.openliberty.tools</groupId>
                <artifactId>liberty-maven-plugin</artifactId>
                <version>3.8.2</version>
            </plugin>
        </plugins>
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>io.openliberty.tools</groupId>
            <artifactId>liberty-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

このままですと,最新のOpen Libertyが利用されます。それでもいいのですが,実際のプロジェクトで利用する場合には,Libertyのバージョンを固定したい,という場合がしばしばあります。その場合は以下のように<plugin><configuration>を追加して,利用するOpen Libertyのバージョンを指定します。この例では,2023年9月に公開されたバージョン23.0.0.9を使用するように構成しています。
(編集したファイルを保存するには,Windows環境ではCtrl+S,Mac環境では⌘+Sを入力します。)

pom.xml
<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.3.2</version>
        </plugin>
        <plugin>
            <groupId>io.openliberty.tools</groupId>
            <artifactId>liberty-maven-plugin</artifactId>
            <version>3.8.2</version>
            <configuration>
                <!-- 利用するランタイムをOpen Liberty v23.0.0.9に固定 -->
                <runtimeArtifact>
                    <groupId>io.openliberty</groupId>
                    <artifactId>openliberty-runtime</artifactId>
                    <version>23.0.0.9</version>
                </runtimeArtifact>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

ちなみにOSSのOpen Libertyでなく,製品のWebSphere Libertyの開発者版を使用する場合には,以下のように<runtimeArtifact>のgroupIdにcom.ibm.websphere.appserver.runtime,artifactIdにwlp-kernelを指定します。
(このハンズオンでは上記のOpen Libertyの構成を使用します)

pom.xml
<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.3.2</version>
        </plugin>
        <plugin>
            <groupId>io.openliberty.tools</groupId>
            <artifactId>liberty-maven-plugin</artifactId>
            <version>3.8.2</version>
            <configuration>
                <!-- 製品版のWebSphere Liberty v23.0.0.9を使用する場合 -->
                <runtimeArtifact>
                    <groupId>com.ibm.websphere.appserver.runtime</groupId>
                    <artifactId>wlp-kernel</artifactId>
                    <version>23.0.0.9</version>
                </runtimeArtifact>
            </configuration>
        </plugin>
    </plugins>
</pluginManagement>

このプロジェクトを利用して,実際にLibertyを実行してみましょう。Liberty Toolsを追加すると,アクティビティ・バーでエスクスプローラーを選んだ際に,左下のサイドバーに「LIBERTY DASHBOARD」のカラムが追加されます。こちらを開いて,表示されたプロジェクト名を右クリックすると,Libertyに対する操作の一覧が表示されます。この中から,「Start」を選択してLibertyを起動します。プロジェクトにはMaven Wrapper(Windows環境ではmvnw.cmd,そのほかの環境ではmvnwが使われる)が含まれており,これがMavenの実行に必要なファイルもMavenのセントラルレポジトリからダウンロードするので,あらかじめMavenが導入されていなくてもプロジェクトのビルドや実行が可能です。
image.png

最初に実行したときには,セントラルレポジトリからビルドに必要な各種ファイルやLiberty自身の導入ファイルなどが自動的にダウンロードされますので,実行までに時間がかかります。二回目以降はダウンロードしたファイルを再利用しますので高速に開発作業が行えます。

Windows環境では,Javaによるサーバーソフトを初めて実行する際には,以下のようなファイアウォールの警告が表示されることがあります。「アクセスを許可する」を選択します。
image.png

Visual Studio Codeの画面右下の「ターミナル」パネルに以下のような表示が出ると,Liberty起動は成功です。

[INFO] [監査      ] CWWKF0011I: defaultServer サーバーは、Smarter Planet に対応する準備ができました。defaultServer サーバーは 38.776 秒で始動しました。
[INFO] CWWKM2015I: 一致番号: 1 は [2023/09/27 20:50:23:266 JST] 0000002b com.ibm.ws.kernel.feature.internal.FeatureManager            A CWWKF0011I: defaultServer サーバーは、Smarter Planet に対応する準備ができました。defaultServer サーバーは 38.776 秒で始動しました。 です。
[INFO] ************************************************************************
[INFO] *    Liberty is running in dev mode.
[INFO] *        Automatic generation of features: [ Off ]
[INFO] *        h - see the help menu for available actions, type 'h' and press Enter.
[INFO] *        q - stop the server and quit dev mode, press Ctrl-C or type 'q' and press Enter.
[INFO] *
[INFO] *    Liberty server port information:
[INFO] *        Liberty server HTTP port: [ 9080 ]
[INFO] *        Liberty server HTTPS port: [ 9443 ]
[INFO] *        Liberty debug port: [ 7777 ]
[INFO] ************************************************************************
[INFO] Source compilation was successful.

ブラウザで http://localhost:9080/ にアクセスすると,Libertyが起動していることが確認できます。
image.png
LIBERTY DASHBOARDの右クリックメニューから「Stop」を選択するとサーバーを停止することができます。
image.png

[INFO] [監査      ] CWWKT0017I: Web アプリケーションが削除されました (default_host): http://localhost:9080/sample-app/
[INFO] [監査      ] CWWKZ0009I: アプリケーション sample-app は正常に停止しました。
[INFO] [監査      ] CWWKT0016I: Web アプリケーションが使用可能です (default_host): http://localhost:9080/sample-app/
[INFO] [監査      ] CWWKZ0003I: アプリケーション sample-app が 0.681 秒で更新されました。
[INFO] CWWKM2001I: Invoke command は ["C:\Users\takakiyo\src\sample-app\target\liberty\wlp\bin\server.bat", stop, defaultServer] です。
[INFO] サーバー defaultServer を停止中です。
[INFO] [監査      ] CWWKE0055I: 2023年9月27日水曜日 20:53 にサーバー・シャットダウンが要求されました。 サーバー defaultServer がシャット
ダウンしています。
[INFO] [監査      ] CWWKE1100I: サーバーが静止するまで最大 30 秒間待機します。
[INFO] [監査      ] CWWKT0017I: Web アプリケーションが削除されました (default_host): https://localhost:9443/sample-app/
[INFO] [監査      ] CWWKZ0009I: アプリケーション sample-app は正常に停止しました。
[INFO] [監査      ] CWWKI0002I: CORBA ネーム・サーバーは corbaloc:iiop:localhost:2809/NameService で使用可能ではなくなりました。
[INFO] [監査      ] CWWKE0036I: 4 分, 17.695 秒後にサーバー defaultServer が停止しました。
[INFO] サーバー defaultServer は停止しました。

ターミナルをクリックして,Ctrlキー+Cを押すことでも,サーバーを停止させることができます。この場合,Windows環境では,最後に「バッチ ジョブを終了しますか (Y/N)? 」という表示が出ることがありますが,yを入力してEnterキーを押してください。

最初の開発:JAX-RSの開発

クライアントからのREST要求に応じて応答を返すJAX-RSのアプリケーションを開発してみます。

まず利用するFeatureを構成します。Visual Studio CodeでLibertyの構成ファイルserver.xmlを開きます。

server.xml
<!-- Enable features -->
<featureManager>
    <feature>jakartaee-8.0</feature>
</featureManager>

最初はJava EE/Jakarta EE 8.0の全てのFeatureを有効にする統合Featureが設定されています。これでもJAX-RSは利用できるのですが,Libertyでは実際に使用するAPIだけを選んで有効にすることができ,その方が使用メモリも少なくなり,起動も高速になります。これを書き換えて,jaxrs-2.1だけを有効にします。

server.xml
<!-- Enable features -->
<featureManager>
    <feature>jaxrs-2.1</feature>
</featureManager>

ダウンロードしたプロジェクトには,JAX-RSのアプリケーションクラスが既に作成されています。これをそのまま利用して/apiのパスの下に機能を実装していきます。

RestApplication.java
package com.ibm.jp.techexchange.rest;

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

@ApplicationPath("/api")
public class RestApplication extends Application {

}

新規にJavaのソースファイルを作成します。エスクスプローラーでjava/com/ibm/jp/techexchange/restのrest部分を右クリックし「新しいファイル...」を選択します。
image.png
ファイル名としてSystemService.javaを入力します。
image.png
作成されたファイルを編集して,現在時刻をJSONで返すサービスを実装します。

SystemService.java
package com.ibm.jp.techexchange.rest;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

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

@Path("/system")
public class SystemService {
    @GET
    @Path("/currentTime")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> getCurrentTime() {
        Map<String, String> response = new HashMap<>();
        String currentTime = OffsetDateTime.now()
            .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
        response.put("Current Time", currentTime);
        return response;
    }
}

編集したファイルを保存したら,LIBETY DASHBOARDからLibertyをStartします。
image.png
ターミナルに以下のような表示が出たら起動は成功です。

[INFO] [監査      ] CWWKT0016I: Web アプリケーションが使用可能です (default_host): http://localhost:9080/sample-app/
[INFO] [監査      ] CWWKZ0003I: アプリケーション sample-app が 0.154 秒で更新されました。

この「CWWKT0016I: Web アプリケーションが使用可能です」メッセージに続くURLにマウスカーソルを合わせると,以下のようなツールチップが出ます。この「リンクにアクセス」をクリックするとブラウザが開きます。
image.png
トップ画面はまだ作っていないので,ブラウザには404表示がでます。
image.png
URLの末尾にapi/system/currentTimeを追加すると,作成したサービスが呼び出せます。
image.png

LIBERTY DASHBOARDの右クリックメニューから「Stop」を選択してサーバーを停止します。
image.png

Java EE開発の基本

簡単な掲示板アプリケーションを実装してみましょう。誰でもメッセージを追加でき,24時間以内のメッセージが表示できるアプリケーションを作成してみます。

このアプリケーションでは,JSP,Servlet,CDIのAPIを使って実装します。Libertyのサーバー構成ファイルserver.xmlを編集して,利用するFeatureとしてservlet-4.0,jsp-2.3,cdi-2.0を追加します。

server.xml
<!-- Enable features -->
<featureManager>
    <feature>jaxrs-2.1</feature>
    <feature>servlet-4.0</feature>
    <feature>jsp-2.3</feature>
    <feature>cdi-2.0</feature>
</featureManager>

Javaのコードを作成します。java/com/ibm/jp/techexchangeを右クリックして新規フォルダーmsgを作成し,その下にMessage.javaを作成します。
image.png
Message.javaはここのメッセージを保持するクラスです。

Message.java
package com.ibm.jp.techexchange.msg;

import java.time.LocalDateTime;

public class Message {

    private String name; // 投稿者の名前
    private String content; // メッセージ内容
    private LocalDateTime timestamp; // 投稿日時

    // コンストラクタ
    public Message(String name, String content, LocalDateTime timestamp) {
        this.name = name;
        this.content = content;
        this.timestamp = timestamp;
    }

    // getter
    public String getName() {
        return name;
    }

    public String getContent() {
        return content;
    }

    public LocalDateTime getTimestamp() {
        return timestamp;
    }

}

同じフォルダーjava/com/ibm/jp/techexchange/msgに,メッセージ一覧を保持するMessageManager.javaを作成します。

MessageManager.java
package com.ibm.jp.techexchange.msg;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class MessageManager {
    // CopyOnWriteArrayListを使用して、複数のスレッドからのアクセスに対応
    private final List<Message> messages = new CopyOnWriteArrayList<>();

    // 新規のメッセージを追加
    public void addMessage(String name, String content) {
        Message message = new Message(name, content, LocalDateTime.now());
        messages.add(message);
    }

    // 過去24時間のメッセージの一覧を取得
    public List<Message> getLast24HoursMessages() {
        List<Message> result = new ArrayList<>();
        LocalDateTime oneDayAgo = LocalDateTime.now().minusDays(1);

        for (Message message : messages) {
            if (message.getTimestamp().isAfter(oneDayAgo)) {
                result.add(message);
            }
        }

        return result;
    }
}

画面の表示を行うJSPを作成します。src/mainフォルダーの下に新規フォルダーとしてwebappを作成し,その下に新規ファイルでindex.jspを作成します。
image.png

index.jsp
<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List,javax.inject.Inject" %>
<%@ page import="com.ibm.jp.techexchange.msg.Message" %>
<%@ page import="com.ibm.jp.techexchange.msg.MessageManager" %>
<%! @Inject MessageManager manager; %>

<html>
<head><title>掲示板</title></head>
<body>

<!-- 新規メッセージ追加フォーム -->
<form action="AddMessageServlet" method="post">
    <fieldset>
        <legend>新規メッセージ追加</legend>
        名前: <input type="text" name="name" required><br>
        メッセージ: <textarea name="content" required></textarea><br>
        <input type="submit" value="送信">
    </fieldset>
</form>

<!-- メッセージ一覧表示 -->
<%
    List<Message> messages = manager.getLast24HoursMessages();
%>
<h2>過去24時間のメッセージ</h2>
<table border="1">
    <thead>
        <tr>
            <th>名前</th>
            <th>メッセージ</th>
            <th>時間</th>
        </tr>
    </thead>
    <tbody>
        <% for (Message message : messages) { %>
        <tr>
            <td><%= message.getName() %></td>
            <td><%= message.getContent() %></td>
            <td><%= message.getTimestamp() %></td>
        </tr>
        <% } %>
    </tbody>
</table>

</body>
</html>

最後にメッセージの追加を行うサーブレットを実装します。java/com/ibm/jp/techexchangeにAddMessageServlet.javaを作成します。
image.png

AddMessageServlet.javaを編集して以下のように実装します。

AddMessageServlet.java
package com.ibm.jp.techexchange;

import java.io.IOException;

import javax.inject.Inject;
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 com.ibm.jp.techexchange.msg.MessageManager;

@WebServlet("/AddMessageServlet")
public class AddMessageServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Inject
    private MessageManager manager;

    public AddMessageServlet() {
        super();
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        // リクエストから名前とメッセージを取得
        request.setCharacterEncoding("UTF-8");
        String name = request.getParameter("name");
        String content = request.getParameter("content");

        // データのバリデーション
        if (name != null && content != null && !name.trim().isEmpty() && !content.trim().isEmpty()) {
            try {
                // メッセージをデータベースに追加
                manager.addMessage(name, content);

            } catch (Exception e) {
                // エラーハンドリング (ここでは単純にスタックトレースを出力)
                e.printStackTrace();
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Message could not be added.");
            }
        }
        // 掲示板ページにリダイレクト
        response.sendRedirect("index.jsp");
    }
}

編集したファイルを保存したら,LIBETY DASHBOARDからLibertyをStartします。
image.png
サーバーが起動したら,「CWWKT0016I: Web アプリケーションが使用可能です」メッセージに続くURLにマウスカーソルを合わせるて,ツールチップから「リンクにアクセス」をクリックしてブラウザを開きます。
image.png
ブラウザに作成したアプリが表示されます。名前とメッセージ入力して送信をクリックし,メッセーノ一覧が更新されることを確認してください。
image.png

LIBERTY DASHBOARDの右クリックメニューから「Stop」を選択するとサーバーを停止することができます。
image.png

Databaseアクセスの基本

現在のアプリケーションはJavaのヒープメモリ上にメッセージを保持しているため,サーバーを再起動するたびにメッセージは失われてしまいます。ここでは,メッセージをデータベースに保存して永続化するように修正してみます。データベースとしてはDBMSをたてることなく,Javaから直接利用することができるApache Derbyを利用します。

プロジェクト構成ファイルのpom.xmlの<dependencies>要素にDerbyを依存関係として追加します。artifactIdがderbyなのが本体で,derbytoolsにはJDBCの実装などが含まれています。

pom.xml
<dependencies>
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>8.0.0</version>
        <scope>provided</scope>
    </dependency>
    <!-- Derbyを依存関係として追加 -->
    <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <version>10.16.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derbytools</artifactId>
        <version>10.16.1.1</version>
    </dependency>
</dependencies>

この依存関係で提供されるJARファイルを,Mavenで構築されるLiberty環境にコピーすることにします。ファイルのコピー自体は,手動で行うのではなく,pom.xmlに設定を記述し,ビルドの際に自動で行われるようにします。

コピー先のLiberty環境は${project.build.directory}/liberty/wlp,デフォルトではtarget/liberty/wlpディレクトリに作成されます。その共有リソース・ディレクトリであるusr/shared/resoucesの下にderbyというディレクトリを作成して,DerbyのJARファイルをコピーします。このusr/sharedはアプリケーションに必要な共有ファイルを格納するディレクトリで,後で実行するパッケージ作成の際にも,構成などと一緒にパッケージに格納されます。

プロジェクト構成ファイルのpom.xmlに<copyDependencies>を追加してファイルのコピーが行われるようにします。

pom.xml
<plugin>
    <groupId>io.openliberty.tools</groupId>
    <artifactId>liberty-maven-plugin</artifactId>
    <version>3.8.2</version>
    <configuration>
        <!-- 利用するランタイムをOpen Liberty v23.0.0.9に固定 -->
        <runtimeArtifact>
            <groupId>io.openliberty</groupId>
            <artifactId>openliberty-runtime</artifactId>
            <version>23.0.0.9</version>
        </runtimeArtifact>
        <!-- DerbyのJARファイルをLibertyの共有リソースディレクトリの下に追加 -->
        <copyDependencies>
            <dependencyGroup>
                <location>${project.build.directory}/liberty/wlp/usr/shared/resources/derby</location>
                <dependency>
                    <groupId>org.apache.derby</groupId>
                    <artifactId>*</artifactId>
                </dependency>
            </dependencyGroup>
        </copyDependencies>
    </configuration>
</plugin>

このJARファイルを用いてデータベースアクセスができるようにLibertyを構成します。まず,JDBCのAPIを提供するFeatureが必要になります。

Libertyの構成ファイルserver.xmlを編集してjdbc-4.2のFeatureを追加します。

server.xml
<!-- Enable features -->
<featureManager>
    <feature>jaxrs-2.1</feature>
    <feature>servlet-4.0</feature>
    <feature>jsp-2.3</feature>
    <feature>cdi-2.0</feature>
    <feature>jdbc-4.2</feature>
</featureManager>

同じくserver.xmlを編集してデータベースにアクセスするためのDataSourceを構成していきます。ファイル末尾の</server>の手前に構成を追加していきます。

まず共有ライブラリを構成します。<library><fileset>を指定してDerbyのJARファイルを共有ライブラリとして読み込みます。Libertyの共有リソースディレクトリは,shared.resource.dirという変数で参照できます。

この共有ライブラリを使用して<dataSource>を定義します。使用する<jdbcDriver>libraryRefに,上で定義した共有ライブラリのIDを指定します。<properties.derby.embedded>でデータベースの構成を記述しますが,作成したデータベースはサーバーの出力ディレクトリのしたのworkareaに保存されるように構成します。

server.xml
    <!-- Default SSL configuration enables trust for default certificates from the Java runtime -->
    <ssl id="defaultSSLConfig" trustDefaultCerts="true" />

    <!-- Configures Derby database -->
    <library id="derby_lib">
        <fileset dir="${shared.resource.dir}/derby" includes="*.jar" />
    </library>
    <dataSource jndiName="jdbc/derbyDS" id="derbyDS">
    	<jdbcDriver libraryRef="derby_lib" />
        <properties.derby.embedded createDatabase="create"
            databaseName="${server.output.dir}/workarea/DERBY_DB" />
    </dataSource>

</server>

続いてメッセージをDBに保存するMessageManagerを作成します。既存のMessageManagerクラスを継承する形でDatabaseMessageManagerを作成します。これにより,MessageManagerをCDIで注入(@Inject)している場所で,代わりにDatabaseMessageManagerが注入できるようになります。

MessageManager.javaと同じディレクトリにDatabaseMessageManager.javaを新規作成し,以下の内容を実装します。

DatabaseMessageManager.java
package com.ibm.jp.techexchange.msg;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.sql.DataSource;

@ApplicationScoped
public class DatabaseMessageManager extends MessageManager {
    @Resource(lookup = "jdbc/derbyDS")
    private DataSource ds;

    // 表の初期化
    @PostConstruct
    public void init() {
        String sql = "CREATE TABLE messages (" +
                        "NAME VARCHAR(255) NOT NULL," +
                        "CONTENT VARCHAR(4000) NOT NULL," + 
                        "TIMESTAMP TIMESTAMP NOT NULL)";
        try (Connection conn = ds.getConnection();
                PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.executeUpdate();
        } catch (SQLException e) {}
    }

    // 新規のメッセージを追加
    @Override
    public void addMessage(String name, String content) {
        String sql = "INSERT INTO messages (name, content, timestamp) VALUES (?, ?, ?)";
        try (Connection conn = ds.getConnection();
                PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setString(1, name);
            stmt.setString(2, content);
            stmt.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now()));
            stmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 過去24時間のメッセージの一覧を取得
    @Override
    public List<Message> getLast24HoursMessages() {
        List<Message> messages = new ArrayList<>();
        String sql = "SELECT name, content, timestamp FROM messages WHERE timestamp > ?";
        try (Connection conn = ds.getConnection();
                PreparedStatement stmt = conn.prepareStatement(sql)) {
            stmt.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now().minusDays(1)));
            try (ResultSet rs = stmt.executeQuery()) {
                while (rs.next()) {
                    Message message = new Message(
                        rs.getString("name"), 
                        rs.getString("content"),
                        rs.getTimestamp("timestamp").toLocalDateTime()
                    );
                    messages.add(message);
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return messages;
    }
}

既存のMessageManagerが注入対象とならないように,MessageManager.javaの@ApplicationScoped@Vetoedに変更します。

MessageManager.java
package com.ibm.jp.techexchange.msg;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.enterprise.inject.Vetoed;

@Vetoed
public class MessageManager {

全てのファイルを保存したら,LIBERTY DASHBOARDからLibertyを起動します。先ほどと同じように掲示板にメッセージが追加できることを確認します。いくつかメッセージを書き込んだら,Libertyを停止してからもう一度起動してみてください。掲示板にメッセージが引き続き掲載されていることが確認できます。

エスクスプローラーで確認してみるとtarget/liberty/wlpの下にLiberty環境が構築され,その下のusr/servers/defautServer/workareaDERBY_DBが作成され,データベースが保存されていることが判ります。

image.png

環境のパッケージ

今回作成したプロジェクトは,そのままZIPファイルにパッケージして他の環境にコピーして実行することが可能です。サーバー構成にはDerbyのJARファイルの位置やDBを作成する場所などを記述していますが,全て変数を介した設定になっているので,他の場所や他の環境にコピーしてもそのまま利用できます。

サーバーのパッケージを作成する際にはいくつか準備が必要です。

まず,Libertyのデフォルトの構成ではlocalhostからの接続のみ受け付けるようになっていて,リモートからの接続ができないようになっています。開発時のテストをするには問題ないのですが,実際にLibertyを導入して動かすときにはリモートから接続できるようにしておかないと,サーバーとして使用することはできません。

Libertyの構成フィルserver.xmlを開き,<httpEndpoint>要素にhost="*"の属性を追加します。

server.xml
<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint id="defaultHttpEndpoint" host="*"
            httpPort="9080"
            httpsPort="9443" />

Libertyプラグインの拡張機能を有効にすると,パッケージングのゴールとしてliberty-assemblyが使用できるようになります。これにより,MavenのpackageゴールでLibertyのランタイムを含んだパッケージを作成できます。

まず,プロジェクト構成ファイルpom.xmlを開き,liberty-maven-pluginの設定箇所に,<extensions>true</extensions>の記述を追加します。

pod.xml
<build>
    <finalName>sample-app</finalName>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.3.2</version>
            </plugin>
            <plugin>
                <groupId>io.openliberty.tools</groupId>
                <artifactId>liberty-maven-plugin</artifactId>
                <version>3.8.2</version>
                <!-- Libertyプラグインの拡張機能を有効にする -->
                <extensions>true</extensions>
                <!-- 修正ここまで -->
                <configuration>

同じくpom.xml<packaging>liberty-assemblyに変更します。

pod.xml
<groupId>com.ibm.jp.techexchange</groupId>
<artifactId>sample-app</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- packagingをwarからliberty-assemblyに変更 -->
<packaging>liberty-assembly</packaging>

パッケージのZIPを作成するときにはtargetの下に作成されたLiberty環境がそのまま格納されますが,server.xmlで設定されているFeatureを動かす最低限のファイルだけを格納するようにすると,ZIPファイルや展開後の環境のサイズを小さくすることができます。

同じくpom.xmlの,プラグインの<configuration>の子要素として<include>minify</include>を追加します。

pom.xml
<plugin>
    <groupId>io.openliberty.tools</groupId>
    <artifactId>liberty-maven-plugin</artifactId>
    <version>3.8.2</version>
    <!-- Libertyプラグインの拡張機能を有効にする -->
    <extensions>true</extensions>
    <!-- 修正ここまで -->
    <configuration>
        <!-- 利用するランタイムをOpen Liberty v23.0.0.9に固定 -->
        <runtimeArtifact>
            <groupId>io.openliberty</groupId>
            <artifactId>openliberty-runtime</artifactId>
            <version>23.0.0.9</version>
        </runtimeArtifact>
        <!-- DerbyのJARファイルをLibertyの共有リソースディレクトリの下に追加 -->
        <copyDependencies>
            <dependencyGroup>
                <location>${project.build.directory}/liberty/wlp/usr/shared/resources/derby</location>
                <dependency>
                    <groupId>org.apache.derby</groupId>
                    <artifactId>*</artifactId>
                </dependency>
            </dependencyGroup>
        </copyDependencies>
        <!-- 作成するパッケージに最低限のファイルのみを格納 -->
        <include>minify</include>
    </configuration>
</plugin>

実際にパッケージを作成します。サーバーが停止している状態でLIBERTY DASHBOARDから(「Start」ではなく)「Start...」を選択します。ターミナル・パネルにフォーカスが移り,コマンドを実行できるようになります。ここから 以下のコマンドを実行します。

Windowsの場合
.\mvnw.cmd package
それ以外の場合
./mvnw package

実行すると,tergetディレクトリの下にsample-app.zipが作成されます。これがLiberty+構成+DerbyのJAR+アプリケーションを含んだZIPファイルになっています。

このファイルを別のディレクトリにコピーして展開すると,完全に稼働するLiberty環境が構築できます(JDKは別途導入しておく必要があります)。展開したbinディレクトリにserverコマンドがありますので,このディレクトリに移動して以下のコマンドを実行すると先ほど作成した掲示板アプリを実行するLibertyが立ち上がってきます。

Windowsの場合
.\server.cmd start
それ以外の場合
./server start

サーバーを停止するには以下のコマンドを実行します。

Windowsの場合
.\server.cmd stop
それ以外の場合
./server stop

以上でハンズオンは終了です。

環境のクリーンアップ

ビルドで作成した環境を削除しソースコードだけ残す場合には,LIBERTY DASHBOARDから「Start...」を選択してターミナルをひらき,以下のコマンドを入力します。

Windowsの場合
.\mvnw.cmd clean
それ以外の場合
./mvnw package clean

プロジェクト自体を使用しない場合は,Visual Studio Codeを終了して,プロジェクトのディレクトリをまるごと削除してください。また,Mavenを今後利用しないのでダウンロードしたキャッシュも不要な場合には,ホームディレクトリに.m2というディレクトリが作成されてキャッシュが保存されていますので,これも削除してください。

さらに詳しく

Open Libertyの詳細や,構成方法について知りたい場合は,2022年6〜8月に開催された「Liberty Dojoシリーズ」の発表資料および録画ビデオを参照ください。

IBM Communityサイトもご利用ください。WebSphere Libertyについての多くの術情報や,Open Libertyについての各種情報へのリンクも提供されています。

また,Open Libertyのガイドページでは,今回使用したようなMavenのプロジェクト形式で,多くのサンプルが公開されています。これらのサンプルは,今回の環境を利用して,自由に実行することが可能です。RESTやReactive,マイクロサービースなど多くののサンプル提供されておりますので,ぜひご参照ください。

トラブルシューティング

プロセスが残っていてMavenからLibertyを起動できない

Visual Studio Codeが異常終了したときなど,実行中のLibertyがそのまま残ってしまい,新規のLibertyの起動ができなくなることがあります。この場合は,Libertyを起動しようとしたとき,以下のように「The server defaultServer is already running」メッセージがでて失敗することがあります。

[ERROR] Failed to execute goal io.openliberty.tools:liberty-maven-plugin:3.7.1:dev (sample-app) on project sample-app: The server defaultServer is already running. Terminate all instances of the server before starting dev mode. You can stop a server instance with the command 'mvn liberty:stop'. -> [Help 1]

その場合,LIBERTY DASHBOARDから「Start...」を選択してターミナルをひらき,以下のコマンドを入力します。

Windowsの場合
.\mvnw.cmd liberty:stop
それ以外の場合
./mvnw liberty:stop

Libertyが起動できない

前章のようにLibertyをとめても(あるいはkillコマンドでのこったLibertyのプロセスを止めても),正常にLibertyが起動しなくなってしまった場合は,以下のコマンドをターミナルから実行して,作成済みのLiberty環境を消去してください。

Windowsの場合
.\mvnw.cmd clean
それ以外の場合
./mvnw clean

再度LIBERTY DASHBOARDから「Start」を実行すると,Liberty環境が再作成されます。

それでもLibertyが正常に起動しない場合は,Mavenのローカルキャッシュを削除してみてください。自身のホームディレクトリの.m2ディレクトリをまるごと削除してください。

mvnwに適切な権限がなくてMavenが実行できない

Windows環境でダウンロードしたZIPを解凍し,WLS環境でmvnwを実行しようとしたとき,以下のように「Permission denied」メッセージで失敗することがあります。

bash: /home/XXXXX/sample-app/mvnw: Permission denied

これは,Windows環境のZIP解凍ツールでは必要なパーミッションが付与されないことがあるためです。同じターミナル画面から以下のコマンドを入力して,実行権限を付与してください。

$ chmod +x mvnw

SSLのハンドシェイクエラーでMavenの実行に失敗する

ネットワーク・ファイヤーウォール製品などを導入した環境では,以下のエラーでMavenの実行に失敗するケースがあります。

> mvnw.cmd liberty:dev
Exception in thread "main" javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:378)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
        at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:316)

この場合は,コマンドラインの後に-Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=trueをつけて実行してみてください。

> mvnw.cmd liberty:dev -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0