2013年11月14日木曜日

Android Eclipse ADTでビルドできていたプロジェクトが急にコンパイルエラーになった時の対処法

EclipseのADT(Android Development Tools)を使いAndroidアプリ開発していると、今まで問題なかったプロジェクトが急にコンパイルエラーになってしまうことがよくある。

自分のところではEclipseを落としてあげなおしたときに良く発生する。

原因としては自動生成のR.javaがなくなるなどgenフォルダの中身がおかしくなってコンパイルエラーというようである。

そうなってしまうと厄介だが、今のところ以下のいづれかで復旧できるようだ。

  • プロジェクトをクリーンする。
  • プロジェクトを右クリックし、"Android Tools"→"Fix Project Properties"を選択、その後プロジェクトをクリーンする。
  • プロジェクトを一旦削除(ファイルは消さないこと!)。その後再度インポート。

ただ状況によってどれが効くというのが異なるので、いろいろ試してみることになる。

2013年8月19日月曜日

PowerPoint 中国語フォントでコピーされてしまう

PowerPoint2010を使っているが、テキストを貼り付ける時に勝手に中国語のフォント PMingLiU になってしまうということがある。 どうやら、たとえばテキストエディタなどからのコピーなど貼り付けるテキストに書式が無い場合に、その内容を見て自動で日本語と中国語の既定フォン トに切り替えるという大変おせっかいな機能がついているらしい。

たとえば「全日本人民会議」は貼り付けてもフォントが変わらない(スライド既定のMSゴシックのまま)、しかし「全国人民会議」を貼り付けるとPMingLiU になってしまう。

オプションを探したがオフにできそうな設定もない。以下でも同じ問題で困っている人がいるようである。

このフォーラムではパッチをあてるとなおることのようだが、とりあえずの回避として、確実に中国語でない文字を一文字入れてやるというのが簡単である。たとえば「全国人民会議あ」のような感じ。

2013年7月8日月曜日

Android 指を近づけただけで応答 FloatingTouch

Galaxy S4のように指を画面に近づけただけで反応するUIをどんなキーワードで調べたらいいかと思っていたが、FloatingTouchというらしい。 しかも、どうも最初に搭載したのはGalaxyではなくSONY XPERIAらしい。全然知りませんでした。

XPERIAの場合APIとしてはView.OnHoverListenerあたりが呼ばれるようです。 HoverはPCではよく使うAPIですがAndroidにもあるので、いつ呼ばれるのだろうと思っていましたがこういう機能向けだったのですね。 Galaxyのほうも同じような仕様なんだろうか…。

うまくつかうとUIの表現方法が広がりそうですが実機が無いので試せないのが残念。 今後搭載端末が増えてきて標準的に使えるようになるのを期待したいです。

それにしてもコマーシャルを見ているとGalaxy特有の機能に見えてしまう。 SONYももっと宣伝すればいいのに… キラーアプリを作れなかったのか?

2013年7月5日金曜日

Android Nexus7にADB接続しようとしてはまる

去年買って使っていたNexus7では特にトラブルなくPCからADB接続できていたのだが、最近買った新しいNexus7を接続しようとしたところではまってしまった。

開発者オプションが無い

設定画面にUSBデバッグを有効化するための開発者オプションが見当たらない。 以下の記事によると隠しコマンドになったようである。どうしてこうなった…

ADBが認識しない

USBデバッグを有効にできたので早速つないでみるとNexus側では "USBデバッグが接続されました" と出るのだが、PC側のADBが "waiting for device" のまま…。 以下記事によると接続の種別を変更する必要があるとのこと。これは分からないわ。

これら方々の記事のおかげで助かった。 Android SDKもバージョンが変わるとわけのわからないトラブルが起きるが、Nexusも同じとは困ったものである。

関連記事

2013年7月2日火曜日

AmazonEC2のSecurity Group

AmazonEC2にSecurity Groupというのがあるが、厳密には違うのかもしれないがファイアウォールのようなものである。 同じSecurity GroupのTCPフィルタの設定を複数のEC2インスタンス(サーバー)で共用して使うことができるのでクラスタを構成したい時などは各インスタンス個別に設定する必要なく便利である。

EC2のインスタンスを作成するときにはSecurity Groupを割り当てる項目があるが、どうやら作成時に設定したものをずっと使うことになり後から変更はできないようである。 うっかり割り当ててしまうと後から面倒なので、用途が違うインスタンスを作成するのであれば新しいSecurity Groupを作成して割り当てるのがよいかと思う。

新しいSecurity Groupの作成はインスタンスの作成ウィザードからもできるようである。

2013年6月20日木曜日

Android カスタムViewの作成

AndroidでカスタムViewを作成するには、ViewまたはViewGroupを継承して作成する。 Java AWTでたとえると(今は分かる人が少ないかも)View=ComponentとViewGroup=Containerの関係に相当すると考えればいいと思う。

複数の子ViewをもつようなカスタムViewの場合、典型的な作り方のパターンとしてはViewGroupを継承してカスタムクラスを作り、最低限以下のメソッドをオーバーライドする。

onMeasureメソッド
ViewGroupのサイズを決める
onLayoutメソッド
実際に子Viewを配置する

Apache Solr

Apache Solr(ソーラーと読む?)という全文検索エンジンを最近よく耳にするようになった。 どうやらApache Luceneをベースとした検索エンジンらしい。 Luceneはだいぶ前に触ったことがありインデックスの作成とその検索をするフレームワークエンジンのようなものだったが、それに周りを作って検索サーバーとして使えるように仕立てたようなもの?

日本語のドキュメントでどれくらい動いてくれるものか気になる。

2013年5月21日火曜日

Android Eclipse ADT22へのアップグレードではまる

Android SDKをバージョン22にアップグレードしたら、Eclipse ADT(Android Development Toolkit)も同じく22にアップグレードしろというのでアップデート。すると、いきなり既存プロジェクトのgenフォルダのファイルが自動生成されないようになりR.javaが参照できないという大量のエラーが出るようになった。

非常に困っていたが以下のページに助けられる。

どうやらAndroid SDK Build-toolsというツールが新しくビルドに必要になったようである。

こんなのわかるわけない…
せめて自動でインストールしてほしい。

まるで古いPCのアドベンチャーゲームをやらされているような感覚である。 そして、後日Nexus7でもやられてしまった。

関連記事

2013年5月19日日曜日

Android Viewのカスタム属性の作り方

自作のViewにカスタム属性を定義してレイアウトXMLから設定を変えられるようにする場合は、attr.xmlのdeclare-styleableで定義をする方法についてのメモ。

背景色の属性

背景色関連の属性は以下のように色(color)や画像(drawable)で設定できるようにできるとよい。
  1. <!-- 色(color)でmyBackgroundを指定する -->  
  2. <myapp.MyView  
  3.   android:id="@+id/myview"  
  4.   android:layout_width="wrap_content"  
  5.   android:layout_height="wrap_content"  
  6.   myapp:myBackground="#ffff0000"  
  7. />  
  8.   
  9. <!-- イメージ(drawable)でmyBackgroundを指定する -->  
  10. <myapp.MyView  
  11.   android:id="@+id/myview"  
  12.   android:layout_width="wrap_content"  
  13.   android:layout_height="wrap_content"  
  14.   myapp:myBackground="@drawable/bg"  
  15. />  

