20201028のJavaに関する記事は12件です。

thymleaf備忘録

#{...}

  • #{...}は、メッセージ式です。外部のテキストを読み込めます。

th:field

  • th:field(タグのid・name・value属性を、HTMLに出力する機能)

th:errorclass= "..."

  • th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加
  • エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、又はname属性から取得するため、これらと一緒に使う必要がある
spring-mvc3/src/main/resources/templates/confirm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- webjars locatorありバージョン管理が不要-->
<link rel="stylesheet"
        th:href="@{/webjars/bootstrap/css/bootstrap.min.css}">
<title>Title</title>
</head>
<body class="bg-light">
        <div class="container">
                <div class="row justify-content-md-center">
                        <div class="col-md-8 order-md-1">
                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                <h4 class="border-bottom my-3" th:text="#{invoice}"></h4>
                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                <p class="text-danger" th:text="#{confirmationMessage}"></p>

                                <div th:object="${invoice}">
                                        <div class="form-group">
                                                <!-- label要素のfor属性は、ラベル付け対象であるラベル付け可能フォーム関連要素のid属性値を指定する属性 -->
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)-->
                                                <label for="name" th:text="#{name}"></label> <input
                                                        type="text" class="form-control" th:field="*{name}"
                                                        disabled>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)-->
                                                <label for="address" th:text="#{address}"></label> <input
                                                        type="text" class="form-control" th:field="*{address}"
                                                        disabled>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)-->
                                                <label for="phoneNumber" th:text="#{phoneNumber}"></label> <input
                                                        type="tel" class="form-control" th:field="*{phoneNumber}"
                                                        disabled>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)-->
                                                <label for="price" th:text="#{price}"></label> <input
                                                        type="text" class="form-control" th:field="*{price}"
                                                        disabled>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)-->
                                                <label for="paymentDeadline" th:text="#{paymentDeadline}"></label>
                                                <input type="date" class="form-control"
                                                        th:field="*{paymentDeadline}" disabled>
                                        </div>
                                </div>

                        </div>
                </div>
        </div>
        <script th:src="@{/webjars/jquery/jquery.min.js}"></script>
        <script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
        <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</body>
</html>
spring-mvc3/src/main/resources/templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- webjars locatorありバージョン管理が不要-->
<link rel="stylesheet"
        th:href="@{/webjars/bootstrap/css/bootstrap.min.css}">
<title>Title</title>
</head>
<body class="bg-light">
        <div class="container">
                <div class="row justify-content-md-center">
                        <div class="col-md-8">
                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                <h4 class="border-bottom my-3" th:text="#{invoice}"></h4>

                                <form th:action="@{/}" th:object="${invoice}" method="post"
                                        novalidate>
                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 -->
                                                <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、
            又はname属性から取得するため、これらと一緒に使う必要がある。 -->
                                                <label for="name" th:text="#{name}"></label> <input
                                                        type="text" class="form-control"
                                                        th:errorclass="is-invalid" th:field="*{name}">
                                                <div class="invalid-feedback" th:errors="*{name}"></div>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 -->
                                                <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、
            又はname属性から取得するため、これらと一緒に使う必要がある。 -->
                                                <label for="address" th:text="#{address}"></label> <input
                                                        type="text" class="form-control"
                                                        th:errorclass="is-invalid" th:field="*{address}">

                                                <div class="invalid-feedback" th:errors="*{address}"></div>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 -->
                                                <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、
            又はname属性から取得するため、これらと一緒に使う必要がある。 -->
                                                <label for="phone" th:text="#{phoneNumber}"></label> <input
                                                        type="tel" class="form-control" th:errorclass="is-invalid"
                                                        th:field="*{phoneNumber}">
                                                <div class="invalid-feedback" th:errors="*{phoneNumber}"></div>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 -->
                                                <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、
            又はname属性から取得するため、これらと一緒に使う必要がある。 -->
                                                <label for="price" th:text="#{price}"></label> <input
                                                        type="text" class="form-control"
                                                        th:errorclass="is-invalid" th:field="*{price}">
                                                <div class="invalid-feedback" th:errors="*{price}"></div>
                                        </div>

                                        <div class="form-group">
                                                <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                                <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 -->
                                                <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、
            又はname属性から取得するため、これらと一緒に使う必要がある。 -->
                                                <label for="paymentDeadline" th:text="#{paymentDeadline}"></label>
                                                <input type="date" class="form-control"
                                                        th:errorclass="is-invalid" th:field="*{paymentDeadline}">
                                                <div class="invalid-feedback" th:errors="*{paymentDeadline}"></div>
                                        </div>
                                        <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 -->
                                        <button class="btn btn-primary btn-lg btn-block my-4"
                                                type="submit" th:text="#{register}"></button>
                                </form>

                        </div>
                </div>
        </div>
        <script th:src="@{/webjars/jquery/jquery.min.js}"></script>
        <script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
        <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>
