Lambda(Java)を使ってLINE Message APIとやり取りする

# 使用サービス
 * AWS API Gateway
 * AWS Lambda
 * LINE Message API

# 開発環境等
 * Java 1.8
 * Eclipse
 * Maven

# 目的

* Line Message APIとの話の仕方を知りたい
* AWS Lambdaの使い方を知りたい。

# この記事で説明を省いていること
 * LINEのアカウント開設やWebhook URLの設定

 * API Gatewayの設定の仕方

 * Line Message API側で"Server IP Whitelist"を設定し、AWS側でも認証させる仕組み

 * リクエストの送信元がLINEであることを確認するために署名検証

 * Lineで設定されるChannel Access TokenはLambdaの環境変数から取得する仕組み

# 手順
## 1. LINE Message API
 1. アカウント開設
 2. Webhook URLの設定

## 2. AWS API Gateway
 1. リソース作成
 2. 作成したリソースとLambda Functionを連携

## 3. Lambda Function

3.1. JSONをPOJOで受け取るためのInputクラス作成
3.2. Outputクラス作成
3.3. InputからreplyToken, text取得し、Outputに格納。合わせてReply Messageに必要な情報もセット。
3.4. OutputをJSONに変換
3.5. リクエストを生成し送信
3.6. 全体コード

### 3.1. JSONをPOJOで受け取るためのInputクラス作成

下記のJSONオブジェクトが送信されるので、Inputクラスもそれに合わせて構築する

```js
{
  "events": [
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "message",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
      },
      "message": {
        "id": "325708",
        "type": "text",
        "text": "Hello, world"
      }
    },
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "follow",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U206d25c2ea6bd87c17655609a1c37cb8"
      }
    }
  ]
}
```

```Input.java
package jp.linebot;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Input {
	
	private Events[] events;
	
}
```

```Events.java
package jp.linebot;

import org.joda.time.DateTime;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Events {
	private String replyToken;
	private String type;
	private Long timestamp;
	private Source source;
	private Message message;	
}
````

```Source.java
package jp.linebot;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Source {
	private String type;
	private String userId;
}
```

```Message.java
package jp.linebot;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message {
	private String id;
	private String type;
	private String text;	
}
```

### 3.2. Outputクラス作成

```Output.java
package jp.linebot;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

@Data
public class Output {

	private String replyToken;
	private List<Messages> messages = new ArrayList<>();
}
```

```Messages.java
package jp.linebot;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Messages {

	private String type;
	private String text;
}
```

### 3.3. InputからreplyToken, text取得し、Outputに格納。合わせてReply Messageに必要な情報もセット。
 
```Java
Output output = new Output();
output.setReplyToken(input.getEvents()[0].getReplyToken());
Messages outMessage = new Messages();
outMessage.setType("text");
outMessage.setText(input.getEvents()[0].getMessage().getText() + "?");
output.getMessages().add(outMessage);
```

### 3.4. OutputをJSONに変換

```java
Gson gson = new Gson();
context.getLogger().log(gson.toJson(output));
```

### 3.5. リクエストを生成し送信
 
```Java
httpPost = new HttpPost("https://api.line.me/v2/bot/message/reply");
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Authorization", "Bearer " + "{Channel Access Token}");
StringEntity entity = new StringEntity(gson.toJson(output), StandardCharsets.UTF_8);
httpPost.setEntity(entity);

try (CloseableHttpClient client = HttpClients.createDefault();
     CloseableHttpResponse resp = client.execute(httpPost);
     BufferedReader br = new BufferedReader(new InputStreamReader(resp.getEntity().getContent(), StandardCharsets.UTF_8)))
{
        int statusCode = resp.getStatusLine().getStatusCode();
        switch (statusCode) {
        case 200:
            br.readLine();
            break;
        default:
        }
    } catch (final ClientProtocolException e) {
    } catch (final IOException e) {
}
return null;
```

### 3.6. 全体コード

```LambdaFunctionHandler.java
package jp.linebot;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.google.gson.Gson;

public class LambdaFunctionHandler implements RequestHandler<Input, Object> {

	@Override
	public Object handleRequest(Input input, Context context) {
		// TODO Auto-generated method stub

		context.getLogger().log("token : " + input.getEvents()[0].getReplyToken());
		context.getLogger().log("text : " + input.getEvents()[0].getMessage().getText());
		
		Output output = new Output();
		output.setReplyToken(input.getEvents()[0].getReplyToken());
		Messages outMessage = new Messages();
		outMessage.setType("text");
		outMessage.setText(input.getEvents()[0].getMessage().getText() + "?");
		output.getMessages().add(outMessage);

		HttpPost httpPost = new HttpPost("https://api.line.me/v2/bot/message/reply");
		httpPost.setHeader("Content-Type", "application/json");
		httpPost.setHeader("Authorization", "Bearer " + "{Channel Access Token}");

		Gson gson = new Gson();
		context.getLogger().log(gson.toJson(output));
		StringEntity entity = new StringEntity(gson.toJson(output), StandardCharsets.UTF_8);
		httpPost.setEntity(entity);
		try (CloseableHttpClient client = HttpClients.createDefault();
                CloseableHttpResponse resp = client.execute(httpPost);
                BufferedReader br = new BufferedReader(new InputStreamReader(resp.getEntity().getContent(), StandardCharsets.UTF_8)))
        {
            int statusCode = resp.getStatusLine().getStatusCode();
            switch (statusCode) {
            case 200:
                br.readLine();
                break;
            default:
            }
        } catch (final ClientProtocolException e) {
        } catch (final IOException e) {
        }
		return null;
	}
	
}
```

# 参考リンク
* [Line Message API](https://devdocs.line.me/ja/) 
* [Jackson で JSON データを POJO に格納](http://blog.kymmt.com/entry/jackson_pojo)
* [【Java】LINEのMessaging APIをサーブレットでさくっと実装してみたら未来が拓けた](http://www.maytry.net/java-line-messaging-api/)