Elasticsearchのindex templateを試す

index templateを使うと、インデックスの各種設定値をあらかじめ決めておくことができます。

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html

index templateは、複数のcomposed templateを含めることができます。composed templateはインデックス設定項目のうち、次の内容を再利用できるよう独立して定義できるものです。

  • mappings
  • settings
  • aliases

composed templateは使いまわすことができるので、共通の設定値を定義しておくといった使い方ができそうです。

次のdocker-compose.ymlを使用して、index templateとcomposed templateを使ってみます。

version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.16.2
    ports:
      - 9200:9200
      - 9300:9300
    environment:
      - discovery.type=single-node
      - action.destructive_requires_name=true
    networks:
      - elastic
  ki01:
    image: docker.elastic.co/kibana/kibana:7.16.2
    environment:
      ELASTICSEARCH_HOSTS: "http://es01:9200"
    ports:
      - 5601:5601
    networks:
      - elastic
networks:
  elastic:
    driver: bridge

kibanaのDevTools画面から操作しました。

composed templateを定義します。

PUT _component_template/component_template1
{
  "template": {
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        }
      }
    }
  }
}

定義を確認します。

GET _component_template/component_template1

// レスポンス
{
  "component_templates" : [
    {
      "name" : "component_template1",
      "component_template" : {
        "template" : {
          "mappings" : {
            "properties" : {
              "@timestamp" : {
                "type" : "date"
              }
            }
          }
        }
      }
    }
  ]
}

この、 component_template1 を使って、index templateを定義します。

PUT _index_template/template1
{
  "index_patterns": ["te*"],
  "template": {
    "settings": {
      "number_of_shards": 3
    },
    "mappings": {
      "_source": {
        "enabled": true
      },
      "properties": {
        "host_name": {
          "type": "keyword"
        },
        "created_at": {
          "type": "date",
          "format": "strict_date"
        }
      }
    }
  },
  "composed_of": ["component_template1"]
}

index templateは次の設定にしました。

  • index_patterns : このindex templateが適用される、インデックス名です。ワイルドカードで、 te から始まるインデックスに対して、適用されるようにしました。複数のインデックス名を指定することもできます。
  • template.settings.number_of_shards : シャード数を3にしています。
  • template.mappings : フィールドのデータ型を定義します。 created_at には、日付の型を定義し、フォーマットで strict_date を指定しました。
  • composed_of : composed indexを列挙します。

インデックスを作成し、各設定を見てみます。

PUT test_index1

GET test_index1/_mapping

// レスポンス
{
  "test_index1" : {
    "mappings" : {
      "properties" : {
        "@timestamp" : {
          "type" : "date"
        },
        "created_at" : {
          "type" : "date",
          "format" : "strict_date"
        },
        "host_name" : {
          "type" : "keyword"
        }
      }
    }
  }
}

index templateとcomposed templateで設定した各項目が反映されているのがわかります。

Elasticsearchのクラスタ構成を試す

クラスタを組んでみます。

ノードは3つで、それぞれをmaster-eligable、data、Ingestの役割を割り当ててみます。

Node | Elasticsearch Guide [7.16] | Elastic

環境はdocker-composeを使いました。

$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c

以下、 docker-compose.yml です。

version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    ports:
      - 9200:9200
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
    networks:
      - elastic
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
    networks:
      - elastic
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
    networks:
      - elastic
networks:
  elastic:
    driver: bridge

それぞれのコンテナの環境変数で定義している、 discovery.seed_hostsクラスタを構成する時に相手を探しに行く先になります。ホスト名、またはIPアドレスを指定します。ポートを省略すると、9300と解釈されます。

https://www.elastic.co/guide/en/elasticsearch/reference/7.16/important-settings.html#unicast.hosts

cluster.initial_master_nodesクラスタを初期構築する際に、 master を決める投票に参加するノードを指定します。

https://www.elastic.co/guide/en/elasticsearch/reference/7.16/important-settings.html#initial_master_nodes

docker-compose up で起動させ、動作確認してみます。

$ curl -XGET http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "es-docker-cluster",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 3,
  "number_of_data_nodes" : 3,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

"status" : "green" となっているので、正しく起動できたようです。

Qodanaを試す

JetBrains製の静的解析ツールが出ていたので、試してみました。

