2011年1月31日月曜日

Java JDBC-ODBC 文字列またはバッファの長さが無効です

JavaアプリからJDBC-ODBCブリッジでデータベースSQL Anywhereに接続していると、不定期に「文字列またはバッファの長さが無効です」というエラーが出る現象に遭遇した。

DriverManager.getConnection()で出てみたり・・・

java.sql.SQLException: [Microsoft][ODBC Driver Manager] 文字列またはバッファの長さが無効です。
at sun.jdbc.odbc.JdbcOdbc.createSQLException(JdbcOdbc.java:6957)
at sun.jdbc.odbc.JdbcOdbc.standardError(JdbcOdbc.java:7114)
at sun.jdbc.odbc.JdbcOdbc.SQLGetDataString(JdbcOdbc.java:3907)
at sun.jdbc.odbc.JdbcOdbcResultSet.getDataString(JdbcOdbcResultSet.java:5698)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(JdbcOdbcResultSet.java:354)
at sun.jdbc.odbc.JdbcOdbcConnection.buildTypeInfo(JdbcOdbcConnection.java:1503)
at sun.jdbc.odbc.JdbcOdbcConnection.initialize(JdbcOdbcConnection.java:381)
at sun.jdbc.odbc.JdbcOdbcDriver.connect(JdbcOdbcDriver.java:174)
at java.sql.DriverManager.getConnection(DriverManager.java:582)
at java.sql.DriverManager.getConnection(DriverManager.java:185)


ResultSet.getString()で出てみたりと発生箇所もさまざま。

java.sql.SQLException: [Microsoft][ODBC Driver Manager] 文字列またはバッファの長さが無効です。
at sun.jdbc.odbc.JdbcOdbc.createSQLException(JdbcOdbc.java:6957)
at sun.jdbc.odbc.JdbcOdbc.standardError(JdbcOdbc.java:7114)
at sun.jdbc.odbc.JdbcOdbc.SQLGetDataString(JdbcOdbc.java:3907)
at sun.jdbc.odbc.JdbcOdbcResultSet.getDataString(JdbcOdbcResultSet.java:5698)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(JdbcOdbcResultSet.java:354)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(JdbcOdbcResultSet.java:411)


調べてみるとある特定の環境だけで頻発するようだ。

英語のエラーメッセージ "Invalid string or buffer length" で検索すると、SQL ServerOracleでも発生している様子。これらは不定期でなくコードの決まった場所で必ず発生するとのことだが、共通するのはx64版のJavaVMを使っていると言うことである。

今エラーが起きているのもx64 JavaVMなので何か関係がありそうである。

JDBC-ODBCをやめてType4ドライバを使うようにしたほうがよさそうだ。

2011年1月19日水曜日

InstallAnywhere VM packの追加方法

インストーラーを作るときに普段使っているのはInstallAnywhereという製品である。

これは、Javaアプリのインストーラーを作成するためのツールであり、複数のプラットフォーム向けのインストーラーを一度に作成できるという特徴がある。以前 Install Shield Multi Platformという同じ用途のツールがあったがその後継製品となっているものである。

さて、InstallAnywhereで複数のプラットフォームのインストーラーを作るには、それぞれのプラットフォーム向けのVM Packというものを入手して設定する必要がある。このツールは取り扱う会社がころころ変わっているおかげでVM Packの入手先がわかりにくく、設定方法でも少しはまったので、ここにメモしておく。

  1. InstallAnywhere: Files & Utilitiesのページを開き、"VM Pack"と書いてあるところを選択。



  2. 下のほうに行くとVM Packの一覧があるので、必要なプラットフォームとJavaのバージョンを選んでダウンロードする。ダウンロードするファイルの拡張子はvmであるが、IEでダウンロードするとなぜかzipになってしまう。*.zip を *.vm に変更して保存する。



  3. ダウンロードしたファイルを、{installer-dir}\resource\installer_vms フォルダに入れる。


自分が使っているのはInstallAnywhere 2009であるが上記ページには特にどのバージョン向けという記述は無いので、InstallAnywhereのシリーズであれば同じVM packが使えるのだと思う。

