20200301のJavaに関する記事は9件です。

bitFlyer Lightning エラーコード集

bitFlyer Lightning エラーコード集

bitFlyer LightningのAPIを叩いていて発生したエラーコードとその対応策をまとめる

  1. {"status":-100,"error_message":"Invalid product","data":null}

  【原因】リクエストヘッダにコンテンツタイプの指定なし

  privateAPIを利用する際、リクエストボディにJSON形式の値を詰めると思うが、その値がどのような形式かを明示的に指定してやる必要がある

connection = (HttpURLConnection) url.openConnection();
(中略)
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BeamのDoFn/SimpleFunction/SerializableFunction

DoFn/SimpleFunction/SerializableFunctionは、Beamの抽象クラス/インターフェイスです。
機能が似ており、時々忘れるのでメモです。

DoFn

説明

ParDo.ofの引数に指定してTransformを定義するためのクラスです。
アノテーションを設定し制御することが出来ます。

  • ProcessElement
    • 入力から出力の変換処理(任意の数の出力)。
  • StartBundle/FinishBundle
  • Setup/Teardown

DoFnが満たすべき性質などについて、ParDoのドキュメントに補足が記載されています。

  • シリアライザブル
  • グローバルな状態を持たない
    • なんかやり取りしたい時はPCollection通してやろう
  • フォールトトレランス性
    • 再実行されることはありうるので、冪等になるようなDoFnにしよう
    • (無理な時はDoFnの外側のどこかで、カバーする必要)
  • 最適化
    • Beam RunnerがParDo同士をくっつけて(fusion)、最適化することもあるよ
    • くっつけやすいように、また、プログラムが理解しやすいように、DoFnは一つのタスクだけに集中した方がよい
    • Dataflowのfusionの説明

使用例


SimpleFunction

説明

名前の通り、シンプルなメソッドを定義する抽象クラスです。

使用例


SerializableFunction

説明

  • 名前の通り、Serializableである必要があります
  • さらに検査例外を投げることも出来ません

使用例

  • FileIO#by
    • 動的に送り先を指定するメソッドです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Ubuntu]Nukkitのサーバーを立てる

NukkitをUbuntuで立てます
半分前記事のコピーです

サーバー環境

・ConoHa VPS
・OS Ubuntu 18.04
・メモリ 512MB
・ストレージ SSD 20GB
・CPU 1core

Javaインストール

Nukkitの動作にはJavaが必要です
今回は例としてopenjdk-11-jreをインストールします

$ sudo apt-get install openjdk-11-jre

ポート開放

今回は例として19132ポート。
コマンドで打つべきなのは#とか$が先頭についてる奴だけです
つまり下でRules updatedって書いてあるのは打たなくていいんですわよ

$ ufw allow 19132/udp
Rules updated

ユーザー作成

今回は例としてnukkitという名前のユーザーを作ります