今の所、以下の言語に対応しているみたいです。Java以外はEAPなんですね。

今回はJavaで試します。

Qodana for JVM

Qodana for JVM はサーバーサイドKotlinかJavaを解析できるようです。Scalaは近日公開とのこと。

手元のKotlinのプロジェクトで試してみます。

Dockerで動かします。

Qodana for JVM Docker image | Qodana

docker run --rm -it -p 8080:8080 \
  -v <source-directory>/:/data/project/ \
  -v <output-directory>/:/data/results/ \
  jetbrains/qodana-jvm-community --show-report

実際に動かしてみます。

docker run --rm -it -p 8080:8080 \
  -v "$(pwd)"/direct/:/data/project/ \
  -v "$(pwd)"/report/:/data/results/ \
  jetbrains/qodana-jvm-community --show-report

/data/project/ にプロジェクトを配置すれば、解析されます。結果は /data/results/ に吐き出されます。

解析には少し(四分程度)かかりました。時間はプロジェクトの規模に関係しているかもしれません。また、解析にはメモリを大量に消費するようです。問題があればDockerのメモリ割り当てを増やす必要があるかもしれません。

By using this Docker image, you agree to the Qodana Community Linters Agreement (https://www.jetbrains.com/legal/docs/agreements/qodana/community-linters/) and JetBrains privacy policy (https://www.jetbrains.com/legal/docs/privacy/privacy/).
                                        
     QQQQQQQ      DDDDDDDDDDD           Qodana for JVM Community linter
   QQ:::::::QQ    D::::::::::DDD        Analyze project written in Java and Kot
 QQ:::::::::::QQ  D:::::::::::::DD      lin
Q::::::QQQ::::::Q DDD::::DDDDD::::D     To see the complete list of supported t
Q:::::O   Q:::::Q   D::::D    D::::D    echnologies and languages,
Q::::O     Q::::Q   D::::D     D::::D   visit the Qodana (https://www.jetbrains
Q::::O     Q::::Q   D::::D     D::::D   .com/qodana) documentation.
Q::::O   QQQ::::Q   D::::D     D::::D   Contact us at
Q:::::O  Q::::::Q   D::::D    D:::DD    qodana-support@jetbrains.com
 QQ::::QQ::::::Q  DDD::::DDDDD::DD      Or via our issue tracker:
   QQ:::::::::Q   D::::::::::DDD        https://jb.gg/qodana-issue
     QQQQQQQ:::QQ DDDDDDDDDDD           Or share your feedback in our Slack:
            QQQQQQ                      https://jb.gg/qodana-slack!
                                        
Starting up IntelliJ IDEA 2021.2.3 (build QDJVMC-212.5672.90) ...done.
Preparing for the Project opening stage ...
(Project opening) done.                                                         
The Project opening stage completed in 4 s 198 ms
Initializing project...Inspecting with the 'qodana.starter' profile
Loaded the 'qodana.sanity' shared project profile
The 'qodana.sanity' profile is configured for sanity checks
The 'qodana.recommended' profile is configured for promo checks
 0%
Preparing for the Project configuration stage ...
(Project configuration) Keep running Project configuration ... so far 4 m 0 s 9 
The Project configuration stage completed in 4 m 24 s 211 ms
Preparing for the Project analysis stage ...
(Project analysis) Analyzing code 94% [QuestionApplicationService.kt]           
The Project analysis stage completed in 4 s 608 ms
---- Qodana - Detailed summary ----

Analysis results: 1 problems detected

Grouping problems by severity: Note - 1
Name                     Severity Count problems
Implicit `Nothing?` type note     1      
-----------------------------------
2021/12/18 03:26:32 IDEA exit code: 0
2021/12/18 03:26:32 Generating html report ...
Generating final reports...
The project doesn't contain qodana.yaml config file
Done
2021/12/18 03:26:33 Serving report on http://localhost:8080 ...

今回は予め意図的に問題のあるコードを忍ばせておきました。結果、その箇所が検出されたようです。

--show-report 引数をつけたので、ブラウザからレポートを確認できます。

f:id:bau1537:20211218123150p:plain

どのような問題が何件あるのか、問題の箇所はどこか、画面で確認できます。

「Open file in」からIntelliJで該当箇所を直接開けます。便利ですね。

レポート画面は /data/results/ に生成されたHTMLからでも確認できるのでDockerコンテナを終了した後でも確認できます。CIに組み込む場合はこのファイルをアーカイブすると、結果を保存できますね。

maven-resources-pluginのエンコーディングの警告に対処する

  • プロジェクトをビルドしていたらふと気になったので。
  • こんな警告が出ていました。
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ direct-core ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

MavenのArchetypesについて

環境

$ mvn --version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-89-generic", arch: "amd64", family: "unix"

Archetypeとは?

  • シンプルに言うと、プロジェクトのテンプレートツールキットのこと
  • 組織はArchetypeを使うことで、プロジェクトのベストプラクティスに従った構成をすばやく構築できる
  • Archetypeは既存で定義されているものもあるし、自分で定義して配布することもできる

Maven Archetype Pluginとは?

使ってみる

  • Project creation を参考に使ってみる
  • 通常、archetypeはリモートリポジトリから取得される
    • 特に設定がされていなければCentralRepositoryが参照されるはず
    • それ以外のリポジトリを参照したければ settings.xml などで定義すること
  • archetype:generateインタラクティブモードでプロジェクトの生成を開始できる
    • 出力の一部がドキュメント違うので、ドキュメントは少し古いのかもしれない
$ mvn archetype:generate
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> am.ik.archetype:elm-spring-boot-blank-archetype (Blank multi project for Spring Boot + Elm)
2: remote -> am.ik.archetype:graalvm-blank-archetype (Blank project for GraalVM)
3: remote -> am.ik.archetype:graalvm-springmvc-blank-archetype (Blank project for GraalVM + Spring MVC)
4: remote -> am.ik.archetype:graalvm-springwebflux-blank-archetype (Blank project for GraalVM + Spring MVC)
5: remote -> am.ik.archetype:maven-reactjs-blank-archetype (Blank Project for React.js)
6: remote -> am.ik.archetype:msgpack-rpc-jersey-blank-archetype (Blank Project for Spring Boot + Jersey)
~~ 以下略 ~~
  • インタラクティブモードでは大量にarchetypeが出力される
  • 最終的に次のような文言とともにユーザーの入力待機状態となる
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains):
  • この状態で、特定のarchetypeの番号を入力すると、プロジェクトの作成に移る
  • また、大量のarchetypeをフィルタして再表示させることもできる
    • フィルタのフォーマットは文言通り、 [groupId:]artifactId となる
    • 例えば org.apache: とすると、groupIdが一致するものがフィルタされ表示される
    • また simple とすると、artifactIdにsimpleが含まれるものがフィルタされ表示される
    • さらに org.apache:simple とすると、groupIdが org.apache でartifactIdに simple が含まれるものがフィルタされ表示される
  • 例えば次のような出力になる
    • 一覧表示させながら絞り込んでいきたい時、この機能は役立ちそうだ
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : org.apache:simple
Choose archetype:
1: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with both Flash and JavaScript output.)
2: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-pure-js-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with only JavaScript output.)
3: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-pure-swf-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with only Flash output.)
4: remote -> org.apache.flex.flexjs.framework:flexjs-simple-library-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS library.)
5: remote -> org.apache.flex.flexjs.framework:flexjs-simple-typedef-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS typedef library.)
~~ 以下略 ~~
  • さらに、 -Dfilter で最初からフィルタを指定して表示させることもできる
  • 例えば以下の通り