それにしても、このツールはドキュメントが不親切であるし使いにくい・・・

2011年1月18日火曜日

Android アプリからツイートする(非正攻法)

AndroidのアプリからTwitterに投稿するには正攻法としてはTwitter APIを使うのがよいであろう。

しかし、OAuthなどが必要だったり敷居が高く感じる人も多いであろう。

端末にTwitter公式アプリが入っているという前提があるなら、Intentを使うことで投稿準備ができた状態でTwitterアプリを起動し最後に投稿ボタンを押してもらうだけでツイートできるようになる。

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setClassName("com.twitter.android", "com.twitter.android.PostActivity");
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "Androidから投稿なう"); // 投稿するメッセージ
try {
startActivity(intent);
}
catch(ActivityNotFoundException ex) {
// Twitter公式アプリが無いときのエラー処理
}


ただし公式アプリのクラスを直接呼び出しているので、将来アプリがバージョンアップされた場合には動かなくなる可能性があることに注意。

とりあえず Twitter公式アプリVersion1.0.5で動作を確認した。他のバージョンでの動作報告をコメントしてもらえるとありがたいです。

Android アプリからSMSを送る

Androidのアプリ中からSMSを送るにはIntentを使う。ネットで調べるとそのパラメータの設定についていろいろなやり方があるようだがOSバージョンや搭載しているSMSアプリの種類によって挙動が変わるのかGalaxyTabでうまくいくパターンがなかった。

試行錯誤の結果、以下のような方法で宛先と本文を埋めた状態で標準SMSアプリが起動することがわかった。あとはユーザーに送信ボタンを押してもらえばSMSが発信される。

Uri uri = Uri.parse("smsto://");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.setType("vnd.android-dir/mms-sms");
intent.putExtra("address", "090xxxxxxxx"); // 電話番号を入れる
intent.putExtra("sms_body", "こんにちは"); // 送信メッセージを入れる
try {
startActivity(intent);
}
catch(ActivityNotFoundException ex) {
// SMSアプリが無いときのエラー処理
}

2011年1月13日木曜日

SQL Anywhere 時間差を求める

SQL Anywhereで2つの時刻の差が何秒あるかを求めるにはdatediff関数を使う。

この関数の書式は以下のようになっており、date-partには求めたい差の単位day,hour,secondなど、date-expression-1,2には差を求めたい2つの時刻を入れる。date-expression-1のほうに古いほうの時刻があると関数の結果は正となり、新しいと負になる。


DATEDIFF( date-part, date-expression-1, date-expression-2 )


たとえば、以下のようなスキーマのテーブルがあり、upd_timeに各行の更新時刻が入っているような場合、

CREATE TABLE mytable (
r_id integer NOT NULL
,r_data varchar(100) NOT NULL
,upd_time timestamp NOT NULL
);


このテーブルから、1時間以内に更新された行を抽出するSQLは次のように書ける。
条件句にDATEDIFF関数を使って現在時刻NOW()関数とupd_time列の差を秒単位で求め、3600秒以内である行のみを抽出する。

SELECT r_id,r_data,upd_time
FROM mytable
WHERE DATEDIFF(second,upd_time,NOW()) <= 3600
ORDER BY start_time


この関数はSQL2003標準であるため、サポートしている別のDB、たとえばSQL Server 2008などでも同じように使えるようだ。

Android 文字列リソースのエラー

EclipseのAndroid Development Toolkitをアップデートしたときにはまったときのメモ

アップデート前まで正常だったプロジェクトで、strings.xmlがこのようなエラーをはくようになりビルドできなくなってしまった。


W/ResourceType(15164): Bad XML block: header size 276 or total size 18088132 is larger than data size 0
...\res\values\strings.xml:13: error: Multiple substitutions specified in non-positional format; did you mean to add the formatted="false" attribute?
...\res\values\strings.xml:13: error: Unexpected end tag string