$ adduser nukkit
Adding user `nukkit' ...
Adding new group `nukkit' (1004) ...
Adding new user `nukkit' (1000) with group `nukkit' ...
Creating home directory `/home/nukkit' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:

こうなったら新ユーザーのパスワード打ってください
打っても何も表示されませんよ。勘違いしないでくださいね

Retype new UNIX password:

もう一度同じパスワードを打ってください

passwd: password updated successfully
Changing the user information for pmmp
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:

Full Name []:からOther []:まではそのままEnterで大丈夫です

Is the information correct? [Y/n]

と聞かれたらYと入力。

Sudo

Sudoグループに入れときます。意味わからなかったらMr.Googleに

$ gpasswd -a nukkit sudo
Adding user nukkit to group sudo

Nukkitインストール

まずはNukkit用のディレクトリ(フォルダー)のnukkitを作ります

$ su - nukkit
$ mkdir nukkit
$ cd nukkit

インストール

インストールします

$ wget https://ci.nukkitx.com/job/NukkitX/job/Nukkit/job/master/lastSuccessfulBuild/artifact/target/nukkit-1.0-SNAPSHOT.jar

ここからはPMMPと異なります
まず、起動するためのシェルスクリプト(使い方あってる?)を作ります

$ vi start.sh

// viを使って↓を張り付けてください。viの使い方はMr.Googleにask!
java -jar nukkit-1.0-SNAPSHOT.jar

起動

$ ./start.sh

※もし./start.sh: line 1: java: command not foundと表示されたら一番上のJavaインストールをやり直してみて下さい
※2もし-su: ./start.sh: Permission deniedと表示されたら権限設定がうまくいっていないのでchmodを使ってなんとかしてください(丸投げ)

11:41:52 [INFO ] Welcome! Please choose a language first!
11:41:52 [INFO ] eng => English
11:41:52 [INFO ] chs => 中文(?体)
11:41:52 [INFO ] cht => 中文(繁體)
11:41:52 [INFO ] jpn => 日本語
11:41:52 [INFO ] rus => Pycc?ий
11:41:52 [INFO ] spa => Espanol
11:41:52 [INFO ] pol => Polish
11:41:52 [INFO ] bra => Portugues-Brasil
11:41:52 [INFO ] kor => ???
11:41:52 [INFO ] ukr => Укра?нська
11:41:52 [INFO ] deu => Deutsch
11:41:52 [INFO ] ltu => Lietuvi?kai
11:41:52 [INFO ] idn => Indonesia
11:41:52 [INFO ] cze => Czech
11:41:52 [INFO ] tur => Turkish
11:41:52 [INFO ] fin => Suomi
> jpn
11:42:53 [INFO ] Loading nukkit.yml ...
11:42:53 [INFO ] Loading server.properties ...
11:42:53 [INFO ] 日本語 (jpn) を言語に選択しました
11:42:53 [INFO ] Minecraft: BEサーバー(v1.14.0に対応)を起動しています
11:42:53 [INFO ] Selected Zlib Provider: 2 (cn.nukkit.utils.ZlibThreadLocal)
> WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
11:42:53 [INFO ] 0.0.0.0:19132上でサーバーを開始しています
11:42:53 [INFO ] このサーバーはNukkitのバージョンgit-db2afe0「」(API 1.0.9)で動 作しています
11:42:53 [INFO ] NukkitはLGPLライセンスに基づき配布されています
11:42:55 [INFO ] Loading recipes...
11:42:55 [INFO ] Loaded 1320 recipes.
11:42:55 [INFO ] Successfully loaded 0 resource packs
11:42:55 [WARN ] ワールド "world" が見つかりません
> WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by io.netty.util.internal.PlatformDependent0$1 (file:/home/nukkit/nukkit/nukkit-1.0-SNAPSHOT.jar) to field java.nio.Buffer.address
WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.PlatformDependent0$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
11:42:55 [INFO ] ワールド "world" を読み込んでいます
11:42:56 [WARN ] ワールド "nether" が見つかりません
11:42:56 [INFO ] No level called "nether" found, creating default nether level.
11:42:56 [INFO ] ワールド "nether" を読み込んでいます
11:42:56 [INFO ] Epoll Status is true
11:42:56 [INFO ] GS4ステータス リスナーを開始
11:42:56 [INFO ] クエリポートを設定: 19132
11:42:56 [INFO ] クエリーは 0.0.0.0:19132 で動作しています
11:42:56 [INFO ] デフォルトゲームタイプ: サバイバルモード
11:42:56 [INFO ] 起動完了(63.552秒)! "help"または"?"でヘルプを表示

途中のエラーの意味は分かりません
ほっといてもいいそうです(PMMPer B氏曰く)
設定はnukkit/nukkit.ymlからどうぞ
できたぞぉぉぉぉ

Screen を使う

Screenを使うことでPMMPをバックグラウンドで常時起動させておくことができます
インストール↓

$ sudo apt-get install screen

セッション

// セッションを開始する
$ screen -S pmmp
$ ./start.sh

// セッションに接続する
$ screen -r pmmp

// セッションから離脱する
Ctrl + A + D キーを同時押し

終わり

グダグダ+適当でごめんなさい
間違ってるとこだらけだと思うんでなんかあったら教えてください

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

【2020年度版】Struts2の入門系記事まとめ

はじめに

業務系プロジェクトでは未だに現役で動いていることもあるStruts2さん。
2020年のこのご時世にまさか自分が触ることになろうとは…。

まったく触ったことがないのでHelloWorldをやってみた。
割と最近まとめてくれている優しい方がいたので触る分には困らなかった。
参考になった記事を紹介する。

自分のスペック

  • Java経験が4年くらい
  • Webシステム開発は1年くらい
    (SpringBootは分かる。新人の頃にSpringMVCも使っていたがよくわからない)
  • ぶっちゃけサーブレットとJSPはあんまり分からない

APIの開発はできるけど、テンプレートを使うやり方がよく分かってない。

環境情報

  • Windos10
  • Eclipse : 2019-12(Pleiades All in One)
  • XAMPP : XAMPP for Windows 7.3.11
  • Java : 1.8.0_221
  • Struts : 2.5.22(最新)

参考記事集

  • 【超初心者向け】Struts2超入門 - 2018年版 - Qiita
    これ見れば終わり。
    全部書いてくれている。2018年度版らしい。
    違っていたのはバージョンくらいかな?
    とりあえずこれ読んで触ってみれば、会社に行っても話が通じないレベルは脱せられそう。
    記事ではallを使っているが、自分が構築したときは最小構成(min)のStrutsで起動できた。

  • Apache Strutsプロジェクトへようこそ
    Strutsの公式ページ。
    ドキュメントやチュートリアルがある(チュートリアルやってないけど)。
    もちろんダウンロードもできる。

  • Struts2 を手動でwarファイルにしてデプロイするには - Qiita
    eclipseでは実行できるけど、他の環境へのデプロイは分からないプログラマにならないために。
    プロジェクト構成が違ったのか、記事のjarコマンドだけだとclassesがデプロイできなかった。
    ただ構成ツリーをサンプルで記載してくれているので合わせたらできた。

  • 2014年度版 Eclipse + Struts2 による Java Web アプリ開発入門 | CYOKODOG
    流し読みしかしていないが、1番上の記事の方が参考にされていた。
    サンプルページがあって参考になる。

作ってみたプロジェクトのソース

おわりに

SpringBootの方が簡単に作れるので、メインフレームワークとして使う気は今のところないがなんとなく触りは理解できた。
レガシーで情報がないことを覚悟していたが、概要をまとめてくれている記事があって本当に助かった。
なので自分も現在の最新状態で作れることを記事として残しておくことにした。
有用なリンクが他にあればぜひコメント等で教えていただけると幸いです。

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

Javaのチェック処理あれこれ

はじめに

プログラムを作成する上でチェック処理は欠かせませんが、ここではJavaにおけるチェック処理あれこれをまとめました。

前提条件

環境:Spring Framework

チェック処理

オブジェクト全般チェック

import java.util.Objects;

class Check {

    void execute() {
        // message == null
        if (Objects.isNull(message)) {
        }

        // message != null
        if (Objects.nonNull(message)) {
        }

        // message == nullの場合にNullPointerExceptionを投げる
        Objects.requireNonNull(message);
    }
}

文字列チェック

import org.springframework.util.StringUtils;

class Check {

    void execute() {
        // (message == null || "".equals(message))
        if (StringUtils.isEmpty(message)) {
        }

        // (str != null && !str.isEmpty())
        if (StringUtils.hasLength(message)) {
        }

    }
}

Collectionのチェック

import org.springframework.util.CollectionUtils;

class Check {

    void execute() {
        List<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");

        // (list == null || list.isEmpty())
        if (CollectionUtils.isEmpty(list)) {
        }

    }
}

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

SeleniumでShiftRightとShiftLeftを使い分ける

3行でわかる概要

  • JavaのSelenium(4.0.0-alpha-4)のWebElement::sendKeysで左右Shiftキーを使い分けようとした際に少し詰まった
  • 用意されているKeysSHIFT/LEFT_SHIFTの中身が同じ
  • Keysを使わずに文字列で"ShiftRight"と指定すると右Shiftとして認識される

問題

JavaでSeleniumを使用してkeyDownさせる場合、下記のようなコードを書く。

public void sendKey(WebDriver webDriver, CharSequence... keys) {
  WebElement element = webDriver.findElement(By.className("hoge"))
  element.sendKeys(keys);
}

WebElement.sendKeysは引数にCharSequence...を取るが、用意されているenumKeysを使用することが多い。
ただし、右Shiftキー/左Shiftキーのように同一機能のキーについて左右で使い分けたい場合は注意が必要。

enumKeysの実装は下記のようになっている。
SHIFT/LEFT_SHIFTがそれぞれ定義されているが、残念なことにどちらを送った場合でも左Shiftキーの挙動になってしまう。

Keys.java
public enum Keys implements CharSequence {
  ...
  SHIFT        ('\uE008'),
  LEFT_SHIFT   (Keys.SHIFT),
  CONTROL      ('\uE009'),
  LEFT_CONTROL (Keys.CONTROL),
  ALT          ('\uE00A'),
  LEFT_ALT     (Keys.ALT),
  LEFT         ('\uE012'),
  ARROW_LEFT   (Keys.LEFT),
  UP           ('\uE013'),
  ARROW_UP     (Keys.UP),
  RIGHT        ('\uE014'),
  ARROW_RIGHT  (Keys.RIGHT),
  DOWN         ('\uE015'),
  ARROW_DOWN   (Keys.DOWN),
  ...
}

解決策

KeysCharSequenceを実装しており、コンストラクタおよびtoString()の実装は下記のようになっている。

Keys.java
public enum Keys implements CharSequence {
  ...

  private final char keyCode;
  private final int codePoint;

  Keys(Keys key) {
    this(key.charAt(0));
  }

  Keys(char keyCode) {
    this.keyCode = keyCode;
    this.codePoint = String.valueOf(keyCode).codePoints().findFirst().getAsInt();
  }

  ...

  @Override
  public String toString {
    return String.valueOf(keyCode);
  }
}

また、WebElement.sendKeysは実質与えられたkeycodeを改行区切りで結合してWebDriverに送っているだけ。
そこで、Keys.SHIFTで定義されたKeyCodeを使用せずに下記のように文字列で指定することで正常に認識されるようになる。

  WebElement element = webDriver.findElement(By.className("hoge"))
  element.sendKeys("ShiftRight");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebFluxのフィルタを実装する(WebFlux基本編)

引き続きWebFluxの話です。
前回までで主にController〜Service層でのリアクティブプログラミングの実装サンプルを紹介しました。

今回はリクエストの前後の共通処理として利用するフィルタの実装例を紹介します。

FilterではなくWebFliterを実装する

Spring MVCでは javax.servlet.Filter を実装しますが、WebFluxでは org.springframework.web.server.WebFilter を実装します。

以下に、リクエスト処理の前後でログ出力するだけの、簡単なフィルタをそれぞれ実装してみました。
違いをみてみましょう。

Spring MVCのFilter実装例

リクエスト処理の前後にログを出力するフィルタ(Filter)
@Slf4j
public class TimestampFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        // before
        doBeforeRequest(req);
        try {
            // do request
            chain.doFilter(request, response);
            // after (success)
            doAfterRequest(req);
        } catch (Throwable throwable) {
            // after (with error)
            doAfterRequestWithError(req, throwable);
            throw e;
        }
    }

    private void doBeforeRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(HttpServletRequest request, Throwable throwable) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

これと同等の処理を WebFilter で実装したのが以下です。

Spring WebFluxのWebFilter実装例

リクエスト処理の前後にログを出力するフィルタ(WebFilter)
@Slf4j
@Component
public class TimestampFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).compose(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        // before
        return Mono.fromRunnable(() -> doBeforeRequest(exchange))
                // do request
                .then(call)
                // after (success)
                .doOnSuccess((done) -> doAfterRequest(exchange))
                // after (with error)
                .doOnError((throwable -> doAfterRequestWithError(exchange, throwable)));
    }

    private void doBeforeRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(ServerWebExchange exchange, Throwable throwable) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }
}

Mono.fromRunnable(~) は、戻り値の無い処理(void)をリアクティブに処理し、その後続を then(~) に記述します。
例では .then( call ) を指定し、次のフィルタまたはコントローラに処理を移譲しています。

さらに後続を doOnSuccess(~)doOnError(~) で繋げることで、事後処理を実装しています。

最後に、もう少し実践的なフィルタの実装サンプルを見てみましょう。

カスタム認証フィルタの実装例

以下は、保護されたリソースへのアクセス時にユーザ認証済みであるかをチェックする、いわゆる認証チェックフィルタの実装例です。

カスタム認証チェックフィルタ
@Slf4j
@Component
@Order(1)
public class AuthFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).compose(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            // セッションからユーザ情報を取得
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    // ユーザがある場合はリクエストを処理
                    .map(user -> call)
                    // ユーザが無い場合は未認証エラーをスロー
                    .orElseThrow(UnauthorizedException::new);
        });
    }
}

最初のセッション情報の取得 exchange.getSession()Mono<WebSession> を返すため、後続の書き方は以前紹介した 複数の順次APIコール が参考になるかと思います。

また、この例では未認証時はカスタムエラー(UnauthorizedException)をスローするので、共通のExceptionHandlerで 401:Unauthrized レスポンスに変換してあげる想定です。

もし、フィルタ内でレスポンスまで生成したい場合は以下のように実装できます。

WebFilter内でレスポンスまで生成
    ...省略...

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            // セッションからユーザ情報を取得
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    // ユーザがある場合はリクエストを処理
                    .map(user -> call) // do request
                    // ユーザが無い場合は未認証エラーレスポンスを生成
                    .orElse(writeUnauthorizedResponse(exchange));
        });
    }

    private Mono<Void> writeUnauthorizedResponse(ServerWebExchange exchange) {
        // 401:UnauthorizedエラーとしてJSONレスポンスを生成
        ServerHttpResponse response = exchange.getResponse();
        String body = "{\"message\":\"ログイン認証が必要です。\"}";
        return writeResponse(response, HttpStatus.UNAUTHORIZED, body);
    }

    private Mono<Void> writeResponse(ServerHttpResponse response, HttpStatus status, String jsonBody) {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        DataBufferFactory dbf = response.bufferFactory();
        return response.writeWith(Mono.just(dbf.wrap(jsonBody.getBytes())));
    }

今回のまとめ

WebFluxでフィルタは

  • WebFilter のサブクラスとして実装する
  • 中身はやはりリアクティブに処理を記述する (前々回の記事を参考)

となります。

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

WebFluxのフィルタを実装してみる

引き続きWebFluxの話です。
前回までで主にController〜Service層でのリアクティブプログラミングの実装を紹介しました。

今回はリクエストの前後の共通処理として利用するフィルタの実装例を紹介します。

FilterではなくWebFliterを実装する

Spring MVCでは javax.servlet.Filter を実装しますが、WebFluxでは org.springframework.web.server.WebFilter を実装します。

以下に、リクエスト処理の前後でログ出力するだけの、簡単なフィルタをそれぞれ実装してみました。
違いをみてみましょう。

Spring MVCのFilter実装例

リクエスト処理の前後にログを出力するフィルタ(Filter)
@Slf4j
public class TimestampFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        // before
        doBeforeRequest(req);
        try {
            // do request
            chain.doFilter(request, response);
            // after (success)
            doAfterRequest(req);
        } catch (Throwable throwable) {
            // after (with error)
            doAfterRequestWithError(req, throwable);
            throw throwable;
        }
    }

    private void doBeforeRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(HttpServletRequest request, Throwable throwable) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

これと同等の処理を WebFilter で実装したのが以下です。

Spring WebFluxのWebFilter実装例

リクエスト処理の前後にログを出力するフィルタ(WebFilter)
@Slf4j
@Component
public class TimestampFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).compose(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        // before
        return Mono.fromRunnable(() -> doBeforeRequest(exchange))
                // do request
                .then(call)
                // after (success)
                .doOnSuccess((done) -> doAfterRequest(exchange))
                // after (with error)
                .doOnError((throwable -> doAfterRequestWithError(exchange, throwable)));
    }

    private void doBeforeRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(ServerWebExchange exchange, Throwable throwable) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }
}

Mono.fromRunnable(~) は、戻り値の無い処理(void)をリアクティブに処理し、その後続を then(~) に記述します。
例では .then( call ) を指定し、次のフィルタまたはコントローラに処理を移譲しています。

さらに後続を doOnSuccess(~)doOnError(~) で繋げることで、事後処理を実装しています。

最後に、もう少し実践的なフィルタの実装サンプルを見てみましょう。

カスタム認証フィルタの実装例

以下は、保護されたリソースへのアクセス時にユーザ認証済みであるかをチェックする、いわゆる認証チェックフィルタの実装例です。

カスタム認証チェックフィルタ
@Slf4j
@Component
@Order(1)
public class AuthFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).compose(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            // セッションからユーザ情報を取得
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    // ユーザがある場合はリクエストを処理
                    .map(user -> call)
                    // ユーザが無い場合は未認証エラーをスロー
                    .orElseThrow(UnauthorizedException::new);
        });
    }
}

最初のセッション情報の取得 exchange.getSession()Mono<WebSession> を返すため、後続の書き方は以前紹介した 複数の順次APIコール が参考になるかと思います。

また、この例では未認証時はカスタムエラー(UnauthorizedException)をスローするので、共通のExceptionHandlerで 401:Unauthrized レスポンスに変換してあげる想定です。

もし、フィルタ内でレスポンスまで生成したい場合は以下のように実装できます。

WebFilter内でレスポンスまで生成
    ...省略...

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            // セッションからユーザ情報を取得
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    // ユーザがある場合はリクエストを処理
                    .map(user -> call) // do request
                    // ユーザが無い場合は未認証エラーレスポンスを生成
                    .orElse(writeUnauthorizedResponse(exchange));
        });
    }

    private Mono<Void> writeUnauthorizedResponse(ServerWebExchange exchange) {
        // 401:UnauthorizedエラーとしてJSONレスポンスを生成
        ServerHttpResponse response = exchange.getResponse();
        String body = "{\"message\":\"ログイン認証が必要です。\"}";
        return writeResponse(response, HttpStatus.UNAUTHORIZED, body);
    }

    private Mono<Void> writeResponse(ServerHttpResponse response, HttpStatus status, String jsonBody) {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        DataBufferFactory dbf = response.bufferFactory();
        return response.writeWith(Mono.just(dbf.wrap(jsonBody.getBytes())));
    }

今回のまとめ

WebFluxでフィルタは

  • WebFilter のサブクラスとして実装する
  • 中身はやはりリアクティブに処理を記述する (前々回の記事を参考)

となります。

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

Android StudioでExpandableListView(折りたたみリスト)を使う方法

はじめに

折りたたむことができるリストのメモになります。

コード

string.xml
<resources>
    <string name="app_name">ExpandableListView</string>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ExpandableListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/exListView"></ExpandableListView>

</LinearLayout>
MainActivity.java
package com.websarva.wings.android.expandablelistview;

import android.app.AlertDialog;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.SimpleExpandableListAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    List<Map<String, String>> parentList;
    List<List<Map<String, String>>> childList;
    List<Map<String, String>> childDataList;
    Map<String, String> childtData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        parentList = new ArrayList<>();
        childList = new ArrayList<>();

        //親リストに表示する内容を生成
        setParentList("parentKey", "親1");
        setParentList("parentKey", "親2");

        //子リストに表示する内容を生成
        String firstItemName1[] = {"子1", "子2"};
        String secondItemName1[] = {"子1段目", "子2段目"};
        setChildDataList("childKey1", firstItemName1, "childKey2", secondItemName1);

        String firstItemName2[] = {"子A", "子B", "子C"};
        String secondItemName2[] = {"子A段目", "子B段目", "子C段目"};
        setChildDataList("childKey1", firstItemName2, "childKey2", secondItemName2);

        //アダプタ作成
        SimpleExpandableListAdapter adapter =
                new SimpleExpandableListAdapter(
                        this,
                        parentList,
                        android.R.layout.simple_expandable_list_item_1,
                        new String[]{"parentKey"},
                        new int[]{android.R.id.text1},
                        childList,
                        android.R.layout.simple_expandable_list_item_2,
                        new String[]{"childKey1", "childKey2"},
                        new int[]{android.R.id.text1, android.R.id.text2}
                );

        ExpandableListView elv = findViewById(R.id.exListView);
        elv.setAdapter(adapter);

    }

    /**
     * 親リストに表示する内容を生成する処理
     * @param parentKey
     * @param value
     * @return parentList
     */
    private List<Map<String, String>> setParentList(String parentKey, String value) {
        Map<String, String> parentData = new HashMap<String, String>();
        parentData.put(parentKey, value);
        parentList.add(parentData);
        return parentList;
    }

    /**
     * 子リストに表示する内容を生成する処理
     * @param childKey1
     * @param firstItemName
     * @param childKey2
     * @param secondItemName
     * @return childDataList
     */
    private List<Map<String, String>> setChildData(String childKey1, String firstItemName, String childKey2, String secondItemName) {
        childtData = new HashMap<String, String>();
        childtData.put(childKey1, firstItemName);
        childtData.put(childKey2, secondItemName);
        childDataList.add(childtData);
        return childDataList;
    }

    /**
     * 子リストを表示数分作成する処理
     * @param childKey1
     * @param firstItemName
     * @param childKey2
     * @param secondItemName
     * @return childList
     */
    private List<List<Map<String, String>>> setChildDataList(String childKey1, String firstItemName[], String childKey2, String secondItemName[]) {
        childDataList = new ArrayList<>();
        //作成する項目数の回数繰り返す
        for (int i = 0; i < firstItemName.length; i++) {
            setChildData(childKey1, firstItemName[i], childKey2, secondItemName[i]);
        }
        childList.add(childDataList);
        return childList;
    }

}

実行結果

新しいビットマップ イメージ.jpeg

解説

SimpleExpandableListAdapterを使用して、ExpandableListViewにリストとして表示したいデータをセットします。
リストとして表示したいデータをSimpleExpandableListAdapterに格納するために、下記の手順で作成します。

手順

1.親リストに表示する内容を生成する処理
2.子リストに表示する内容を生成する処理
3.子リストの項目の数、繰り返す処理
4.親リストに表示するデータをMapに格納する
5.子リストに表示するデータをMapに格納する
6.親リストと、子リストをSimpleExpandableListAdapterへ格納する

各データの格納

setParentListメソッドを呼び出した数だけ、親リストを作成する。
引数は、MAPのに格納するキーと値を渡す。

setParentList.親データの格納
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        parentList = new ArrayList<>();

     ~省略~

        //親リストに表示する内容を生成
        setParentList("parentKey", "親1");
        setParentList("parentKey", "親2");

     ~省略~
    }

private List<Map<String, String>> setParentList(String parentKey, String value) {
        Map<String, String> parentData = new HashMap<String, String>();
        parentData.put(parentKey, value);
        parentList.add(parentData);
        return parentList;
    }

setChildDataListメソッドを呼び出した数だけ、親リストを作成する。
引数は、MAPのに格納するキー(第1、第3引数)と値(第2、第4引数)を渡す。

setChildDataList.子データの格納
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

     ~省略~

        childList = new ArrayList<>();

     ~省略~

        //子リストに表示する内容を生成
        String firstItemName1[] = {"子1", "子2"};
        String secondItemName1[] = {"子1段目", "子2段目"};
        setChildDataList("childKey1", firstItemName1, "childKey2", secondItemName1);

        String firstItemName2[] = {"子A", "子B", "子C"};
        String secondItemName2[] = {"子A段目", "子B段目", "子C段目"};
        setChildDataList("childKey1", firstItemName2, "childKey2", secondItemName2);

     ~省略~
     }

private List<Map<String, String>> setChildData(String childKey1, String firstItemName, String childKey2, String secondItemName) {
        childtData = new HashMap<String, String>();
        childtData.put(childKey1, firstItemName);
        childtData.put(childKey2, secondItemName);
        childDataList.add(childtData);
        return childDataList;
    }

    private List<List<Map<String, String>>> setChildDataList(String childKey1, String firstItemName[], String childKey2, String secondItemName[]) {
        childDataList = new ArrayList<>();
        //作成する項目数の回数繰り返す
        for (int i = 0; i < firstItemName.length; i++) {
            setChildData(childKey1, firstItemName[i], childKey2, secondItemName[i]);
        }
        childList.add(childDataList);
        return childList;
    }

アダプタの格納

親、子のデータをアダプタに格納する

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

     ~省略~

        //アダプタ作成
        SimpleExpandableListAdapter adapter =
                new SimpleExpandableListAdapter(
                        this, ・・・①
                        parentList, ・・・②
                        android.R.layout.simple_expandable_list_item_1, ・・・③
                        new String[]{"parentKey"}, ・・・④
                        new int[]{android.R.id.text1}, ・・・⑤
                        childList, ・・・②
                        android.R.layout.simple_expandable_list_item_2, ・・・③
                        new String[]{"childKey1", "childKey2"}, ・・・④
                        new int[]{android.R.id.text1, android.R.id.text2} ・・・⑤
                );

        ExpandableListView elv = findViewById(R.id.exListView);
        elv.setAdapter(adapter);

    }

①ExpandableListViewが関連付けられるContext
②List
③各行のレイアウトを表すR値
④MAPのキー
⑤レイアウト内での文字を表示するTextViewのID

以上、ExpandableListView(折りたたみリスト)の使用方法になります。

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