システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

VirtualBoxの共有フォルダ設定

いつも忘れる、VirtualBoxの共有フォルダ設定のメモ

ツールのインストールに必要なものをインストール

sudo apt install gcc make perl -y

VirtualBoxツールをインストール

インストールディスクを挿入

ディスクのフォルダへ移動

sudo sh VBoxLinuxAdditions.run

ゲストOS再起動

ユーザーをvboxsfグループに加える

sudo gpasswd -a (ユーザー名) vboxsf

共有フォルダを設定

ゲストOS(Linux)のmnt/nas へマウントしたい場合*1

f:id:vermeer-1977:20210830092036p:plain
共有フォルダ設定

*1:サーバーキッティングの予行演習なので 外部ストレージ疑似としてNASへのマウントを意図しています

文字列をBase64に変換する

はじめに

ファイルパス(もしくはそれに準ずる文字列)をURLパラメータに使いたいと思いましたが、そのままではエスケープをあれこれしないといけないし、マルチバイトが入ってくると長くなります。そこで文字列をバイナリー圧縮してBase64変換したら良いかな?と思ってやってみました。

開発環境

Java 11

何が嬉しいの?

  • 文字列にURLに使用不可な文字があっても使用できる*1
  • 文字列の難読化(暗号化ではない)ができる

変換処理

処理の流れは以下の通り

  1. 文字列をバイナリー圧縮する
  2. バイナリーをBase64に変換する
  3. URLに使用してはいけない文字列を置換する

デコードは、この逆をします。

実装

import java.util.Arrays;
import java.util.Base64;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

public class StringCompressor {

    private static final Logger logger = Logger.getLogger(StringCompressorImpl.class.getName());

    public String deflate(String value) {
        byte[] input = value.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        byte[] output = new byte[256];
        Deflater compresser = new Deflater();
        compresser.setInput(input);
        compresser.finish();
        int compressedDataLength = compresser.deflate(output);
        var trimed = Arrays.copyOfRange(output, 0, compressedDataLength);
        var base64 = Base64.getEncoder().encode(trimed);
        var encoded = new String(base64, java.nio.charset.StandardCharsets.UTF_8);

        //base64で使用されていてURLで使用されることが推奨されていない文字を置換
        var replaced = encoded
                .replaceAll("\\+", "_")
                .replaceAll("/", "-")
                .replaceAll("=", "!");

        return replaced;
    }

    public String inflate(String value) {
        try {
            // deflateで置換した文字列をbase64用に置き換える
            var base64 = value
                    .replaceAll("_", "+")
                    .replaceAll("-", "/")
                    .replaceAll("!", "=");

            var decoded = Base64.getDecoder().decode(base64);

            Inflater decompresser = new Inflater();
            decompresser.setInput(decoded);
            byte[] output = new byte[256];
            int resultLength = decompresser.inflate(output);
            decompresser.end();

            var result = new String(output, 0, resultLength, java.nio.charset.StandardCharsets.UTF_8);
            return result;
        } catch (DataFormatException ex) {
            logger.warning(() -> ex.getMessage() + "::String could not decode " + value + "::");
            return value;
        }
    }

}

参考情報

Base64 - Wikipedia

URLに使用できる文字、できない文字 – ysklog

さいごに

短い文字列だと変換後の文字列の方が長くなってしまうことがあります*2
本来の目的が「ファイルパスのような文字列をURLのパラメータとして使いたいため」なので、それは仕方ないかなぁと。
難読化はしていますが暗号化はしていないので、ご利用時には注意ください。

*1:受信後デコードが必要ですが

*2:おそらくほぼ確実に

JSFの本番環境用設定

JSFの本番環境用設定に関するメモ

web.xmlの切り替え

本番環境向けの設定

javax.faces.PROJECT_STAGEを使う - Challenge Engineer Life !

やっておくと良さそうなもの

パーフェクト Java EEより

javax.faces.FACELETS_SKIP_COMMENTS
javax.faces.FACELETS_REFRESH_PERIOD

NIO2のメモ

今更ながら…

レッスン:基本的なI/O(Java?チュートリアル > 重要なクラス)

パス操作(Java?チュートリアル > 重要なクラス > 基本的なI/O)

ディレクトリの作成と読取り(Java?チュートリアル > 重要なクラス > 基本的なI/O)

