20200604のJavaに関する記事は11件です。

Ruby vs Java: What makes a perfect fit for your custom software?

Let's pay attention to the choice between Java and Ruby performance and the long-term prospects for choosing either one.Java and Ruby performance and the long-term prospects for choosing either one.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebSocket通信におけるJSONデータの利用方法 (Java , JavaScript)

背景

チャットアプリ作成を基準とし、サーバー⇔ブラウザ間でのWebSocket通信の記事を書いた。
サーバーをJava、クライアントをjavascriptで実装。

[ソケット通信に関する過去記事]
JavaとJavaScriptでwebブラウザとのソケット通信①
JavaとJavaScriptでwebブラウザとのソケット通信②

今回はチャットアプリを改造しつつ、『JSONデータの取扱方法』について学んでいく。
また、『ソケット通信でのJSONデータの送受信』についても学ぶ。

目的

  1. Java、JavaScriptでのJSONの扱い方法を学ぶ。
  2. Webソケット通信においてJSONでのデータの送受信方法を学ぶ。
  3. JSONを用いてWebソケット通信の複数パスを実現する。

前提

この記事では主にJSONの扱い方を中心に記述していく。
ソケット通信についての解説は過去記事やググったページを参考にしてほしい。
⇒ サーバープログラムはJava、クライアントプログラムはJavaScriptで実装。

JSONとは

http://www.tohoho-web.com/ex/json.html
例:{ "name": "Tanaka", "age": 26 }
連想配列のような形で値を保持しているもの。
要はただの文字列と考えると楽に理解することができる。(本当は違うけど)
詳細は割愛。

実践内容

  1. JavaScriptでJSONデータの取り扱い (エンコード、デコード)
  2. JavaでJSONデータの取り扱い (エンコード、デコード)
  3. ソケット通信におけるJSONデータの送受信方法
  4. JSON送受信を利用した複数パスのチャットアプリ作成

1. JavaScriptでJSONデータの取り扱い

JavaScriptでは容易にJSONデータを扱うことができる。

エンコード

オブジェクト ⇒ JSON
JSON.stringify()メソッドを使用する。

使用例
var obj = {
    name: '太郎',
    age: 30,
    area: 'Tokyo'
}
var json = JSON.stringify(obj);
console.log(json);
実行結果
{"name":"Taro Tanaka","age":30}
  1. 変数objに連想配列の形式で値を代入。(オブジェクトの作成)
  2. JSON.stringify(obj)を用いてエンコードし変数jsonに代入。(JSONへ変換)
  3. console.log(json)でJSONデータをコンソールに表示。

実行結果を見ると、連想配列形式のオブジェクトがJSON形式に変換されていることが分かる。
⇒ キーや文字列は「" "」で囲われている。数値は裸のまま。

デコード

JSON ⇒ オブジェクト
JSON.parse()メソッドを使用する。

使用例
var obj1 = {
        name: '太郎',
        age: 30,
        area: 'Tokyo'
    }
var json = JSON.stringify(obj1);
//-----ここまでJSONデータの準備-----
var obj2 = JSON.parse(json);
console.log(obj2);
console.log(obj2.name);
実行結果
{name: "太郎", age: 30, area: "Tokyo"}
太郎
  1. 変数obj1に連想配列の形式で値を代入。(オブジェクトの作成)
  2. JSON.stringify(obj1)を用いてエンコードし変数jsonに代入。(JSONへ変換)
    ----------ここまでJSONデータの準備 (前項のエンコードと同様)----------
  3. JSON.parse(json)を用いてデコードし変数obj2に代入。(オブジェクトへ変換)
  4. console.log(obj2)でオブジェクトをコンソールに表示。
  5. obj2.~で各プロパティにもアクセス可能。

実行結果を見ると、JSONデータがオブジェクトに変換されていることが分かる。
obj1(オブジェクト) ⇒ json(JSON) ⇒ obj2(オブジェクト)

2. JavaでJSONデータの取り扱い

Javaのオブジェクトはクラスを指し、JavaScriptのように簡単に作成できない。
JSONとオブジェクトを変換する場合、事前にJSONの変数に対応したプロパティを持つクラスを作成しておく必要がある。

JavaでJSONを扱うなら、外部ライブラリを使用することを推奨。
⇒ Java標準APIにもJSONを扱うものは用意されているが、かなり手間がかかる。

JSONを扱う外部ライブラリは以下のものが有名。

  • Jackson
  • GSON
  • JSONIC
  • JSON in Java などなど

基本の利用方法は似たようなものだが、今回は「Jackson」を使用する。
他ライブラリの使用方法や外部ライブラリの適用方法は、参考ページを見るか適宜ググってほしい。

エンコード

オブジェクト ⇒ JSON
エンコードにはObjectMapperクラスのwriteValueAsString()メソッドを使用する。

使用方法
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(オブジェクトインスタンス);
使用例
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;

//JSONに変換するプロパティを持つクラス
class Info {
    public String name = "Taro Tanaka";
    public int age = 30;
}