</body>
</html>

参考にした記事(いつもありがとうございます。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メールの添付ファイル名が化ける

現象

Spring Boot 2.3.4 (+ spring-boot-starter-mail)で開発したアプリから送信したメールの添付ファイルの名前が文字化けする場合があることに気づきました。
image.png
これはOutlookで受信したメールの添付ファイルの表示です。
以下はその簡略化したメール送信コードです。

import org.springframework.mail.javamail.JavaMailSender
import org.springframework.mail.javamail.MimeMessageHelper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SampleMailApplication : CommandLineRunner {

  @Autowired
  lateinit var mailSender: JavaMailSender

  override fun run(vararg args: String?)
    val message = mailSender.createMimeMessage()
    val helper = MimeMessageHelper(message, true)
    helper.setTo(username)
    helper.setFrom(username)
    helper.setSubject("送信")
    helper.setText("テスト")

    helper.addAttachment("あいうえおかきくけこ", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("あいうえおかきくけこさ", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("あいうえおかきくけこさし", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("あいうえおかきくけこさしす", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("あいうえおかきくけこさしすせ", ByteArrayResource("あいうえお".toByteArray()), "plain/text")

    helper.addAttachment("1234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("12345678901234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text")
    helper.addAttachment("123456789012345678901234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text")

    mailSender.send(message)
  }
}

fun main(args: Array<String>) {
  runApplication<SampleMailApplication>(*args)
}

ファイル名が全角13文字以上だと、文字化けというかエンコードされたままの表示になります。

メールの確認

メールのソースでContent-dispositionを確認すると

// あいうえおかきくけこさし
Content-disposition: attachment;
    filename*=UTF-8''%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%E3%81%8B%E3%81%8D%E3%81%8F%E3%81%91%E3%81%93

// あいうえおかきくけこさしす
Content-disposition: attachment;
    filename="=UTF-8B44GC44GE44GG44GI44GK44GL44GN44GP44GR44GT44GV44GX44GZ44Gb="

全角12文字と13文字を境に、属性名がfilename*とfilenameだとか、エンコードされた文字列のフォーマットが変わっていました。
またOutlookで同じ添付ファイルを送ると、当然ファイル名が化けることなく、ソースを確認すると全角13字以上のファイル名でもfilename*のパターンが使われていました。

ソースコードの調査

そこで調べていくとMimeMessageHelperに次のコードがありました。

public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException {
  Assert.notNull(attachmentFilename, "Attachment filename must not be null");
  Assert.notNull(dataSource, "DataSource must not be null");
  try {
    MimeBodyPart mimeBodyPart = new MimeBodyPart();
    mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT);
    mimeBodyPart.setFileName(isEncodeFilenames() ?
      MimeUtility.encodeText(attachmentFilename) : attachmentFilename);

文字数で処理が分岐する訳ではないですが、フラグでMimeUtility.encodeTextするか制御しているので何かあるのかも?
そこでhelper.addAttachmentを呼び出す前に以下のコードを追加すると、全角13文字以上でもファイル名が化けなくなりました。

helper.isEncodeFilenames = false
  • なお、このフラグは丁度Spring Boot 2.3で使われているSpring Framework 5.2から追加されていました。

根本的な原因まで深堀りできませんでしたが、とりあえず解決できたので良しとします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaでSSLデバッグするときの設定

目的

件名の通り、JavaでSSLデバッグするときの設定の覚書です

設定方法

Javaの起動オプションで javax.net.debug のプロパティ値を allssl に設定します

PaaSなどで起動オプションを明示的に利用しないときは、環境変数 JAVA_OPTS-Djavax.net.debug=all のように設定しています

参考

https://docs.oracle.com/javase/jp/11/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-31B7E142-B874-46E9-8DD0-4E18EC0EB2CF

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

〇〇env系ツールをanyenvで一括管理する

はじめに

この記事は フューチャーAdvent Calendar 2020 の20日目の記事です。

環境構築に便利な〇〇env系ツールを、もっと便利に扱える anyenv に感動したので、ブログ化しました。

開発言語のバージョン管理

複数プロジェクトに関わる場合、開発言語のバージョン管理は必須スキルです。
他の開発メンバーと利用バージョンを一致させなければ、フォーマッタ適用 → PRでの差分爆発が生じてレビュアーに袋叩きにされます。

なので、各エンジニアはそれぞれの創意工夫により、開発言語のバージョン管理問題を解決しています。

私の場合、環境構築ポリシーとして
- ローカル環境への直ダウンロードは厳禁
- 環境構築は基本的にDockerを使う

という方針を、ずっと貫いています。

...

スミマセン。強がりです。
貫けていませんでした。

〇〇env の多用

コロナ影響で在宅勤務へ移行した結果、NWの問題によりDockerがうまく使えない状況が増え、仕方なく〇〇env系ツールの利用を開始しました。

インストールした〇〇env系ツールは
- PJ開発はメインでgoを使う → goenv
- 環境Aはpythonのみ動く → pyenv
- 環境Bはrubyのみ動く → rbenv
- AWSのMFA(多要素認証)でet-otpを使う → jenv

というもので、開発作業で新しい言語が必要になるたびに、対応するツール1つ1つ追加していました。

これらは同じコマンド操作で設定可能なため、ストレスになるのはパッケージのダウンロード時間くらいだと思っていましたが、知らぬ間に開発環境汚染が進んでいました。

bash_profileが汚れていた

担当業務で Vue.js のコードに触れる機会があり、新たに nodenv をインストールしようとしたところ
- 〇〇env系ツールの環境構築って、いっつも同じような環境変数を追加しているな
- bash_profileのメンテしてたっけ?

ということに気づき、cat ~/.bash_profile すると、↓こんな感じになっていました

...
# goenv
export GOENV_ROOT=$HOME/.goenv
export PATH=$GOENV_ROOT/bin:$PATH
eval "$(goenv init -)"

# pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

# jenv
export JENV_ROOT=$HOME/.jenv
export PATH="$HOME/.jenv/bin:$PATH"
eval "$(jenv init -)"

(rbenv, nodenv などの設定が続く...)
...

あれ?

開発環境が汚れないよう注意していたはずなのに
所々で微妙な違いがあり、綺麗な状態とは言い難い...

〇〇env系ツールが増えるごとに
bash_profileに追記し続けるのを止めたい

...

anyenv が便利そう!

anyenvを使って綺麗にする

anyenv自体の説明前に、このツールを使うと何が綺麗になるか?から説明します。

すでにインストール済みのgoenv, pyenvなどはanyenv経由での再設定が必要になりますが、それを乗り越えれば、bash_profile に必要な 〇〇env系ツールの記述 は、以下のみで十分となります。将来的に新しい 〇〇env が必要になった場合でも、bash_profile への追記は不要です。

...
# anyenv
eval "$(anyenv init -)"
...

goenv, jenvごとにPATHを設定 & initする煩わしい記述から解放され、かつ、操作方法は従来の〇〇env系コマンドを利用可能なので、開発環境汚染を少しでも減らしたいエンジニアであれば、このツールを使わない理由は無いと思います。

anyenvとは?

anyenv - All in one for **env

This is a simple wrapper for rbenv style environment managers. You don't have to git clone or modify your shell profile for each **env anymore if you install anyenv.

https://github.com/anyenv/anyenv#anyenv---all-in-one-for-env

端的に言うと、複数の〇〇envを一括管理できる便利ツールです。

執筆時点(2020.12.10)では、以下〇〇envパッケージがanyenvでは利用可能です。
有名どころは網羅しているので、多くの開発プロジェクトで導入可能だと思います。

$ anyenv install -l
  Renv       #R
  crenv      #Crystal
  denv       #D
  erlenv     #Erlang
  exenv      #Elixir
  goenv      #Go
  hsenv      #Haskell
  jenv       #Java
  jlenv      #Julia
  luaenv     #Lua
  nodenv     #Node.js
  phpenv     #PHP
  plenv      #Perl
  pyenv      #Python
  rbenv      #Ruby
  sbtenv     #Sbt
  scalaenv   #Scala
  swiftenv   #Swift
  tfenv      #terraform

上記から欲しい言語をインストールすれば、あとはいつも通りの〇〇env操作コマンドで環境構築可能です。

anyenvのインストール手順

macの場合

Homebrewを利用します。

$ brew install anyenv
$ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l

$ anyenv install --init # enter "y"

Linux環境の場合

GitHubから直接引っ張ってきます。

$ git clone https://github.com/anyenv/anyenv ~/.anyenv
$ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l

$ anyenv install --init # enter "y"

最後に $ anyenv install 〇〇env で欲しいパッケージを取得すれば、これまで通りの〇〇envコマンドが利用できます。

ex) node.jsのv15.0.1が欲しい場合

anyenvを利用し、nodenvを取得します。

$ anyenv install nodenv
$ exec $SHELL -l

$ anyenv versions
nodenv:
Warning: no Node detected on the system

次は、nodenvでv15.0.1を取得します。

$ nodenv install 15.0.1
$ nodenv global(local) 15.0.1

$ node -v
v15.0.1

以上により、Node.js:v15.0.1 が利用可能となりました。

私の開発では goenv, pyenv, jenv, nodenv が必須なので、以下の設定を入れています。
$ anyenv versions により、各envの設定状況が一覧できます。

$ anyenv versions
goenv:
  system
  1.XX.0
* 1.YY.0 (set by /Users/<username>/.anyenv/envs/goenv/version)
jenv:
  system
* XX.0 (set by /Users/<username>/.anyenv/envs/jenv/version)
nodenv:
  system
* XX.0 (set by /Users/<username>/.anyenv/envs/nodenv/version)
pyenv:
  system
  XX.0
* YY.0 (set by /Users/<username>/.anyenv/envs/pyenv/version)

anyenvインストール以前に設定したgoenvやpyenvの環境変数が残っている場合は、bash_profileから削除することも忘れないようご注意ください。

まとめ

  • 環境変数、echo 'export hogehoge"' >> ~/.bash_profileで追加したまま放置していませんか
  • 複数の〇〇env系ツールを使っているなら、anyenvの利用がオススメ
  • anyenvでbash_profileがスッキリ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Exchange OnlineでOAuth 2.0認証でメールを受信する

前回の記事でメール受信できたけど、結局業務で日の目を見ませんでした。
約1年が経って、新たにメール受信するアプリ開発の話がでて、楽勝だと安請け合いしたら世の中はこんなことになってたのね。。。

どうせ1年もしないうちに改造が必要なら、最初から対応しておきたいのが人情。
事前に調べたらExchange Online側を弄る必要があるとのことで、現運用環境を使うわけには行かず、1ヶ月間無料のExchange Onlineを申し込んで、OAuth 2.0に基づく先進認証なるものでメールの受信を試してみました。

ちなみに開発目的のアプリは、定期的にアプリ専用のメールアカウントで受信したメールをテキストファイルに出力する常駐アプリで、認証方法としては"使用しない"ことを勧められているROPCフローを想定しているため、以下はその実装例となってます。

アプリケーションの登録

大まかな処理の流れと、この手順はチュートリアルを参考にしました。

  1. Azure Active Directory 管理センターにアプリがメール受信で使用するアカウントでログインし、[Azure Active Directory]-[アプリの登録]-[新規登録]をクリック。
    image.png

  2. アプリの名前を入力。今回はアプリを使用するのは特定のアカウントのみなので[この組織ディレクトリのみに含まれるアカウント]を選択。リダイレクトURIは使用しないから触らない。そして[登録]をクリック。
    image.png

  3. [パブリック クライアント フローを許可する]を[はい]にして、[保存]をクリック。
    image.png

  4. [APIのアクセス許可]の[アクセス許可の追加]で、[Microsoft Graph]の[委任されたアクセス許可]から[IMAP.AccessAsUser.All]を追加。今回はログインしているアカウントが管理者でもあるので、そのまま[〜に管理者の同意を与えます]をクリック。
    image.png

    • プロトコルでPOPを使用するなら[POP.AccessAsUser.All]を許可する。
    • ここでは別途、送信も試すので[SMTP.Send]を許可している。
    • ログインしているアカウントが管理者でなければ(大抵はそうでしょうけど)、管理者に同意を依頼することになる。管理者アカウントでログインし、[アプリの登録]で[すべてのアプリケーション]を選択して該当のアプリを操作してもらうことになる。

基礎となるアプリケーションの作成

簡素化するためにSpring Boot 2.3.4とKotlinを使用しました。
spring initializerの依存関係で、Java Mail Sender (つまりspring-boot-starter-mail)のみ追加して、プロジェクト作成後のpom.xmlへMicrosoft Authentication Library for Javaを追加。

pom.xml
<dependency>
  <groupId>com.microsoft.azure</groupId>
  <artifactId>msal4j</artifactId>
  <version>1.7.1</version>
</dependency>

CommandLineRunnerを実装し、runメソッドの中に処理を書いて行きます。

@SpringBootApplication
class SampleMailApplication : CommandLineRunner {
  override fun run(vararg args: String?) {
    // 実装
  }
}

アクセストークン取得

上記で参照したチュートリアルのこの手順がイケてません。なんだ「App.xamlを開き」って、C#の記事の残りかよ。。。
しかたなくネットで調べて、以下のコードで動くようになりました。

import com.microsoft.aad.msal4j.PublicClientApplication
import com.microsoft.aad.msal4j.UserNamePasswordParameters

// 割愛
override fun run(vararg args: String?) {
  // 登録したアプリの概要ページにあるアプリケーションIDを指定する
  val applicationId = "..."
  // ...部分に概要ページにあるディレクトリIDを指定する
  val authEndpoint = "https://login.microsoftonline.com/.../oauth2/v2.0/authorize"
  // 使用するプロトコルにあわせて指定する
  val scope = setOf("https://outlook.office365.com/IMAP.AccessAsUser.All",
    "https://outlook.office365.com/SMTP.Send")
  // アカウントのメールアドレスを指定する
  val username = "..."
  // アカウントのパスワードを指定する
  val password = "..."

  val pca = PublicClientApplication.builder(applicationId)
    .authority(authEndpoint)
    .build()

  val parameters = UserNamePasswordParameters
    .builder(scope, username, password.toCharArray())
    .build()
  val result = pca.acquireToken(parameters).join()
  println("アクセストークン: ${result.accessToken()}")
  • APIのアクセス許可で管理者の同意が得られていないと、acquireTokenメソッドで「com.microsoft.aad.msal4j.MsalInteractionRequiredException: AADSTS65001: The user or administrator has not consented to use the application」の例外が発生します。

メール受信

var props = Properties()
// 認証にOAuth 2.0を使用
props["mail.imaps.auth.mechanisms"] = "XOAUTH2"
var session: Session = Session.getInstance(props)
val store: Store = session.getStore("imaps")
// パスワードにアクセストークンを使用
store.connect("outlook.office365.com", 993, username, result.accessToken())
val folderInbox: Folder = store.getFolder("INBOX")
folderInbox.open(Folder.READ_ONLY)
folderInbox.messages.forEach { println("件名: ${it.subject}") }

最初のポイントはmail.imaps.auth.mechanismsで、説明によるとデフォルト値はサポートされている認証からXOAUTH2を除く全てで、Exchange Onlineでは基本認証が使われます。そこでXOAUTH2を指定してOAuth 2.0認証が行われるようにします。

次にパスワードの代わりにアクセストークンを指定するだけで、OAuth 2.0認証でメールの受信が可能になりました。

(補足)メール送信

当面継続ということは、そのうち廃止となるであろうSMTPの基本認証についても、OAuth 2.0認証を試してみたいと思います。
しかし、OAuth 2.0認証どころか、基本認証でも

javax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessful

になるではありませんか!メールボックスで認証済みSMTPはオンになってるのに。。。

そのページで「認証ポリシーによって SMTP の基本認証が無効になっている場合、この記事で説明されている設定を有効にしても、クライアントは SMTP 認証プロトコルを使用できません。」とあるので[セキュリティの既定値群の有効化]を[いいえ]したらメール送信が成功するようになったのですが、今度は[セキュリティの既定値群の有効化]を[はい]に戻しても、もうエラーは起きません。(他に弄った何かが影響している可能性は否定できません。。。)

なんだか良く判らない、、、

一応、成功した送信ロジックも記載します。

val props = Properties()
props["mail.smtp.auth"] = "true"
props["mail.smtp.auth.mechanisms"] = "XOAUTH2";
props["mail.smtp.starttls.enable"] = "true"
val session = Session.getInstance(props)
val transport = session.getTransport("smtp")
transport.connect("smtp.office365.com", 587, username, result.accessToken())

val sendMessage = MimeMessage(session)
sendMessage.addRecipients(Message.RecipientType.TO, username)
sendMessage.setFrom(username)
sendMessage.subject = "送信"
sendMessage.setText("送信テスト")

transport.sendMessage(sendMessage, sendMessage.allRecipients)

なお、送信自体はできているのですが、送り先から「550 5.7.501 Service unavailable. Spam abuse detected from IP range. 」で最終的な配達まできていません。これは onmicrosoft.com ドメインからメールはスパムとしてフィルターされるからで、試しにOutlookから送信しても同じ結果になるので、ロジックとしては問題ないと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacにJavaを簡単に入れてみた

概要

MacにJavaをインストールします。
(注: Homebrewは用意されている前提です。)

環境

  • macOS Catalina 10.15.7
  • Homebrew 2.5.7
  • Java openjdk 15.0.1 2020-10-20

手順

Javaのインストール

Homebrewを使ってインストールします。

brewの更新

brew update

バージョン指定なし(最新)の場合

AdoptOpenJDKをインストール
brew cask install adoptopenjdk

バージョン指定したいの場合

リポジトリをフォーミュラとしてHomebrewに追加

brew tap AdoptOpenJDK/openjdk

AdoptOpenJDK15をインストール

brew cask install adoptopenjdk15

確認

以下のコマンドでjavaのバージョンが表示されたらOKです。
bash
java --version

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javasilver勉強会 備忘録

Javasilver勉強会

p96 問1

  • int[] 型のtoStringメソッドはオーバライドされていない

  • →ObjectクラスのtoStringメソッドが実行される

  • String型のtoStringメソッドはオーバライドされている

  • →StringクラスのtoStringメソッドが実行される

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Sentryの情報にアプリケーションのバージョン情報を追加する方法

前提

Sentryを使用してエラー情報を検知しているアプリケーションであること。

使用言語、フレームワーク

  • Java 8
  • Spring Boot(Maven)

概要

Sentryに送信されるエラー情報がいつのバージョンのアプリケーションで発生したエラーのものであるか確認できるようにしたい。

メリット

  • 発生したエラーのアプリケーションのバージョンの特定が一目でわかる。
  • Sentryの画面で特定のバージョンで発生したエラーの一覧を以下のように検索することができるようになる。
スクリーンショット 2020-10-15 11.33.52.png

前提条件

  • アプリケーションのバージョンが定義されていること。
  • リリースの度にバージョンが更新されていること。

以下の定義ファイルの <version> 内をSentryに送信します。
今回は、バージョン情報をSentryにタグとして送信します。

定義ファイル

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmls="~">
     :
    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>100.0.100</version>
     :
</project>

修正手順

以下の順番で修正を行います。
1. 依存関係の追加
2. エンドポイントの追加
3. エンドポイントに表示する情報の追加
4. Sentryへの送信処理の追加

1. 依存関係の追加

pom.xml
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

2. エンドポイントの追加

application.yml
management:
  endpoints:
    web:
      exposure:
        include: "info"
      base-path: "/"

base-pathは環境に応じて変更してください。

3. エンドポイントに表示する情報の追加

application.yml
info:
  application:
    version: @project.version@

ここまで設定すると http://localhost:8080/info にアクセスすることで以下のようにここまでの手順で設定したバージョン情報を確認することができます。

{"application":{"version":"100.0.100"}}

4. Sentryへの送信処理の追加

設定ファイル:@SpringBootApplicationアノテーションを設定しているクラスに以下の処理を追加します。
※ すでにSentryへの送信処理を実装している場合は、version 関連の処理を追加してください。
 (引数の追加とtagの送信部分です。)
※ 戻り値の型は

  @Bean
  public HandlerExceptionResolver sentryExceptionResolver(
    @Value("${sentry.url:#{null}}") Optional<String> sentryUrl,
    @Value("${info.application.version:#{null}}") Optional<String> appVersion) {

    if (sentryUrl.isPresent()) {
      try {
        SentryClient sentryClient = Sentry.init(sentryUrl.get());
        // ここで、タグとしてバージョン情報を追加しています。
        appVersion.ifPresent(s -> sentryClient.addTag("version", s));
      } catch (InvalidDsnException e) {
        LOGGER.warn(e.getMessage(), e);
      }
    }
    return new SentryExceptionResolverImpl();
  }

sentry.urlinfo.application.version と同様に application.yml に定義しています。

設定後

上記設定を完了後、アプリケーション内でエラーが発生した場合、以下のような versionのタグが表示されるようになります。
versionの数値部分をクリックすることで、冒頭でメリットとして挙げた検索結果を確認できます。

スクリーンショット 2020-10-15 11.27.03.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Javaの全体像

Javaエディション(環境)

  • JavaSE(Java Platform, StandardEdition)

    • JavaSEはJavaアプリを動作させるための基本的な実行/開発環境を提供
    • 文字列/数値、日付の操作、入出力処理など、汎用的に利用できるライブラリ
    • 基本的にこれしか使わない
  • JavaEE(Java Platform, Enterprise Edition)

    • JavaEEは、このJavaSEの上で動作するサーバーアプリ開発のためのライブラリ
    • 主に企業向けのシステム開発を想定した機能
  • JavaME(Java Platform, Micro Edition)

    • リソースの限られた環境への組み込み用(携帯、家電など)
    • 近年のIoTの流れの中で、JavaSEに統合

Javaの歴史

  • Java2 (version1.2)で環境に応じてエディションが整理されたのち、JDKがJ2SE(Java2 Platform, Standard Edition)と呼ばれるようになった
  • Ver6では、J2SEという呼称もJavaSEに改められる
  • Java8、9でラムダ式、モジュールなどのモダンな機能が追加される
  • Java11でOracleJDKが有償化(OpenJDKは無償)
  • OpenJDKにはLTS提供なし(Long Term Support:長期サポート)

環境

  • JDK(Java Development Kit)

    • JDKは、Javaアプリを開発/実行するために必要なソフトウェア
    • Java仮想マシン、コンパイラー、クラスライブラリ、デバッガーを備えた開発キット
    • OracleJDK、OpenJDK(オープンソース)、AdoptOpenJDK(IBM、マイクロソフトなど)
  • 統合開発環境(IDE:Integrated Development Environment)

    • コーディングを支援する諸機能を取りそろえたソフトウェア
    • コードエディター、デバッガー、プロジェクト管理機能など
    • ボタン1つでコンパイルから実行までを一気に実行
    • Eclipse、NetBeans、IntelliJIDEAなど

Javaコードの全体像

Class

  • Javaによるプログラムの基本
  • クラスはアプリの中で特定の機能を担うかたまり
    • String Class:文字列
    • Math Class:数学機能
    • FileReader/FileWriter:テキストファイルの読み書き
  • これらのクラスを組み合わせアプリを作る
  • Javaでは、ファイルとクラスを対応関係にする
    • ex:HelloクラスはHello.javaというファイルで定義

メソッド

  • class{...}配下に含まれる要素のことをメンバ(フィールド、メソッド、コンストラクターなど)
  • メソッドは、クラスの「機能」を表すためのメンバー、アプリで実行すべき処理を表す
  • Javaではアプリを起動したときに、まずmainメソッドを探して実行:エントリーポイントという

パッケージ

  • クラスを分類するためのいれもの
  • package com.example.demo.trySpring;配下にあるHelloControllerクラスなど

名前解決

  • クラスはパッケージ+クラス名で識別:完全修飾名(FQCN:Fully Qualified Class Name)という
  • FQCN:package com.example.demo.trySpring.HelloController

ドキュメンテーションコメント

  • あとでドキュメントの生成に利用できるコメントでアスタリスク2個で開始(複数行コメントは*1個)
  • クラスやそのメンバーの説明を記述する
/***Reads text from a character input stream,...  //クラスの概要
*
*@see FileReader //関連項目
*@author MarkReinhold //作者
*@since1.1 //導入されたバージョン
*/

/***Create a buffering character-input stream from…  //コンストラクタの概要
*@param in A Reader //引数
*@param sz Input-buffer size //引数
*@exception //例外
*@retutn //戻り値
*/

デバッグの基本

  • ブレークポイント設置
    • 実行中のプログラムを一時停止する機能
    • エディターから行番号のひいだりクリック
    • ブレークポイントでプログラムの状態確認する
  • デバッグ実行
    • ブレークポイントからは文単位でコード実行できる:ステップ実行
      • ステップイン:1行実行
      • ステップオーバー:途中メソッド呼び出し実行し次の行へ
      • ステップリターン:現メソッド呼びだし元に戻るまで実行
  • 実行再開/終了
    • 通常実行に戻すときは再開,終了したいときは終了ボタン
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

手動で@ConfigurationProperties相当の処理をする

Spring Bootの@ConfigurationPropertiesはプロパティファイルの設定値を自動的に読み込み仕組みだが、それと似たような処理を手動で記述したい。

たとえば、いま下記のようなkafka.propertiesがあり、これをorg.springframework.boot.autoconfigure.kafka.KafkaPropertiesに読み込みたい、とする。

kafka.properties
spring.kafka.bootstrap-servers=localhost:32770
spring.kafka.consumer.group-id=java-consumer-group
KafkaProperties
@ConfigurationProperties(prefix = "spring.kafka")
public class KafkaProperties {
  ...

これをするには、Mapを設定値のソースとするMapConfigurationPropertySourceを使用する(PropertiesHashtableを継承している)。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;

public class HogeMain {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.load(Files.newInputStream(Paths.get("kafka.properties")));

        ConfigurationPropertySource source= new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(source);

        KafkaProperties kafkaProperties = binder.bind("spring.kafka", KafkaProperties.class).get();
    }
}

yamlの場合、YamlPropertiesFactoryBeanからPropertiesが得られるので、これを経由させる。

YamlPropertiesFactoryBean y = new YamlPropertiesFactoryBean();
y.setResources(new ClassPathResource("hoge.yml"));
Properties object = y.getObject();

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】DBマイグレーション(Flyway)

DBマイグレーション(スキーマ管理)ライブラリであるFlywayを利用する。
以下のような目的で利用する。
・DBの定義やその変更に関する情報を一箇所で管理する。
・その情報に基づき最新のDBの状態を自動で再現する。

MavenプロジェクトでFlywayを利用するための設定

pom.xml
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>

SpringBootの設定ファイルを修正(application.yml)

SpringBootはデフォルトで、flywayが起動時に自動実行される。

flyway:
  baseline-on-migrate: true
  baselineVersionAsString: 0.0.0
  baseline-description: Initial

テーブルの定義ファイルを追加

以下フォルダにsqlファイルを追加する。
src/main/resources/db/migration/

・マイグレーションファイル
管理対象のスキーマに適用する変更内容を記述したSQLファイル
以下の規則で作成する。
V{version}__{description}.sql

例)
V0_0_1__createTable.sql

・リピータブルマイグレーションファイル
ビュー、プロシージャ、ファンクションなどのオブジェクトのマイグレーションを行いたい場合に使用する。
以下の規則で作成する。
R__{description}.sql

例)
R__createView.sql

公式サイト

https://flywaydb.org/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】パスワード生成(Pasay)

パスワードポリシー実施ライブラリであるPasayを利用する。
設定可能なルールセットを使用してパスワードを生成することができる。

MavenプロジェクトでPasayを利用するための設定

pom.xml
<dependency>
    <groupId>org.passay</groupId>
    <artifactId>passay</artifactId>
    <version>1.6.0</version>
</dependency>

大文字小文字英数字混合の8桁のパスワードを生成するコード

Password.java
public class Password {

    public static String generate() {
        List<CharacterRule> rules = Arrays.asList(
                  new CharacterRule(EnglishCharacterData.UpperCase, 1),
                  new CharacterRule(EnglishCharacterData.LowerCase, 1),
                  new CharacterRule(EnglishCharacterData.Digit, 1)
            );
        PasswordGenerator generator = new PasswordGenerator();
        String password = generator.generatePassword(8, rules);
        return password;
    }
}

テストコード

PasswordTest.java
public class PasswordTest {

    @Test
    public void testGenerate() {
        String password = Password.generate();
        assertTrue(password.length() == 8);
        assertTrue(password.matches(".*[a-z].*"));
        assertTrue(password.matches(".*[0-9].*"));
        assertTrue(password.matches(".*[A-Z].*"));
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む