プロジェクトが壊れてしまったのかと思いあわてたが、ここの情報によると、書式文字列のチェックが厳しくなったようで、%sなどの置換部分が2個以上ある場合はそれぞれに位置を指定する必要があるらしい。

たとえば、今までこうしていたのは、

<string name="message">製品名=%s %d円です</string>


このように書き換える必要がある。

<string name="message">製品名=%1$s %2$d円です</string>


おまけ

アップデート直後にEclipseを起動すると "sdk platform tools component is missing" というエラーが出るかもしれない。その場合は、メニューから Android SDK and AVD Managerを起動してインストールされているパッケージすべてをUpdate Allしてやると復旧するようです。

2011年1月6日木曜日

SQL Anywhere データベースをコピーする

稼働中のデータベースファイルをとめないでコピーするというのはメンテナンス作業の中でも頻度が高いであろう。こんなときSQL Anywhereではdbbackupコマンドを使う。

一般的には以下の使い方が多いだろう。接続文字列にDBへの接続方法、保存先フォルダにはデータベースファイル(とログなどの付属ファイル)を保存する先を指定する。
dbbackup -c {接続文字列} {保存先フォルダ}

たとえば、以下のようにすると ODBCデータソース "mydb" で接続できるデータベースをmybackupフォルダにコピーする。
dbbackup -c "dsn=mydb" mybackup

実行結果は以下のようになる。

途中フォルダが無いよと言ってくるので y を入力してフォルダを作成する。

C:\temp>dbbackup -c "dsn=mydb" mybackup
SQL Anywhere バックアップ・ユーティリティ バージョン 11.0.1.2044
ディレクトリ "mybackup" は存在しません。作成しますか ? (Y/N) y
(6195/6193 ページ、100% 完了)
データベースのバックアップが完了しました。

C:\temp>dir mybackup
ドライブ C のボリューム ラベルがありません。
ボリューム シリアル番号は 1454-FF9A です

C:\temp\mybackupのディレクトリ

2011/01/06 15:41 <DIR> .
2011/01/06 15:41 <DIR> ..
2010/12/29 16:49 13,320,192 mydb.db
2010/12/30 15:48 12,050,432 mydb.log
2 個のファイル 25,370,624 バイト
2 個のディレクトリ 27,779,186,688 バイトの空き領域


dbbackupを実行する前には%SQLANY11%\Bin32などSQL Anywhereのコマンドがある場所にパスが通っているのを忘れないように

xcopyでフォルダごとコピー

コマンドラインでのフォルダコピーにはWindows VISTA以降であればrobocopyを使うのが便利だが、XP以前ではxcopyしか標準では使えないので、まだまだ使う機会が多い。

やりたいこと
フォルダ srcdir の内容全部を、フォルダ destdir にコピーする。
フォルダ destdir はまだ存在しない。

コマンド
xcopy /i /s /e /h srcdir destdir


ここで指定したオプションは以下のとおり、xcopy /? で出力される説明だとわかりにくいがおおむね以下のとおりである。

  • /i : コピー先フォルダがなければ作る
  • /s,/e : セットで使うとフォルダツリー全体をコピーする
  • /h : 隠しファイル(.svnなど)もコピーする



自分が使うパターンはこれしか無いのだが、つけるオプションを忘れがちなのでここにメモ。


おまけ robocopy編
最初にも書いたが、Windows VISTA以降であればrobocopyを使うのが便利だと思う。個人的には次の2つのパターンをよく使う。どちらもsrcdirからdestdirにフォルダを丸ごとコピーする。オプションはxcopyと似ているが若干異なる。

(1)destdirに重ね書きする
srcdirにあってdestdirにもあるファイルやフォルダは上書き、srcdirに無くてdestdirにあるものは残る。
robocopy /s /e srcdir destdir


(2)srcdirとdestdirを同期
destdirの内容をsrcdirと同一にする。srcdirにあってdestdirにもあるファイルやフォルダは上書き、srcdirに無くてdestdirにあるものは消す。rmdirとxcopyを使えば同等のことはできるが、robocopyは差分でコピーするので速い。
robocopy /mir srcdir destdir