20200402のJavaに関する記事は10件です。

javaのclassファイルを逆コンパイルする方法

  • 環境
    • Windows10 64bit バージョン1903
    • Eclipse Version: 2019-12 (4.14)

Javaのデコンパイラーをインストールする

  1. JAD Decompiler DownloadからJad 1.5.8g for Windows 9x-NT-2000 on Intel platform.zipをダウンロードする
  2. zipを解凍する
  3. zipから出てきたjad.exe$JAVA_HOME/binに格納する
    • $JAVA_HOMEに入れないでPATHを通してもOK
jadでヘルプ的なものが出ればOK
$ jad
Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov (jad@kpdus.com).
Usage:    jad [option(s)] <filename(s)>
Options: -a       - generate JVM instructions as comments (annotate)
...省略...

classファイルを逆コンパイルする

# 1. classファイルの場所を確認する
$ find /c/app/pleiades/workspace/ponsuke/ -type f -name PonsukeMain.*                                                                             /c/app/pleiades/workspace/ponsuke/src/main/java/ponsuke/PonsukeMain.java
/c/app/pleiades/workspace/ponsuke/target/classes/ponsuke/PonsukeMain.class

# 2. 逆コンパイルする
$ jad -a /c/app/pleiades/workspace/ponsuke/target/classes/ponsuke/PonsukeMain.class
Parsing C:/app/pleiades/workspace/ponsuke/target/classes/ponsuke/PonsukeMain.class... Generating PonsukeMain.jad

# 3. 現在の場所に逆コンパイルされたjadファイルが出力されるのでエディタなどで見る
$ ls -la | grep PonsukeMain
-rw-r--r-- 1 ponsuke 197609    3296 4月   2 23:32 PonsukeMain.jad
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

createSelectSql

package createSelectSql;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

/**
 * 指定したスキーマのselect文を出力する。
 */
public class CreateSelectSql {

    public static void main(String[] args) {

        Connection conn = null;
        ResultSet rs = null;
        CreateSelectSqlProcess process = new CreateSelectSqlProcess();

        try {
            // 出力ファイル生成
            File file = new File(CreateSelectSqlProperty.outputFilePath);
            if (file.exists()) {
                file.delete();
            }
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(CreateSelectSqlProperty.outputFilePath), StandardCharsets.UTF_8));

            // 接続
            Properties props = new Properties();
            props.setProperty("user", CreateSelectSqlProperty.user);
            props.setProperty("password", CreateSelectSqlProperty.pass);
            conn = DriverManager.getConnection(CreateSelectSqlProperty.url, props);
            Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);

            // 対象スキーマのテーブル一覧取得
            List<String> tableList = process.getTableList(st);

            for (String table : tableList) {
                process.outputSql(bw, st, table);
            }

            conn.commit();
            bw.close();

        } catch (SQLException | IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }

}
package createSelectSql;

import java.io.BufferedWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class CreateSelectSqlProcess {

    /**
     * 対象スキーマのテーブル一覧を取得
     *
     * @param st
     * @return
     * @throws SQLException
     */
    public List<String> getTableList(Statement st) throws SQLException {

        List<String> tableList = new ArrayList<>();

        // 対象スキーマのテーブル一覧を検索
        ResultSet rs = st.executeQuery("select tablename from pg_tables where tablename not like 'pg_%' and schemaname = '" + CreateSelectSqlProperty.schema + "';");

        while (rs.next()) {
            for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                tableList.add(rs.getString(i));
            }
        }

        return tableList;
    }

    /**
     * selectSQLを出力する
     *
     * @param bw
     * @param table
     * @throws SQLException
     * @throws IOException
     */
    public void outputSql(BufferedWriter bw, Statement st, String table) throws SQLException, IOException {

        String sql = "select ";

        // 対象テーブルのカラム一覧を取得
        ResultSet rs = st.executeQuery("select * from information_schema.columns where table_schema = '" + CreateSelectSqlProperty.schema + "' and table_name = '" + table
                + "' order by ordinal_position;");

        ResultSetMetaData rsmd = rs.getMetaData();

        while (rs.next()) {
            String colName = "";
            String colType = "";

            for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) {
                if ("column_name".equals(rsmd.getColumnName(i))) {
                    colName = rs.getString(i);

                } else if ("column_name".equals(rsmd.getColumnName(i))) {
                    colType = rsmd.getColumnTypeName(i);
                }
            }

            // select col1,
            sql += colName + ",";
        }

        // 最後の「,」を削除
        sql = sql.substring(0, sql.length() - 1);

        // select col1,col2,....col10 from [shcema].[table] where 1=1 ;
        sql += " from " + CreateSelectSqlProperty.schema + "." + table + " where 1=1 ;";

        bw.write(sql);
        bw.newLine();
    }

}
package createSelectSql;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Properties;

