結局web.xmlのスキーマ指定はどうするのがいいのか

```xml:web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee web-app_6_0.xsd"
         version="6.0">
```

`web.xml` の冒頭に書く、この `xmlns` とかの呪文の話。
結局、どう書くのが正解なのか調べたことをメモする。

# 結論

色々調べたり試したりした結果、以下のように `xsi:schemaLocation` のスキーマファイルの場所に、インターネットで公開されている xsd ファイルの URL を指定するのがいいのではないだろうか、というのが個人的な結論。
あっているかはわからない。

```xml:web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
```

以下、調べたこととかのメモ。

# 前提知識
## XML Schema
- XML には、ある XML 文書で使用できるタグや属性を定義できる仕組みが存在する
- これを XML Schema と呼ぶ(もしくは XML Schema Definition Language(XSD))
- XSD 自体も XML で記述されており、拡張子 `*.xsd` とかのファイルで用意されていたりする
- たとえば、 Jakarta EE が提供している各 xsd ファイルは以下で確認できる
    - https://jakarta.ee/xml/ns/jakartaee/
- XML Schema があることで、次のようなことができるようになる
    - ある XML 文書が XML Schema で定義されたルール通りに記載できているかどうかチェックできる
    - IDE などで、 XML のタグの入力補完ができる

## XML Schema の Namespace
- XML Schema は、他の XML Schema と区別できるように、 Namespace(名前空間) という固有の識別子を持っている
- ある XML Schema の Namespace は、 xsd ファイルで `targetNamespace` という属性で定義されている
- 例えば、 Jakarta Servlet 6.0 の `web.xml` で使用できるタグを定義した `web-app_6_0.xsd` では、以下のように定義されている

```xml:web-app_6_0.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.w3.org/2001/XMLSchema"
            targetNamespace="https://jakarta.ee/xml/ns/jakartaee" ★これ
            xmlns:jakartaee="https://jakarta.ee/xml/ns/jakartaee"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified"
            version="6.0">
```

- Namespace は URI 形式で定義されているが、これはあくまで XML Schema を識別するための値(識別子)にすぎない
    - この URI の場所に xsd ファイルが置いてあるわけではない

## XML Schema を指定する

- ある XML 文書がどの XML Schema に従って書かれているかを宣言するために、 `xmlns` という属性が存在する

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<hello xmlns="https://example.com/xml/ns/foo">
  <world>foo</world>
</hello>
```

- ここでは、 `https://example.com/xml/ns/foo` という Namespace の XML Schema が指定されている
- つまり、 `<hello>`, `<world>` というタグは、 `https://example.com/xml/ns/foo` という XML Schema で定義されたタグであることが分かる

### 複数の XML Schema を指定する
- 1つの XML 文書の中で複数の XML Schema が提供するタグを組み合わせて使いたくなることもある
- そういう場合は、次のように `xmlns` を宣言する

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<hello xmlns="https://example.com/xml/ns/foo"
       xmlns:bar="https://example.com/xml/ns/bar">
  <world>foo</world>
  <bar:message>BAR!!</bar:message>
</hello>
```

- `https://example.com/xml/ns/bar` という Namespace の XML Schema を使用することを宣言している
- このとき、 `xmlns` の後ろに `:bar` とつけることで、 `https://example.com/xml/ns/bar` の XML Schema は `bar` というプレフィックスを使って区別することを宣言している
- これにより、 `https://example.com/xml/ns/bar` で定義されたタグや属性は `bar` というプレフィックスを付けて利用できるようになる

## xsd ファイルの場所を指定する

- `xmlns` は、あくまで XML Schema の Namespace を指定しているにすぎない
- XML Schema を定義した xsd ファイルの場所は Namespace だけではわからない
- xsd ファイルがないと、その XML 文書が本当に xsd ファイルに書かれた定義通りに記載されているかどうかは判定できない
- そこで、 Namespace ごとに xsd ファイルの場所を指定できる方法が用意されている

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<hello xmlns="https://example.com/xml/ns/foo"
       xmlns:bar="https://example.com/xml/ns/bar"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           https://example.com/xml/ns/foo https://example.com/xml/ns/foo/foo.xsd
           https://example.com/xml/ns/bar https://example.com/xml/ns/foo/bar.xsd">