//エンコードを実行するクラス
public class Main {
    public static void main(String[] args) {
        Info info = new Info();//JSONへ変換するクラスをインスタンス化
        ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成
        try {
            //writeValueAsString()メソッドでエンコード実施
            String script = mapper.writeValueAsString(info);
            System.out.println(script);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
実行結果
{"name":"Taro Tanaka","age":30}
  1. InfoクラスにてJSONに変換するためのプロパティを作成する。
  2. エンコードを実行するMainクラスを作成。
  3. Info info = new Info()にてJSONに変換するオブジェクトをインスタンス化。
  4. ObjectMapper mapper = new ObjectMapper()にてObjectMapperクラスをインスタンス化。
  5. mapper.writeValueAsString(info)にてオブジェクトからJSONに変換。
    このとき、try,catchでのエラー対応が必要。
  6. System.out.println(script)でJSONデータをコンソールに出力。

実行結果を見ると、オブジェクトクラスがJSON形式に変換されていることが分かる。
各プロパティの変数名と値がペアとなって格納されている。

デコード

JSON ⇒ オブジェクト
デコードにはObjectMapperクラスのreadValue()メソッドを使用する。

使用方法
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(JSONデータ,オブジェクトクラス.class);
使用例
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;

//JSONから変換されるプロパティを持つクラス
class Info {
    public String name;
    public int age;
}

//デコードを実行するクラス
public class Main {
    public static void main(String[] args) {
        String script = "{ \"name\":\"Taro Tanaka\", \"age\":30}";//文字列としてJSONデータを作成
        ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成
        try {
            //readValue()メソッドでデコード実施
            Info info = mapper.readValue(script, Info.class);
            System.out.println(info.name);
            System.out.println(info.age);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
実行結果
Taro Tanaka
30
  1. InfoクラスにてJSONから変換されるプロパティを持つクラスを作成する。
  2. デコードを実行するMainクラスを作成。
  3. ここではStringの文字列としてJSONデータを用意する。
  4. ObjectMapper mapper = new ObjectMapper()にてObjectMapperクラスをインスタンス化。
  5. mapper.readValue(script, Info.class)にてJSONからオブジェクトに変換。
    このとき、try,catchでのエラー対応が必要。
  6. System.out.println(info.~)でオブジェクトプロパティをコンソールに出力。

実行結果を見ると、JSONデータがオブジェクトクラスに変換されていることが分かる。
デコード後は指定したオブジェクトの各プロパティにアクセスすることで値を取得することができる。

3. ソケット通信におけるJSONデータの送受信方法

サーバープログラム:Java
クライアントプログラム:JavaScript

ソケット通信を行う際、JSONでの送受信方法を記述する。
JavaとJavaScriptで扱い方が異なるため、それぞれ解説する。

JavaScriptでのJSON送受信

JavaScriptに関しては特筆すべきことはない。
前述した通り、JSON.stringify()メソッド及びJSON.parse()メソッドで簡単にオブジェクト ⇔ JSON間の変換が可能。
クライアントから送信前にエンコード、受信後にデコードすれば問題無い。

送信時エンコード

オブジェクト ⇒ JSON

送信時
var obj = { type:'A' , msg:'a' };
var json = JSON.stringify(obj);
socket.send(json);
  1. オブジェクトを用意。
  2. JSON.stringify()メソッドでJSONへエンコード。
  3. WebSocketのsend()メソッドでJSONデータ送信

受信時デコード

JSON ⇒ オブジェクト

受信時
socket.onmessage = function(e) {
    var obj = JSON.parse(e.data);
};
  1. onmessageでJSONデータを受信。
  2. JSON.parse(e.data)でオブジェクトへデコード

JavaでのJSON送受信

Javaのソケット通信時にJSONを用いる場合、JavaScriptのように簡単にはいかない。
⇒ エンコーダー、デコーダー、オブジェクトクラスの3つを用意する必要がある。

送信時エンコード

オブジェクト ⇒ JSON

ソケット通信でJSONデータを送信する場合、エンコーダーを用いてオブジェクトからJSONに変換してから送信する。

通常、テキストデータを送信する場合にはsendText()メソッドを使用するが、JSONを送信する場合はsendObject()メソッドを使用する。
引数をオブジェクトとし、エンコーダーによってJSONに変換したのち送信する。

  1. 送信するオブジェクトを作成する。
  2. エンコーダーを作成する。
  3. @ServerEndpointアノテーションにエンコーダーを登録する。

1. 送信するオブジェクトを作成

通常のクラスと同様にプロパティを持つクラスを作成する。
コンストラクタ、セッター、ゲッターも通常通り記述。(無くても変換可能。)

オブジェクトクラス
public class JsonObj {

    private String type = "type1";
    private String msg = "msg1";

    //コンストラクタ
    public JsonObj() {}

    //セッター
    public void setType(String type) {this.type = type;}
    public void setMsg(String msg) {this.msg = msg;}

    //ゲッター
    public String getType() {return type;}
    public String getMsg() {return msg;}
}

2. エンコーダーを作成

エンコーダーはjavax.websocketパッケージのEncoder.Textクラスを実装する。
(Encoder.Binaryクラスも存在するが、バイナリデータを扱うためのクラスなので割愛)

Encoder.Text<>のジェネリクスにはエンコードするオブジェクトクラスを記述しておく。

エンコーダー
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonEncoder implements Encoder.Text<JsonObj>{

    @Override//初期化は何もしない
    public void init(EndpointConfig config) {}

    @Override//エンコード処理 ( オブジェクト → JSON )
    public String encode(JsonObj obj) throws EncodeException {
        ObjectMapper mapper = new ObjectMapper();
        String json = "";
        try {
            json = mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return json;
    }

    @Override//破棄は何もしない
    public void destroy() {}
}

Encoder.Textクラスを継承した場合、以下のメソッドをオーバーライドする必要がある。

  1. init(EndpointConfig config):エンコーダーが起動したときの処理。
  2. encode(Object obj):エンコード処理。
  3. destroy():エンコーダが破棄された時の処理。

基本的にinit()及びdestroy()は何も処理しなくてよい。
encode(Object)にて引数をオブジェクトとし、エンコード後に戻り値としてJSONを指定。

3. エンコーダーを登録

@ServerEndpoint
//エンコーダークラスを指定
@ServerEndpoint(value = "/json" , encoders = JsonEncoder.class)
public class JsonTest  {
    //中略

    //sendObject()の引数はオブジェクト
    //送信前にエンコードされる
    session.getAsyncRemote().sendObject(obj)

    //中略
}

エンコーダーを利用するとき、@ServerEndpoint()にエンコーダークラスを指定する。
ここで指定しておくことで、オブジェクト送信時に指定したエンコーダーによってJSONデータへ変換される。

  • sendObject(obj) ⇒ エンコード処理 (obj→JSON) ⇒ 送信

受信時デコード

JSON ⇒ オブジェクト

ソケット通信でJSONデータを受信する場合、デコーダーを用いてJSONからオブジェクトに変換してから受信する。

通常はonMessage()メソッドの引数にString型文字列として受信するが、デコーダーを用いることで受信前にデコード処理が実施され、オブジェクトとして受信することとなる。

  1. 受信するJSONの変換先オブジェクトを作成する。
  2. デコーダーを作成する。
  3. @ServerEndpointアノテーションにデコーダーを登録する。

1. 変換先のオブジェクトを作成

デコードする場合、JSONの要素に対応したプロパティを持つオブジェクトクラスを用意しておく必要がある。
複数のJSONを受信するとき内容や形式が異なるなら、それぞれのJSONに対応した複数のオブジェクトクラスを用意しなければならない。

エンコードの場合、全てのオブジェクトは任意のタイミングでJSONに変換し送信することが可能
デコードの場合、受信するJSONの内容をあらかじめ把握し、デコード前に受け口となるオブジェクトクラスを用意しておかなければならない。

オブジェクトクラス
public class JsonObj {

    private String type;
    private String msg;

    //コンストラクタ
    public JsonObj() {}

    //セッター
    public void setType(String type) {this.type = type;}
    public void setMsg(String msg) {this.msg = msg;}

    //ゲッター
    public String getType() {return type;}
    public String getMsg() {return msg;}
}

2. デコーダーを作成

デコーダーはjavax.websocketパッケージのDecoder.Textクラスを実装する。
(Decoder.Binaryクラスも存在するが、バイナリデータを扱うためのクラスなので割愛)

Decoder.Text<>のジェネリクスにはデコード先のオブジェクトクラスを記述しておく。

デコーダー
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonDecoder  implements Decoder.Text<JsonObj> {

    @Override//初期化は何もしない
    public void init(EndpointConfig config) {}

    @Override//デコードできるかの判定
    public boolean willDecode(String text) {
        return (text != null);
    }

    @Override//デコード処理 ( JSON → オブジェクト)
    public JsonObj decode(String text) throws DecodeException {
        ObjectMapper mapper = new ObjectMapper();
        JsonObj obj = null;
        try {
            obj = mapper.readValue(text, JsonObj.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return obj;
    }

    @Override//破棄は何もしない
    public void destroy() {}
}

Decoder.Textクラスを継承した場合、以下のメソッドをオーバーライドする必要がある。

  1. init(EndpointConfig config):デコーダーが起動したときの処理。
  2. willDecode(String text):デコード処理を実行するかどうかの判定。
  3. decode(Object obj):デコード処理。
  4. destroy():デコーダが破棄された時の処理。

基本的にinit()及びdestroy()は何も処理しなくてよい。
willDecode()引数をJSONとし、戻り値がtrueなら以下のデコードを実行。falseならデコードされず以降の@OnMessageメソッドは実行されない。
decode()にて引数をJSONとし、エンコード後に戻り値としてオブジェクトを指定。

3. デコーダーを登録

@ServerEndpoint
//デコーダークラスを指定
@ServerEndpoint(value = "/json" , decoders = JsonDecoder.class)
public class JsonTest  {
    //中略

    //@OnMessageメソッドの前にデコードされる
    @OnMessage
    public void onMessage(JsonObj obj , Session mySession) {
    }

    //中略
}

デコーダーを利用するとき、@ServerEndpoint()にデコーダークラスを指定する。
ここで指定しておくことで、データ受信時に指定したデコーダーによってオブジェクトへ変換される。
@OnMessageメソッドの引数はオブジェクト型となる。(通常はString型)

  • 受信 ⇒ デコード処理 (JSON→obj) ⇒ @OnMessageメソッド

4. 複数パスのチャットアプリ作成

過去に作成したチャットアプリを改変する。

変更点

  1. 送受信するデータをテキストからJSONへ変更 (JSONの使用)
  2. チャット欄を1つから2つへ増加 (複数パスの実現)

WebSocket通信ではデータを受信するための受け口が1つしかない。
つまりワンパスのため複数の送信元があっても見分けがつかない。
もし見分けるなら、文字列の内容を分解して区別する他無いだろう。
どうせデコードするのならJSONを扱うのが都合がよい。

実際は 1.テキスト 2.バイナリ 3.PingPong と3種類の受信メソッドがあるが、ここではテキスト形式のみを扱うため受け口は1つと考える。

作成ファイル

  1. JsonIndex.html:ブラウザ表示用のHTMLファイル
  2. JsonSocket.js:ソケット通信のクライアントプログラム
  3. JsonTest.java:ソケット通信のサーバープログラム
  4. JsonObj.java:JSONと相互変換するオブジェクトクラス
  5. JsonEncoder.java:Javaエンコーダー
  6. JsonDecoder.java:Javaデコーダー

1. 表示用HTML

JsonIndex.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>JSON送受信</title>
    <script type="text/javascript" src="JsonSocket.js"></script>
</head>
<body>
    <div
        style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;"
        id="show1"></div>
    <input type="text" size="80" id="msg1" name="msg1" />
    <input type="button" value="送信" onclick="sendMsg1();" />
    <p></p>
    <div
        style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;"
        id="show2"></div>
    <input type="text" size="80" id="msg2" name="msg2" />
    <input type="button" value="送信" onclick="sendMsg2();" />
</body>
</html>

ポイント
1. チャット欄を2つに増加した。

表示枠はそれぞれshow1show2、テキストボックスはmsg1msg2とした。

後述するJavaScriptファイルでこれらを操作する。

2. クライアントプログラム

JsonSocket.js
//JSON用のオブジェクト作成
var obj = { type:null , msg:null };

//WebSocketオブジェクト生成
var wSck= new WebSocket("ws://localhost:8080/jsonTest/json");

//ソケット接続時のアクション
wSck.onopen = function() {
    document.getElementById('show1').innerHTML += "接続しました。" + "<br/>";
    document.getElementById('show2').innerHTML += "接続したよ~" + "<br/>";
};

//メッセージを受け取ったときのアクション
wSck.onmessage = function(e) {
    //JSONデータをオブジェクトへデコード
    var json = JSON.parse(e.data);

    //JSONデータのtype値によって実行内容を変更
    if(json.type === 'msg1'){document.getElementById('show1').innerHTML += json.msg + "<br/>";}
    else if(json.type === 'msg2'){document.getElementById('show2').innerHTML += json.msg + "<br/>";}
};

//メッセージ送信1
var sendMsg1 = function(val) {
    var element = document.getElementById('msg1')
    obj.type = element.name;//オブジェクトの内容を代入
    obj.msg = element.value;
    var json = JSON.stringify(obj);//オブジェクトをJSONへエンコード
    wSck.send(json);//JSONを送信
    element.value = "";//内容をクリア
};

//メッセージ送信2
var sendMsg2 = function(val) {
    var element = document.getElementById('msg2');
    obj.type = element.name;
    obj.msg = element.value;
    var json = JSON.stringify(obj);
    wSck.send(json);
    element.value = "";
};

ポイント
1. JSON用のオブジェクトobjを作成 ⇒ これがJSONへ変換される。
2. ソケット接続時のアクションをチャット欄増加に合わせて追加。
3. メッセージ受信時にJSONをオブジェクトへデコードし、type値によって処理内容を変更。
4. メッセージ送信1,2:objに値を代入後、サーバーへの送信前にJSONへの変換を実施。

3. サーバープログラム

JsonTest.java
package jsonTest;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

// 引数にデコーダー、エンコーダーを記述
@ServerEndpoint(value = "/json" , decoders = JsonDecoder.class , encoders = JsonEncoder.class)
public class JsonTest  {

    private static Set<Session> user = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session mySession) {
        System.out.println("connect ID:"+mySession.getId());
        user.add(mySession);
    }

    //このメソッドの前にデコードされる
    @OnMessage
    public void onMessage(JsonObj obj , Session mySession) {
        for (Session user : user) {
            user.getAsyncRemote().sendObject(obj);//送信するものはオブジェクト(送信前にエンコードされる)
            System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました!");
        }
        if(obj.getMsg().equals("bye")) {onClose(mySession);}
    }

    @OnClose
    public void onClose(Session mySession) {
        System.out.println("disconnect ID:"+mySession.getId());
        user.remove(mySession);
        try {
            mySession.close();
        } catch (IOException e) {
            System.err.println("エラーが発生しました: " + e);
        }
    }
}

ポイント
1. @ServerEndpointにデコーダー及びエンコーダーのクラスを指定。
2. クライアントからデータを受信したとき、@OnMesaageメソッドが実行される前に指定したデコーダーが実行されJSONからオブジェクトに変換される。

@OnMesaageメソッドの引数はObj型となる。
3. sendObject(obj)メソッドでオブジェクトを送信するとき、クライアントへ送信する前に指定したエンコーダーが実行されオブジェクトからJSONに変換される。

※ 文字列を送信する場合はsendText()を使用するが、オブジェクトを送信する場合はsendObject()を使用する。

4. オブジェクトクラス

JsonObj.java
package jsonTest;

public class JsonObj {

    private String type;
    private String msg;

    //コンストラクタ
    public JsonObj() {}

    //セッター
    public void setType(String type) {this.type = type;}
    public void setMsg(String msg) {this.msg = msg;}

    //ゲッター
    public String getType() {return type;}
    public String getMsg() {return msg;}
}

ポイント
1. このクラスのプロパティはtypemsgの2つ。

⇒ クライアントプログラムのオブジェクト (受信するJSON) に対応したクラスを作成している。
2. デコード後、typeプロパティ値によって送信元を判断する。

5. エンコーダー

JsonEncoder.java
package jsonTest;

import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonEncoder implements Encoder.Text<JsonObj>{

    @Override//初期化は何もしない
    public void init(EndpointConfig config) {}

    @Override//エンコード処理 ( オブジェクト → JSON )
    public String encode(JsonObj obj) throws EncodeException {
        ObjectMapper mapper = new ObjectMapper();
        String json = "";
        try {
            json = mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return json;
    }

    @Override//破棄は何もしない
    public void destroy() {}
}

ポイント
1. Encoder.Text<JsonObj>を実装したクラスを作成。
2. encode()メソッドの引数はオブジェクト、戻り値はJSONとなる。
3. エンコードにはObjectMapperクラスのwriteValueAsString()メソッドを使用する。

6. デコーダー

package jsonTest;

import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonDecoder  implements Decoder.Text<JsonObj> {

    @Override//初期化は何もしない
    public void init(EndpointConfig config) {}

    @Override//デコードできるかの判定
    public boolean willDecode(String text) {
        return (text != null);
    }

    @Override//デコード処理 ( JSON → オブジェクト)
    public JsonObj decode(String text) throws DecodeException {
        ObjectMapper mapper = new ObjectMapper();
        JsonObj obj = null;
        try {
            obj = mapper.readValue(text, JsonObj.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return obj;
    }

    @Override//破棄は何もしない
    public void destroy() {}
}

ポイント
1. Decoder.Text<JsonObj>を実装したクラスを作成。
2. decode()メソッドの引数は文字列(JSON)、戻り値はオブジェクトとなる。
3. エンコードにはObjectMapperクラスのreadValue()メソッドを使用する。

実行結果

ChromeにてJsonIndex.htmlを実行。
JSONチャット.gif

チャット欄が2つに増加した。
各チャット欄で独立して送受信が可能。
⇒ 複数パスの実現が完了。

実行順序

長くなったので、クライアントとサーバーでどのような処理が行われているか記述する。

  1. HTMLでページを表示。
  2. ハンドシェイク完了後、ソケット通信開始。
  3. テキストボックスにメッセージを記述し送信ボタンをクリック。
  4. JavaScriptによりオブジェクトをJSONにエンコードしサーバーへ送信。
  5. サーバーでJSONデータを受信。
  6. デコーダーによりJSONからオブジェクトへデコード。
  7. デコードされたオブジェクトを引数に@OnMessageメソッドを実行。
  8. sendObject()メソッドでオブジェクトを送信。
  9. 送信前にエンコーダーによりオブジェクトからJSONへエンコードし、クライアントへ送信。
  10. クライアントでデータ受信。
  11. JSONデータをオブジェクトへデコード。
  12. typeプロパティ値によって送信元を判別し、各送信元へメッセージを表示。

その他

改善点

・未知のJSONデータを受信すると、対応したオブジェクトクラスを用意していないためエラーとなる。
⇒ 対応している人はいるので、そのうち調べるかも。結構ややこしそう。

・複数の型のJSONデータを受信する場合に未対応。
⇒ そこまで難しくなさそう。デコーダーとオブジェクトクラスを増やしてJSONの中身に対応すればできる。

・入れ子構造のJSONに対応するにはどういうオブジェクトクラスを作成すればいいか不明。
⇒ ググれば出るし、必要となれば調べる。

・バイナリデータの扱いは理解が進んでいないため、バイナリデータの送受信には未対応。
⇒ 画像の送受信など、必要となれば調べる。

感想

思った以上にややこしかった。長くなってしまったので反省。
もっとまとめるべきだった。

JavaでJSONを扱うのに、外部ライブラリを使用したりオブジェクトクラスを用意したりと面倒。
JavaScriptなら1行で終わるのにね。

Javaのソケット通信でJSONを扱うときに、エンコードクラスやデコードクラスが用意されているのは便利なのかどうなのか。
わざわざエンコーダークラスやデコーダークラスを用意しなくちゃいけないのは、面倒といえば面倒。

面倒だが、JSONを使いたいならしょうがない。
JavaでのJSONの扱い方について理解が深まって良かった。
個人制作レベルなら利用していけそう。

参考ページ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

○ヶ月から、○年○ヶ月に変換する

概要

月数から、年数と残りの月数を求める方法です。(Javaで書いてます)

35ヶ月は2年11ヶ月です

このような感じ。

変換方法

コードはこんな感じ。

YearMonth.java
public class YearMonth{
    public static void main(String args[]){
        int monthCount = 35;
        System.out.println(monthCount + "ヶ月は" + monthCount/12 + "年" + monthCount%12 + "ヶ月です");
    }
}

変数monthCountに格納された35ヶ月が、年月に変換されています。

年とヶ月は別々で計算しました。

年の方は、35ヶ月を12ヶ月(1年)で割り、Math.floorで小数点を切り捨て。
そしてそのままだと小数点が.0と付いてしまったので、前に(int)を置いて整数に。

ヶ月の方は、35ヶ月から12ヶ月(1年)を割った余りにすることで計算できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaによるWebアプリケーション構造とプレゼンテーション層における処理の流れ

はじめに

新入社員研修において、Javaを用いたWebアプリケーションの開発を行ったので、そこで学んだことを簡単にまとめていきたいと思います。
間違い等も多くあると思いますので、ご指摘してくださると嬉しいです。

Webアプリケーションのサーバーサイドにおける処理の流れを中心に取り上げていきます。想定しているサンプルとしては、簡単な1件検索のサンプルになります。

サーバ間のやりとり

動的なWebページを実装する場合のクライアントとサーバー間における動的な情報のやりとりについて簡単に説明します。

まず、クライアントからWebサーバーにリクエストを送信します。このクライアントとWebサーバー間の送受信はHTTPプロトコルによって行われております。動的なWebページを表示する場合、Webサーバー側で何らかの処理(プログラム)を行う必要があります。その処理がWebアプリケーションであり、それをJavaやPHPなどのサーバーサイド言語で実装していくことになります。
今回はJavaでの実装を想定としています。

そのアプリケーションを実行するためには、Webサーバーとは別にアプリケーションサーバーを用意する必要があります。
( JavaのアプリケーションサーバーではTomcatが有名です。)
アプリケーションサーバーも、クライアントとWebサーバーの送受信と同様に、Webアプリケーションと送受信を行うには、あるプロトコルが必要になります。上記のようにアプリケーションサーバーにはTomcatを使用して、WebサーバーにはApatchを使用する場合、Apatchにmod_jkという連携用モジュールを拡張機能として実装します。
そのmod_jkのajp13というプロトコルを使用してアプリケーションサーバーとWebサーバーは送受信を行うことができます。
Tomcatは簡易的なWebサーバーを保有しているため、特に連携しなくても送受信は可能です。

Webアプリケーションの構造

では、そのサーバーサイドでどのようにWebアプリケーションが処理されているのかを見ていきます。

WebアプリケーションはMVCアーキテクチャをモデルとして設計されており、大きく分けて次の3つの階層に分けて設計されています。

・プレゼンテーション層
・ビジネス層
・インテグレーション層

それぞれの層に含まれるプログラムの役割は明確に分けられています。

次に、クライアントからのリクエスト情報を元にどのように処理がなされているのかを記します。

サーバーサイドにおけるアプリケーションの処理の流れ

スクリーンショット 2020-06-04 20.05.12.png

リクエスト情報は、まずFrontControllerに渡されます。このFrontControllerはサーブレットであり、Webアプリケーションの処理を一元管理しています。

そのFrontControllerはリクエスト情報に応じて処理を振り分けます。(switch-case文が有効)
その中でActionを呼び出し、そのActionが入力値のチェックやセッション管理(セッションオブジェクトの生成や解放)、ビジネス層の業務Logicへの処理依頼を行います。
(余談ですが、セッション管理ではサーバリソースを有効に活用するために一定時間アクセスが途絶えたらセッションを無効化するなど、出来るだけ早く解放するほうが良いそうです)

そして、肝となる業務Logicでは業務処理を制御します。
業務処理に必要な情報は、データベースへのアクセスによって取得します。
データベースへアクセスするためにはConnectionManagerを用います。具体的には、ConnectionManagerのgetConnectionメソッドを用いてConnectionオブジェクト(データベースへの接続)を取得します。

そのConnectionオブジェクトをDAOのコンストラクタの引数に渡して、DAOオブジェクトを生成します。
そして、DAOのメソッドを呼び出し、Entityを用いて戻り値を受け取ります。

DAO内の戻り値の設定処理方法(1件検索の場合)
while(res.next())で検索結果がある場合に{}内の処理を行う。{}内ではres.get~を用いて戻り値を設定する

業務LogicがEntityのオブジェクトを受けとった後、定義したオブジェクトに格納し、戻り値に設定します。

Actionにおいて業務Logicを生成し、メソッドを呼び出します。その結果をスコープなどに設定します。
戻り値としては遷移ページ先を設定します。

FrontControllerにおいてActionから遷移ページ先を受け取り、フォワードします。

プレゼンテーション層におけるアプリケーションの動作

サーブレットとJSP

Webページを表示するHTMLファイルを生成するためにはサーブレットというプログラムが必要になります。サーブレットはTomcatなどのWebアプリケーションサーバーによって実行されます。

このサーブレット単体でもWebページは表示させることは可能なのですが、プログラムの記述が煩雑になってしまい、綺麗なプログラムではなくなってしまうので、HTMLにJavaコードを埋め込んだJSPにWebページの表示を依頼することになります。

また、Webページを遷移させていくためには、クライアントが送信したリクエストにパラメータを付加させる必要があります。
そのリクエストの送信方法としてはGETとPOSTの二種類があります。

GETとPOST

GETとPOSTのどちらの方法でも、任意の値をパラメータとして設定する必要があります。

GETオペレーションでは、パラメータに設定した任意の値がURLに組み込まれる形でWebサーバーに送信されます。
ゆえに、Webページを再度閲覧したいという場合は簡単にアクセスすることができます。
つまり、ブックマークはパラメータごとに保存がされているという状況になります。
デメリットとしてはパラメータの長さに制限があることや、上記のようにセキュリティ面が低いことが挙げられます。

一方、POSTオペレーションはパラメータに設定した任意の値はURLに組み込まれず、リクエストのメッセージボディに格納される形でWebサーバーに送信されます。
ゆえに、セキュリティ面ではGETオペレーションより高いです。また、パラメータの長さにも制限がなく、大量のデータを送信することができます。

アプリケーションでの処理ではユーザー定義サーブレットで、HttpServletにおいて定義されているdoGetメソッドとdoPostメソッドをオーバーライドする形で記述します。そのことによりHTTPの機能を使用することができます。

まとめ

今回はサーバーサイドにおけるWebアプリケーションの動作についてとプレゼンテーション層におけるアプリケーションの動作について簡単に解説しました。
これ以上は長くなってしまうので、ビジネス層とインテグレーション層に関しては別記事で解説できたらと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HashMap のvalue で値をソートしたい.

javaでMap(HashMap)をvalueを使ってソートする際のやり方をまとめておきます.
ここでは,B_arrayを使ってA_arrayの値も同時にソートしたい状況を考えています.
HashMapのentry型を持つリストをソートする形なので,TreeMapなど,他のMapでも使えます.

コード
Solution.java
import java.util.*;

class Solution {
    public static void main(String[] args) {
        int[] A_array = {1,6,3,10,5};
        int[] B_array = {8,21,13,4,5};
        tryComperator(A_array, B_array);
    }
    public static void tryComperator(int[] a, int[] b){
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        //配列の要素をhashmapに格納.
        for (int i = 0; i < a.length; i++){
            map.put(a[i], b[i]);
        }
        //Hashmapの各要素をリストに格納.
        List<Map.Entry> mapValuesList = new ArrayList<Map.Entry>(map.entrySet());
        //mapValuesList を出力してみる.(10=4 のような形で出力されるらしい.)
        for (Map.Entry v: mapValuesList){
            System.out.println("map>> "+v+",key>> "+v.getKey()+"value>> "+v.getValue());
        }
        //Comparatorクラスをインスタンス化
        Comparator<Map.Entry> comparator = new Comparator<Map.Entry>() {
            public int compare(Map.Entry entry1, Map.Entry entry2) {
                return ((Integer) entry1.getValue()).compareTo((Integer) entry2.getValue());
            }
        };
        //comparator を使ってソートする.
        Collections.sort(mapValuesList, comparator);
        //確認のための出力
        for (Map.Entry s : mapValuesList) {
            System.out.println(s);
        }
    }
}
出力
map>> 1=8,key>> 1value>> 8
map>> 3=13,key>> 3value>> 13
map>> 5=5,key>> 5value>> 5
map>> 6=21,key>> 6value>> 21
map>> 10=4,key>> 10value>> 4
10=4
5=5
1=8
3=13
6=21

valueの昇順に出力されました.

参考: http://kevin3sei.blog95.fc2.com/blog-entry-159.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Visual Studio Codespaces を使って Azure Functions の Java アプリケーションの Hello World まで

背景

Visual Studio Codespaces が登場して、VDIのマシンを立てずとも、Azure 上の仮想マシンを使ってのコーディングがやり易くなりました。しかも WindowsLinuxが選択できます。Visual Studio Code との連携がシームレス過ぎて感動さえ覚えます。

Visual Studio Codespaces:
https://visualstudio.microsoft.com/ja/services/visual-studio-codespaces/

Visual Studio Codespaces - document:
https://docs.microsoft.com/ja-jp/visualstudio/online/overview/what-is-vsonline

  • 執筆時点 (2020/6/5) は Public Preview です!

一方、Azure Functions は Serverless で人気のホスティング環境です。

Azure Functions:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-overview

その実行エンジンはGitHub上で開発されており、オンプレミスだけでなく、AWS や GCP、あるいは k8s 上などあらゆる環境にホスティングして動かす事ができます。

https://github.com/Azure/azure-functions-host

ここでは、それらを使っての Java アプリケーションの Hello World までの手順をまとめておきます。
ローカルに Java でさえインストールしていない点にご注意ください?

image.png

必要なもの

手順

1. Visual Studio Codespaces の環境作成

こちらのドキュメントに従って作成してください?

Visual Studio Codespaces Quickstart:
https://docs.microsoft.com/ja-jp/visualstudio/online/quickstarts/browser

数十秒で作成されます!
Container ベースであることも読み取れて興味深いですね。

image.png

2. Visual Studio Codespaces に Visual Studio Code から接続

Visual Studio Codespaces はデフォルトでブラウザー内での作業になることが多いです。ですが、私はローカルでのコーディングが好きなので?、ここからはローカルの Visual Studio Code で作業をしていきます。

  • Visual Studio Codespaces のサイトに移動します

https://online.visualstudio.com/

  • 作成したインスタンスを選んで [Open in VS Code] を選びます。

image.png

  • ブラウザーから、Visual Studio Code を開こうとしているというメッセージが出てきます。[開く]で先に進みます。

image.png

こんな形で、リモートに接続ができます。

image.png

  • もし認証がうまくできないなどで、接続がうまくいかない場合は、画面左下の緑の[Codespace: <インスタンス名>]をクリックして、[Codespaces: Open Codespace in New Window]にて、別のVisual Studio Codeを起動してみてください.

image.png

3. 開発に必要なモジュールを Visual Studio Codespaces にインストール

開発に必要な幾つかのモジュールが入っていません。ターミナル から、それらをインストールします。

  • Visual Studio Code でターミナルを開きます 実際にはリモートのContainerに接続しているんですよね?

image.png

3.1. Maven

sudo apt-get install maven

3.2. Azure Functions Core Tools

Visual Studio Codespaces 上で Azure Functions を実行するためですね。リモートでのローカル実行になります?

sudo apt-get install azure-functions-core-tools

Azure Functions Core Tools の操作:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=linux%2Ccsharp%2Cbash

3.3. Azure Functions - Visual Studio Code Extension

Visual Studio Code の Extension です。Visual Studio Codespaces の方にインストールします。

image.png

これで、Visual Studio Code のアクティビティバーに Azure のアイコンが表示されます。

image.png

3.4. Java Extension Pack - Visual Studio Code Extension

Visual Studio Code の Java 用の各種 Extension です。デバッグのためですね。

image.png

これで、ほぼ既存の Azure Functions のドキュメントの手順の通り作業ができます?

4. Azure Functions のローカルプロジェクトの作成

手順はこちらですね。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-java#create-your-local-project

ちなみに、以下の様なJavaのコードのひな型を作成してくれます。

/src/main/java/com/function/Function.java

package com.function;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;

import java.util.Optional;

/**
 * Azure Functions with HTTP Trigger.
 */
public class Function {
    /**
     * This function listens at endpoint "/api/HttpExample". Two ways to invoke it using "curl" command in bash:
     * 1. curl -d "HTTP Body" {your host}/api/HttpExample
     * 2. curl "{your host}/api/HttpExample?name=HTTP%20Query"
     */
    @FunctionName("HttpExample")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");

        // Parse query parameter
        final String query = request.getQueryParameters().get("name");
        final String name = request.getBody().orElse(query);

        if (name == null) {
            return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build();
        } else {
            return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
        }
    }
}

5. Visual Studio Codespace 上で、Azure Functions のコードをデバッグする

ドキュメントの通りですと、F5 でローカルでAzure Functionsが起動します。ここでは、Visual Studio Codespace 側、つまり、リモートで起動させます。

  • [ターミナル] を起動して、以下のコマンドを発行します。
mvn clean package

結構時間かかります?...
image.png

mvn azure-functions:run

で、見慣れた Azure Functions の実行画面に。

image.png

これで、Visual Studio Codespaces 上で、Azure Functions が起動しました!

ちなみに、debug実行する際には以下のコマンドになります。
ブレークポイントが動いていませんが...?

mvn azure-functions:run -DenableDebug

6. Visual Studio Codespace 上の Azure Functions にアクセスする

Azure Functions Core Tools がデフォルトで作成してくれる アクセス用のURLである http://localhost:7071/api/HttpExample は、自分のPCからそのままではアクセスできませんよね。

Visual Studio Codespaceのインスタンス上の Azure Functionsに接続するため、ここでは、Visual Studio Code の Remote Extensionに入っているポートフォワーディングを利用します。

  • アクティビティーバーの [リモートエクスプローラー] を選択します。

image.png

  • [Forward port...] を選択し、ローカルでのポート番号を入れます。Azure Functions Core Tools が設定してくれたポート番号である[7071]を設定します。

image.png

  • 作成した Forward Ports を選択すると、URLをコピーするアイコンが表示されます。

image.png

この文字列がクリップボードにコピーされます。

http://127.0.0.1:7072/

そのままブラウザーのURLに張り付けてコピーすると、Azure Functions が稼働していることが確認できます。

image.png

このサンプルは、Visual Studio Codespaces 側では以下のURLで待っています。

http://localhost:7071/api/HttpExample

これを以下の様に書き換えます。
QueryStringの name の先はどんな文字列でも良いです?

http://127.0.0.1:7072/api/HttpExample?name=Hello-VisualStudioCodespace

Azure Functions が呼び出せましたね!

image.png

まとめ

Visual Studio Codespaces が、Azure上のリモート環境という事を忘れなければ、普段の開発に近い形で行えると思います。

  • Container の 作成・削除は容易
  • 追加モジュールもインストール可能
  • アイドル時間設定で、勝手に止めてくれる

是非、楽しんでください!

参考

Visual Studio Codespaces VS Code How-to:
https://docs.microsoft.com/ja-jp/visualstudio/online/how-to/vscode

Maven Plugin for Azure Functions:
https://docs.microsoft.com/ja-jp/java/api/overview/azure/maven/azure-functions-maven-plugin/readme?view=azure-java-stable#azure-functionsrun

Azure Functions の Java 開発者向けガイド:
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java?tabs=consumption

Visual Studio Code での Remote 環境での開発:
https://code.visualstudio.com/docs/remote/remote-overview

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaで"cls"みたいなことをしたい

JavaでWindowsのclsやLinuxのclearみたいなことをしたいときのプログラム。

Javaで外部コマンドを実行するときに、それぞれのコマンドを指定することで、それっぽくしてます。

/**
 * コンソールをクリアする(Windows,Linuxのみ)
 */
public void executeCls(){
    // OSの確認
    String osName = System.getProperty("os.name").toLowerCase();

    try {
        // OSがWindowsの時
        if(osName.startsWith("windows")) {
            new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor();
        // OSがLinuxの時
        }else if(osName.startsWith("linux")) {
            new ProcessBuilder("clear").inheritIO().start().waitFor();
        }else {
            // Windows, Linux以外のOSの場合
            System.out.println("コンソールはクリアされません。");
        }
    }catch(IOException e) {
        e.printStackTrace();
    }catch(InterruptedException e) {
        e.printStackTrace();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Play Framework 2.8 (Java) からSlackに投稿する

概要

Play Framework 2.8でSlack APIを使用する方法です。
シンプルな投稿機能の実装をします。

SlackのWeb APIを簡単に利用できるSlack公式のJava用クライアントを使います。

公式の記事にある内容をPlay Frameworkで実践しようという趣旨の記事です。
https://slack.dev/java-slack-sdk/guides/ja/web-api-basics

下準備

プロジェクトの作成

省略します。
Play FrameworkのJava用テンプレートを使い、空のプロジェクトを用意してください。

依存性の記述

SlackのWeb APIをJavaで楽に扱うためのクライアントをSlack公式が用意しています。
これを先程作ったプロジェクトにインストールしていきます。

↓セットアップについての公式ページ
https://slack.dev/java-slack-sdk/guides/ja/web-api-client-setup

公式ページではMavenを使ってプロジェクトにインストールする方法を紹介していますが、Play FrameworkではMaven2を使用しており、簡単な記述でインストールを行うことができます。

プロジェクトファイルの一番上のディレクトリにあるbuild.sbtを開き、以下の依存性を追記します。

build.sbt
libraryDependencies += "com.slack.api" % "slack-api-client" % "1.0.8"

依存性の内容は、左からグループID・アーティファクトID・バージョンですので、必要に応じてしてください。

Play Frameworkプロジェクトでの準備は以上です。
初回のコンパイル時にクライアントのダウンロードやインストールも自動でやってくれます。
あとはjavaファイル中のimport文でクライアントを使えるようになります。

トークンの取得

今度はSlack側の設定です。
トークンはSlackのワークスペースから発行されるもので、いわば「それを持ってたらSlackに投稿してもいいよ、という許可証」です。
この文字列があればSlackに投稿できるようになります。

詳細は省きますが、

  • 新しいアプリケーションの作成
  • 必要なトークン情報
  • ワークスペースへのアプリケーションの追加

をすればトークンの発行が可能です。

トークンの種類として、ボットとユーザーがありますが投稿を試すだけならどちらでも構いません。
スコープにはchat:writeが必要です。

以下の公式ページを参考にしてみてください。

https://slack.com/intl/ja-jp/help/articles/115005265703-%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E3%83%9C%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90
https://slack.com/intl/ja-jp/help/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E7%94%9F%E6%88%90%E3%81%A8%E5%86%8D%E7%94%9F%E6%88%90

実装

今回は簡単のために、起動時に実行されるHomeController.javaのindexメソッドに記述しています。
実際のアプリケーション作成のときはモデルかなんかに書きましょう。

HomeController.java
package controllers;
import com.slack.api.model.Message;
import play.mvc.*;
import com.slack.api.Slack;
import java.io.IOException;
import com.slack.api.methods.response.chat.ChatPostMessageResponse;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import com.slack.api.methods.SlackApiException;

public class HomeController extends Controller {

  public Result index() {
    Slack slack = Slack.getInstance();
    String token = "トークンの文字列";
    MethodsClient methods = slack.methods(token);
    ChatPostMessageRequest request = ChatPostMessageRequest.builder().channel("#general").text(":wave: Hi from a bot written in Java!").build();

    try {
      ChatPostMessageResponse response = slack.methods(token).chatPostMessage(request);
      if (response.isOk()) {
        Message postedMessage = response.getMessage();
        System.out.println("成功");
      } else {
        String errorCode = response.getError();
        System.out.println("失敗1"+errorCode);
      }
    } catch (SlackApiException requestFailure) {
      System.out.println("失敗2");
    } catch (IOException connectivityIssue) {
      System.out.println("失敗3");
    }
    return ok(views.html.index.render());
  }

}

トークンの文字列の部分には取得したトークンが入っていれば大丈夫です。
公式ページでは、環境変数に格納したトークンを取得する方式を取っています。
直接入力するのは、汎用性の観点でもセキュリティの観点でもアウトなので実際のアプリケーションでは絶対にやめましょう。

流れとしては、

  • Slackインスタンスの初期化
  • トークンの格納
  • リクエスト生成用のインスタンスにトークンをセット
  • リクエストの生成
  • (リクエストを送って)レスポンスを受け取る(例外処理付き)

って感じです。

Slack Web APIに沿ってリクエストを生成しているので、どうやってリクエストを生成しているかはわかりやすいと思います。

例外処理を書かないと実行時に注意されるので、公式ページを参考に書いてください。

うまくいけば実行時にコンソールに成功と表示され、指定したチャンネルに投稿されると思います。
失敗してしまう場合は公式ドキュメントを参考にエラーを確認してみてください。
自分の場合ははトークンがちゃんと設定できていなかった、投稿チャンネルの指定が間違っていたというエラーを吐かれていました。

まとめ

  • Slack APIのクライアントをPlay Frameworkで利用できるようにした。
  • Play FrameworkからSlackにメッセージを投稿した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CICS-Javaアプリケーションを動かす - (1)単純なサンプルアプリの稼働

はじめに

CICS Transaction Server for z/OS のアプリケーション開発言語と言えば、COBOL, PL/I, Asembler辺りが使われることが多いですが、ずいぶん前からJavaもサポートされています。つまり、CICSのアプリケーションをJavaで書けるということです。EXEC CICSコマンドに相当するJavaのクラスライブラリ(JCICS)が提供されるので、Javaのメソッドを使ってEXEC CICS LINKとかEXEC CICS STARTみたいな操作を行うことができます。
ここではCICS-Javaアプリのサンプル(OSGi準拠)を実際に動かすところをやってみます。

※ちなみにCICS上でJavaEEアプリを動かすということもできます。
参考: CICS上でJavaEEアプリケーションを動かす

関連記事

CICS-Javaアプリケーションを動かす - (1)単純なサンプルアプリの稼働
CICS-Javaアプリケーションを動かす - (2)Mavenによるビルド管理
CICS-Javaアプリケーションを動かす - (3)Gradleによるビルド管理

環境情報

開発環境
Windows10
CICS Explorer V5.6(Beta)

実行環境
z/OS V2.4
CICS Transaction Server for z/OS V5.6(Beta)

CICSのJavaサポートについて

CICSのJavaサポートについて整理しておきます。
CICSではOSGi準拠のJavaアプリケーションがサポートされます。
Javaで実装したCICSアプリを動す場合、以下のような手続きが必要になります。

ランタイム準備
Javaアプリケーションを実行するために、ターゲットのCICSリージョンにJVMSERVERという資源定義を作成しJavaの実行環境を用意しておきます。

開発
開発作業はEclipseベースの無償ツール"CICS Explorer"で行います。
JCICSのライブラリーをimportしたJavaアプリを作成し、OSGiバンドル(jarの拡張のようなもの)という形でパッケージングします。(参考:OSGI概要 ) この時、CICSプログラムとして認識させたい先頭のロジックはmain関数で実装する必要があります。
いくつかのOSGi Bundleを"CICSバンドル"という単位にパッケージングします。
※CICS上ではLibertyも稼働させることができ、JavaEEアプリをCICS上のLibertyで稼働させることもできます。その場合JavaEEアプリはWARやEARの単位でパッケージされますが、その場合もそれらを"CICSバンドル"という単位にパッケージングするという考え方は同様です。

デプロイ
CICSバンドルを実行環境のz/OSのUSS上に転送します。
CICSリージョンの資源定義でBUNDLE定義を作成し、USS上に転送したCICSバンドル(Javaアプリ)を認識させます。
CICSリージョンの資源定義でPROGRAM定義を作成し、BUNDLEに含まれるJavaクラスをCICSプログラムとして登録します。

各CICS資源定義と実体との関連は以下の通りです。
image.png

事前準備

開発環境

以下の辺りを参考にCICS ExplorerというEclipseベースのツールをダウンロードしてセットアップしておきます。
参考:Downloading and starting CICS Explorer
※このツールはCICS利用者は無償で使えるツールです。

ホスト接続定義として、ターゲットとなるCICSが稼働するz/OSに対してFTP接続構成をしておきます。
image.png

実行環境

サンプルを稼働させるCICSリージョンを用意します。
ここでは、CT56B4A1という名前のリージョンを使うことにします。
そこに、Javaアプリケーションを稼働させるためのJVMServerという資源を追加します。

JVMプロファイル

USS上にJVMプロファイルを準備します。(JVMServerに関するプロパティーは実質このUSS上のファイルに指定します。JVMSERVER資源定義ではこのJVMプロファイルのファイル名をポイントすることになります。)
サンプルが提供されているのでそれをコピーして使用します。

/var/cicsts/cicsts56/CT56B4A1/JVMProfiles というディレクトリを作成し、そこに/usr/lpp/cicsts/cicsts56/JVMProfiles/DFHOSGI.jvmprofile(CICS導入ディレクトリ下に配置されているOSGi用のサンプル定義)をコピーします。
環境に合わせて適宜カスタマイズします。

DFHOSGI.jvmprofile抜粋
JAVA_HOME=/usr/lpp/java/J8.0_64/
WORK_DIR=/var/cicsts/cicsts56/CT56B4A1/work
-Xms32M
-Xmx256M
-Xmso1M
-Xgcpolicy:gencon
-Xscmx256M
-Xshareclasses:name=cicsts%g,groupAccess,nonfatal
-Xtune:virtualized
-Dcom.ibm.tools.attach.enable=no
_BPXK_DISABLE_SHLIB=YES

SIT

上のプロパティーファイルを配置したディレクトリを、SITパラメーター"JVMPROFILEDIR"に指定します。

JVMPROFILEDIR=/var/cicsts/cicsts56/CT56B4A1/JVMProfiles 

変更反映のためにリージョンを再起動します。

JVMServer定義

JVMSERVER資源定義を準備します。
製品提供のDFH$OSGIというグループにあるJVMSERVER定義"DFHJVMS"を適当なグループにコピーしてインストールします。

OBJECT CHARACTERISTICS                                    CICS RELEASE = 0730 
 CEDA  View JVmserver( DFHJVMS  )                                             
  JVmserver      : DFHJVMS                                                    
  Group          : TAG$OSGI                                                   
  DEScription    : CICS JVM server to run OSGi samples                        
  Status         : Enabled            Enabled | Disabled                      
  Jvmprofile     : DFHOSGI                                        (Mixed Case)
  Lerunopts      : DFHAXRO                                                    
  Threadlimit    : 015                1-256                                   
 DEFINITION SIGNATURE                                                         
  DEFinetime     : 06/02/20 17:28:17                                          
  CHANGETime     : 06/02/20 17:28:17                                          
  CHANGEUsrid    : CICSUSER                                                   
  CHANGEAGEnt    : CSDApi             CSDApi | CSDBatch                       
  CHANGEAGRel    : 0730                                                       

※Jvmprofile: DFHOSGIとなっていますが、これはSITのJVMPROFILEDIRに指定されたディレクトリ下のDFHOSGI.jvmprofileというファイルがJVMプロパティーファイルとして使用されることを意味します。

CEMT I JVMSERVERで見てEnableになっていればOK。

I JVMS                                               
STATUS:  RESULTS - OVERTYPE TO MODIFY                
 Jvm(DFHJVMS ) Ena     Prf(DFHOSGI ) Ler(DFHAXRO )   
    Threadc(000) Threadl( 015 ) Cur(9965280)         

bundleファイル配置用ディレクトリ

bundleファイルを配置するディレクトリをUSS上に作成しておきます。
ここでは、/var/cicsts/cicsts56/CT56B4A1/bundles/というディレクトリを作成しておくことにします。

サンプルアプリ稼働確認

参考: Java samples: JCICS examples

サンプルプロジェクトの作成

CICS Explorerのメニューからウィンドウ-設定を選択して設定ウィンドウを開きます。
プラグイン開発-ターゲット・プラットフォームを選択し、追加をクリックします。
image.png

テンプレートで対象のCICSのバージョンを選択します。ここではCICS TS V5.6を選択。
image.png

追加されたCICS TS 5.6をチェックして適用します。
image.png

Javaパースペクティブで、メニューからファイル-新規-その他を選択
image.png

CICSバンドルOSGiサンプルを選択
image.png

そのまま次へ
image.png

そのまま終了
image.png

サンプル・プロジェクトが作成されます。
image.png

サンプルの中身確認

このプロジェクトではいくつかのサンプルアプリが提供されていますが、そのうちの一番シンプルなHello Worldのソースを見てみます。
(com.ibm.cics.server.examples.helloプロジェクト(OSGiバンドル)に含まれるexamples.hello.HelloCICSWorld.java)

HelloCICSWorld.java
package examples.hello;

import com.ibm.cics.server.CommAreaHolder;
import com.ibm.cics.server.Task;

public class HelloCICSWorld
{
    public static void main(CommAreaHolder CAH)
    {
        Task t = Task.getTask();
        if ( t == null )
            System.err.println("HelloCICSWorld example: Can't get Task");
        else
            t.out.println("Hello from a Java CICS application");
    }
}

JCICSのcom.ibm.cics.server.Taskが使われています。
Task.outは端末出力のためのPrintWriterを意味するので、端末に対して単純な文字列を送信するロジックになっています。
参考: Javadoc - Task

同プロジェクトのMETA-INF/MANIFEST.MFを確認してみます。

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello Plug-in
Bundle-SymbolicName: com.ibm.cics.server.examples.hello
Bundle-Version: 1.0.0
Bundle-RequiredExecutionEnvironment: J2SE-1.4,
 J2SE-1.5,
 JavaSE-1.6
Import-Package: com.ibm.cics.server;version="[1.0.0,2.0.0)"
CICS-MainClass: examples.hello.HelloCICSWorld, examples.hello.HelloWorld

CICS-MainClassの指定で、mainメソッドを持つクラスを指定しています。ここで指定されたクラスのmainメソッドがCICSプログラムとして認識可能です(プログラム定義と紐づけ可能)。

com.ibm.cics.server.examples.bundleというCICSバンドルプロジェクトを確認します。
META-INF/cics.xmlをCICSバンドルマニフェストエディターで開くと、上のOSGiバンドルが含まれていることが確認できます。
image.png

com.ibm.cics.server.examples.hello.osgibundleを開きます。
image.png

ここに指定されているjvmserverの値を確認します。ここには稼働させるJVMSERVER定義の名前を指定する必要があります。事前準備の所で作成したJVMSERVER定義の名前に合わせて修正します。サンプル定義を同名でコピーしている場合はDFHJVMSになっていると思うので変更は不要です。

アプリケーションをCICSへのデプロイ

CICSへのデプロイは、"CICSバンドル"の単位で行います。
com.ibm.cics.server.examples.bundleプロジェクトを右クリックしてz/OS UNIXファイルシステムへのバンドル・プロジェクトのエクスポートをクリック
image.png

ファイルシステム内の特定のロケーションにエクスポートを選択して次へ
image.png

先に作成しておいたbundleファイル配置用のディレクトリを指定して終了
image.png

メッセージを確認
image.png
バンドルファイルがUSS上に配置されました。

CICS提供のDFH$OSGIグループの各リソースを適当なグループにコピーします。
BUNDLE定義"DFH$OSGB"をカスタマイズして、BUNDLEDIRの値を、上でバンドルファイルを配置したディレクトリ(/var/cicsts/cicsts56/CT56B4A1/bundles/com.ibm.cics.server.examples.bundle_1.0.0)に置き換えます。

OVERTYPE TO MODIFY                                        CICS RELEASE = 0730
 CEDA  ALter Bundle( DFH$OSGB )                                              
  Bundle         : DFH$OSGB                                                  
  Group          : TAG$OSGI                                                  
  DEScription  ==> CICS bundle containing OSGi sample bundles                
  Status       ==> Enabled            Enabled | Disabled                     
  BUndledir    ==> /var/cicsts/cicsts56/CT56B4A1/bundles/com.ibm.cics.server.
  (Mixed Case) ==> examples.bundle_1.0.0                                     
               ==>                                                           
               ==>                                                           
               ==>                                                           
  BAsescope    ==>                                                           
  (Mixed Case) ==>                                                           
               ==>                                                           
               ==>                                                           
               ==>                                                           
 DEFINITION SIGNATURE                                                        
  DEFinetime     : 06/02/20 18:27:37                                         
  CHANGETime     : 06/02/20 18:27:37                                         

インストールしてEnableになることを確認します。

I BUNDLE                                                 
STATUS:  RESULTS - OVERTYPE TO MODIFY                    
 Bun(DFH$OSGB) Ena         Par(00003) Tar(00003)         
    Enabledc(00003) Bundlei(com.ibm.cics.server.exampl)  

ちなみにCICS Explorerで資源定義確認すると、BUNDLE定義だけでなくBUNDLEに含まれるBUNDLEPARTSも確認できます。
image.png

サンプル実行

今回はHello Worldのサンプルを実行してみるので、このサンプルに関連する資源定義を追加でインストールします。
参考: Running the Hello World example

プログラム定義"DFJ$JHE2"と、トランザクション定義"JHE2"ですね。実体であるJavaプログラムはBUNDLEとして先にインストール済みです。
いずれもDFH$OSGIに提供されているのでコピーしてそのままインストールします。
ちなみにDFH$JHE2の定義を覗いてみると、

OBJECT CHARACTERISTICS                                    CICS RELEASE = 0730 
 CEDA  View PROGram( DFJ$JHE2 )                                               
  PROGram        : DFJ$JHE2                                                   
  Group          : TAG$OSGI                                                   
  DEScription    : OSGi Hello CICS world sample program                       
  Language       :                    CObol | Assembler | Le370 | C | Pli     
  RELoad         : No                 No | Yes                                
  RESident       : No                 No | Yes                                
  USAge          : Normal             Normal | Transient                      
  USElpacopy     : No                 No | Yes                                
  Status         : Enabled            Enabled | Disabled                      
  RSl            : 00                 0-24 | Public                           
  CEdf           : Yes                Yes | No                                
  DAtalocation   : Any                Below | Any                             
  EXECKey        : Cics               User | Cics                             
  COncurrency    : Required           Quasirent | Threadsafe | Required       
  Api            : Cicsapi            Cicsapi | Openapi                       
 REMOTE ATTRIBUTES                                                            
  DYnamic        : No                 No | Yes                                
  REMOTESystem   :                                                            
  REMOTEName     :                                                            
  Transid        :                                                            
  EXECUtionset   : Fullapi            Fullapi | Dplsubset                     
 JVM ATTRIBUTES                                                               
  JVM            : Yes                No | Yes                                
  JVMClass       : examples.hello.HelloCICSWorld                              
  (Mixed Case)   :                                                            
                 :                                                            
                 :                                                            
                 :                                                            
  JVMServer      : DFHJVMS                                                    
  JVMProfile     :                                                (Mixed Case)
 JAVA PROGRAM OBJECT ATTRIBUTES                                               
  Hotpool        : No                 No | Yes                                
...

こんな感じで、JVM:Yes, JVMClass:examples.hello.HelloCICSWorld, JVMServer:DFHJVMSが指定されています(BUNDLEPARTSでインストールされているクラス)。※事前準備で作成したJVMServer名が違う場合はそれに合わせて適宜修正してください。

これで一通り資源は整ったので実行してみます。
CICS端末からJHE2トランザクションを実行します。
image.png
Hello from a Java CICS applicationというJavaプログラムから出力している文字列が表示されました!
これでJavaがCICSプログラムとして動いたことが確認できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaでAWS S3オブジェクトの削除

AWS SDK for Javaを利用して、S3オブジェクトの削除ができます。

1つのオブジェクトの削除

bucket名とオブジェクトキーを指定すれば、指定オブジェクトの削除ができます。

try {
    final AmazonS3 s3 = new AmazonS3Client();
    s3.deleteObject(bucket_name, object_key);
} catch (AmazonServiceException e) {
    e.printStackTrace();
}

指定フォルダーにある全てのオブジェクトの削除

bucket名とフォルダー名を指定すれば、指定フォルダーにある全てのオブジェクトの削除ができます。

try {
    final AmazonS3 s3 = new AmazonS3Client();
  ObjectListing objListing = s3.listObjects(bucket_name,folder_nm);
    List<S3ObjectSummary> objList = objListing.getObjectSummaries();
    for (S3ObjectSummary obj : objList) {
         s3.deleteObject(bucket_name, obj.getKey());
    }
} catch (AmazonServiceException e) {
    e.printStackTrace();
}

s3.listObjectsは以下のようなポリシー設定は必要。

PolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Action: "s3:*"
          Resource:
            - "arn:aws:s3:::bucket-test-01"
            - "arn:aws:s3:::bucket-test-01/*"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaで作る年齢当てゲーム

とあるテレビ番組を見ていて

BSで「プロスタ」というScratchを題材にした子供向けプログラミング教育番組が
放送されているのを拝見。
私が見た回の中で、安藤先生という方の年齢を少ない質問数で当ててみようという
コーナーがあったのでその部分を参考にJavaで年齢当てゲームを作ってみました。

Main.java
package andouteacher;

import java.util.Random;
import java.util.Scanner;

// 安藤先生の年齢を当てるゲームです
// 指定された回答数の中で年齢を当てましょう
// コツは2分探索です
// 効率の良い答え方で候補を絞っていきましょう

public class Main {
    public static void main(String[] args) {
        Random rnd = new Random();
        int age = rnd.nextInt(80);
        int limit = 5;

        Scanner sc = new Scanner(System.in);
        System.out.println("安藤先生は何歳に見えますか?");
        System.out.println("回答するチャンスは"+ limit +"回までです");
        for(int i = 0; i < limit; i++) {
            int answer = sc.nextInt();
            if(answer == age) {
                System.out.println("正解です");
                break;
            } else if(answer > age){
                System.out.println("それよりも年下です");
            } else {
                System.out.println("それよりも年上です");
            }
        }
        System.out.println("正解は" + age + "歳でした");
    }
}

番組内では質問できる回数に制限はありませんでしたが、このプログラムでは
回数を5回までに制限しています。int型のlimitという変数がそれにあたります。
すごくシンプルなゲームですがプレイしてみると意外に楽しめます。
興味を持たれた方は是非、このコードを実行して遊んでみてください(^-^)

追記

ちなみにですが安藤先生は「70歳以上ですか?」と質問されると少しションボリします。
(これは番組内であったやりとりの話であって、このプログラムには一切関係ありません・・・)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む