$ mvn archetype:generate -Dfilter=org.apache:simple
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with both Flash and JavaScript output.)
2: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-pure-js-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with only JavaScript output.)
3: remote -> org.apache.flex.flexjs.framework:flexjs-simple-application-pure-swf-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS application with only Flash output.)
4: remote -> org.apache.flex.flexjs.framework:flexjs-simple-library-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS library.)
5: remote -> org.apache.flex.flexjs.framework:flexjs-simple-typedef-archetype (Maven archetype for creating FlexJS Maven project for building a simple FlexJS typedef library.)
~~ 以下略 ~~
  • ここでarchetypeを選んだら、必要なプロパティ(groupId,artifactId,versionなど)を入力してプロジェクトを作成する
  • 例えば以下の通り
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 9
Choose org.apache.maven.archetypes:maven-archetype-simple version: 
1: 1.3
2: 1.4
Choose a number: 2: 
Define value for property 'groupId': org.demo
Define value for property 'artifactId': simple
Define value for property 'version' 1.0-SNAPSHOT: : 
Define value for property 'package' org.demo: : 
Confirm properties configuration:
groupId: org.demo
artifactId: simple
version: 1.0-SNAPSHOT
package: org.demo
 Y: : Y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: maven-archetype-simple:1.4
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.demo
[INFO] Parameter: artifactId, Value: simple
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: org.demo
[INFO] Parameter: packageInPathFormat, Value: org/demo
[INFO] Parameter: package, Value: org.demo
[INFO] Parameter: groupId, Value: org.demo
[INFO] Parameter: artifactId, Value: simple
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Project created from Archetype in dir: /home/bookstore/IdeaProjects/demo-maven-archetype/simple
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:51 min
[INFO] Finished at: 2021-11-07T16:52:51+09:00
[INFO] ------------------------------------------------------------------------
$ tree .
.
└── simple
    ├── pom.xml
    └── src
        ├── main
        │   └── java
        │       └── org
        │           └── demo
        │               └── App.java
        ├── site
        │   └── site.xml
        └── test
            └── java
                └── org
                    └── demo
                        └── AppTest.java