```

- `xsi:schemaLocation` で、 Namespace ごとの xsd ファイルの場所を宣言している
    - `<NamespaceのURI> <xsdファイルのURI>` という形式で列挙する(空白区切り)
- `schemaLocation` 属性は `http://www.w3.org/2001/XMLSchema-instance` という Namespace の XML Schema で定義されているものなので、それを `xsi` というプレフィックスで利用できるように `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"` と宣言している

### schemaLocation はあくまで参考情報

- `schemaLocation` で xsd ファイルの場所を指定できるが、これはあくまで XML パーサーの参考情報としてしか利用されない
    - ここで指定された xsd ファイルを使って解析しなければならない、という仕様ではない
- ここで指定された xsd ファイルをどう扱うかは、その XML を使用するアプリケーションに委ねられている
    - あるアプリケーションはここで指定された xsd ファイルを利用するかもしれないが、別のアプリケーションは無視して自分が保有している(組み込まれている) xsd ファイルを使うかもしれない

## Jakarta EE のスキーマ指定は version も必要
- Jakarta EE が提供している xsd ファイルを見ると、同じ種類のスキーマのバージョン違いでは Namespace が同じであることがわかる
    - [web-app_5_0.xsd](https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd)
    - [web-app_6_0.xsd](https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd)
    - どちらも `targetNamespace` は `https://jakarta.ee/xml/ns/jakartaee` となっている