Javaファイル関連メモ2(Hishidama's Java Files Memo)

java:walkFileTreeメソッドの使い方 – サイゼントの技術ブログ

備忘録的なblog: NIO2で再帰的にファイルを扱う方法

WatchService で再帰的にディレクトリ監視で難あり - A Memorandum

WatchService 使い方メモ - Qiita

java - JSF + WatchService + Thread - Stack Overflow

JakartaBatchのメモ

Jakarta Batch

Java EE 7 jBatchの使い方──『Java EE 7徹底入門』番外編 第3回 - builder by ZDNet Japan

Java EE 7 検証環境構築(12) jBatch 簡易サンプル作成と Arquillian でユニットテスト | Glob

Chunk方式のStepを使ってみる

javaee7-samples/batch at master · javaee-samples/javaee7-samples · GitHub

jBatch(Java EE 7)をNetBeans+GlassFish+Mavenではじめました - Instructor's memo

jBatchで、Chunk、Decision、Batchletをつなげて遊んでみる - CLOVER🍀

jBatch RI を Java SE でまともに(JPA,CDI,Transactionalつきで)動かす #javaee - kencharosの日記

Glassfish jBatch Checkpoint - @//メモ

Glassfish jBatch JSLで書けること整理 - @//メモ

JBatch (Payara 4.1.153) · khasunuma/Payara Wiki · GitHub

GitHub - jGauravGupta/jBatchSuite: jBatch Suite allows the developers to design java batch application model and automates Java code generation(Java Batch 1.0 JSR - 352)

https://dspace.cvut.cz/bitstream/handle/10467/61785/F3-DP-2015-Milata-Tomas-java-ee-batch-editor.pdf?sequence=13&isAllowed=y

JPAをJUnitでテスト

WebAPIであればE2Eテストで十分かもしれませんが、各レイヤーでテストが出来る仕組みがあると後々便利です。
ということで 直近のエントリーで作った JPA(with NativeQuery)を拡張してJUnitでテストをしたいと思います。

はじめに

JPAのテスト実装は 調べると 多少はありました。
ただ Springを使う例はあれど、Java EE/Jakarta EE の例は思ったよりも少なかったです。 普通(?)の使い方はあるのですが、orm/xmlが絡むと極端に少なかったので まとめてみました。 また、EE(JPA)がDBベンダーの差分を抽象化してくれるということで H2をインメモリで使うことで可搬性の高いものにしているつもりです。

なお、今回の実装ですが、Jakarta EE8を使っています*1

リポジトリ

jakarta-ee9-sample/jpa-part-6-junit5 at main · vermeer-1977-blog/jakarta-ee9-sample · GitHub

環境など

環境設定

まずは環境設定周りから。

pom.xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.junit</groupId>
            <artifactId>junit-bom</artifactId>
            <version>5.7.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-api</artifactId>
        <version>8.0.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.200</version>
        <!--            
        <scope>runtime</scope>
        <optional>true</optional>
        -->
    </dependency>

    <!-- test -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>2.7.7</version>
        <scope>test</scope>
    </dependency>
    <!-- test -->

</dependencies>


<build>
        
    <!-- for unitTest-->
    <testResources>
        <testResource>
            <directory>src/test/resources</directory>
            <filtering>true</filtering>
        </testResource>
    </testResources>
    
    <plugins>
    
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <encoding>${project.build.sourceEncoding}</encoding>
                <compilerArgument>-proc:none</compilerArgument>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M5</version>
        </plugin>

(... Cargo周りは省略)

</project>

JUnitに関連する依存を足すときには、maven-surefire-pluginも追加します。
H2については、今回 実行するDBとしても使っているのでruntimeを外しました*3

JPA関連としては、JUnit(つまりJava SE)でJPAを実行するための依存org.eclipse.persistenceと、testフォルダの関連資産の読み込みをするためのtestResourcesの追記です。
参考にしたブログでは testResourcesについての言及が無かったので、ひょっとしたら orm.xmlを使わなければ不要な設定なのかもしれません*4

persistence.xml

<persistence-unit name="TEST_PU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    
        <!-- read main class -->
        <jar-file>${project.basedir}/target/classes</jar-file>

        <!-- read orm.xml  -->
        <mapping-file>META-INF/orms/orm.xml</mapping-file>
    
    <properties>
        <property name="javax.persistence.schema-generation.database.action" value="create"/>
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1"/>
        <property name="javax.persistence.jdbc.user" value="sa"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="eclipselink.logging.level" value="WARNING"/>
        <property name="eclipselink.logging.level.sql" value="FINE"/>
        <property name="eclipselink.logging.level.connection" value="WARNING"/>
    </properties>
</persistence-unit>

transaction-type="RESOURCE_LOCAL"とするのは、JUnitが EEコンテナではなく Java SEで実行されるからです。

ポイントは <jar-file>${project.basedir}/target/classes</jar-file> で mainフォルダでビルドした資産を testフォルダで参照します。
orm.xml は testフォルダでも参照するので、mainと同じパス<mapping-file>META-INF/orms/orm.xml</mapping-file>を指定します。
<jar-file> でパスとしては同じ場所にあるので 何も書かなくても test側でも参照するかと思ったのですが、そういうものではありませんでした。

なお、orm.xmlをMETA-INFの直下に1つだけおいた構成の場合は、<mapping-file>の記述が無くても読み込まれました*5

JUnit

DB起動と接続

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class JpaTestExtension implements BeforeAllCallback, AfterAllCallback {

    public static final String PU_NAME = "TEST_PU";

    private static EntityManagerFactory emf;
    public static EntityManager em;

    public static org.h2.tools.Server server;

    @Override
    public void beforeAll(ExtensionContext ec) throws Exception {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shutdownServer();
        }));

        server = org.h2.tools.Server.createTcpServer().start();
        emf = Persistence.createEntityManagerFactory(PU_NAME);
        em = emf.createEntityManager();
    }

    @Override
    public void afterAll(ExtensionContext ec) throws Exception {
        shutdownServer();
    }

    private static void shutdownServer() {
        if (em != null && em.isOpen()) {
            em.close();
            em = null;
        }

        if (emf != null && emf.isOpen()) {
            emf.close();
            emf = null;
        }
        if (server != null) {
            server.stop();
            server = null;
        }
    }

}