11 directories, 4 files
  • pom.xml は以下の内容
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.demo</groupId>
  <artifactId>simple</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>simple</name>
  <description>A simple simple.</description>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <artifactId>maven-project-info-reports-plugin</artifactId>
      </plugin>
    </plugins>
  </reporting>
</project>

バッチモードでプロジェクトを作成する

  • 上記のように、インタラクティブにプロジェクトを作ることもできるが、時には必要なプロパティを最初から指定したいときもある
  • そんなときはバッチモードが使用できる
    • Generate project in batch mode
    • -B をつけるとバッチモードでプラグインが起動する
    • 必要なプロパティをそれぞれ -D で渡す
      • archetypeGroupId, archetypeArtifactId, archetypeVersion はarchetypeによらず必要になる
      • groupId, artifactId, version, package もそれぞれ必要となるがそれ以外にも必要なプロパティがある場合がある
        • 詳しくはarchetypeのドキュメントを読めとのこと
  • 具体的には下記のようにして使用する
$ mvn archetype:generate -B \
> -DarchetypeGroupId=org.apache.maven.archetypes \
> -DarchetypeArtifactId=maven-archetype-simple \
> -DarchetypeVersion=1.4 \
> -DgroupId=org.demo \
> -DartifactId=archetype \
> -Dversion=SNAPSHOT-1.0 \
> -Dpackage=org.demo
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO] 
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO] 
[INFO] 
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [org.apache.maven.archetypes:maven-archetype-simple:1.4] found in catalog remote
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: maven-archetype-simple:1.4
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: org.demo
[INFO] Parameter: artifactId, Value: archetype
[INFO] Parameter: version, Value: SNAPSHOT-1.0
[INFO] Parameter: package, Value: org.demo
[INFO] Parameter: packageInPathFormat, Value: org/demo
[INFO] Parameter: package, Value: org.demo
[INFO] Parameter: groupId, Value: org.demo
[INFO] Parameter: artifactId, Value: archetype
[INFO] Parameter: version, Value: SNAPSHOT-1.0
[INFO] Project created from Archetype in dir: /home/bookstore/IdeaProjects/demo-maven-archetype/archetype
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.684 s
[INFO] Finished at: 2021-11-09T14:00:27+09:00
[INFO] ------------------------------------------------------------------------

ServletContextの初期化を捉える

f:id:bau1537:20210925143925j:plain

ServletContext

環境

  • プロジェクト構成
    • いろいろ試しながらやってるんで、ここでやりたいこととは無関係なファイル/ディレクトリがちらほらある
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── example
        │           ├── filter
        │           │   ├── DemoFilter1.java
        │           │   └── DemoFilter2.java
        │           ├── listener
        │           │   └── DemoServletContextListener.java
        │           └── servlet
        │               └── DemoServlet.java
        └── webapp
            └── WEB-INF
                └── web.xml

10 directories, 6 files
  • pom
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>servlet</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>Servlet Maven Webapp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
$ java -jar $JETTY_HOME/start.jar --version

