Plugin could not be resolved. Ensure the plugin’s groupId, artifactId and version are present.が表示される

問題点

Eclipseでpom.xml開くと下記artifactIdの行にワーニングが表示されていた。

<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
==省略==
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-project-info-reports-plugin</artifactId>
==省略==

Plugin could not be resolved. Ensure the plugin's groupId, artifactId and version are present.
Additional information: Unable to resolve org.apache.maven.plugins:maven-javadoc-plugin
Plugin could not be resolved. Ensure the plugin's groupId, artifactId and version are present.
Additional information: Unable to resolve org.apache.maven.plugins:maven-project-info-reports-plugin

Maven Repository上に存在はしている。
https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-javadoc-plugin
https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-project-info-reports-plugin

修正点

原因は<reporting>のみ記述していたこと。下記のように<pluginManagement>にも記述したらワーニングは表示されなくなった。

<pluginManagement>
    <plugins>
        <plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-project-info-reports-plugin</artifactId>
            </plugin>

Jxls 2から3にアップグレードする

<!-- https://mvnrepository.com/artifact/org.jxls/jxls-poi -->
<dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls-poi</artifactId>
    <version>3.0.0</version>
</dependency>

メジャーバージョンが3に上がり、Javaコードを殆ど書き直すことになったため、そのメモを残す。

参考URL:https://jxls.sourceforge.net/migration-to-v3-0.html

Jxls 2

殆ど全てが変わったので、比較対象にならないが、変更前のコードは下記の通りとなっている。

import org.jxls.area.Area;
import org.jxls.builder.AreaBuilder;
import org.jxls.builder.xls.XlsCommentAreaBuilder;
import org.jxls.common.CellRef;
import org.jxls.common.Context;
import org.jxls.util.TransformerFactory;

// 出力部分抜粋
try (InputStream is = getTemplateSource(getUrl(), request);
        OutputStream os = response.getOutputStream()) {
    var transformer = TransformerFactory.createTransformer(is, os);
    AreaBuilder areaBuilder = new XlsCommentAreaBuilder(transformer);
    List<Area> xlsAreaList = areaBuilder.build();
    var xlsArea = xlsAreaList.get(0);
    var context = new Context();

    model.entrySet().forEach(mode -> context.putVar(mode.getKey(), mode.getValue()));

    xlsArea.applyAt(new CellRef("Sheet1!A1"), context);
    transformer.write();
}

※getTemplateSourceはテンプレート用のエクセルファイルを読み込んでいるメソッド。

Jxls 3

Java部分は下記の通りかなりシンプルになった。テンプレートファイル自体は変更の必要なかった。

import org.jxls.builder.JxlsStreaming;
import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;

// 出力部分抜粋
try (InputStream is = getTemplateSource(getUrl(), request);
        OutputStream os = response.getOutputStream()) {
    JxlsPoiTemplateFillerBuilder.newInstance()
        .withStreaming(JxlsStreaming.STREAMING_ON)
        .withTemplate(is)
        .buildAndFill(model, new JxlsOutputStream(os));
}

ファイル出力用のクラスはファイルをローカルに保存するJxlsOutputFile.classのみ提供されているため、必要に応じて自分で実装する。今回はHttpResponseのOutputStreamに直接乗せたかったので、下記の通り実装した。

import java.io.IOException;
import java.io.OutputStream;

import org.jxls.builder.JxlsOutput;

/**
 * Excelを書き込むためのOutputStreamを提供するクラス.
 */
public class JxlsOutputStream implements JxlsOutput {

    private OutputStream os;

    public JxlsOutputStream(OutputStream os) {
        this.os = os;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public OutputStream getOutputStream() throws IOException {
        return os;
    }
}

Switch Expressionsに書き換える

しばらく放置していたが、Java 14から正式に追加されたSwitch Expressions、Java 21から追加されたPattern Matching for switchで既存の処理を書き換えることにした。

Java 11から21までの間のSwitch文の大きな変更は2つあって、分けて考えた方がわかりやすい。

Java 14で追加されたSwitch Expressions

これに書き換えることのメリットは2つと考えている。

1.breakが不要

Switch文から抜けるためのbreakが不要になった。breakがなくても同じように機能する。

例)変更前
switch (code) {
case 401, 403:
    model.addAttribute("errorTitle", "403");
    break;
case 404:
    model.addAttribute("errorTitle", "404");
    break;
default:
    model.addAttribute("errorTitle", "error");
}
変更後
switch (code) {
case 401, 403 -> model.addAttribute("errorTitle", "403");
case 404 -> model.addAttribute("errorTitle", "404");
default -> model.addAttribute("errorTitle", "error");
};