- このままでは Namespace の指定だけでは xsd を一意に特定できないので、 `version` 属性を合わせて指定しなければならい
    - [Jakarta EE XML Schemas](https://jakarta.ee/xml/ns/jakartaee/#using) で、以下のように説明されている

> All Jakarta EE 9 and newer Deployment Descriptor Schemas share the namespace http://jakarta.ee/xml/ns/jakartaee. Each schema document contains a version attribute that contains the version of the specification.
> For example, the XML Schema document for the Jakarta Servlet specification contains the version attribute value “5.0”, pertaining to the specific version of the specification as well as the schema document itself.
> (中略)
> For example, Jakarta servlet Deployment Descriptor instances that must be processed with the servlet 5.0 version must indicate the version within the version attribute of the instance document, for example, “5.0”. 
> 
> Jakarta EE 9 以降のデプロイメント記述子スキーマはすべて、名前空間 http://jakarta.ee/xml/ns/jakartaee を共有します。 各スキーマ ドキュメントには、仕様のバージョンを含む version 属性が含まれています。
> たとえば、Jakarta Servlet 仕様の XML スキーマ ドキュメントには、仕様の特定のバージョンとスキーマ ドキュメント自体に関連するバージョン属性値「5.0」が含まれています。
> (中略)
> たとえば、サーブレット 5.0 バージョンで処理する必要がある Jakarta サーブレット デプロイメント記述子インスタンスは、インスタンス ドキュメントの version 属性内でバージョンを示す必要があります (たとえば、「5.0」)。

## アプリケーションサーバが web.xml のスキーマをチェックするかは任意？

[Jakarta Servlet 6.0 の仕様](https://jakarta.ee/specifications/servlet/6.0/jakarta-servlet-spec-6.0.html#rules-for-processing-the-deployment-descriptor) には、次のように記載されている。

> 14.2. Rules for Processing the Deployment Descriptor
> 
> The deployment descriptor must be valid against the schema. Web containers and tools that manipulate web applications have a wide range of options for checking the validity of a WAR. This includes checking the validity of the deployment descriptor document held within.
> 
> デプロイメント記述子は、スキーマに対して有効である必要があります。 Web アプリケーションを操作する Web コンテナとツールには、WAR の有効性をチェックするための幅広いオプションがあります。 これには、保持されているデプロイメント記述子ドキュメントの有効性のチェックが含まれます。

- 「デプロイメント記述子ドキュメント(`web.xml`)の有効性のチェック」が、「WAR の有効性をチェックするための幅広いオプション」の1つとされている
- 「幅広いオプション」のオプションが任意という意味であれば、 `web.xml` のスキーマのチェックは任意ということになる？
    - 後述するが、 Tomacat ではチェックがされていないようだった

# Jakarta Servlet の仕様に記載されている記述方法
- [Jakarta Servlet 6.0 の仕様](https://jakarta.ee/specifications/servlet/6.0/jakarta-servlet-spec-6.0.html#a-basic-example) では、 `web.xml` のスキーマの指定は次のように記載されている

```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         web-app_{spec-version-underscore}.xsd"
         version="{spec-version}">
```

- `{spec-version-underscore}` と `{spec-version}` の部分を、例えば 6.0 用に置き換えると次のようになる

```xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         web-app_6_0.xsd"
         version="6.0">
```

- ここまでの前提知識をもとに見ると、それぞれの指定が何を意味しているかが分かるようになる
    - `xmlns="https://jakarta.ee/xml/ns/jakartaee"`, `version="6.0"`
        - Jakarta EE が提供している XML Schema を使用することを宣言している
        - ただし、 Namespace だけでは一意にならないので `version="6.0"` も指定している
    - `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`, `xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee web-app_6_0.xsd"`
        - xsd ファイルの場所を指定している

# xsd ファイルの場所の指定が問題
- 前述のとおり、 `schemaLocation` で指定している xsd ファイルの場所はあくまで参考情報にすぎない
- このため、この指定がどう解釈されるかは IDE やアプリケーションサーバーによって異なる

# 各IDEやアプリケーションサーバの挙動
- 前述の Jakarta Servlet の仕様書に書かれているスキーマの指定で記述したときに、それぞれのアプリケーションでどう解釈されるか見てみる
- 検証用に、以下のようなプロジェクトを作成して確認した

```text:フォルダ構成
|-build.gradle
`-src/main/webapp/WEB-INF/
  `-web.xml
```

```gradle:build.gradle
plugins {
    id "war"
}

sourceCompatibility = 17
targetCompatibility = 17

compileJava.options.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    compileOnly "jakarta.servlet:jakarta.servlet-api:6.0.0"
}
```

```xml:web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee web-app_6_0.xsd"
         version="6.0">
    <hoge></hoge>
</web-app>
```

## IDE

### IntelliJ
- IntelliJ IDEA 2022.1.3 (Community Edition) で確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/6df382dc-1dc1-48f4-15ed-b603e87c808d.png)

- スキーマのチェックは行われている
    - 存在しないタグ(`<hoge>`)を書くと怒られる
- 入力補完が効く
- ただし、 `schemaLocation` のところで指定している xsd ファイルの場所の指定がエラーになっている
    - エラーメッセージ > `Cannot resolve file 'web-app_6_0.xsd'`
    - この場合は相対パス扱いになるので、`web-app_6_0.xsd` ファイルは `web.xml` と同じところに配置されている必要がある
- 以下のようにインターネット上で公開されている xsd ファイルの URL を指定すれば、警告はなくなった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/ad29a1f6-4f32-8711-c268-91e08fccb184.png)


#### クラスパス内に含まれる xsd ファイルを勝手に検索しているっぽい
- いろいろ試したところ、以下のように `schemaLocation` の指定を削除してもスキーマのチェックや入力補完は効くことが分かった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/03bd346e-fc0c-bf2a-4e3b-08cc137e6697.png)

- この状態で `xmlns` のところの Namespace を Ctrl + クリックすると、 xsd ファイルにジャンプできる
- この xsd ファイルは、 Jakarta Servlet の API の jar の中に組み込まれているものになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/08950c2c-48e8-919e-ecc4-77a30bf81865.png)

- IntelliJ の仕様などを調査したわけではないのであくまで推測だが、どうやら IntelliJ はクラスパス内に含まれる xsd ファイルをすべてチェックして、 `xmlns` で指定された Namespace の URI に一致する xsd ファイルがあればそれを利用してくれるようになっているっぽい




### Eclipse
- [Pleiades All in One Eclipse](https://mergedoc.osdn.jp/) の 2022 の Java で確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/a7fd3285-15db-9421-36ee-b45ddf747cfe.png)

- スキーマのチェックは行われていない
    - 存在しないタグ(`<hoge>`)を書いても、何も警告されない
- 入力補完も効かない
- 以下のようにインターネット上で公開されている xsd ファイルの URL を指定すれば、スキーマのチェックや入力補完が効くようになった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/7e47b062-7187-b749-db7a-53d0c3a30cae.png)