こんなときはまず attr.xmlでは以下のようにreferencecolorの属性として定義する。

  1. <resources>  
  2.   <declare-styleable name="MyView">  
  3.     <attr name="myBackground" format="reference|color"/>  
  4.   </declare-styleable>  
  5. </resources>  

自作ViewのJava側ではDrawableとして受け取り、背景色を設定したいviewにsetBackgrund()で設定する。 colorもDrawableで受けることができ、背景色として設定できるようだ。 この方法でよいのか公式なドキュメントを確認できていないが問題なく動いているようである。

  1. public MyView(Context context, AttributeSet attrs) {  
  2.   super(context, attrs);  
  3.   
  4.   Drawable myBackground = a.getDrawable(R.styleable.MyView_myBackground);  
  5.   if (myBackground != null) {  
  6.     myView.setBackgroundDrawable(myBackground);  
  7.   }  
  8. }  
ちなみに、AttributeSet.getColor()で受けると以下のようなエラーが出る。 背景色を設定できるようにする場合は特に理由がなければDrawableで受けるようにしておいたほうが無難だろう。

Caused by: android.view.InflateException: Binary XML file line #35: Error inflating class myapp.MyView
    at android.view.LayoutInflater.createView(LayoutInflater.java:613)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:687)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
    at android.view.LayoutInflater.rInflate(LayoutInflater.java:749)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
    at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:270)
    at android.app.Activity.setContentView(Activity.java:1881)
    at myapp.MyActivity.onCreate(MyActivity.java:58)
    at android.app.Activity.performCreate(Activity.java:5104)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
    ... 11 more
Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Constructor.constructNative(Native Method)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at android.view.LayoutInflater.createView(LayoutInflater.java:587)
    ... 24 more
Caused by: android.content.res.Resources$NotFoundException: File res/drawable-hdpi/bg.png from drawable
resource ID #0x7f020011: .xml extension required
    at android.content.res.Resources.loadColorStateList(Resources.java:2094)
    at android.content.res.TypedArray.getColor(TypedArray.java:319)
    at myapp.SampleQueryView.(SampleQueryView.java:54)
    ... 27 more

2013年5月16日木曜日

Android Twitter4JでOAuth認証する

Twitter4JでOAuth認証するための方法を調査した時のメモ。

Callback URLを使わずお手軽にすませる方法を使った。 この場合はTwitterログインの後にPIN番号が画面に表示されるので、それを手で入力して認証を完了する。 大まかな手順は次のようである。

  1. TwitterにRequestTokenトークン要求
  2. Twitterから認証のためのログインページURLが返されるのでブラウザで表示
  3. ユーザーがログインしてアプリを許可するとTwitterがPIN番号を発行
  4. TwitterにPINを渡してAccessトークン取得

今回紹介するコードはAndroidアプリつぶマップで使用しているので、 どのように動くか興味ある方はインストールしてもらえるとよいかと思う。

「つぶマップ」ダウンロードサイトへ

Twitter4Jラッパークラス

RequestTokenトークン要求とAccessトークン取得の際、素のままのTwitter4J APIを呼び出してもよいが、 それぞれの処理間での値の保持のためラップしたクラスを作成し呼び出すようにした。
  1. public class TwitterTest {  
  2.   
  3.   private static final String CONSUMER_KEY = "アプリに発行されたConsumer Key";  
  4.   private static final String CONSUMER_SECRET = "アプリに発行されたConsumer Secret";  
  5.     
  6.   private Twitter twitter;  
  7.   private RequestToken requestToken;  
  8.     
  9.   public String requestToken() {  
  10.     // インスタンスの初期化  
  11.     twitter = TwitterFactory.getSingleton();  
  12.   
  13.     // Twitterに登録したアプリのConsumer key と Consumer secretを設定  
  14.     twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);  
  15.   
  16.     // RequestTokenトークン要求  
  17.     // 認証ページのURLを返すのでWebViewで表示しユーザーにログインしてもらう  
  18.     requestToken = twitter.getOAuthRequestToken();  
  19.     return requestToken.getAuthorizationURL();  
  20.   }  
  21.     
  22.   // Accessトークン取得。引数にWebで表示されたPINを入れる。  
  23.   // 認証済みのTwitterインスタンスを返す  
  24.   public Twitter getAccessToken(String pin) throws TwitterException {  
  25.     if (requestToken == nullreturn false;  
  26.       
  27.     // RequestTokenとPINよりAccessトークン取得  
  28.     AccessToken _accessToken = twitter.getOAuthAccessToken(requestToken, pin);  
  29.     return twitter;  
  30.   }  
  31. }  

アプリ側

アプリ側ではラッパークラスを使い次のようにコードを書く。

まず、TwitterにRequestTokenトークン要求する。ラッパークラスのrequestToken()メソッドを呼び出す。

  1. TwitterTest t = new TwitterTest();  
  2. String url = t.requestToken();  

メソッド呼び出しの結果、認証のためのTwitterへのログインページURLが返されるのでこのURLをWebViewに表示する。 以下のような画面が開く。同じ画面の上部にはPIN番号を入力するためのEditTextを配置した。

ログインページでユーザーがログインしてアプリを許可するとTwitterがPIN番号を発行する。 画面表示されたPIN番号をユーザーに入力してもらう。

入力されたPINを引数にラッパークラスのgetAccessToken()メソッドを呼び出すことで認証が完了する。

  1. String pin = "ユーザー入力のPIN番号";  
  2. Twitter twitter = t.getAccessToken(pin);  

メソッドの返り値である認証済みTwitterインスタンスを使ってツイート取得や検索ができる。 以下は検索の例である。

  1. Query q = new Query();  
  2. q.setQuery("キーワード");  
  3. QueryResult qr = twitter.search(q);  

皆さんもぜひ試してみてください。

2013年5月2日木曜日

MySQL 行の作成日と更新日を自動でつける その2

「MySQL 行の作成日と更新日を自動でつける」の記事で、 行の作成日と更新日を自動でつけるDDL(テーブル定義)の方法を紹介した。 この方法では自動でつけられるのは作成日と更新日どちらか1テーブルに1つの制約があった。

その後の調査で分かったのだが、MySQLではNOT NULLなTIMESTAMP型の列にNULLを書き込むと現在時刻に置き換えられるという仕様がありそれを利用することで、一応自動で付与するということもできるようだ。

以下のようなテーブル定義として、ts1に行の作成日時、ts2に行の更新日時を入れることとする。

  1. CREATE TABLE t3 (  
  2.   rid INTEGER,  
  3.   txt VARCHAR(20),  
  4.   ts1 TIMESTAMP DEFAULT 0,  
  5.   ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  
  6. );  

INSERT時にts1にはNULL値を書き込むことで書き込みの発生した現在時刻が自動で入る。

mysql> INSERT INTO t3 (rid,txt,ts1) VALUES(1,'aaa',NULL);
Query OK, 1 row affected (0.08 sec)

mysql> INSERT INTO t3 (rid,txt,ts1) VALUES(2,'bbb',NULL);
Query OK, 1 row affected (0.09 sec)

mysql> SELECT * FROM t3;
+------+------+---------------------+---------------------+
| rid  | txt  | ts1                 | ts2                 |
+------+------+---------------------+---------------------+
|    1 | aaa  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
|    2 | bbb  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
+------+------+---------------------+---------------------+
2 rows in set (0.00 sec)

