2014年4月4日金曜日

java.lang.IllegalStateExceptionの対処法

JavaEE7のRemoteEndpoint.Async.sendText()を連続して呼び出すと、java.lang.IllegalStateExceptionがスローされることがあります。

これは、sendText()の処理が終わる前に、再度sendText()が呼び出されたためです。
sendText()の処理は非同期で行われるために、メソッドが戻ってきたとしてもメッセージ送信が終了している保証はありません。

これを防ぐためには、sendText()の戻り値であるFutre型のオブジェクトのget()を呼び出して、処理が終わるのを待つ必要があります。



 public void sendMessageToBrowser(String message) {
  for (Session s : sessions) {
   Future future = s.getAsyncRemote().sendText(message);
   try {
    future.get();
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   } catch (ExecutionException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

2014年4月1日火曜日

websocketのサンプルコードをJavaEEで書いた

JavaEE7にはwebsocketを実装したパッケージ(javax.websocket)が提供されています。

これを使ったサンプルコードを書いてみました。
ブラウザから入力された数字の数だけ、適当な文字列(数字列)を接続している全ブラウザに表示するコードです。

まずはJava側。Servletクラスとかを継承しなくていいみたいです。
注意が必要なのは、RemoteEndpoint.Async.sendText()はFuture型のオブジェクトを返すということ。つまり、sendText()メソッドの処理は非同期で行われます。

sendText()はその仕様として処理が終わる前に、再び呼び出されるとjava.lang.IllegalStateExceptionを送出します。そのため、for文などを用いて繰り返し、メッセージを送りたい場合はFuture.get()などを用いて、処理が終わるのを待ってから次のメッセージを送るようにします。

import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

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

@ServerEndpoint("/endpoint")
public class Main {
    static Set<session> sessions = Collections.synchronizedSet(new HashSet<session>());
    @OnMessage
    public void onMessage(String message) {
        int count = Integer.valueOf(message);
        run(count);
    }

    @OnOpen
    public void open(Session sess) {
        sessions.add(sess);
    }

    @OnClose
    public void close(Session sess) {
        sessions.remove(sess);
    }

    public void sendMessageToBrowser(String message) {
       for (Session s : sessions) {
           Future future = s.getAsyncRemote().sendText(message);
           try {
                   future.get();
           } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
           } catch (ExecutionException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
           }
       }
    }
 
    private void run(int count){
        Random rnd = new Random();
  
        for(int i = 0; i &lt count; i++){
                 String s = String.valueOf(rnd.nextInt());
                sendMessageToBrowser(s);
        }
    } 
}

続いてhtml。

<!DOCTYPE html>
<html>
    <head>
        <title>WebSocketテスト</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script src="http://code.jquery.com/jquery-1.9.0.js"></script>
        <script type="text/javascript">
            var socket;
            $(document).ready(function(){
             var server="ws://localhost:8080/temporary/endpoint";
                socket = new WebSocket(server);
                
                socket.onmessage = function(message){
                    $('#messageFromServer').append(message.data + "<br/>");
                };

                $('#send').click(function(){
                    var text = $('#msg').val();
                    socket.send(text);
                })

            });
        </script>        
    </head>
    <body>
        <div id="messageFromServer">
        input the integer</div>
        <input id="msg" type="text"/>
        <button id="send">go</button>
    </body>
</html>

参考文献
WebSocketをネタにJava EE 7正式版を試してみる

2014年3月14日金曜日

Mac X 10.8.5(Mountain Lion)のEclipseでJDK7を利用する方法

Mac OS X上のEclipseでJDK7を入れる方法をググると情報が錯綜しているようなので私がやった方法をまとめてみた。

1. まずOracleのサイトからJDKを取得(*)

2. dmgを叩いてインストール。

3. Eclipse->Preference->Java->Installed JREを開き、下記の場所をJRE Homeに指定。

Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home/

(*)Apple公式から落とすとの情報もあったが、ファイル名にバージョンの表記がなくよくわからなかったので、Oracleから落とした。

JDKのインストール場所がわからなくって結構困った。

またjava preferences というアプリが、/Utility/Application配下にあり、ここにパス情報が書かれているとの情報が良く出てくるが、最近ではなくなっているようだ。

参考文献。
MacにJDK7をインストールしたからといってAppleのJDK6を消してはいけない

java preferencesがない

2014年3月7日金曜日

Jerseyのサンプルコード

JavaでRestfulなサーバを実装するためのライブラリであるJerseyを触ってみました。
アノテーションを利用することによって、URLとJavaのメソッドの呼び出しを簡単に実装できるようになってます。


import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("sample/{samplePathParam}")
public class Main {
 @Path("get1")
 @GET
 @Produces(MediaType.TEXT_PLAIN)
 public String get1(
   @DefaultValue("2") @QueryParam("testInt") int testInt,
   @DefaultValue("true") @QueryParam("testBoolean") boolean testBoolean,
   @DefaultValue("value") @QueryParam("testString") String testString) {
  return String.valueOf(testInt) + ";" + String.valueOf(testBoolean)
    + ";" + String.valueOf(testString);
 }
 @Path("get2")
 @GET
 @Produces(MediaType.TEXT_PLAIN)
 public String get2(
   @PathParam("samplePathParam") String param) {
  return param;
 }
 @Path("get3/{samplePathParamWithConstraint: [a-z]*}")
 @GET
 @Produces(MediaType.TEXT_PLAIN)
 public String get3(
   @PathParam("samplePathParam") String param1,
   @PathParam("samplePathParamWithConstraint") String param2) {
  return param1 + ":" + param2;
 }
}

これに対して色々アクセスしてみました。
http://localhost:8080/SampleServer/webapi/sample/foo/get1?testInt=10&testBoolean=false&testString=hoge
=>10;false;hoge
get1以下のクエリストリングがjavaの引数に渡されています。
http://localhost:8080/SampleServer/webapi/sample/foo/get2
=>foo
@Path("sample/{samplePathParam}")で指定したsamplePathParamがget2の引数に引き継がれてます。
http://localhost:8080/SampleServer/webapi/sample/foo/get3/bar
=>foo:bar
@Path("sample/{samplePathParam}")で指定したsamplePathParamと@Path("get3/{samplePathParamWithConstraint: [a-z]*}")で指定したsamplePathParamWithConstraintがget3の引数に引き継がれてます。

アノテーションの使い方の詳細は公式ドキュメントがわかりやすいです。

参考文献
Chapter 3. JAX-RS Application, Resources and Sub-Resources

2013年12月15日日曜日

Paizaのオンラインハッカソンに参加してみた

ちょっと前に話題になったpaizaのオンラインハッカソンに参加してみました。

とりあえず言語はjavaを選択。下記のような方針でコードを書きました。

1. 商品の金額データを昇順にソートする。
2. ソート済みのデータに対して先頭と後尾から挟み撃ちで検索をかけ、指定されたキャンペーン金額以下の最大値を検索する。

計算量は1がN(O*logN)で2がO(N)。なので全体としてはN(O*logN)になります。
2の探索はbinary searchを利用すればもう少し早くなりそうですが、どのみち1のソートでO(N*logN)かかるので、手を抜きました。

実際に書いたコードは次のようになります。







import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;


public class Main {

    public static void quickSort(int[] arr, int left, int right){
        if (left <= right) {
            int p = arr[(left+right) / 2];
            int l = left;
            int r = right;
            
            while(l <= r) {
                while(arr[l] < p){ l++; }
                while(arr[r] > p){ r--; }
                
                if (l <= r) {
                    int tmp = arr[l];
                    arr[l] = arr[r];
                    arr[r] = tmp;
                    l++; 
                    r--;
                }
            }
    
            quickSort(arr, left, r);
            quickSort(arr, l, right);
        }
    }
 public static void main(String args[]){

     try {
         BufferedReader br = 
           new BufferedReader(new InputStreamReader(System.in));         

         String nd[] = br.readLine().split(" ");
         int n = Integer.parseInt(nd[0]);
         int d = Integer.parseInt(nd[1]);
         
         int narr[] = new int[n];
         
         int min = Integer.MAX_VALUE;
         for(int i = 0; i < n; i++){
          int a = Integer.parseInt(br.readLine());          
          if(a < min){
           min = a;
          }
          
          narr[i] = a;

         }
         
         quickSort(narr,0,n-1);
         for(int i = 0; i < d; i++){
          
          int a = Integer.parseInt(br.readLine());          
          if(a <= min * 2){
           System.out.println(0);
          }else{
           int start = 0;
           int end = n -1;
           int max = 0;
           while(start < end){
            int sum = narr[start] + narr[end];
            if(sum <= a && sum > max) max = sum;
            
            if(sum == a){

             break;
            }
            if(sum > a){
             end--;
            }else{
             start++;
            }
           }
           System.out.println(max);
            
          }
         }

     } catch (Exception e) {
      e.printStackTrace();
         System.err.println("Error:" + e.getMessage());
     }

 }
}


結果を見ると無事case3まで実行完了できているようでした。

Test case 1 通過 実行時間:0.08 秒
Test case 2 通過 実行時間:0.09 秒
Test case 3 通過 実行時間:0.22 秒

と、クリアしてしまったので、ここで満足しちゃいましたが、この先更に速度を挙げためのアイデアがいくつか考えられます。

1. 探索をbinary searchにする
2. 探索時に一度探索したデータをキャッシュ化して覚えておく。ただしキャンペーン日数が75しかないので、今回のデータセットではあまり効果が薄いかもしれません。

参考文献

1. 「クイックソートのサンプルコード」
http://homepage3.nifty.com/teranet/JavaAlgorithm/QuickSort.html

2013年11月26日火曜日

在中日本人108人のそれでも私たちが中国に住む理由

第1章 あのデモがあった日
第2章 それでも「日本」が要る!
第3章 暮らしのなかの「島」問題
第4章 日中の誤解を生むもの
第5章 「反日」下のビジネス
第6章 国境を超え伝える
第7章 隣人との未来へ




悪化する日中関係のさなかにおいて、中国に住む日本人は日々友人や家族から安否を気遣われている。日本のメディアを通してみる反日としての中国は、攻撃的で破壊的であるようにしか映らない。

しかしながら現地に住む日本人によれば、日本のメディアで報じられているほど危険な状態ではなく、またすべての中国人が日本嫌いなわけではなく、親日的な人たちも少なからずいるとのことだ。

本書ではそのような現地の人達の声をまとめた本である。

本を読むについて、日本人と接触したことがなく、反日教育の影響でただ当然のように日本を嫌っている中国の現状が窺えてくる。そのため、わずか数分の日本人との交流で、急に友好的な態度に改める者も少なくないようであり、そのような記述が随所に描かれている。実態を知らないからこそ、盲目的に嫌うことが出来るのであろう。

確かに、私自身を思い返して中国に大してマイナスなイメージを持っていないのは、日本で知り合った多くの中国人留学生のおかげである。彼ら彼女らが非常に友好的で、なんら日本人と変わらず付き合えることを知っているし、今でも同じ職場で仕事をしているおかげで、日本のメディアの報道を客観的に受け止められているといえる。

そう考えると、国レベルではなく、個人レベルでの日中交流は、決して絶やしては行けないのかもしれない。ここまで関係が悪化すると実現自体のハードルも高くなるが、だからこそ、最低限のできることとして人と人との交友は絶えないことを願いたい。

一方でそうは言うものの、日本人というだけでタクシーの乗車拒否をしたり、安全確保の名目のためとはいえ、有事には公共の場で日本語を使うのをやめるように警察から指示されるなど、冷静に考えれば異様な事態である。本書の中の人物はどなたも淡々と受け止めている様子だが、部外者の私には侮辱にも程があるように感じる。もはや反日は中国にとって文化や条件反射にさえ感じられ、関係改善は相当路が険しいと感じた。

2013年11月19日火曜日

Google App Engineのデータをエクスポートする方法

iOSのランキングを日々取得しているiOSランキングトラッカーは、無課金のGoole App Engineで動かしていますが、そろそろ容量の上限値1Gに達しそうな勢いです。

収益があるわけでもない、ただの趣味Webサービスに金をかけることも出来ないので、古いデータを外出しする方法を調べてみました。

今回はそのうち、Google App EngineのBigQuery上にあるデータを手元のデスクトップPCにコピーする手順を紹介します。

1. app.yamlを編集する

下記の二行を追加する。

builtins:
- remote_api: on

2. コマンドを実行してアプリケーションを更新する。

<GAEのインストールパス>/appcfg.py update <app-directory>
注: <app-directory>は直下にapp.yamlがおいてあるディレクトリを指定する必要がある。


3. コピーの実行

%><gae-install-dir>/appcfg.py download_data --application=<app-id> --url=http://<appname>.appspot.com/[remote_api_path] --filename=<data-filename>
<app-id>はapp.yamlに定義した"application"の値。
ただし、high-replication datastoreを使ってる場合、appidの先頭にs~をつける必要がある。
例) applicationの値がexampleで、high-replication datastoreを使ってる場合は下記のようになる。
%><gae-install-dir>/appcfg.py download_data --application=s~example --url=http://example.appspot.com/_ah/remote_api --filename=/tmp

補足

どうやら、このコマンド実行もDataStoreのリクエスト制限の対象のようで、OverQutaErrorが出てしまいました。
OverQuotaError: The API call datastore_v3.RunQuery() required more quota than is available.
 このような場合は下記のようにkindを指定したほうがよさそうです。
%><gae-install-dir>/appcfg.py download_data --application=example --kind=<kind> --url=http://example.appspot.com/_ah/remote_api --filename=/tmp

参考文献

データのアップロードとダウンロード