Jetty Server Classpath:
-----------------------
Version Information on 22 entries in the classpath.
Note: order presented here is how they would appear on the classpath.
      changes to the --module=name command line options will be reflected here.
 0:                    (dir) | ${jetty.base}/resources
 1:             2.0.0-alpha1 | ${jetty.home}/lib/logging/slf4j-api-2.0.0-alpha1.jar
 2:                   11.0.6 | ${jetty.home}/lib/logging/jetty-slf4j-impl-11.0.6.jar
 3:                    5.0.2 | ${jetty.home}/lib/jetty-jakarta-servlet-api-5.0.2.jar
 4:                   11.0.6 | ${jetty.home}/lib/jetty-http-11.0.6.jar
 5:                   11.0.6 | ${jetty.home}/lib/jetty-server-11.0.6.jar
 6:                   11.0.6 | ${jetty.home}/lib/jetty-xml-11.0.6.jar
 7:                   11.0.6 | ${jetty.home}/lib/jetty-util-11.0.6.jar
 8:                   11.0.6 | ${jetty.home}/lib/jetty-io-11.0.6.jar
 9:                   11.0.6 | ${jetty.home}/lib/jetty-jndi-11.0.6.jar
10:                   11.0.6 | ${jetty.home}/lib/jetty-security-11.0.6.jar
11:                   11.0.6 | ${jetty.home}/lib/jetty-servlet-11.0.6.jar
12:                   11.0.6 | ${jetty.home}/lib/jetty-webapp-11.0.6.jar
13:                   11.0.6 | ${jetty.home}/lib/jetty-plus-11.0.6.jar
14:                    2.0.0 | ${jetty.home}/lib/jakarta.transaction-api-2.0.0.jar
15:                   11.0.6 | ${jetty.home}/lib/jetty-annotations-11.0.6.jar
16:                      9.1 | ${jetty.home}/lib/annotations/asm-9.1.jar
17:                      9.1 | ${jetty.home}/lib/annotations/asm-analysis-9.1.jar
18:                      9.1 | ${jetty.home}/lib/annotations/asm-commons-9.1.jar
19:                      9.1 | ${jetty.home}/lib/annotations/asm-tree-9.1.jar
20:                    2.0.0 | ${jetty.home}/lib/annotations/jakarta.annotation-api-2.0.0.jar
21:                   11.0.6 | ${jetty.home}/lib/jetty-deploy-11.0.6.jar
  • web.xml に書いてある内容は今回関係ないのでパス

Event Listener で ServletContext が初期化された時を捕捉する

  • ではやってみましょー
  • Event Listener というものを使っていく
  • Event Listener を使うと、ウェブアプリケーションで発生する特定のイベントを捕捉することができる
    • HttpSession とか ServletRequest とかのイベントもOK
  • ServletContext のライフサイクルに関係するイベントを捕捉するのであれば、 jakarta.servlet.ServletContextListener を継承すればよさそう
    • Event Listener として登録しないと動いてくれないので、 WebListener アノテーションをつける必要
  • 次に示すとおり、 contextInitialized メソッドで、 ServletContext 初期化のタイミングを捕捉する
package org.example.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class DemoServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContextListener.super.contextInitialized(sce);
        System.out.println("context initialized");
        System.out.println("getServerInfo : " + sce.getServletContext().getServerInfo());
        System.out.println("getServletContextName : " + sce.getServletContext().getServletContextName());
    }

}
  • デプロイして動かすとログが出力される
    • ログの出力タイミングから推測すると、サーブレットクラスや、フィルタークラスよりも先にServletContextが初期化されてる
    • それもそうで、サーブレットクラス/フィルタークラスの初期化メソッドの引数には初期化済みのServletContextが渡されるので、先に初期化しないといけない
context initialized
getServerInfo : jetty/11.0.6
getServletContextName : /servlet-1.0-SNAPSHOT
  • 確かに、ウェブアプリケーション共通の情報にアクセスできる
  • 公式では contextInitialized の使い方の例としてデータベースのコネクションの作成&保持と書いてる
    • HTTPリクエストを受け付ける前にやっておきたい作業場所と考えるのがいいだろう
    • Servletの初期化メソッドでも同じことができるけど、Jettyだと最初のリクエストが来ないと実行されないし、最初のリクエストだけレスポンスに時間がかかってしまう