2.無駄な記述が減る

breakが不要になったことも含めて、無駄な記述を減らせる。

例)変更前
int multiple = 0;

switch (unitSign) {
case M_BYTE:
    multiple = 1024 * 1024;
    break;
case K_BYTE:
    multiple = 1024;
    break;
case BYTE:
    multiple = 1;
}
変更後
int multiple = switch (unitSign) {
case M_BYTE -> 1024 * 1024;
case K_BYTE -> 1024;
case BYTE -> 1;
};

Java 21で追加されたPattern Matching for switch

パターンマッチが使えるようになったが、正直すぐ使いそうなのはnullのチェックくらいだろうか。

例)変更前
public static FileConverter createConverter(FileType fileType) {
    if (fileType == null) {
        throw new FileException("errors.fileType");
    }

    switch (fileType) {
    case XML:
        return createXmlConverter();
    case EXCEL:
        return createExcelConverter();
    case CSV:
        return createCsvConverter();
    default:
        throw new FileException("errors.fileType");
    }
}
変更後
public static FileConverter createConverter(FileType fileType) {
    return switch (fileType) {
    case XML -> createXmlConverter();
    case EXCEL -> createExcelConverter();
    case CSV -> createCsvConverter();
    case null, default -> throw new FileException("errors.fileType");
    };
}

Gradle 7から8にアップグレードする

Gradle 7から8にアップデートした時のメモ。

非推奨になった箇所を修正した。
参考URL
https://docs.gradle.org/current/userguide/upgrading_version_8.html#changes_8.2

sourceCompatibility = '21'

java {
    sourceCompatibility = JavaVersion.VERSION_21
}

他にもpluginsで読み込んだものは、war{}のように囲んでから、プロパティを記述する必要がありそう。

例)

plugins {
    id 'war'
}

war {
    webAppDirectory = file('src/main/webapp')
}

PyTorch 2.2にアップグレードする

環境

Windows 11
Microsoft Store版 Python 3.10

動機

1年ほど前からバージョンアップしていなかったので、なんとなく更新することにした。

python -c "import torch; print( torch.__version__ )"
2.0.1+cu118

必要となるCUDAのバージョンの確認

PyTorchのページにアクセスし、使用可能なCUDAのバージョンを確認する。PyTorch 2.2.1では、11.8と12.1となっている。今回は12.1を使用してみる。

CUDA Toolkit 12.1.1のインストール

NVIDIA DeveloperのページからCUDA Toolkit 12.1.1をダウンロードし、インストールする。

なぜか、高速インストールだとエラーになってしまったので、カスタムを選択する。

CUDAのRuntime、Documentation、Developmentのみチェックして、次へをクリックすると正常にインストールが始まった。Nsightは後で入れることにする。

正常にインストール完了した。

cuDNN v8のインストール

NVIDIA DeveloperのページからcuDNN v8.9.7 (December 5th, 2023), for CUDA 12.xをダウンロードし、任意のファルダに解凍、binフォルダにパスを通す。

PyTorch 2.2のインストール

1.旧バージョンを削除

pip uninstall torch torchvision torchaudio

2.新バージョンをインストール

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

3.確認

インストール完了後、下記のコマンドを実行しバージョンを確認する。

python -c "import torch; print( torch.__version__ )"
2.2.1+cu121
import torch

print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name())

2.2.1+cu121
True
NVIDIA GeForce RTX 3060 Ti

OAuth 2.0を使用して Office365のSMTPでメール送信したい

注意

Microsoft Exchangeのアカウント(例:@の後ろが企業名とか独自のドメインになっているもの)でないとSMTPのOAuth認証を有効に出来ないような。アカウント持っていないので詳細はわからない。

個人アカウントでも、Microsoft Entraでアプリの登録が出来るので、トークン取得部分だけ動作確認した。Microsoft Exchangeのアカウントがあれば、通しでも多分動くはず。

build.gradle
plugins {
	id 'java'
}

group = 'oauth2hotmail'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
    implementation 'com.microsoft.azure:msal4j:1.14.2'
    implementation 'com.sun.mail:jakarta.mail:2.0.1'
}

tasks.named('test') {
	useJUnitPlatform()
}
HotmailOAuth.java
public class HotmailOAuth {

