GAE/Java8試行（その7：「ウォームアップリクエスト(ServletContextListener使用版)」）

# お題
毎リクエストで処理するには重く、Webアプリ起動時に１回だけやっておけばよい処理の書き方。
例えば、設定ファイルのメモリ展開。
[GAE-Javaのドキュメント](https://cloud.google.com/appengine/docs/standard/java/warmup-requests/configuring?hl=ja)では以下の３つの方法が載っている。

- <load-on-startup> サーブレットを使用する
- ServletContextListener を使用する
- カスタム ウォームアップ サーブレットを使用する

今回は「ServletContextListener」を使って、静的なSQLファイル（※本来は動的にSQLを組み立てるために[MyBatis](http://www.mybatis.org/mybatis-3/)等を使うだろうけど今回は本当にただの静的ファイル）をWebアプリ起動時に読み込んで、ファイル名をキーにマップに保持する。
ちなみに、このSQLはBigQueryに流す想定とする。

# 参考
https://cloud.google.com/appengine/docs/standard/java/warmup-requests/configuring?hl=ja#servlet-context-listener

# GAE試行Index
- [GAE/Java8試行（その0：「App Engineについて」）](https://qiita.com/sky0621/items/6175d578d0521858a944)
- [GAE/Java8試行（その1：「Java8でWebアプリ作ってデプロイ」）](https://qiita.com/sky0621/items/8437da4e96bffef69250)
- [GAE/Java8試行（その2：「Javaアプリ解説」）](https://qiita.com/sky0621/items/65ce0406fb895f547ae5)
- [GAE/Java8試行（その3：「Javaアプリテストコード解説」）](https://qiita.com/sky0621/items/874c8b6462fdc5a0829d)
- [GAE/Java8試行（その4：「Datastoreへのアクセスロジック」）](https://qiita.com/sky0621/items/c150f6e66320e240add8)
- [GAE/Java8試行（その5：「ローカル開発用コンソール」）](https://qiita.com/sky0621/items/70a17e243d6e5175ed20)
- [GAE/Java8試行（その6：「デプロイ失敗」）](https://qiita.com/sky0621/items/d89c7473fd5207edcfd5)

# 開発環境
### # OS
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
```

### # Java
```
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
```
### # IDE
みんな大好き[IntelliJ IDEA](https://www.jetbrains.com/idea/)

# 実践
## ■今回の成果物

https://github.com/sky0621/java-webapi-for-gae-study/tree/1980d822a9104346fa40f5de7c52cd00d9de3f73/sky0621

```
$ pwd
/work/src/java/github.com/sky0621/java-webapi-for-gae-study/sky0621/src/main
$
$ tree -R
.
├── java
│   └── com
│       └── example
│           └── sky0621
│               ├── BigQueryService.java
│               ├── HelloAppEngine.java
│               └── InitListener.java
└── webapp
    └── WEB-INF
        ├── appengine-web.xml
        ├── bigquery
        │   ├── TEST_001.sql
        │   └── TEST_002.sql
        ├── logging.properties
        └── web.xml
```

## ■Webアプリ起動時に実行されるリスナーの作成

### SQLマップ保持クラスの定義

ファイル名をキー、SQL内容を値とするマップを保持して、キーに該当するSQL文を返すサービスクラス

```java

public class BigQueryService {
    private static Map<String, String> sqlMap;

    public static void init(Map<String, String> m) {
        sqlMap = Collections.unmodifiableMap(m);
    }

    public static String getSql(String key) {
        return sqlMap.get(key);
    }
}
```

https://github.com/sky0621/java-webapi-for-gae-study/blob/1980d822a9104346fa40f5de7c52cd00d9de3f73/sky0621/src/main/java/com/example/sky0621/BigQueryService.java

### ServletContextListener実装クラス

まず、ServletContextListenerを実装したクラスを定義

```java

public class InitListener implements ServletContextListener {
　　　〜〜
}
```

contextInitializedメソッドを実装（SQLファイルの内容をメモリに展開する方法は適当）

```java

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext context = servletContextEvent.getServletContext();
        String path = context.getRealPath("/WEB-INF/bigquery/");
        Path pt = Paths.get(path);

        Map<String, String> m = new HashMap<>();
        try(final Stream<Path> pathStream = Files.walk(pt)) {
            pathStream.forEach(p -> {
                String sql = null;
                try {
                    sql = new String(Files.readAllBytes(p));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                m.put(p.getFileName().toString(), sql);
            });
        } catch (IOException e2) {
            e2.printStackTrace();
        }
        BigQueryService.init(m);
    }
```

```String path = context.getRealPath("/WEB-INF/bigquery/");```

でSQLファイルを格納しているパスを取得。

```try(final Stream<Path> pathStream = Files.walk(pt)) {```

でディレクトリウォークしながらSQLファイルを探索。

```sql = new String(Files.readAllBytes(p));```

でSQLファイルの中身を一気に読み込む。（SQLファイルの分量は、たかがしれている想定）

最後に、```BigQueryService.init(m)``` でマップ保持完了。

https://github.com/sky0621/java-webapi-for-gae-study/blob/1980d822a9104346fa40f5de7c52cd00d9de3f73/sky0621/src/main/java/com/example/sky0621/InitListener.java

## ■作ったリスナーをweb.xmlに登録

```xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <listener>
    <listener-class>com.example.sky0621.InitListener</listener-class>
  </listener>
</web-app>
```

https://github.com/sky0621/java-webapi-for-gae-study/blob/1980d822a9104346fa40f5de7c52cd00d9de3f73/sky0621/src/main/webapp/WEB-INF/web.xml

## ■メモリ保持した内容をログ出し

```java
@WebServlet(name = "HelloAppEngine", value = "/hello")
public class HelloAppEngine extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String id = req.getParameter("id");
        String name = req.getParameter("name");

        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
        Entity e = new Entity(KeyFactory.createKey("book", Integer.parseInt(id)));
        e.setProperty("bookName", name);
        ds.put(e);

        System.out.println("======================================================");
        System.out.println("======================================================");
        System.out.println(BigQueryService.getSql("TEST_001.sql"));
        System.out.println("======================================================");
        System.out.println("======================================================");
        System.out.println(BigQueryService.getSql("TEST_002.sql"));
        System.out.println("======================================================");
        System.out.println("======================================================");
    }
}
```

https://github.com/sky0621/java-webapi-for-gae-study/blob/1980d822a9104346fa40f5de7c52cd00d9de3f73/sky0621/src/main/java/com/example/sky0621/HelloAppEngine.java

## ■ログ確認

```
$ mvn appengine:run
```

して、POST叩いてみると。

```
[INFO] GCLOUD: 11 05, 2018 9:42:15 午前 com.google.appengine.api.datastore.dev.LocalDatastoreService init
[INFO] GCLOUD: 情報: Local Datastore initialized: 
[INFO] GCLOUD: 	Type: High Replication
[INFO] GCLOUD: 	Storage: /work/src/java/github.com/sky0621/java-webapi-for-gae-study/sky0621/target/sky0621-1.0-SNAPSHOT/WEB-INF/appengine-generated/local_db.bin
[INFO] GCLOUD: 11 05, 2018 9:42:15 午前 com.google.appengine.api.datastore.dev.LocalDatastoreService load
[INFO] GCLOUD: 情報: Time to load datastore: 36 ms
[INFO] GCLOUD: ======================================================
[INFO] GCLOUD: ======================================================
[INFO] GCLOUD: SELECT
[INFO] GCLOUD:   a_id,
[INFO] GCLOUD:   b_name
[INFO] GCLOUD: FROM
[INFO] GCLOUD:   tbl_123
[INFO] GCLOUD: WHERE
[INFO] GCLOUD:   a_id < 10
[INFO] GCLOUD: ;
[INFO] GCLOUD: 
[INFO] GCLOUD: ======================================================
[INFO] GCLOUD: ======================================================
[INFO] GCLOUD: SELECT
[INFO] GCLOUD:   c_id,
[INFO] GCLOUD:   d_name
[INFO] GCLOUD: FROM
[INFO] GCLOUD:   tbl_456
[INFO] GCLOUD: WHERE
[INFO] GCLOUD:   c_id < 20
[INFO] GCLOUD: ;
[INFO] GCLOUD: 
[INFO] GCLOUD: ======================================================
[INFO] GCLOUD: ======================================================
```

うん、読み込めてる。