まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:java

とにかく色々突っ込んでブラックホール化していた自宅のストレージ内を調べていたら、2000~2003 年頃に自分が作っていたアプリケーションソースコードを見つけました!
2017103100


その中に、今でもある程度動くものがありました。当時は github とかなかった(よね?)ので普通に自宅内で管理してたのですが、改めて公開することにしました:
https://github.com/dotnsf/dtopo


以下でその使い方を紹介しますが、まず前提条件として IBM Notes の環境が必要です(というか、公開しているのはノーツ用のアプリケーションです)。またアプリケーションは Eclipse 上で実行するつもりで作られているので、Eclipse が導入されている必要があります。これらはあらかじめインストールされているものとして以下を紹介します。


このアプリケーションはノーツ環境を設定するドミノディレクトリ内のメールルーティング情報および複製ルール情報を元に各サーバー間の関係を視覚化するものです。基本的にはこれらの情報が記述されたドミノディレクトリファイル(サーバーの names.nsf)があれば動かすことができます。試しに今回はこのようなサーバー接続文書情報を持ったドミノディレクトリ(dev/names.nsf)が手元にあるものとして、この内容を視覚化するケースを紹介します:
2017103101
(クリックすると拡大します)


まず上記 github の URL からプロジェクトをダウンロード&展開するか、git clone します。そしてそのプロジェクトを Eclipse のプロジェクトとして開きます。またこのプロジェクトの JRE は Notes にインストールされている Java に切り替える必要があります(詳しくはこちら)。加えてこのプロジェクトに外部 JAR ファイルとしてノーツフォルダ内にある Notes.jar および websvc.jar を追加設定する必要があります:
2017103102


次にプロジェクト内の dtopo.ini をテキストエディタで開きます。そして用意したドミノディレクトリファイルのファイルパスを n: で始まる行に指定してください(以下の例ではドミノデータディレクトリ以下の dev\names.nsf ファイルを指定しています)。ic, nic, cic はアイコン画像ファイルを指定しています。特に変更する必要はありません:
2017103103


そして同プロジェクトソース内の me.juge.dtopo.dtopo.java ファイルを右クリックし、"Run As ..." -> "Java Application" を選んで実行します:
2017103104


実行すると2つのウィンドウフレームが起動します。指定したドミノディレクトリファイルから読み取ったメールルーティング情報と複製ルール情報が別々のウィンドウフレームで表示されています:
2017103105
(↑図は複製ルール(フレーム上部のタイトルに注目))


この図の矢印の視点および終点付近はドラッグ&ドロップで位置を初期位置から調整することができます。またウィンドウフレーム自体のサイズも変更することができます。例えば今回の例では以下のようになりました(上:複製ルール、下:メールルーティング):
2017103101


2017103102