Servlet Request が作成されるタイミングも捕捉してみる

  • Servlet Request が作成されるタイミングを補足できるようなので、アクセスログとかを出したいならこれかも?と思ってやってみた
  • jakarta.servlet.ServletRequestListener を継承する
  • requestInitialized メソッドをオーバーライドする
package org.example.listener;

import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpServletRequest;

@WebListener
public class DemoServletRequestListener implements ServletRequestListener {

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        sre.getServletContext().log("request url : " + httpServletRequest.getRequestURL());
    }

}
  • デプロイしてアクセスするとログが出てくる
2021-10-02 21:47:07.839:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-26: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/WEB-INF/
2021-10-02 21:47:13.633:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-29: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/demo
2021-10-02 21:47:41.114:INFO :oejshC.servlet_1_0_SNAPSHOT:qtp1175259735-32: request url : http://localhost:8080/servlet-1.0-SNAPSHOT/filter
  • フィルターでもログは出せるけど、Event Listenerの場合はどのパスにアクセスが来た場合でもログが出る
    • フィルターは対象のURLを絞れる
    • 正直アクセスログの使い分けはどちらにすればいいか悩みどころ
    • どんなリクエストも出したい場合は Event Listener がいいかも?
  • Event Listener では Servlet Response の作成を捕捉できない
  • ちなみに Event Listener クラスへのイベント通知は必ずしも同期的ではない

Ubuntu でキーマップを変更する

  • 普段使用しているUbuntuのキーボードの設定を変えようと思いまして、その記録です
  • やりたいことは、JIS配列キーボードのスペースキー左右にある「英語」、「かな」キーを押して、ローマ字入力、日本語入力を切り替えるようにすること
    • 手元のキーボードだと「eng」「kana」とあるので、表記はキーボードそれぞれ違うと思います
    • 普通は「無変換」「変換」かもしれない
  • Windowsではこの設定してたんですが、まぁいいかって感じで今までしてませんでした
  • 普通、左上の全角半角で日本語とローマ字を切り替えていると思うんですが、それには次の2つのデメリットがあると思ってます
    • トグル方式なので、文字を打ってから切り替えることがしばしば起きる
    • キーボードの中央から遠い
  • ちなみに今使ってるキーボードはこれ

OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

やったこと

  • 全体としてはこんな感じ
    • xev でキーコードを調べる
    • xmodmap でキーを割り当てる
    • mozc で入力方法が切り替わるよう設定する

xev でキーコードを調べる

  • xev というコマンドでキーボードのキーコードを調べることができるみたいです
  • ターミナルを開いて xev と打つと小さいウィンドウが出てくる
  • そのウィンドウにフォーカスがあたった状態で該当のキーを押すと、ターミナルにイベント情報が表示される
  • イベント情報の中にキーコードがあるので、それをメモる
    • 私の環境では英語が 131 日本語が 130 でした

xmodmap でキーを割り当てる

  • xmodmap を使ってキーマップを変更します
  • xmodmap に読み込ませる設定ファイルを作成
    • ファイル名は .Xmodmap としました
    • 英語キーを無変換に
    • 日本語を全角半角に
keycode 130 = Zenkaku_Hankaku
keycode 131 = Muhenkan
  • xmodmap を使うと他にもいろいろと設定ができそうな気がしますが、それはおいおいで
  • 次のコマンドで xmodmap に設定ファイルを食わせます
xmodmap ~/.Xmodmap
  • ただ、xmodmap へ設定した内容は永続化されず、毎度設定する必要があります
  • なので、下記をシェルスクリプトとして書いて、PCが起動したときに自動的に読み込むよう設定しました
    • Ubuntu で 「自動起動するアプリケーションの設定」に設定できます
xmodmap ~/.Xmodmap
  • こうすればPCが起動すると、キーマップの設定が行われるようになります

mozc で入力方法が切り替わるよう設定する

  • あとは mozc の設定画面から「キー設定の変更」の「編集」を選び、
    • 「Muhenkan」がすでに割り当てられている設定は削除
    • すべての「IMEを無効化」コマンドの入力キーを「Muhenkan」へ
    • すべての「IMEを有効化」コマンドの入力キーを「Hankaku/Zenkaku」へ
  • これで完了です