UPDATE時にはts1には値を書き込まないようにする。このようにすることでts1の日時は更新されず、行が作成された日時を示すようになる。

mysql> UPDATE t3 SET txt='zzz' WHERE rid=1;
Query OK, 1 row affected (0.10 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t3;
+------+------+---------------------+---------------------+
| rid  | txt  | ts1                 | ts2                 |
+------+------+---------------------+---------------------+
|    1 | zzz  | 2013-04-23 16:21:44 | 2013-04-23 16:21:49 |
|    2 | bbb  | 2013-04-23 16:21:44 | 2013-04-23 16:21:44 |
+------+------+---------------------+---------------------+
2 rows in set (0.00 sec)

まあ、でもINSERT時にNULLを指定して書き込まないといけないので、ここに現在時刻を指定するようにしてもプログラムの質としてはあまり変わらないような気がする。 むしろその方がトリッキーでなくわかりやすいかも。

2013年4月23日火曜日

MySQL 行の作成日と更新日を自動でつける

MySQLで日付型の列に行の作成日と更新日を自動で入れる方法を調査した。 以前使った、SQL Anywhereの場合だと「行の作成日と更新日を自動でつける」で書いたように行の作成日時、更新日時を列のデフォルト値とする構文があった。

MySQLでも同様のデフォルト値CURRENT_TIMESTAMPはあるようだがTimestamp型にのみ指定できるようである。 記法は以下2通りある。

DEFAULT CURRENT_TIMESTAMP
行が挿入された時刻
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
行が挿入または更新された時刻

行が挿入された時刻をデフォルト値とする

DEFAULT CURRENT_TIMESTAMPを使う。

  1. CREATE TABLE t1 (  
  2.   rid INTEGER,  
  3.   txt VARCHAR(20),  
  4.   ts1 TIMESTAMP DEFAULT CURRENT_TIMESTAMP  
  5. );  
このようなテーブルに対して以下の行を挿入すると挿入時刻が自動で入る。
mysql> INSERT INTO t1 (rid,txt) VALUES(1,'aaa');
Query OK, 1 row affected (0.08 sec)

mysql> INSERT INTO t1 (rid,txt) VALUES(2,'bbb');
Query OK, 1 row affected (0.09 sec)

mysql> SELECT * FROM t1;
+------+------+---------------------+
| rid  | txt  | ts1                 |
+------+------+---------------------+
|    1 | aaa  | 2013-04-23 13:26:52 |
|    2 | bbb  | 2013-04-23 13:26:58 |
+------+------+---------------------+
2 rows in set (0.00 sec)

行の更新では変化しない。

mysql> UPDATE t1 SET txt='zzz' WHERE rid=1;
Query OK, 1 row affected (0.14 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t1;
+------+------+---------------------+
| rid  | txt  | ts1                 |
+------+------+---------------------+
|    1 | zzz  | 2013-04-23 13:26:52 |
|    2 | bbb  | 2013-04-23 13:26:58 |
+------+------+---------------------+
2 rows in set (0.00 sec)

行が挿入または更新された時刻をデフォルト値とする

DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPを使う。
  1. CREATE TABLE t2 (  
  2.   rid INTEGER,  
  3.   txt VARCHAR(20),  
  4.   ts1 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  
  5. );  
このようなテーブルに対して以下の行を挿入すると挿入時刻が自動で入る。
mysql>> INSERT INTO t2 (rid,txt) VALUES(1,'aaa');
Query OK, 1 row affected (0.11 sec)

mysql>> INSERT INTO t2 (rid,txt) VALUES(2,'bbb');
Query OK, 1 row affected (0.12 sec)

mysql>> SELECT * FROM t2;
+------+------+---------------------+
| rid  | txt  | ts1                 |
+------+------+---------------------+
|    1 | aaa  | 2013-04-23 13:27:09 |
|    2 | bbb  | 2013-04-23 13:27:12 |
+------+------+---------------------+
2 rows in set (0.00 sec)

行を更新すると自動で書き換わる。

mysql> UPDATE t2 SET txt='zzz' WHERE rid=1;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t2;
+------+------+---------------------+
| rid  | txt  | ts1                 |
+------+------+---------------------+
|    1 | zzz  | 2013-04-23 13:28:00 |
|    2 | bbb  | 2013-04-23 13:27:12 |
+------+------+---------------------+
2 rows in set (0.00 sec)

1つのテーブルにDEFAULT CURRENT_TIMESTAMPDEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPを両方定義しようとすると"Incorrect table definition"のエラーになってしまう。 両方同時に使えると作成時刻と更新時刻の両方が管理でき便利なのだが残念。 ただし、DDLとINSERTのSQLを少し工夫することで同等のことができるようだ。 その方法は「その2」のほうに書いてみた。

  1. CREATE TABLE t3 (  
  2.   rid INTEGER,  
  3.   txt VARCHAR(20),  
  4.   ts1 TIMESTAMP DEFAULT CURRENT_TIMESTAMP,  
  5.   ts2 TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP  
  6. );  

詳しくはリファレンスに書いてある。

2013年4月4日木曜日

MySQLのwait_timeout

my.iniでwait_timeoutを設定してみたがshow variablesでみるとデフォルト値と変わっていないのだが @@GLOBAL.wait_timeoutのほうは設定値が反映されている。
mysql> show variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout  | 28800 |
+---------------+-------+
1 row in set (0.00 sec)

mysql> SELECT @@GLOBAL.wait_timeout;
+-----------------------+
| @@GLOBAL.wait_timeout |
+-----------------------+
|                   600 |
+-----------------------+
1 row in set (0.00 sec)
どっちが正しいのだろうか?謎。

2013年4月3日水曜日

EclipseがCPUを占有してしまう

特に処理をしているわけでもないのに何かのタイミングでEclipse(3.7 SP2)がCPU 1つを100%占有するようになってしまう。 こうなるとEclipseを落としてやらないと元に戻らないのだが原因がわからず困っていた。

最近分かってきたのだが、どうやら入れているプラグインの一つ「プロパティエディタ」が怪しいようである。 EclipseのJVMのプロファイルをJava VisualVMでとってみると、このチケットと同じようにSynchtonizableDocument.getLockObject()メソッドはじめスタックトレースに出ているほかのメソッドが実行時間の上位に出てくる。

プロパティエディタは日本語で直接Javaのプロパティをかけるので手放せないのだが、残念なことに最近アップデートされていない。 このチケットの対応も今後無いだろうと思う。 代わりのプラグインが何かあるとよいのだが英語圏の人には不要なツールなので難しいだろう。

どうするか…

Android ParcelでString[]を読んだり書いたり

AndroidのJavaオブジェクトのシリアライズ機構ParcleにStringの配列を読み書きする方法。

メソッド名の対応からしてwriteStringArray()で書いたものをreadStringArray()で読み込むのだろうと思っていたが読むほうはcreateStringArray()になるようである。 以下のような感じになるらしい。

  1. public class MyObj implements Parcelable {  
  2.   
  3.   private String[] texts;  
  4.   
  5.   // 読むほう  
  6.   public MyObj(Parcel in) {  
  7.     texts = in.createStringArray();  
  8.   }  
  9.   
  10.   // 書くほう  
  11.   @Override  
  12.   public void writeToParcel(Parcel out, int flags) {  
  13.     out.writeStringArray(texts);  
  14.   }  
  15.   
  16.   ...  
  17. }  

readStringArray()でも読み込めるようだが、書き込んだ長さと同じ配列をあらかじめ準備して引数に指定するらしい。 長さが決まってない配列に使うのは面倒そうだし、nullは扱えなさそうである。 固定長の配列を連続読み込みするような場合には使えそうだが、それ以外はあまり出番が無いのではと思う。

2013年3月28日木曜日

Googleグループの共同トレイ

Google AppsのGoogleグループ機能について調べもの。

今まではGoogleグループ=メールエイリアス機能と言う程度の認識しかなかったが、 ここの説明によると共同トレイトピックという機能がありうまく使うとサポートセンタ的な使い方ができそうだというのを最近知った。

ただ、自分の使っている無料版Google Appsにはこれらの設定項目が全く出てこないので有料版のみの機能なのかもしれない。

それにしても公式のヘルプは、いまいちわかりづらい。

2013年3月16日土曜日

Twitter4JでStatusNetにアクセスする

前回の記事でTwitter風のマイクロブログStatusNetをWindows環境に立ち上たが、今回はこれにJavaプログラムからアクセスしてみる。 StatusNetはTwitter準拠のAPIを持っているとのことなのでJavaでTwitterにアクセスするためのライブラリTwitter4Jが使えないかと思い試してみることにした。

結果としては以下のようなコードでユーザーのタイムラインが取得できた。 StatusNetはBasic認証でも認証ができるので今回はその方法を使った。

  1. import twitter4j.ResponseList;  
  2. import twitter4j.Status;  
  3. import twitter4j.Twitter;  
  4. import twitter4j.TwitterException;  
  5. import twitter4j.TwitterFactory;  
  6. import twitter4j.User;  
  7. import twitter4j.auth.BasicAuthorization;  
  8. import twitter4j.conf.Configuration;  
  9. import twitter4j.conf.ConfigurationBuilder;  
  10.   
  11. public class TwitterConsole {  
  12.     
  13.   public static void main(String[] args) {  
  14.     try {  
  15.       TwitterConsole app = new TwitterConsole();  
  16.       app.start();  
  17.     } catch(Throwable ex) {  
  18.        ex.printStackTrace(System.err);  
  19.     }  
  20.   }  
  21.   
  22.     private void start() throws TwitterException {  
  23.           
  24.     // StatusNetのAPIのBase URL  
  25.     String baseURL = "http://localhost/statusnet/api/";  
  26.   
  27.     // Twitter4J設定。APIのBase URLをStatusNetのサーバーに変更  
  28.     ConfigurationBuilder cb = new ConfigurationBuilder();  
  29.     cb.setRestBaseURL(baseURL);  
  30.     cb.setIncludeEntitiesEnabled(true);  
  31.     cb.setJSONStoreEnabled(true);  
  32.     Configuration conf = cb.build() ;  
  33.   
  34.     // Basic認証で認証する  
  35.     BasicAuthorization auth = new BasicAuthorization("username""password");  
  36.     Twitter twitter = new TwitterFactory(conf).getInstance(auth);  
  37.       
  38.     // ユーザーのタイムラインを取得  
  39.     ResponseList<status> tweets = twitter.getHomeTimeline();  
  40.   
  41.     // 取得したタイムラインを表示  
  42.     int i = 0;  
  43.     for(Status tweet : tweets) {  
  44.       i++;  
  45.       User tweetuser = tweet.getUser();  
  46.       System.out.printf("[%d] %s %s%n%s%n-----------%n",  
  47.           tweet.getId(),  // 投稿ID  
  48.           tweet.getCreatedAt().toString(),  // 投稿時刻  
  49.           tweetuser.getName(),  // 投稿者  
  50.           tweet.getText());     // 投稿内容  
  51.     }  
  52.   }  
  53. }  
  54. </status>  

2013年3月14日木曜日

HttpClient4とMicrosoft Translator APIで翻訳する

前回の記事でAPIの呼び方を解説したが、その手順をJavaのプログラムにしてみた。 API呼び出しにはHttpClient4を使った。access_tokenの取得結果はJSONで返ってくるためその切り出しにはJSONICを使っている。

  1. import java.io.IOException;  
  2. import java.util.ArrayList;  
  3. import java.util.List;  
  4. import java.util.Map;  
  5. import net.arnx.jsonic.JSON;  
  6. import org.apache.http.HttpEntity;  
  7. import org.apache.http.HttpResponse;  
  8. import org.apache.http.NameValuePair;  
  9. import org.apache.http.client.entity.UrlEncodedFormEntity;  
  10. import org.apache.http.client.methods.HttpGet;  
  11. import org.apache.http.client.methods.HttpPost;  
  12. import org.apache.http.impl.client.DefaultHttpClient;  
  13. import org.apache.http.message.BasicNameValuePair;  
  14. import org.apache.http.util.EntityUtils;  
  15.   
  16.   
  17. public class MSTranslatorTest {  
  18.   
  19.   public static void main(String[] args) {  
  20.     try {  
  21.       MSTranslatorTest app = new MSTranslatorTest();  
  22.       app.translate();  
  23.     } catch(Throwable ex) {  
  24.       ex.printStackTrace(System.err);  
  25.     }  
  26.   }  
  27.   
  28.   private void translate() throws IOException {  
  29.     DefaultHttpClient client = new DefaultHttpClient();  
  30.       
  31.     // Step1 access_tokenを取得  
  32.     // 取得のためのリクエストを準備  
  33.     HttpPost httpPost = new HttpPost("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");  
  34.     List<NameValuePair> params = new ArrayList<NameValuePair>();  
  35.     params.add(new BasicNameValuePair("grant_type""client_credentials"));  
  36.     // クライアント IDを設定  
  37.     params.add(new BasicNameValuePair("client_id""...."));  
  38.     // 顧客の秘密を設定  
  39.     params.add(new BasicNameValuePair("client_secret""...."));  
  40.     params.add(new BasicNameValuePair("scope""http://api.microsofttranslator.com"));  
  41.     httpPost.setEntity(new UrlEncodedFormEntity(params));  
  42.     // 取得実行  
  43.     String accessToken;  
  44.     HttpResponse response1 = client.execute(httpPost);  
  45.     try {  
  46.       // 結果はJSONとして返ってくるのでJSONICでaccess_tokenを切り出す  
  47.       HttpEntity entity = response1.getEntity();  
  48.       String reponseText = EntityUtils.toString(entity);  
  49.       Map json = JSON.decode(reponseText, Map.class);  
  50.       accessToken = (String)json.get("access_token");  
  51.     } finally {  
  52.       httpPost.releaseConnection();  
  53.     }  
  54.   
  55.     // Step2 翻訳する  
  56.     // 翻訳リクエストURLを作成  
  57.     // ・翻訳元=日本語 (from=ja)  
  58.     // ・翻訳先=スペイン語 (to=es)  
  59.     // ・翻訳する文字列 (text)  
  60.     String text = "こんにちは";  
  61.     String uri = String.format("http://api.microsofttranslator.com/V2/Http.svc/Translate?from=ja&to=es&text=%s", text);  
  62.     // access_tokenをヘッダに付与。"Bearer "を前につける。  
  63.     String authorization = String.format("Bearer %s", accessToken);  
  64.     HttpGet httpGet = new HttpGet(uri);  
  65.     httpGet.setHeader("Authorization", authorization);  
  66.     // 翻訳実行  
  67.     HttpResponse response2 = client.execute(httpGet);  
  68.     try {  
  69.       // 結果はXMLで返ってくる。そのままコンソールに表示。  
  70.       HttpEntity entity = response2.getEntity();  
  71.       String reponseText = EntityUtils.toString(entity);  
  72.       System.out.println(reponseText);  
  73.     } finally {  
  74.       httpGet.releaseConnection();  
  75.     }  
  76.   }  
  77. }  

実行するとコンソールに次のような結果が表示される。

  1. <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">¡Buenas noches!</string>  

関連記事

2013年3月12日火曜日

マイクロブログStatusNetを立ち上げ

Twitterのようなメッセージと掲示板の合体したような仕組みをマイクロブログと呼ぶようだが、 オープンソースのマイクロブログのサーバーの一つにStatusNetというものがある。 TwitterというよりはYammerに近い感じであるが、今回Windows上に環境構築をしたのでそのメモ。

他にもオープンソースの有名どころとしてはSharetronixというのがありモバイル対応や機能が豊富そうであるが、ビジネスユースで使いにくそうな感じだったので今回はパス。

StatusNetはPHP+MySQLのWebアプリとして動作するため以下のものをセットアップする。 64bitのマシンを使っているので無謀にも64bitバイナリで揃えてみた。 (一応リンクを貼っておくが消える可能性もあるのでご注意)

インストール

ApacheとMySQLはインストーラーをつかいデフォルトの設定でインストールした。 PHPは展開し C:\Program Files\php-5.3.2-Win32-VC9-x64に移動。 StatusNetは展開してApacheのhtdocsフォルダ以下にstatusnetというフォルダとして配置した。

Apache設定

PHPを使えるようにする一般的な設定をすればよいと思う。 参考までにhttpd.confの以下を変更しセットアップした。 普通のやり方とちょっと違う点としては、一般的にはphp.iniはC:\Windowsに置くことが多いようだが、この環境ではPHPインストール先に置くこととした。
  1. # LoadModuleを追加  
  2. LoadModule php5_module "C:/Program Files/php-5.3.2-Win32-VC9-x64/php5apache2_2.dll"  
  3. LoadModule php5_module "C:/Program Files/php-5.3.2-Win32-VC9-x64/php5ts.dll"  
  4.   
  5. <Directory "C:/Program Files/Apache Software Foundation/Apache2.2/htdocs">  
  6.     ...  
  7.     # .htaccessを使えるようオーバーライドを許可しておく  
  8.     AllowOverride All  
  9.     ...  
  10. </Directory>  
  11.   
  12. # index.phpを追加  
  13. <IfModule dir_module>  
  14.     DirectoryIndex index.html index.php  
  15. </IfModule>  
  16.   
  17. # 拡張子.phpのmime type追加  
  18. <IfModule mime_module>  
  19.     ...  
  20.   
  21.     AddType application/x-httpd-php .php  
  22. </IfModule>  
  23.   
  24. # PHP INIを置くフォルダ。  
  25. PHPIniDir "C:/Program Files/php-5.3.2-Win32-VC9-x64"  

PHPの設定

PHPを展開した先にphp.iniを作成する。同じフォルダにphp.ini-developmentがあるのでそれをコピーして以下を変更した。
  1. output_handler = mb_output_handler  
  2.   
  3. ; エラーレベル設定  
  4. error_reporting = E_ALL & ~E_NOTICE  
  5.   
  6. default_charset = "UTF-8"  
  7.   
  8. includeのパス  
  9. include_path = ".;c:\Program Files\php-5.3.2-Win32-VC9-x64\includes"  
  10.   
  11. ; ApacheのDocumentRoot  
  12. doc_root = "C:/Program Files/Apache Software Foundation/Apache2.2/htdocs"  
  13.   
  14. ; extension のパス  
  15. extension_dir = "C:\Program Files\php-5.3.2-Win32-VC9-x64\ext"  
  16.   
  17. ; 有効にするextension  
  18. extension=php_curl.dll  
  19. extension=php_gd2.dll  
  20. extension=php_mbstring.dll  
  21. extension=php_mysqli.dll  
  22.   
  23. ; mbstring関連設定  
  24. mbstring.language = Japanese  
  25. mbstring.internal_encoding = utf-8  
  26. mbstring.http_input = auto  
  27. mbstring.http_output = utf-8  
  28. mbstring.encoding_translation = On  
  29. mbstring.detect_order = auto  
  30. mbstring.substitute_character = none;  
  31. mbstring.func_overload = 0  
エラーレベルは警告抑制のためである。既定のままだと "PHP Strict Standards: Non-static method PEAR::setErrorHandling() should not be called statically." のような警告が大量に出たため変更した。

MySQLデータベース作成

StatusNetのデータ記録用データベースを作成する。 データベース名はstatusnetとした。日本語を使いたいので文字セットはutf-8としておく。 MySQLのコンソールを立ち上げて次のコマンドで作成。
  1. CREATE DATABASE statusnet CHARACTER SET utf8;  
このデータベースのためのユーザーも作成。ユーザー名はstatusnet。パスワードはsql。
  1. GRANT ALL PRIVILEGES ON statusnet.* TO statusnet@127.0.0.1 IDENTIFIED BY 'sql' WITH GRANT OPTION;  

StatusNetの設定

展開したStatusNetのフォルダにあるhtaccess.sampleをコピーし.htaccessというファイルを作成する。 Windowsのエクスプローラーからは名前なしのファイルが作れないのでコマンドプロンプトからmoveコマンドなどでやるとよい。 このファイルの内容を一か所書き換える。以下の /mublog/となっているところを /statusnet/に書き換える。これをするとFancy URLsという機能が使えるようになる。
  1. #RewriteBase /mublog/  
  2. RewriteBase /statusnet/  

StatusNetのメインの設定はWeb画面より行う。 ApacheとMySQLが起動した状態でブラウザより http://localhost/statusnet/install.phpにアクセスすると設定画面が表示される。最低限以下の項目を入力しSubmitするとデータベースにテーブルが作成されセットアップが完了する。

Site settings - Site name
サイトの名前。何でもよい。
Database settings - Hostname
データベースサーバー名。localhostだとタイムアウトしてしまったので "127.0.0.1"を指定。
Database settings - Name
データベース名 "statusnet"
Database settings - DB username
データベースユーザー名 "statusnet"
Database settings - DB password
データベースパスワード "sql"
Administrator settings - Administrator nickname
StatusNet管理者のアカウント名
Administrator settings - Administrator nickname
StatusNet管理者のパスワード

Submit後に以下のようなデータベースのエラーで止まってしまうこともあるようである。


Adding notice source data to database...
ERROR (DB Error: unknown error) for SQL 'INSERT INTO notice_source (code, name, url, created) VALUES ('adium', 'Adium', ...
どうやらStatusNet1.1.0ではテーブルのセットアップスクリプトにバグがあるようである。 statusnet\db\notice_source.sql の内容をすべてコメントアウトすることでセットアップできるようである。 コメントアウトするとこのテーブルの初期データが入らないのだが実質は問題なく使用できるようである。 (たぶん通知関連のマスタデータかなにかではないかと思う) 一度このエラーが出てしまった場合には再実行の際に別のエラーが出るようなので、DBを一旦DROPし再作成してからするのが確実である。

とりあえず、ここまでできればStatusNetが使えるようになる…はず。 おつかれさまでした。

2013年3月7日木曜日

サーブレットで非同期処理

サーブレットで非同期処理をするためのAsyncContextというクラスがServlet3.0から追加されている。

そもそもサーブレットでは時間のかかる処理を書いたり、スレッドを作りバックグラウンドで処理をしてはいけないという決まりがあるらしい。しかしTomcatなど特にチェックなどしておらずやろうと思えばできてしまうため結構使っている人も多いのではないでしょうか?(自分もそうです)

そういう場合にServlet3.0からはAsyncContextを別スレッドを使って実行することができるようです。

2013年3月6日水曜日

PHPからMySQLへの接続ではまる

WindowsでPHPからmysqliでMySQL接続しようとしてはまった時のメモ。 以下の接続時にエラーが出た。
  1. $mysqli = new mysqli("localhost""user""pass""dbname");  
エラーメッセージが文字化けして読めず何が悪いのかさっぱり不明。
Warning: mysqli::mysqli() [mysqli.mysqli]: [2002] ڑς݂̌Ăяo悪̎Ԃ߂ĂȂ (trying to connect via tcp://localhost:3306) in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\mysqli.php on line 6

Warning: mysqli::mysqli() [mysqli.mysqli]: (HY000/2002): ڑς݂̌Ăяo悪̎Ԃ߂ĂȂ߁Aڑł܂łB܂͐ڑς݂̃zXgȂ߁Amꂽڑ͎s܂B in C:\Program Files\Apache Software Foundation\Apache2.2\htdocs\mysqli.php on line 6
ネットで調べると「mysql.sockの設定がおかしいのでは」などの情報がたくさん出てくるがWindowsだとそれらしいものも無い。 とても悩んだが、どうもlocalhostがIPv6として解決されているのが原因のだったようである。 試しにIPアドレスに変えてみるとすんなり接続できた。
  1. $mysqli = new mysqli("127.0.0.1""user""pass""dbname");  
確かにlocalhostにpingをかけてみるとIPv6アドレスを見に行っているようである。
C:\Temp>ping localhost

MyPC [::1]に ping を送信しています 32 バイトのデータ:
::1 からの応答: 時間 <1ms
::1 からの応答: 時間 <1ms
::1 からの応答: 時間 <1ms

こちらの情報を参考にさせていただきました。 こちらのページのようにhostsを変更して解決する方法もあるようです。

2013年2月22日金曜日

Microsoft Translator APIで翻訳 (改)その2

Microsoft Translator APIの使い方であるが次の手順でAPIを呼び出すことで翻訳ができる。

  1. access_tokenを取得する
  2. 翻訳する

API呼び出しにはMicrosoft Translator APIの登録が必要になるがその方法については前のblogを参照してください。

また、これらAPI実行するにはHTTP POSTやリクエストヘッダへの値の設定が必要になる。 普通にブラウザからURLを叩くだけでは実行できないので何かプログラムを作成するか、開発者向けのブラウザ拡張ツールを使うといいだろう。Chromeの場合Postmanというアプリを使っているが結構使いやすい。

access_tokenを取得する
access_tokenを取得するには、以下のリクエストURLにHTTP POSTでパラメータを送る。
リクエストURL
https://datamarket.accesscontrol.windows.net/v2/OAuth2-13
メソッド
POST
リクエストのパラメータには以下を指定する。
grant_type
client_credentials
client_id
アプリケーションの登録時に指定したクライアント ID
client_secret
アプリケーションの登録時に取得した顧客の秘密
scope
http://api.microsofttranslator.com

POSTするときはcontent-type=application/x-www-form-urlencoded で送ること。 multipart/form-dataで送るとは受信できないというエラーになる。

応答として以下のようなJSONが返ってくる。

  1. {  
  2.     "token_type""http://schemas.xmlsoap.org/ws/2009/11/swt-token-profile-1.0",  
  3.     "access_token""http%3a%2f%2fschemas.xmlsoap.org%2fws%2f2005%2f05%2fidentity%2fclaims%2fnameidentifier=....",  
  4.     "expires_in""600",  
  5.     "scope""http://api.microsofttranslator.com"  
  6. }  
このJSONにaccess_tokenが含まれているのでその部分の文字列をコピーする。 access_tokenには有効期間があって取得時から10分のようである。

翻訳する
翻訳は、以下のリクエストURLに先に取得したaccess_tokenと合わせてパラメータを送る。
リクエストURL
http://api.microsofttranslator.com/V2/Http.svc/Translate
メソッド
とりあえずHTTP GETで試してみた。POSTでもよい?

access_tokenはリクエストのヘッダに設定する必要がある。 ヘッダ名Authorizationに値として Bearer (空白) (先に取得したaccess_token)を設定する。 なぜ、Bearerという文字列をaccess_tokenの前につけるか謎だが必須になっている。 access_tokenは有効期限内ならば何度でも使えるようである。 有効期限が切れたaccess_tokenを使うと400 bad requestのエラーになった。

リクエストのパラメータには以下を指定する。

from
翻訳元言語のコード
to
翻訳先言語のコード
text
翻訳するテキスト

翻訳元言語と翻訳先言語にはこのページの一覧のものが使えるようである。

ためしに「こんばんわ」をスペイン語に翻訳する。Authorizationヘッダを設定してリクエストを投げる。

http://api.microsofttranslator.com/V2/Http.svc/Translate?from=ja&to=es&text=こんばんわ

結果が得られた。

  1. <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">¡Buenas noches!</string>  

スペイン語はよくわからないがたぶん合っているんだろう…

関連記事

Microsoft Translator APIで翻訳 (改)その1

以前の投稿で翻訳サービスのMicrosoft Translator APIの使い方を書いたのだが、書いた直後にAPIの利用方法が変わってしまっていた。久々に使う機会があったので再度調査し使い方をまとめてみた。

Windows Azure Marketplaceへの登録
まずはWindows Azure Marketplaceへの登録が必要となる。 以下のURLより「Microsoft アカウント」でサインインをする。 このアカウントは、以前はWindows Live IDと呼ばれていたものと同じではないかと思う。自分はそのIDでログインできた。持っていなければログイン画面の新規登録のリンクをたどって作成しする。 Windows Azure Marketplaceを初めて使うのであればいろいろ聞かれるので必要な情報を入力して登録を完了させる。

Windows Azure Marketplace
http://datamarket.azure.com/

Microsoft Translator APIのサインアップ
次にMicrosoft Translator APIの利用登録を行う。 Windows Azure Marketplaceにサインインした状態で以下のURLを開く。 Microsoft Translator APIは突き当りの利用文字数による従量課金だが、2,000,000文字/月までは無料なので、 右のほうに値段の書いたメニューの一番下にあるサインアップを押し、必要な情報を入力してサインアップを完了させる。

Microsoft Translator
https://datamarket.azure.com/dataset/1899a118-d202-492c-aa16-ba21c33c06cb

サインアップ後は以下に現在どれだけ利用しているかが表示されるようである。

マイ データ
https://datamarket.azure.com/account/datasets

アプリケーション登録
Microsoft Translator APIを使用するには自分のアプリケーションを登録し、client_idとclient_secretというキーを取得する必要がある。 APIをサインアップした画面の下のほうに、小さい文字で「アプリケーションの登録」というリンクがあるのでそれを押すか、次のURLへ行く。

アプリケーションの登録
https://datamarket.azure.com/developer/applications/register

新しいアプリケーションの作成のために以下の情報を入力する。 詳細には調べていないが、おそらくそれぞれの意味を持つ。

クライアントID
アプリにつけるID。他の人が既に使っているものと被らないものにする。
名前
アプリの名前。なんでもよい。
リダイレクトURI
Translator APIでは使わないが、登録に必須なので http://localhost/ など適当な値を入れる。httpだと安全でないと警告されるが無視
登録できたら、一覧に表示されるので「編集」を押してクライアント ID顧客の秘密を記録しておく。 これがそれぞれclient_idclient_secretと呼ばれるものになる。

これでやっとMicrosoft Translator APIを使う準備が整った。長くなったので使い方はまた次回…

関連記事

2013年2月12日火曜日

Google Driveでファイルを置き換え

Google Driveでファイルを置き換えるには次の手順でできる。

  1. 置き換えたいファイルを右クリック
  2. 「変更を管理...」を選ぶ
  3. ダイアログに「新しい版をアップロード」のリンクがあるので押して置き換えたいファイルをアップロード

単純にアップロードしただけだと同じファイル名で古いのと新しいのが共存してしまうので、どうしたものかと今まで困っていたがこの方法で置き換えができるようだ。 ただし、Google Driveは操作方法が頻繁に変わるので将来的にはやり方が変わるかもしれない。

2013年2月7日木曜日

JSONICでis-a関係を扱う

JSONとPOJO(Javaオブジェクト)を相互変換するのに JSONIC という便利なライブラリがある。 実際の使い方はリンク先を見ていただくとして、単純な変換であればJSON→POJO、POJO→JSONとも1行のメソッド呼び出しで書けるのでとても楽。 ただ、is-a関係のあるオブジェクトを含むJSONをPOJOに変換する場合にどうしたらよいのか今までやり方がわからなくて困っていた。 たとえば、以下のような3つのクラスがあって、AとBがis-a関係、SとAが has-a 関係にあるとする。
  1. public class A {  
  2.     private String fieldA;  
  3.     public String  getFieldA() { return fieldA; }  
  4.     public void setFieldA(String fieldA) { this.fieldA = fieldA; }  
  5. }  
  6.   
  7. public class B extend A {  
  8.     private String fieldB;  
  9.     public String  getFieldB() { return fieldB; }  
  10.     public void setFieldB(String fieldB) { this.fieldB = fieldB; }  
  11. }  
  12.   
  13. public class S {  
  14.     private A obj;  
  15.     public A getObj() { return obj; }  
  16.     public void setObj(A obj) { this.obj = obj; }  
  17. }  
このときSのsetObj()にクラスBを持たせたPOJOをJSONに変換すると以下のようになる。
{
    "Obj" : { "fieldA":"~", "fieldB":"~" }
}
しかしJSONになると型情報がなくなるため、このJSONを以下のようにPOJOに逆変換すると S.getObj()で得られるインスタンスはクラスAになってしまう。
  1. S myobj = JSON.decode(jsonText, S.class);  
  2.   
  3. // o はクラスAのインスタンス  
  4. A o = myObj.getObj();  
このときクラスBを再生するにはJSONICの変換ロジックをオーバーライドしてカスタマイズすることでできるらしい。 このように変換方法をカスタマイズするにはJSON.decode()メソッドではなく、postparse()をオーバーライドしたJSONクラスのインスタンスを作りparse()メソッドで変換をするようにするらしい。 少しややこしいが以下の方法でクラスBが再生できるようになった。
  1. JSON json = new JSON() {  
  2.   // parse時の変換方法をカスタマイズする  
  3.   @Override  
  4.   protected <T> T postparse(Context context, Object value, Class<? extends T> c, Type type) throws Exception {  
  5.     // クラスAの変換であればクラスBとみなして変換をする  
  6.     if (c.equals(Class.A)) {  
  7.       return c.cast(super.postparse(context, value, Class.B, Class.B));  
  8.     }  
  9.     // クラスA以外ならば既定の方法で変換  
  10.     else {  
  11.       return super.postparse(context, value, c, type);  
  12.     }  
  13.   }  
  14. };  
  15. S myobj = json.parse(jsonText, S.class);  
  16.   
  17. // o はクラスBのインスタンス  
  18. A o = myObj.getObj();  
この例だとSのsetObj()にあるのはクラスBと決め打ちしてしまっているが、ここを動的に変えるような場合はpostparse()メソッドの中で引数valueの内容を判別して処理を分岐すればできるのではないかと思う。 もしかするとannotationとか使うともっと楽なやり方があるのかもしれないが、まだよくわかっていないので今後の課題。

2013年2月4日月曜日

Facebook Graph API: February 2013 Breaking Changes 緊急修正

以下の連絡がFacebookから送られてきた
February 2013 Breaking Changes 緊急修正

あなたのアプリ「....」は、February 2013 Breaking Changesのために更新する必要があります。
アプリが対応したならば、アプリダッシュボードの詳細設定セクションの移行設定を「有効」にしてください。
この変更は2013年2月6日から1日以内に、すべてのアプリに永続的に適用されます。
何を意味している連絡なのかいまいち解りにくいが、 メッセージ中にDeveloper Roadmap のページへのリンクがあり、Facebook Graph APIでの仕様変更があるので該当機能をアプリで使っている場合はすぐ修正せよという警告のようである。 2月6日というと明後日なのでいきなり言われても困るし、アプリでとくに該当機能は使っていなさそうなので何をするべきなのかは謎。 アプリダッシュボードの詳細設定セクションの移行設定を「有効」にするだけでいいのだろうか…?

2/5追記

聞いた話だと、アプリダッシュボードの移行設定を「有効」にすると利用できなくなる機能に制限がかかった状態になるため2月6日以前に動作確認できるというものらしい。該当機能を使っていてもいなくてもこれが「無効」になっていると無条件で警告が飛んでくるような感じである。

該当機能を使っていない場合はどうやら放置しておいても問題ないようである。 自分のアプリは影響なかったようなのでまずは一安心。 ただし、認証関連の部分で大きい変更があるらしく、知っている方のいるところではこの方法を使っていたとのことで大騒ぎになったそうだ。

3月と4月にも March 2013 Breaking Changes, April 2013 Breaking Changesが控えているようだがこちらも今のところ大丈夫そうであった。

2/12追記

別のアプリではこの警告が今日来た。2/6期限のお知らせが今日来てもいかがなものかと思うが… 今日急にこのブログへのアクセスが増えていたのだが、もしかすると今日初めて警告を受け取った方々なのかも…

2013年1月28日月曜日

bashの環境設定

bashの環境設定は難しい…

.profileや.bashrcなど環境設定として使えるものがいろいろありどれに書くべきか迷う。

.profileの環境変数で ~を含んだパスを書いたがホームディレクトリに展開されないのは仕様?

2013年1月23日水曜日

BATファイル 直近のファイルを残して古いファイルを消す

Windowsコマンドプロンプトのバッチファイルで直近のファイルを残して古いファイルを消すにはforを使うとよい。 以下はC:\Tempにあるzipファイルのうち直近5個を残して古いものから削除する例である。
  1. for /f "skip=5" %%i in ('dir /b /o-d C:\Temp\*.zip'do (  
  2.     del C:\Temp\%%i  
  3. )  
dir /b /o-d ~ でファイルを新しい順にリストしそれを順次for文で処理する。 for文に skip=5 を付けているので、リスト先頭から5行、つまり最新のzipファイル5つは無視し、後は del で消すという意味になる。%%iにはファイル名のみが入りパスは入らないので、delするときに改めて指定する必要があることに注意。

2013年1月10日木曜日

Java VisualVM

何時のJDKのバージョンから付属するようになったのかわからないがJava開発にJava VisualVMというツールがすごく便利である。 Javaプロセスのプロファイルを取得するツールで以下情報がGUIでモニタできる。

  • 設定されているJVMオプション
  • 設定されているシステムプロパティ 
  • CPU使用率
  • Heapメモリの最大サイズと使用済みサイズ
  • PermGen領域の最大サイズと使用済みサイズ
  • 動作中のスレッド一覧、タイムライン、スレッドダンプ
スレッドタイムラインやPermGen領域をリアルタイムモニタできる機能が便利でサーバー系システムでだんだんとゴミがたまっていく問題を分析するのにとても役立った。

ツールはJDK のjavaにパスを通した状態でコマンドラインよりjvisualvmで起動できる。GUIのツールなので特に説明を見なくてもJavaのことをある程度知っている人ならば使いこなせると思う。

英語ですがドキュメントへのリンクはっておきます。



2013年1月7日月曜日

Kindle Fire HD 機能制限をかける

最近Kindle Fire HD 32GBを手に入れた。

Amazonのストアとの連携性は高く電子書籍などのコンテンツを読むには非常に便利な端末であるが怖い面もある。まずクレジットカードを登録し1-Clickを購入を有効にしておかないとストアからアプリやコンテンツを入手できず、しかもKindle上でストアの商品ページの[¥xxxで購入]のボタンをさわっただけで購入されてしまう。ストアの商品ページはトップページから最短1クリックで出てしまうので誤操作や子供がちょっと触ったなどだけでも購入される可能性がある。間違って購入したものはキャンセルもできるが気がつかないとちょっと怖い。

そんな誤爆を防ぐためには、機能制限をかけておくとよいと思う。 「機能制限の設定方法」のページで機能制限を設定した後に設定画面で「ショッピングをパスワード保護」をオンにすればよい。これで購入のボタンを押したときにパスワード入力を求められるようになる。

まあ、単に画面ロックをかけておけばよい話ではあるが、他にもメールやウェブなど細かく機能制限をかけられるので使いこなすと便利かもしれないと思った。

SQL Anywhere 連番を生成する

一行のSQLで処理を書きたいときなど連番テーブルがあると簡単に書けることがあるが、 SQL Anywhereの場合rowgeneratorテーブルを使って1~255までの連番を生成することができる。 rowgeneratorテーブルにはrow_num列が一つだけありその列に連番が入っている。
  1. SELECT row_num  
  2. FROM rowgenerator  
  3. WHERE row_num <=10  
  4. ORDER BY row_num  
実行すると以下のような連番が返ってくる。
row_num
-------
1
2
3
4
5
6
7
8
9
10

(10 ロー)
試していないのだが、255よりたくさん連番がほしい場合は入れ子のSELECTにするなど複数回組み合わせればできると思う。

2013年1月5日土曜日

サーブレットでリクエストパラメータを書き換える

サーブレットにリクエストパラメータを書き換えて渡したいときの方法。たぶんFilterを使っても同じことができると思うが、今回はRequestDispatcherでforwardする方法を使ってみた。

以下のサンプルでは /myWrapperServlet.do で受けたリクエストのパラメータ名p2をp1に書き換えて /myServlet.doに渡す。パラメータの書き換えは直接できないので、少々わかりにくいが HttpServletRequestWrapperのサブクラスを作り元のリクエストをラップしてgetParameter()などで書き換えロジックを書いてやる必要がある。パラメータ名p2をp1に書き換えるにはgetParameter()でパラメータp1を要求されたときに元リクエストのパラメータp2の値を返すようにする。
  1. package test;  
  2. // import文は省略  
  3.   
  4. public class MyWrapperServlet extends HttpServlet {  
  5.   
  6.   @Override  
  7.   protected void doGet(HttpServletRequest req, HttpServletResponse res)  
  8.     throws ServletException, IOException {  
  9.   
  10.     // パラメータの書き換えを行うHttpServletRequestを設定して  
  11.     // myServlet.doにforward  
  12.     RequestDispatcher dispatcher = req.getRequestDispatcher("/myServlet.do");  
  13.     MyServletRequestWrapper _req = new MyServletRequestWrapper(req);  
  14.     dispatcher.forward(_req, res);  
  15.   }  
  16.   
  17.   public static class MyServletRequestWrapper extends HttpServletRequestWrapper {  
  18.   
  19.     public MyServletRequestWrapper(HttpServletRequest request) {  
  20.       super(request);  
  21.     }  
  22.   
  23.     @Override  
  24.     public String getParameter(String name) {  
  25.       if ("p1".equals(name)) { name = "p2"; }  
  26.       return getRequest().getParameter(name);  
  27.     }  
  28.   
  29.     @Override  
  30.     public String[] getParameterValues(String name) {  
  31.       if ("p1".equals(name)) { name = "p2"; }  
  32.       return getRequest().getParameterValues(name);  
  33.     }  
  34.   }  
  35.   
  36. }  

以下はweb.xmlに記述するエントリの例。
  1. <servlet>  
  2.   <servlet-name>myServlet</servlet-name>  
  3.   <servlet-class>test.MyServlet</servlet-class>  
  4. </servlet>  
  5. <servlet>  
  6.   <servlet-name>myWrapperServlet</servlet-name>  
  7.   <servlet-class>test.MyWrapperServlet</servlet-class>  
  8. </servlet>  
  9.   
  10. <servlet-mapping>  
  11.   <servlet-name>myServlet</servlet-name>  
  12.   <url-pattern>/myServlet.do</url-pattern>  
  13. </servlet-mapping>  
  14. <servlet-mapping>  
  15.   <servlet-name>myWrapperServlet</servlet-name>  
  16.   <url-pattern>/myWrapperServlet.do</url-pattern>  
  17. </servlet-mapping>  

結果として上URLのリクエストが下URLのリクエストと同じになりサーブレットtest.MyServletクラスが呼び出される。
http://{server}:{port}/myapp/myWrapperServlet.do?p2=abcdefg
http://{server}:{port}/myapp/myServlet.do?p1=abcdefg

パラメータ名を書き換えるのでMyServletRequestWrapperクラスの他メソッドも(特にパラメータ名に関連するもの)もちゃんと実装したほうが良いと思うのだが、とりあえず動くので今回はここまで…

2013年1月4日金曜日

MySQL 先頭の指定件数だけselectする

SQLで先頭のN件のみselectしたい場合、SQL AnywhereとSQL ServerはTOP句を使って指定する。
たとえば3件だけとってきたい場合は以下のように記述する。
  1. SELECT TOP 3 * FROM mytable ORDER BY mycol1;  


MySQLで同じことをする場合はLIMIT句を使うようである。
  1. SELECT * FROM mytable ORDER BY mycol1 LIMIT 0,3;  

ORDER BY句の後につけるんですね。初めて知りました。