    private static final String SCOPE = "https://graph.microsoft.com/.default";
    // アプリケーション(クライアント) ID
    private static final String CLIENT_ID = "";
    // クライアント シークレット
    private static final String CLIENT_SECRET = "";
    // OAuth 2.0 トークン エンドポイント (v2)
    private static final String AUTHORITY = "https://login.microsoftonline.com/ディレクトリ(テナント) ID/oauth2/v2.0/token";
    private static final String MAIL_FROM = "sender@foo.bar";
    private static final String MAIL_TO = "receiver@foo.bar";

    public static void main(String[] args) throws MalformedURLException, AddressException, MessagingException {
        // アクセス トークンを取得
        var token = getAccessTokenFromSecret();

        // SMTP セッションを作成
        var props = new Properties();
        props.put("mail.smtp.host", "それぞれのSMTPホスト名");
        props.put("mail.smtp.port", "587");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
        props.put("mail.smtp.starttls.enable", "true");
        var session = Session.getDefaultInstance(props);

        // メールを作成
        var message = new MimeMessage(session);
        message.setFrom(new InternetAddress(MAIL_FROM));
        message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO));
        message.setSubject("OAuth 2.0認証を使って送信したメール");
        message.setText("このメールは、OAuth 2.0 認証を使って送信しました。");

        // メールを送信
        var transport = session.getTransport("smtp");
        transport.connect(MAIL_FROM, token);
        transport.sendMessage(message, message.getAllRecipients());
        transport.close();

        System.out.println("メールを送信しました。");
    }

    public static String getAccessTokenFromSecret() throws MalformedURLException {
      var credential = ClientCredentialFactory.createFromSecret(CLIENT_SECRET);

      var cca = ConfidentialClientApplication
              .builder(CLIENT_ID, credential)
              .authority(AUTHORITY)
              .build();

      var parameters = ClientCredentialParameters
              .builder(Set.of(SCOPE))
              .build();

      var result = cca.acquireToken(parameters).join();

      return result.accessToken();
    }
}

Hibernate Search 7.0 Migration

Hibernate Search 7.0がリリースされていたので、アップグレードした。今回プログラムの修正は必要なかった。

pom.xmlの変更点

  • lucene-analyzers-kuromojiをlucene-analysis-kuromojiに変更
  • hibernate-search-mapper-orm-orm6をhibernate-search-mapper-ormに変更
<!-- Hibernate Search & Lucene -->
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analysis-kuromoji</artifactId>
    <version>9.8.0</version>
</dependency>
<dependency>
    <groupId>org.hibernate.search</groupId>
    <artifactId>hibernate-search-mapper-orm</artifactId>
    <version>7.0.0.Final</version>
</dependency>
<dependency>
   <groupId>org.hibernate.search</groupId>
   <artifactId>hibernate-search-backend-lucene</artifactId>
   <version>7.0.0.Final</version>
</dependency>

バージョンを変えるだけでなく、artifactIdを変更する必要がある。luceneを使用している場合はそちらも変更する必要がある。

OAuth 2.0を使用して GmailのSMTPでメール送信したい

前提条件

Gmailのアカウントを持っていること。

Google Cloudの設定

https://console.cloud.google.com/にアクセスして、プロジェクトを作成する。

わかりやすい名前を付ける。

User Typeは「外部」を選択する。

テスト用なのでアプリ情報は適当に入力する。

プロジェクトを作成したら、「認証情報」タブの「認証情報を作成」をクリックする。

「OAuthクライアントID」を選択する。

アプリケーションの種類には「デスクトップアプリ」を選択する。メールを送信するだけなので、ライブラリからGmail APIを追加する必要はない。

作成が完了したら、クライアント ID、クライアント シークレットが発行されるので、メモしておく。

プログラムの例

build.gradle
plugins {
	id 'java'
}

group = 'oauth2gmail'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
    implementation 'com.google.api-client:google-api-client:2.2.0'
    implementation 'com.sun.mail:jakarta.mail:2.0.1'
}

tasks.named('test') {
	useJUnitPlatform()
}
GmailOAuth.java

メモしておいたクライアント ID、クライアント シークレットを、CLIENT_ID、CLIENT_SECRET変数にそれぞれ設定する。

今回リダイレクト処理は必要ないので、リダイレクトURLにはurn:ietf:wg:oauth:2.0:oobを設定する。これを設定するとリダイレクトせずに直接画面に認証コードが表示される。