## アプリケーションサーバー
### GlassFish
- [Eclipse GlassFish 7.0.0, Jakarta EE Platform, 10](https://glassfish.org/download) で確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/21278147-e10f-1102-41ff-1228070545ad.png)

> 要素'{"https://jakarta.ee/xml/ns/jakartaee":hoge}'で始まる無効なコンテンツが見つかりました。

- スキーマのチェックが行われ、存在しないタグがあるとデプロイがエラーになった
- 試しに xsd ファイルの名前を以下のように適当なもの(`test.xsd`)に変えてみた

```xml:web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee test.xsd"
         version="6.0">
    <hoge></hoge>
</web-app>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/039c88e1-b4ef-6002-321e-bdb3c006fad3.png)

- 指定された xsd ファイルが見つからないとしてデプロイに失敗した
- なので、 `schemaLocation` の指定は見ている様子
- xsd ファイルの指定を `https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd` に変えた場合は、 `web-app_6_0.xsd` と指定した場合と同じようになった（スキーマのチェックが行われた）

### WildFly
- [27.0.1.Final](https://www.wildfly.org/downloads/) で確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/bb4e43e2-ff60-c8d9-ff7c-90d69c16159d.png)

> Message: Unexpected element '{https://jakarta.ee/xml/ns/jakartaee}hoge' encountered"}}

- スキーマのチェックにひっかかってデプロイに失敗した
- `web-app_6_0.xsd` を `test.xsd` に変えてデプロイしたところ、エラーは変わらなかった(`hoge` という存在しないタグを検知してエラー)
    - つまり、 `schemaLocation` の指定は見ていない様子
- `web-app_6_0.xsd` を `https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd` に変えた場合も、変化はなかった（普通にスキーマのチェックが行われた）

### Tomcat
- [10.1.5](https://tomcat.apache.org/download-10.cgi) で確認
- エラー無くデプロイできた
    - スキーマのチェックは行われていない
- `web-app_6_0.xsd` を `test.xsd` や `https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd` に変えても、変化はなかった（デプロイに成功した）

# まとめ

- Jakarta Servlet の仕様書では、 `schemaLocation` を設定する例が記載されている
- しかし、 `schemaLocation` の指定を実際に使うかどうかはアプリケーション依存
    - GlassFish は見てる
    - WildFly は、スキーマのチェックはしてるけど `schemaLocation` は見てない
    - Tomcat は見てないし、スキーマのチェックすらしない
    - IntelliJ は見てるけど、クラスパスから勝手に見つけてくれるので無くてもいい
    - Eclipse は見てる(IntelliJ みたいにクラスパスから勝手に見つけてはくれない)
- Jakarta Servlet の仕様書に記載のとおりの形だと、
    - IntelliJ だと警告が出てちょっとうざい
    - Eclipse だと、スキーマのチェックや入力補完が効かずに不便
- インターネットで公開されている xsd ファイルの URL (`https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd`)を指定すると、すべての IDE, アプリケーションサーバでいい感じで動作する
- IDE で警告も出ずチェックや補完が効いて、アプリケーションサーバでも問題なく動作するということで、 xsd ファイルのパスにインターネットで公開されている URL を指定する以下の形が良いのではないかなと思った
    - この URL は Jakarta Servlet の仕様書にも「この URL で見れるよ」と記載されているものなので、使っても問題ないと思う
    - https://jakarta.ee/specifications/servlet/6.0/jakarta-servlet-spec-6.0.html#deployment-descriptor-2

```xml:web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                             https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
```

:::note
インターネットアクセスが必要になる？
`https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd` 指定にすると、チェックのときにインターネット経由で xsd ファイルを落としてきたりするのかなと思ったので、試しに LAN ケーブルを引っこ抜いた状態で GlassFish で確認してみた。
すると、普通にスキーマのチェックが行われてエラーになった。
どういう仕組みなのかはわからない。
:::


# 参考
- [XML Schema（XSD）とは - 意味をわかりやすく - IT用語辞典 e-Words](https://e-words.jp/w/XML_Schema.html)
- [8．NamespaceとXML Schema 1 | TECHSCORE(テックスコア)](https://www.techscore.com/tech/XML/XML_Schema/Schema8/8_1)