public class CreateSelectSqlProperty {

    /* プロパティファイルパス */
    public static final String PROPERTIES_FILE_PATH = "CreateSelectSql.properties";

    /* プロパティ:key */
    public static final String URL = "url";
    public static final String USER = "user";
    public static final String PASS = "pass";
    public static final String DB = "db";
    public static final String SCHEMA = "schema";
    public static final String OUTPUT_FILE_PATH = "outputFilePath";

    public static String url;
    public static String user;
    public static String pass;
    public static String db;
    public static String schema;
    public static String outputFilePath;

    static {
        Properties properties = new Properties();
        try {
            properties.load(Files.newBufferedReader(Paths.get(PROPERTIES_FILE_PATH), StandardCharsets.UTF_8));
            url = properties.getProperty(URL);
            user = properties.getProperty(USER);
            pass = properties.getProperty(PASS);
            db = properties.getProperty(DB);
            schema = properties.getProperty(SCHEMA);
            outputFilePath = properties.getProperty(OUTPUT_FILE_PATH);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
url=jdbc:postgresql://localhost:5432/postgres
user=postgres
pass=postgres
db=postgres
schema=sample
outputFilePath=select.sql
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mavenのversionに日付とその日のビルド番号を自動的に付与する

Mavenプロジェクトのversionは、基本的にpom.xmlに書いておきますが、基本的に手動でバージョンを上げなければなりません。

追記2020/04/03
releaseプラグインを使えば、x.y.zのzは自動的にインクリメントしてくれるそうです。

でも、日付やタイムスタンプ、ビルド番号を付与したいって言われたら?

pom.xmlをshスクリプトとかで書き換える?マルチモジュールのとき、大変すぎない?
mvn versions:set -DnewVersion=whateverで毎回一斉に書き換える?
手間が増えるよね?結局ビルドにスクリプトが必要になっちゃう?

JenkinsとかCIツールだったらプラグインで出来るみたいだけど・・・

なお、ここでやりたいのは、Arfictの生成物であるファイル名をどうにかしたいのではなく、Artifactのバージョンそのものを付ける方法の話です。

Goal

mvn installなどで、モジュールのversionに自動的に日付とビルド番号を付与する。
例)0.0.1.20200401-2

自前でスクリプトなどを書かずに実現すること。

なお、一部にCIの機能を使うことは許容する。
・・・どういうことかというと、とりあえずversionのテンプレートなどが定義できていて、一部の値をCIツールからビルドコマンドで渡せればOK、という意味です。

環境

ツールなど バージョンなど
MacbookPro macOS Mojave 10.14.5
IntelliJ IDEA Ultimate 2019.3.3
Java AdoptOpenJDK 11
apache maven 3.6.3
Spring Framework 5.2.4.RELEASE
JUnit 5.6.0
Tomcat apache-tomcat-8.5.51

実現方法

結論から言うと、一部にCIの機能を使うことは許容することで、日付とビルド番号を付与できます。
なお、このプロジェクトは、マルチモジュール構成となっていて、すべてのモジュールが親のバージョンを使い完全にバージョンを一致させる運用としています。
簡単に言うと、サブモジュールでは

pom.xml
    <artifactId>openfhir-api</artifactId>
    <packaging>war</packaging>

とだけして、versionタグを入れません。ルートのpom.xmlにだけ、入れておきます。
そうすると、サブモジュールもすべて同じバージョンになります。

1.ルートpomへの<version>の設定

root/pom.xml
    <version>0.0.1.${buildDate}-${revision}</version>

2.ルートpomへの<properties>の設定

root/pom.xml
    <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- バージョニングに使う日付 -->
        <buildDate>${maven.build.timestamp}</buildDate>
        <!-- バージョニングに使う日付のフォーマット -->
        <timestamp>${maven.build.timestamp}</timestamp>
        <maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>

        <!-- バージョニングに使うリビジョン番号。
        CI/CDツールにて -Drevision=buildNumber で上書きする -->
        <revision>SNAPSHOT</revision>
    </properties>

コメントにある通り、<revision>はコマンドラインから指定可能です。省略すると、SNAPSHOTが付くことになります。

3.サブモジュールの<parent><version>も変更する

submodule/pom.xml
    <parent>
        <groupId>jp.example.myapp</groupId>
        <artifactId>testsample</artifactId>
        <version>0.0.1.${buildDate}-${revision}</version>
    </parent>

すべてのサブモジュールに対して行うのを忘れないように。

4.ビルド

mvn clean installと、mvn install -Drevision=2としてビルドしてみてください。
各モジュールのtargetフォルダのpom.propertiesや、生成されたjar/warファイルのファイル名で確認できます。(<build>/<finalName>で上書きしていない場合)

(1)revision指定なし

$ mvn clean install
$ ls -la **/target/*.?ar
17:02 submodule/target/submodule-0.0.1.20200402-SNAPSHOT.war
17:02 core/target/core-0.0.1.20200402-SNAPSHOT.jar
submodule/target/maven-archiver/pom.properties
#Generated by Maven
#Thu Apr 02 17:02:10 JST 2020
groupId=jp.example.myapp
artifactId=testsample
version=0.0.1.20200402-SNAPSHOT

(2)revision指定あり

$ mvn clean install -Drevision=2
$ ls -la **/target/*.?ar
17:29 submodule/target/openfhir-api-0.0.1.20200402-2.war
17:29 core/target/openfhir-core-0.0.1.20200402-2.jar
submodule/target/maven-archiver/pom.properties
#Generated by Maven
#Thu Apr 02 17:29:47 JST 2020
groupId=jp.example.myapp
artifactId=testsample
version=0.0.1.20200402-2

5.CIでビルド番号を付与する

CIでの実行コマンドで、mvn installなどしていると思うので、そこに-Drevision=オプションを追加すればよいですね。
「その日のビルド番号」は、CIツールでそれぞれ取り方があると思うので、そちらを参照してください。

とりあえず、JenkinsだとVersion Numberというプラグインを使うと、BUILDS_TODAYというのが使えそうです。(ただ、リセットされるタイムゾーンは不明ですが・・・)

Azure Pipelinesだと、Configure run or build numbersのページによれば、$(Rev:r)を使えば行けそうです。

参考

-Dreivisionのやり方の参考になりました。
https://stackoverflow.com/questions/18456111/what-is-the-maven-way-for-automatic-project-versions-when-doing-continuous-deliv

マルチモジュールでのバージョニング管理について参考になりました。
https://qiita.com/ms0_mtRiver/items/85593b17d6ebb8fb2cfd

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

エラー:Gradle sync failed: Cause: invalid type code: 85 の対応[AndroidStudio]

概要

  • 2~3ヶ月ぶりにAndroidStudioを起動したら、既存、新規を含めてすべてのプロジェクトが動かなくなった件。

  • 結論としてはJDKをAndroidStudio内蔵の物に変更したらエラーがなくなって動くようになった。

  • イベントログに表示されていたエラーコードは以下になります。
    Gradle sync failed: Cause: invalid type code: 85

引用情報

下記のサイトの内容を参考に対応をした。
Gradle Sync Failed in Android Studio 3.6 Cause invalid type code :85

エラーコードでググっただけなのでもっと詳しい情報もあるかもしれないが対応できたので深くは調べない

対応内容

特定のプジェクトだけではなく全てのプロジェクトが一律で動かなくなったので個別のエラーではなく環境の問題であると考える。
イベントログを確認するとどうもgradleの起動(?)に失敗しているらしいので「また、gradleか!」という気分になった。

とりあえず下記のエラーで検索をかけるとトップにそれらしいサイトは出てきたが英語の質問サイトだったので備忘録も含めてここに日本語でも残す。
Gradle sync failed: Cause: invalid type code: 85

上記のサイトを機械翻訳をかけた結果AndroidStudio3.5からAndroidStudio3.6にアップグレードで発生している問題らしい。
対応としてはプロジェクトのJDKロケーションを外部のものではなくAndroidStudio内蔵のものに変更するといいとあるので。

以下の手順で対応した。

  1. 上部の「ファイル」メニューから「プロジェクト構造」を選択。

無題3.png
2. プロジェクト構造画面が表示されるので左の一覧の「SDKロケーション」を選択
3. 中央の「JDKロケーション」から「Embedded JDK:~」を選択。「適用」もしくは「OK」を押下して閉じる。
4. gradleのアップグレードが問われるのでアップグレードもしくはインストールを選択。

ここまで進めるとプロジェクトが問題なく動いた。
要はgradleの更新に失敗していると思われるが詳しいことはよくわからんかった。

ちなみに、エラーが直った後にJDKロケーションを元に戻しても問題なく動いたのでやはりgradleの更新に失敗が原因だったのだろう。
(元々OpenJDKを使用していたが戻しても問題なく動いた。)

後記

また、gradleか!という感想しか出てこない。
また、よく言われているようにAndroidStudiohはむやみやたらに最新化するものではないという戒めを残さないといけない
(常に最新化しないとは言っていない)

今回は3.5から3.6へのアップグレード固有の問題なので多発はしないと思われる。
願わくばこの記事が不要になるような環境になるよう祈っている

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

mac上のqemuでARM-cpuな環境を作ってjavaを動かす【結果→失敗】

本内容は結果として構築失敗です。備忘録として残します。

最終的にopenJDK(JRE)は動かせませんでした。

↓以下書こうとしてた内容

ちょっとARMなCPUでしか動かないバイナリがあり、それをmac上で動かしたかった。
まぁmacはIntelなので当然そのままでは無理。

なので、ARMが載ったPCとかを検討したが新規にインストとかするのがちょっとめんどくさいので、エミュを探すと
QEMUというものがあった。(ちなみにラズパイも所持しているがめんど(

qemu arm [raspbian]

ということで構築。
最初はカーネルとかダウンロードして起動させようとしたがめちゃ大変&失敗したので割愛。

なのでこちらを使用させていただいた。
https://github.com/karaage0703/raspbian_on_qemu
https://qiita.com/karaage0703/items/366eb17906a3341f1999
こちらも参考にさせていただきました。ありがとうございます。

sshも使いたかったので、
sudo raspi-configでsshを有効に。
qemuコマンドにポートフォワードを追加

$ qemu-system-arm -kernel kernel-qemu-4.19.50-buster -dtb versatile-pb.dtb -m 256 -M versatilepb -cpu arm1176 -serial stdio -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw" -drive file=2019-09-26-raspbian-buster.img,format=raw -no-reboot -net nic -net user,hostfwd=tcp::10022-:22

(ちなみにメモリを256から増やしたかったが、qemuの制限なのかこれが上限みたい)

-net nic -net user,hostfwd=tcp::10022-:22
を追加した。
これでlocalhost -p 10022でsshがつながるようになった。

ディスク拡張

そういえばデフォルトだとディスクサイズが小さいので、aptとかがサイズ無いよ、って止まる。
なのでimgを拡張
qemu-img resize 2019-09-26-raspbian-buster.img +4G
で起動してfdisk
sudo resize2fs /dev/sda2

そこで元々の要件、javaを動かすことだったが、これからが大変。

java

そのままだとjavaがエラーを吐いて動かない。
Server VM is only supported on ARMv7+ VFP
↑こんなエラー

なので色々な記事を参考にしつつ、構築していく。
まずは今のjavaを削除しまくる。(apt removeとかで)

それでもなんか残ってたので、
which /usr/bin/java ls -la /usr/bin/java
とかでjavaの実体を探す。
/usr/lib/jvm/java-9-openjdk-armhf/bin/java
だいたいこのあたりにあるはず。
dpkg -S /usr/lib/jvm/java-9-openjdk-armhf/bin/java
で詳細
openjdk-9-jre-headless:armhf: /usr/lib/jvm/java-9-openjdk-armhf/bin/java
とあったので、apt remove openjdk-9-jre-headless
で消す。

そして再インストールなど行うが、結局上記エラーメッセージが出るだけでJavaを動作させることはできなかった。
諦めて別の方法を模索することとした。

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

イメージファイル重ね合わせ

public class ImageTest {

    public static void main(String[] args) throws IOException {
        BufferedImage bufferedImage1 = ImageIO.read(new File("C:\\image_test\\tree.jpg"));
        BufferedImage bufferedImage2 = ImageIO.read(new File("C:\\image_test\\star2.png"));

        Graphics graphics1 = null;

        try {
            graphics1 = bufferedImage1.getGraphics();
            int x = 2;
            int y = 2;
            graphics1.drawImage(bufferedImage2, x, y, null);

        } finally {
            graphics1.dispose();
        }

        ImageIO.write(bufferedImage1, "jpg", new File("C:\\image_test\\dst_jpg2.jpg"));
    }

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

WSL Ubuntu18.04にJavaをインストールする

環境

・ホストOS: Windows10 Home
・仮想OS: WSL Ubuntu18.04 lts
(windows10に導入したwslを利用してUbuntu18.04を導入しています)

概要

・Javaをubuntu18.04にインストールする。

手順

① パッケージのアップデート

$ sudo apt update

② OPEN JREをインストールする

$ sudo apt-get install default-jre

JREがインストールできているか下記のコマンドで確認します

$ java --version
openjdk 11.0.6 2020-01-14
OpenJDK Runtime Environment (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.6+10-post-Ubuntu-1ubuntu118.04.1, mixed mode, sharing)

② OPEN JDKをインストールする

$ sudo apt-get install default-jdk

先ほどと同じようにインストールできているか確認します。javacはJavaコンパイラを扱うコマンドです。

$ javac --version
javac 11.0.6

① ②ともにバージョン指定をしてインストールすることもできますが、慣れないうちはdefault-jre, default-jdkで最新版をインストールするのが無難だと思います。

③ 環境変数の設定

まずは下記のコマンドでjavaのインストールパスを確認します。

$ sudo update-alternatives --config java
There is only one alternative in link group java (providing /usr/bin/java): /usr/lib/jvm/java-11-openjdk-amd64/bin/java
Nothing to configure

Javaのインストールパスが/usr/lib/jvm/java-11-openjdk-amd64 であることを確認できました。

続いて 下記のコマンドでbashファイルを開きます。

$ vi ~/.bashrc

bashファイルに下記の環境変数を設定します。

export JDK_HOME = "/usr/lib/jvm/java-11-openjdk-amd64"

export PATH="$JDK_HOME:$PATH"

:wq!コマンドで編集内容を保存した後、bashファイルを閉じます。

※Javaの環境構築について書かれた記事を読むと環境変数を$JAVA_HOMEとしている場合が多かったですが、そうするとVScodeの拡張機能が働かず、不都合に感じました。

環境変数が設定されているのを確認します。

$ echo $JDK_HOME
/usr/lib/jvm/java-1.11.0-openjdk-amd64

無事Javaをインストールできました。

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

【Java】Optional覚書

レガシーコードとは縁のないOptional

最近初めて業務で使ってうれしくなっちゃったので、次からも使えるように使用例のメモです。

2019年の完全新規案件でJava1.6を強制されることのある現場に在籍しています。
この期に及んでJava8に触れる機会があまりありません。
そんな中で「Java8からはnullチェックが簡単に書けるらしい」というJava8登場時に聞いた情報で書きました。
一応複数のサイトを確認して書いていますが、マサカリとか改善方法とかがありましたら、ぜひコメントをお願い致します。
(私のコードレビューが可能なレビュアーが案件に不在のため)(他のメンバーのレビューは私がしています)

getについて

使いどころがわからなかったので、使っていません。
いくつかサイトを見ると「get使うな論」を見かけたので、逆に正解だったのでは?感ある。

注意

「Optionalは単純に従来のnullチェックを置換できる」は正しい認識ではありませんでした。
「Optionalを使うとnullチェック忘れの可能性撲滅のためにnullチェックを強制できる」が正しいですね…
なのでサンプルコードは動きますが、Optionalの使い方としては正しい認識のもと書かれたコードではありません。
忘れないためにこの記事は削除せずに残しますが、「Optional初めて~!」な私みたいな方は引っかからないようにお気を付けください。
@sikani さん、ありがとうございました!やっぱり有識者にレビューしてもらわないとダメだ…
Optionalで書くことを他人のレビュー指摘で挙げてなかったのが不幸中の幸い。(Java8っぽいコードを強制されない案件なので)

サンプルコード

Java1.6はわざとアレな書き方をしています。Java8はすっきり書けますね:relaxed:
Java8の方はちょっと使い方間違ってるけど。
Java1.6でも三項演算子でワンライナーで書けますけどもね…retは無駄なだけだしアーリーリターンすらもせずにif~else使うしprivateメソッドも切らないで毎回if文書いてるタイプの現場でまじイケてないレガシーコードが大半なので、リファクタリングでいつも心が死ぬ。更に言うとJava8使ってもJava1.6の書き方そのままのメンバー多すぎて最早笑う。自分を意識高い系と誤解しそうになるので困る…最近賑わってた格上格下論争を思い出します。愚痴ですまない。

Java1.6
public String toStringIfNotNull(final Object obj) {
    String ret;
    if (obj == null) {
        ret = null;
    } else {
        ret = obj.toString();
    }
    return ret;
}
Java8
public String toStringIfNotNull(final Object obj) {
    return Optional.ofNullable(obj).map(Object::toString).orElse(null);
}

nullチェックの代わりにOptionalを使うなら、ofよりもofNullableorElseの組み合わせが最適解なのではないかな!?という印象。
nullじゃなければ元の値をそのまま返すのであれば、mapとか中間処理?が不要になるだけですし。

mapの他には、filterflatMapがあります。
filterはnullとfilter結果がfalseの場合にorElseの引数が返ります。
flatMapはnullの場合はorElseの引数が返りますが、flatMap処理の結果がnullの場合、NullPointerExceptionがスローされます。(mapの場合は処理結果がnullでもorElseの引数が返る)

nullかどうかで値を返すのではなく処理を分岐する場合は、if (Optional.ofNullable(obj).isPresent())って書けば大丈夫らしいぞ!
また、isPresentは真偽値が返るだけですが、ifPresentにして引数でラムダ式を渡せば、null以外の時のみ引数のラムダ式を実行、という風に書けるそうな。
まあでもJava7以降であれば、isPresentよりif (Objects.isNull(obj))(nonNull)で分岐したほうがすっきり書ける気はするけど。

ここまで書いて気付いたけど、サンプルコードの処理内容なら、Objects.toString(obj, null)(StringUtils.defaultStringみたいなやつ)でよかったね…確認不足でした。

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

Slack に Java のライブラリがどのようなリクエストを投げているかを調べるため Charles を使って HTTP トレースをとった話

概要

レガシーの Java アプリケーションが slack-api を使って通知していましたが、2020 年 3 月初旬になったら通知に失敗するようになりました。
Mac でトラブルシュートをするため、通知に失敗しているリクエストのペイロードがどのような内容であるかを確かめたいと考えました。
Fiddler を使ってトレースを取ろうとしましたが、乗ってきてくれないので、Charles を使うことに。
curl では、こちら を参考に、localhost:8888 へのプロキシを -x で指定すると、Chales でログが取得できました。

curl -x localhost:8888 -X POST --data-urlencode "payload={\"channel\": \"#general\", \"username\": \"gpedro Bot\", \"text\": \"これは #general に投稿されています。\", \"icon_emoji\": \":ghost:\"}" https://hooks.slack.com/services/<YOUR>/<TOKEN>

Java アプリケーションでは、どのように Charles で Http トレースをとるのか、というのがいまいちわからなかったので調べることにしました。

やり方

私のアプリケーションは、このライブラリ を使っていてました。随分メンテナンスが放って置かれていますし、マイナー感がでていますので、このライブラリについては、この記事をご覧になられている方々はまちがいなく使われていないのではないかと思います。
ただ、Proxy クラス やあるいはそれに相当する設定を実施して、なんらかの形で HTTP リクエストを Proxy 経由で送信する方法は、どのライブラリでも存在しているのではと考えています。1
このライブラリを使う際についても、デフォルトで API をコールするインスタンスを生成するのではなく、Proxy クラス を指定して、インスタンスを作ることができました。この方法でインスタンスを生成すると、Charles を使ってこのライブラリのリクエストの HTTP トレースをとることができました。もしも、トラブルシュートをしたいライブラリので、なんらかの形で参考になれば幸いです。

Charles のインストール

こちらの記事 を参考にインストールします。キーチェーンに入った、Charles Proxy.... という証明書を信頼します。

Charles の証明書をエクスポート

キーチェーンにて、Charles Proxy... の証明書を右クリック -> [Charles Proxy CA を書き出す] にて証明書をエクスポートします。
証明書の名前にはエスケープ文字が多く含まれるので、必要に応じて名前をシンプルなものに変更します。

JDK のライブラリへ証明書インポート

こちらの記事 の通り、インポートします。
私の場合は、AWS Corretto 8 を使っていたので下記でインポートしました。パスワードは上述の記事の通りでした。

sudo keytool -import -trustcacerts -file ~/Documents/charles_proxy.cer  -keystore /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/jre/lib/security/cacerts -alias ca

Proxy を指定してリクエストする

curl のときと同様に、Charles のポート 8888 を指定して、リクエストします。
このライブラリでは、

import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackMessage;

import java.net.InetSocketAddress;
import java.net.Proxy;

public class SlackExecute {

    public static void main(String[] args) {

        SlackMessage slackMessage = new SlackMessage(
                "#genearal",
                "gpedro Bot",
                "gpedro で Slack リクエストできますか"
        );
        slackMessage.setIcon(":ghost:");
        SlackApi api = new SlackApi(
                "https://hooks.slack.com/services/<SOME>/<TOKEN>", 
                new Proxy(
                    Proxy.Type.HTTP,
                    new InetSocketAddress("localhost", 8888)
                )
         );
        api.call(slackMessage);
    }
}

Charles を起動してアプリケーションを実行

これで、Charles の左ペーンに、https://hooks.slack.com が現れて内容をトレースできます。

{
    "channel": "#genearal",
    "username": "gpedro Bot",
    "icon_emoji": ":ghost:",
    "unfurl_media": false,
    "unfurl_links": false,
    "link_names": false,
    "text": "gpedro でリクエストできますか"
}

  1. 例えば Spring 5 にはリクエストを行う WebClient インターフェース があり、この記事 にプロキシの指定の仕方が記載されています。このクラスの場合、WebClient.Builder#clientConnector メソッドがあります。このメソッドの引数には、ClientHttpConnector クラス が指定できます。この実装クラスの ReactorClientHttpConnectorコンストラクタ には、HttpClient が指定できます。HttpClienttcpConfiguration メソッド は、TcpClient クラス を引数にとる関数を引数にし、TcpClient クラス の、proxy メソッド は、ProxyProvider.TypeSpec の実装を引数とする関数を引数に取ります。ProxyProvider.TypeSpec インターフェースの type メソッド で、Proxy のタイプを、ProxyProvider.TypeSpec インターフェースの type メソッドの戻り値の ProxyProvider.AddressSpec の実装で、ホストまたは InetSocketAddress クラス を指定できます。Proxy クラス を使いませんが、同じことを実現しています。 

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

Slack に Java のライブラリがどのようなリクエストを投げているかを調べるため Charles を使って HTTP トレースをとった話

概要

レガシーの Java アプリケーションが slack-api を使って通知していましたが、2020 年 3 月初旬になったら通知に失敗するようになりました。
Mac でトラブルシュートをするため、通知に失敗しているリクエストのペイロードがどのような内容であるかを確かめたいと考えました。
Fiddler を使ってトレースを取ろうとしましたが、乗ってきてくれないので、Charles を使うことに。
curl では、こちら を参考に、localhost:8888 へのプロキシを -x で指定すると、Chales でログが取得できました。

curl -x localhost:8888 -X POST --data-urlencode "payload={\"channel\": \"#general\", \"username\": \"gpedro Bot\", \"text\": \"これは #general に投稿されています。\", \"icon_emoji\": \":ghost:\"}" https://hooks.slack.com/services/<YOUR>/<TOKEN>

Java アプリケーションでは、どのように Charles で Http トレースをとるのか、というのがいまいちわからなかったので調べることにしました。

やり方

私のアプリケーションは、このライブラリ を使っていてました。随分メンテナンスが放って置かれていますし、マイナー感がでていますので、このライブラリについては、この記事をご覧になられている方々はまちがいなく使われていないのではないかと思います。
ただ、Proxy クラス やあるいはそれに相当する設定を実施して、なんらかの形で HTTP リクエストを Proxy 経由で送信する方法は、どのライブラリでも存在しているのではと考えています。1
このライブラリを使う際についても、デフォルトで API をコールするインスタンスを生成するのではなく、Proxy クラス を指定して、インスタンスを作ることができました。この方法でインスタンスを生成すると、Charles を使ってこのライブラリのリクエストの HTTP トレースをとることができました。もしも、トラブルシュートをしたいライブラリので、なんらかの形で参考になれば幸いです。

Charles のインストール

こちらの記事 を参考にインストールします。キーチェーンに入った、Charles Proxy.... という証明書を信頼します。

Charles の証明書をエクスポート

キーチェーンにて、Charles Proxy... の証明書を右クリック -> [Charles Proxy CA を書き出す] にて証明書をエクスポートします。
証明書の名前にはエスケープ文字が多く含まれるので、必要に応じて名前をシンプルなものに変更します。

JDK のライブラリへ証明書インポート

こちらの記事 の通り、インポートします。
私の場合は、AWS Corretto 8 を使っていたので下記でインポートしました。パスワードは上述の記事の通りでした。

sudo keytool -import -trustcacerts -file ~/Documents/charles_proxy.cer  -keystore /Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home/jre/lib/security/cacerts -alias ca

Proxy を指定してリクエストする

curl のときと同様に、Charles のポート 8888 を指定して、リクエストします。
このライブラリでは、

import net.gpedro.integrations.slack.SlackApi;
import net.gpedro.integrations.slack.SlackMessage;

import java.net.InetSocketAddress;
import java.net.Proxy;

public class SlackExecute {

    public static void main(String[] args) {

        SlackMessage slackMessage = new SlackMessage(
                "#genearal",
                "gpedro Bot",
                "gpedro で Slack リクエストできますか"
        );
        slackMessage.setIcon(":ghost:");
        SlackApi api = new SlackApi(
                "https://hooks.slack.com/services/<SOME>/<TOKEN>", 
                new Proxy(
                    Proxy.Type.HTTP,
                    new InetSocketAddress("localhost", 8888)
                )
         );
        api.call(slackMessage);
    }
}

Charles を起動してアプリケーションを実行

これで、Charles の左ペーンに、https://hooks.slack.com が現れて内容をトレースできます。

{
    "channel": "#genearal",
    "username": "gpedro Bot",
    "icon_emoji": ":ghost:",
    "unfurl_media": false,
    "unfurl_links": false,
    "link_names": false,
    "text": "gpedro でリクエストできますか"
}

  1. 例えば Spring 5 にはリクエストを行う WebClient インターフェース があり、この記事 にプロキシの指定の仕方が記載されています。このクラスの場合、WebClient.Builder#clientConnector メソッドがあります。このメソッドの引数には、ClientHttpConnector クラス が指定できます。この実装クラスの ReactorClientHttpConnectorコンストラクタ には、HttpClient が指定できます。HttpClienttcpConfiguration メソッド は、TcpClient クラス を引数にとる関数を引数にし、TcpClient クラス の、proxy メソッド は、ProxyProvider.TypeSpec の実装を引数とする関数を引数に取ります。ProxyProvider.TypeSpec インターフェースの type メソッド で、Proxy のタイプを、ProxyProvider.TypeSpec インターフェースの type メソッドの戻り値の ProxyProvider.AddressSpec の実装で、ホストまたは InetSocketAddress クラス を指定できます。Proxy クラス を使いませんが、同じことを実現しています。 

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