ソースコードは・・・さすがに当時の自分のスキル不足もあって決して見やすいとは思えません(苦笑)。ただ GUI アプリなのでマウスイベントに対するハンドリングを各所でしています。肝となるドミノディレクトリから複製/ルーティング情報を取り出すのは dtopo.java の 555 行目あたりからになります。ノスタルジックな意味合いもあって当時の汚いコードを残したままにしているのですが、興味のある方はこの辺りを参考にしてください(そういえば当時はノーツには  Mac クライアントも Linux クライアントもなかったので、これらの環境で動くかどうかは未検証です):
           :
           :
        Session s = Session.newInstance();
        String dbpath;
        String sserv, dserv, reptype, enabled, tasks;
        Vector sservs;
        String sdom, ddom;

        dbpath = nab;
        if( base.length() > 0 ){
          dbpath = base + "\\" + dbpath;
        }
        Database db = s.getDatabase( server, dbpath );
        if( !db.isOpen() ){
        	db.open();
        }

        //. 接続文書を検索する
        DocumentCollection docs = db.search( "Type = \"Connection\"" );
        for( int dcnt = 0; dcnt < docs.getCount(); dcnt ++ ){
            Document doc = docs.getNthDocument( dcnt + 1 );
           :
           :


ノーツデータを扱う Java アプリケーションの開発を考えている人の参考になれば嬉しいです。


ところで、このアプリケーションを作ったのは(ファイルのタイムスタンプによると)2002 年頃で、ギリギリ Java がクライアントアプリケーション開発にも使われていた頃です(標準の awt に加えて、swing や swt といった GUI のライブラリが出はじめていた頃でした。ちなみに今回紹介しているツールは awt で作っています)。アプリケーションだけでなくフレームワークにも時代を感じさせるものがありますが、その頃のノーツ R5 向けに作ったアプリケーションが 15 年以上の時を経て最新のノーツ9(のドミノディレクトリ)でもちゃんと動くという点がノーツらしいし、まさに Java らしい "Write once, Run anywhere!" だと感じました。

そういえばオラクル資本になってから、あまり "Write once, Run anywhere." って耳にしなくなりましたね。。。

 

Java のアプリ開発をしていて、こんな実行時エラーに遭遇することがあります:
java.lang.UnsatisfiedLinkError: C:\IBM\Notes\nlsxbe.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform


最近は Windows といえば 64bit 版が普通になってきました(よね?)。自分の開発環境も 64bit 版 Windows で、Java 開発環境も(JDK も JRE も Eclipse も)64bit 版を使っています。ここまでは珍しくないと思っています。

しかし開発するアプリケーションによっては外部ライブラリ(JAR ファイル)を指定する必要があり、その JAR ファイルが 32bit のネイティブライブラリを使っていたりするなど、64bit の JDK で 32bit 前提のコードを実行しなければならなくなった場合に上記のようなエラーが発生するのでした。

典型例としては、IBM Notes の Java API を使ったアプリケーション開発が挙げられます。2017/Oct/01 現在、Windows 版の IBM Notes は 32bit 版しか存在していません。Java で IBM Notes の API を実行するには IBM Notes 同梱の Notes.jar 他をインポートして使う必要がありますが、この Notes.jar は 32bit の DLL を参照するため、64bit Java から実行すると上記のエラーが発生します。


エラーの原因が分かったところで、どのように回避すればいいでしょうか? これも解決策としてはシンプルで 32bit Java(JRE) から実行すればよいことになります。32bit JRE をダウンロードして実行してもいいですが、上記のような IBM Notes の JAR を使った場合であれば、IBM Notes が使っている Java をそのまま指定して実行すればよいことになります。

ちとややこしいのが Eclipse のような IDE を使っている場合の指定方法です。まず Java - Installed JREs の中に Standard VM として Notes Java を追加しておく必要があります。Notes Java は(ノーツをインストールしたディレクトリ)/jvm にあるので、このディレクトリを指定して追加します:
2017100101


そして Eclipse の Java プロジェクトの中で JRE System Library として、上記で設定した Notes Java を指定します。これでこのプロジェクト内で作成したアプリを実行する際には Notes Java 内の java.exe が利用されるようになるので、上記のエラーが回避できるようになります:
2017100102



 

ラズパイ(ラズベリーパイ)上で検索エンジンである ElasticSearch が動くことがわかったので、自分も試してみました:
2017070601


まず、ElasticSearch そのものにラズパイネイティブ版が存在しているわけではありません。 ElasticSearch は Java アプリケーションなので実行には Java が必要であり、Java が有効な環境であれば理論上は動きます。というわけで最初にラズパイに JDK 8 を導入します(導入済みであれば、ここは読み飛ばしても可)。
$ sudo apt-get install oracle-java8-jdk

$ java -version
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

Java が使えるようになったので改めて ElasticSearch を導入します。ダウンロードページで確認すると、この記事を書いている 2017/Jul/06 時点での最新バージョンは 5.4.3 でした:
2017070602



これをラズパイ上にダウンロードして展開します:
$ cd ~
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.3.zip
$ unzip elasticsearch-5.4.3.zip
$ rm elasticsearch-5.4.3.zip
$ mv elasticsearch-5.4.3 elasticsearch
$ cd elasticsearch

さて、他のプラットフォームだとこのまま実行してもいいのですが、ラズパイの場合はシステムスペックの問題でデフォルトの設定のままだとメモリ不足になりやすいという問題があります。そこで実行前にメモリの設定を変更しておきます。22 行目と 23 行目(か、そのあたり)で、"-Xms2g" と "-Xmx2g" という指定がされており、これによって ElasticSearch に 2GB のメモリを使うよう指定されています。ここを以下の赤字のように書き換えて、メモリ使用量を 256MB にするよう変更します:
$ vi config/jvm.options

  :
  :
-Xms256m   ← -Xms2g から変更
-Xmx256m   ← -Xmx2g から変更
  :
  :

これでメモリの問題は解決した(はず)なので、改めて ElasticSearch を起動します:
$ ./bin/elasticsearch
[2017-07-06T10:41:17,203][WARN ][o.e.b.Natives ] unable to load JNA native support library, native methods will be disabled. : : [2017-07-06T10:41:39,511][INFO ][o.e.h.n.Netty4HttpServerTransport] [lonXjYa] publish_address {127.0.0.1:9200}, bound_addresses {[::1]:9200}, {127.0.0.1:9200} [2017-07-06T10:41:39,537][INFO ][o.e.n.Node ] [lonXjYa] started [2017-07-06T10:41:39,579][INFO ][o.e.g.GatewayService ] [lonXjYa] recovered [0] indices into cluster_state

いくつかの警告メッセージが表示されますが、ラズパイ上で ElasticSearch 5.4.3 が起動しています。確認のため、同じマシンから curl でアクセスしてみましょう:
$ curl http://localhost:9200/
{
  "name" : "lonXjYa",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "4hqvLhXQT0uqTaKOI02idg",
  "version" : {
    "number" : "5.4.3",
    "build_hash" : "eed30a8",
    "build_date" : "2017-06-22T00:34:03.743Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}

↑こんな感じのメッセージが表示されれば成功です。なお、ElasticSearch を終了するには実行中の端末で Ctrl+C を入力します:
    :
    :
[2017-07-06T10:41:39,537][INFO ][o.e.n.Node               ] [lonXjYa] started
[2017-07-06T10:41:39,579][INFO ][o.e.g.GatewayService     ] [lonXjYa] recovered [0] indices into cluster_state

^C[2017-07-06T10:52:09,484][INFO ][o.e.n.Node               ] [lonXjYa] stopping ...
[2017-07-06T10:52:09,579][INFO ][o.e.n.Node               ] [lonXjYa] stopped
[2017-07-06T10:52:09,581][INFO ][o.e.n.Node               ] [lonXjYa] closing ...
[2017-07-06T10:52:09,668][INFO ][o.e.n.Node               ] [lonXjYa] closed

$ 
  (↑Ctrl+C(赤字部分)を入力して終了する様子)


実はいま、個人的にはラズパイを開発環境として使う機会がそれなりにあります(ラズパイにリモートログインして vi でガシガシ、という感じ)。そのローカルシステム内に ElasticSearch 環境まで構築できる時代になったとは・・・今以上に開発がはかどりますね。


Java で byte 型配列に格納した画像データの情報に対して、幅や高さを調べたり、少し手を加えたりする目的でいったん BufferedImage 型に変換したり、その逆の変換を行う機会があったので、その時の作業メモです。

byte 型配列から BufferedImage 型への変換
byte[] img = new byte[];
    :
  (img にデータを格納する処理(省略))
    :
try{
  BufferedImage image = ImageIO.read( new ByteArrayInputStream( img ) );
    :
}catch( Exception e ){
}

BufferedImage 型から byte 型配列への変換
byte[] img = new byte[];
BufferedImage image = null;
    :
  (image にデータを格納する処理(省略))
    :
try{
  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  BufferedOutputStream os = new BufferedOutputStream( bos );
  image.flush();
  ImageIO.write( image, "png", os ); //. png 型
  img = bos.toByteArray();
    :
}catch( Exception e ){
}

byte 型配列から BufferedImage 型への変換は ImageIO クラスを使って一発でできますが、逆の BufferedImage 型から byte 型配列へはフォーマット(上記の場合だと PNG)を指定する必要がある、という点に注意です。


Java で(Web)アプリケーションから REST API を実行する時など、HTTP のクライアント機能を java.net.* から作るのは面倒です。現実的にはなんらかのライブラリを使うことになると思います。

そんな場合によく使われるのが Apache HTTP Client だと思ってます。2017/Mar/16 現在の最新バージョンは 4.5.3 でした。モジュールはこちらからダウンロードできます:
https://hc.apache.org/downloads.cgi

2017031501


上記サイトの HttpClient カテゴリから 4.5.3.zip と書かれたリンクをクリックすると 4.5.3 のバイナリが zip アーカイブとして取得できます(ファイル名は httpcomponents-client-4.5.3-bin.zip)。ダウンロードした zip ファイルを展開し、lib フォルダから jar ファイル群を取り出します(今回のサンプルで最低限必要なのは以下の6ファイルです):
  • commons-codec-1.4.jar
  • commons-logging-1.2.jar
  • httpclient-4.5.3.jar
  • httpclient-cache-4.5.3.jar
  • httpcore-4.4.6.jar
  • httpmime-4.5.3.jar

Eclipse 等で Java のウェブアプリケーションプロジェクトを作成し、lib フォルダ(Webcontent/WEB-INF/lib など)に上記作業で取り出した jar ファイル群をまとめてコピーしておきます。これで準備完了:
2017031502


では実際にこれらのモジュールを使って HTTP アクセスを実現するプログラムを書いて実行してみます。今回はスタンドアロンに HTTP GET を実行する、こんなプログラムにしてみます:
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient1 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://www.ibm.com/developerworks/jp/"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      String html = EntityUtils.toString( entity, "UTF-8" );
      System.out.println( html ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

指定した URL(上記の場合は https://www.ibm.com/developerworks/jp/")に HTTP でアクセスして、GET した結果をコンソールに出力する、というものです。この内容を記述したファイル(HttpClient1.java)を Eclipse から実行します:
2017031503


で、指定した URL  の HTML が取得できることを確認します。HTTP GET は呼び出すだけなのでシンプルですね:
2017031504


アクセス先として HTML のようなテキストではなく、画像のようなバイナリデータの場合は以下のように byte 配列として結果を取得します(HTTP リクエストヘッダを設定する例も加えています):
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient2 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://dw1.s81c.com/developerworks/i/f-ii-ibmbluemix.png"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      get.addHeader( "User-Agent", "MyBot/1.0" );  //. HTTP リクエストヘッダの設定
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      byte[] img = EntityUtils.toByteArray( entity );
      System.out.println( "" + img.length ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

一方、HTTP POST の場合も同様ですが、GET の時との違いとしてポストデータを送信する必要もあります。以下はテキスト情報とファイルのアップロードを同時に(Multipart で)送信する場合の例です:
import java.io.File;
import java.io.FileInputStream;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient3 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://xxx.com/posturl"; //. HTTP POST する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpPost post = new HttpPost( url );

//. 文字情報2つとファイル1つをポスト MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody( "name", "K.Kimura", ContentType.TEXT_PLAIN ); builder.addTextBody( "email", "dotnsf@jp.ibm.com", ContentType.TEXT_PLAIN ); File f = new File( "./logo.png" ); builder.addBinaryBody( "image_file", new FileInputStream( f ), ContentType.APPLICATION_OCTET_STREAM, f.getName() ); HttpEntity multipart = builder.build(); post.setEntity( multipart ); CloseableHttpResponse response = client.execute( post ); int sc = response.getStatusLine().getStatusCode(); //. 200 の想定 HttpEntity entity = response.getEntity(); String html = EntityUtils.toString( entity, "UTF-8" ); System.out.println( html ); //. 取得結果をコンソールへ client.close(); }catch( Exception e ){ e.printStackTrace(); } } }

PUT や DELETE の場合も同様に。

このページのトップヘ