MAIL_FROMにGmailのアドレスを設定する。MAIL_TOにはテストメールを送っても大丈夫なメールアドレスを設定する。

public class GmailOAuth {

    private static final String SCOPE = "https://mail.google.com/";
    private static final String CLIENT_ID = "CLIENT_ID";
    private static final String CLIENT_SECRET = "CLIENT_SECRET";
    private static final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
    private static final String MAIL_FROM = "sender@foo.bar";
    private static final String MAIL_TO = "receiver@foo.bar";

    public static void main(String[] args) throws IOException, MessagingException {
        // 取得した認証コードを保存するフォルダを指定
        var fileDataStoreFactory = new FileDataStoreFactory(new File(System.getProperty("java.io.tmpdir")));

        // OAuth 2.0 の認証フローを作成
        var flow = new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(),
                GsonFactory.getDefaultInstance(), CLIENT_ID, CLIENT_SECRET, Set.of(SCOPE))
                        .setCredentialDataStore(StoredCredential.getDefaultDataStore(fileDataStoreFactory)).build();

        // アクセス トークンを取得
        var credential = flow.loadCredential("user");

        // 取得済みの認証情報があるか、またそれは有効か確認
        if (credential == null || (credential.getExpiresInSeconds() < 100 && !credential.refreshToken())) {
            // 有効なコードがなかった場合、新たに認可コードを取得
            var url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
            System.out.println("Please open the following URL in your browser then type the authorization code:");
            System.out.println("  " + url);
            System.out.println("Please enter your authentication code:");

            try (var s = new Scanner(System.in)) {
                var code = s.nextLine();
                var tokenResponse = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
                credential = flow.createAndStoreCredential(tokenResponse, "user");
            }
        }

        // SMTP セッションを作成
        var props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.port", "587");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
        props.put("mail.smtp.starttls.enable", "true");
        var session = Session.getDefaultInstance(props);

        // メールを作成
        var message = new MimeMessage(session);
        message.setFrom(new InternetAddress(MAIL_FROM));
        message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO));
        message.setSubject("Gmail から送信したメール");
        message.setText("このメールは、Gmail の SMTP で OAuth 2.0 認証を使って送信しました。");

        // メールを送信
        var transport = session.getTransport("smtp");
        transport.connect(MAIL_FROM, credential.getAccessToken());
        transport.sendMessage(message, message.getAllRecipients());
        transport.close();

        System.out.println("メールを送信しました。");
    }
}

実行

初回実行時、下記のメッセージが表示されるようになっている。表示されているURLにブラウザでアクセスし、アクセス権を付与する。

Please open the following URL in your browser then type the authorization code:
  ここに表示されるURLにブラウザでアクセスする
Please enter your authentication code:

ChromeブラウザでGmailにログイン済みの状態であれば、下記の通りアカウントの選択に表示される。もちろん別のアカウントを選択しても良い。

テスト用のプロジェクトのため、注意事項が表示されるがそのまま続行する。

アクセス権の付与が間違いないことを確認して、続行する。

表示された認証コードをコピーして、「Please enter your authentication code:」の下に貼り付けEnterを押下する。

「メールを送信しました。」と表示されたら、送信先のメールアドレスにメールが送信されたことを確認する。

2回目以降は、認証コードの有効期限内であれば、上記の手順無しでメール送信できる。逆にもう一度最初から実行したい場合は、「System.getProperty(“java.io.tmpdir”)」フォルダ内の「StoredCredential」というファイルを削除する。

ChromeでYouTube視聴中マウスを動かすとノイズが表示される

ChromeでYouTube視聴中マウスを動かすと、黒と透過の市松模様のノイズが画面に表示されることがある。

Chromeの設定変更で直ると記載があったので、下記の通り変更してみた。

1.Chromeのアドレスバーに「chrome://flags/」と入力してEnter

2.検索ボックスに「Choose ANGLE graphics backend」と入力

3.プルダウンを選択し、DefaultからOpenGLに変更

4.Chromeを再起動

Spring IOのport out of range -1エラー

Spring Tools Suite、またはそのプラグインを入れたEclipseで「Failed to fetch Generation from Spring IO: port out of range:-1」というエラーが表示される場合、以下の設定を変更することでエラー解消することが出来る。

Eclipseの設定を開き、一般>ネットワーク接続のアクティブ・プロバイダーを「直接」に変更する。