Extensionにして、JPAのテストをしたいケースに付与できるようにしました。 複数のテスト用PUを作った場合は、同様の Extensionを作って対応できます。
PUが1つだけであれば クラスにして継承でも実現できる機能ですが 複数のPUを指定できる @ExtendWithを使うやり方の方が良いと思います。

ちなみにPersistence.createEntityManagerFactory(PU_NAME)のところが なかなか解決できずに苦労しました。

テストコード

@ExtendWith(JpaTestExtension.class)
public class UserDataQueryImplTest {

    @Test
    public void test01() {
        var userData = new UserData(1L);
        userData.setNickName("NickTest");
        userData.setUserName("UserName1 Test2");

        var tx = JpaTestExtension.em.getTransaction();
        tx.begin();
        JpaTestExtension.em.persist(userData);
        tx.commit();

        var query = new UserDataQueryImpl(JpaTestExtension.em);
        var results = query.getUserData();

        assertEquals(1, results.size());
        assertEquals("NickTest", results.get(0).getNickName());
        assertEquals("UserName1", results.get(0).getUserName().getFirstName());
        assertEquals("Test2", results.get(0).getUserName().getLastName());
    }
}

先に作成したExtensionを @@ExtendWithで適用します。
staticなので、利用するときは JpaTestExtension.em のようにして参照します。
なお、PUが1つだけの場合は static importを使うと emだけになるので記述がよりスッキリします。

余談

EntityManagerですが、コンストラクタインジェクションできるようにしています。
Payara ServerのEE9 RCでは出来なかったのですが、EE8を対象にすると出来たので見直しをしています*6。 なお フィールドインジェクションでも、アクセス修飾子をdefault(無印)にしておけば Mockなど使わなくてもインジェクションは出来ます。

該当箇所の抜粋は以下。

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Dependent
public class ResourceProvider {

    @PersistenceContext
    private EntityManager entityManager;

    @Produces
    @Dependent
    public EntityManager getEntityManager() {
        return entityManager;
    }
}
@RequestScoped
public class UserDataQueryImpl implements UserDataQuery {

    private final EntityManager em;

    @Inject
    public UserDataQueryImpl(EntityManager em) {
        this.em = em;
    }

(...)
}

差分はコミットログを

Constructor Injection · vermeer-1977-blog/jakarta-ee9-sample@d6089d5 · GitHub

参考

JUnit 5 ユーザーガイド

JUnitでJPAのテスト | KATSUMI KOKUZAWA'S BLOG

テストケースからのH2起動 - A Memorandum

H2をインメモリで動かすときの注意 - Yamkazu's Blog

DBUnitとH2 Databaseを使ってみた。 - /dev/null

java - How to configure JPA for testing in Maven - Stack Overflow

JUnit5 使い方メモ - Qiita

さいごに

やろうとしていることは そんなに難しいはずはないのに 毎回毎回「ズバリ」が無いのは何故なんだろうと思います。
今回はテストクラス側で Persinstenceを取得するところで思ったようにいかず あれこれ試しました。
とはいえ、ハマるときは かなり長い時間途方にくれることが多いのですが*7GitHubで他の人の実装を検索するというのをやるようになって視点や観点の切り替えのヒントを得るようにしたら、以前よりは ハマる時間が減ったような気がします。
ということで、最近はブログのコードはGitHubへ上げるようにしています。

*1:Jakarta EE9 RCではありません。試したみたのですが うまくいかなかったためです

*2:EE9-RCではありません

*3:text,runtimeという書き方もあるのですがWARNINGが出るので scope自体を外すことにしました

*4:少なくとも 私は testフォルダのpersistance.xmlが読まれなかったため PersistenceUnitが取得出来ませんでした

*5:この辺りの差は良く分かりません。

*6:Payara Serverの公式対応はEE8なので、まだできないこともあるのは仕方ないことです

*7:技術力無いので