タイトル/名前 | 更新者 | 更新日 |
---|---|---|
日記/2012/01/03/nosecret.pyを修正 | msakamoto-sf | 2013-06-30 16:58:57 |
日記/2011/11/06/GnuPG使ったIDManagerの置換 | msakamoto-sf | 2013-06-30 16:57:56 |
日記/2011/10/30/Pythonでファイル内容をクリアするスクリプト作成。 | msakamoto-sf | 2013-06-30 16:57:11 |
Java/JavaMail/サンプル | msakamoto-sf | 2013-06-29 19:45:34 |
Java/Maven3/exec-maven-plugin メモ | msakamoto-sf | 2013-06-24 00:40:57 |
技術/TDD/DbUnitでCSV Export/Import を使うデモ + 制御コード混じりのバイナリ文字列を扱う時の注意点 | msakamoto-sf | 2013-06-23 22:39:42 |
技術/TDD/TestNG/デフォルトのAssertion, カスタムAssertionの作成 | msakamoto-sf | 2013-06-22 23:25:12 |
日記/2013/06/22/調べ物: Groovy Spock, TestNG, Java ORM, ActiveRecord, Fluent Interface, Gradleデバッグ実行 | msakamoto-sf | 2013-06-22 12:42:24 |
日記/2013/05/25/本日の調査メモ:PowerAssertってJavaから使えるの?+軽量ServletContainer現況 | msakamoto-sf | 2013-05-25 00:47:16 |
日記/2013/05/08/GL07SとHTTP(S) Proxy設定 | msakamoto-sf | 2013-05-08 20:04:12 |
今頃になって冷静に考えなおすと、GnuPGで鍵作ってどうのこうのする以前に、OpenSSLでPassword Based EncryptionでAESとかDES3暗号化・復号化サポートされてるんだからそっち使えば良かったじゃないか。
ということで、OpenSSLのコマンドラインインターフェイスを使うように修正しました。これで面倒な鍵の移動からおさらばできる。
https://github.com/msakamoto-sf/toybox/tree/master/nosecret
Windows用のOpenSSLバイナリについてはこのへんから適当にGET:
適当にPython組み合わせたやっつけ仕事。
https://github.com/msakamoto-sf/toybox/blob/master/nosecret/nosecret.py
Pythonならpy2exeも使える。一応Python 2.4以降(subprocess使ってるから)。
これでIDManagerからGnuPGによる暗号化に乗り換えられる。WinとMac当分は両方で使うので、とりあえずこれで役目は果たすはず。
tempファイル監視されてるようなマルウェアが動いてたら結構厳しいかもしれないけど、そこまで考慮してファイルを使わないようにしても、ローカルでそういうマルウェアが動いている時点でメモリダンプ取られればアウトだし、そもそもがやっつけ仕事なので90%の安心どころかせいぜい50%の安心を満たせればOKなのでこの辺が落としどころかな。
追記:windows用のショートカットbatファイルや、Unix/OSX用のショートカットshファイル追加しました。あとエラー処理とか環境変数とか調整。
GnuPGの導入とか実験していて、復号化したファイルを単に削除するだけじゃなくて、ファイル内容を一旦0クリアしたりランダムバイナリ書いたりしてから削除したくなったので、適当にPythonで作成。
https://github.com/msakamoto-sf/toybox/blob/master/toyshred/toyshred.py
フリーソフトとかで有るのは知ってるんだけど、Win/Unix/Macで使い回したかったので、LL言語で適当にさっくり環境整えられるPythonででっち上げた。
将来的にはgpgコマンドと組み合わせて、一時ファイルに復号化→操作が終わったら一時ファイルを削除、というのに使いまわしたい。
exec-maven-plugin のメモ
メモ:
練習:
DbUnitでテーブルデータをCSVでExport/Importするやり方と、varcharなどに改行コードを始めとする制御コードが混ざってきた場合の注意点について簡単にまとめます。
参考:
動作確認:
DbUnit 2.4.9
サンプル :
CSVでExportするには、 org.dbunit.dataset.csv.CsvDataSetWriter のwrite()メソッドが利用できます。
サンプルは色々混ざってるので、CsvDataSetWriterに関する部分だけ抜き出します。
File tmpDir; // ... tmpDir = FileDirHelper.createTmpDir(); //... // export only t1, t2 table to CSV QueryDataSet exportDataSet = new QueryDataSet(dbunit_conn); exportDataSet.addTable("t1"); exportDataSet.addTable("t2"); CsvDataSetWriter.write(exportDataSet, tmpDir);
write()の第一引数にはIDataSetの実装クラスのインスタンスを渡します。上の例では、対象テーブルを指定するため QueryDataSet を使っていますが、全体をダンプしたい場合は IDatabaseConnection.createDataSet() でも問題ないと思います。
第二引数は、出力先のディレクトリを指定したFileオブジェクトを指定します。出力に成功すると、各テーブル名 + ".csv"というファイルと、"table-orderning.txt"というテキストファイルが生成されます。"table-orderning.txt"は、後でCSVをimportする場合に、テーブルのインポート順序を制御出来ます。
CsvDataSetWriterクラスのrow()メソッドの中で、各行の各カラムの値ごとに以下のコードで""囲みの文字列を生成しています。
String stringValue = DataType.asString(value); final String quoted = quote(stringValue);
org.dbunit.dataset.datatype.DataType.asString() により文字列に変換しています。DataTypeは抽象クラスになっていますが、asString() メソッド自体は実装済みで、中身は以下の1行です。
return (String)DataType.VARCHAR.typeCast(value);
DataType.VARCHARは org.dbunit.dataset.datatype.StringDataType のインスタンスで、そのtypeCast()メソッドが、受け取ったObjectの実際の型に応じて文字列に変換しています。
結論からいうと、byte[]とBlobはbase64エンコードされ、それ以外は基本的に toString() の結果が採用されます。
CsvDataSetWriterで書きだしたCSVファイルセットは、CsvDataSetでそのままIDataSetのインスタンスとしてロード出来ます。CsvDataSetのコンストラクタの第一引数に、CsvDataSetWriterで書きだしたディレクトリのFileオブジェクトを渡せば、後はDatabaseOperationなどにそのまま渡せます。
IDataSet csvDataSet = new CsvDataSet(tmpDir); DatabaseOperation.CLEAN_INSERT.execute(dbunit_conn, csvDataSet);
なおCsvDataSetの実体は、CachedDataSetを継承し、以下のようにCsvProducerクラスをCachedDataSetのコンストラクタに渡しているだけです。そのため、CSVデータのインポート処理の実体はCsvProducerの方でコーディングされています。
public class CsvDataSet extends CachedDataSet { //... public CsvDataSet(File dir) throws DataSetException { super(new CsvProducer(dir)); } }
CsvDataSetWriterが出力するCSVは、一行目がカラム名のCSVレコードで、2行目から実際のデータのCSVレコードが始まります。型情報はCSVには出力されません。
CSVからImportする時、文字列として読み取った各カラムの値をどうやって各型に変換しているのか、ちょっとソース追って見ましたがすぐにはわかりませんでした。
org.dbunit.dataset.csv.CsvProducer.produceFromFile(File theDataFile) 中の以下で、parseしたCSVの各カラムごとに処理しているところまでは確認できたのですが、実際の処理は"_consumer"インスタンス、つまりIDataSetConsumerのインスタンスで、これは IDataSetProducer.setConsumer() により恐らくDatabaseOperationなどからセットされるため、ちょっと辿りきれませんでした・・・。
_consumer.startTable(metaData); for (int i = 1 ; i < readData.size(); i++) { List rowList = (List)readData.get(i); Object[] row = rowList.toArray(); for(int col = 0; col < row.length; col++) { row[col] = row[col].equals(CsvDataSetWriter.NULL) ? null : row[col]; } _consumer.row(row); } _consumer.endTable();
ただし、一応、export -> import した後のDB内容を確認してみたところ、標準的なカラムに当たり障りの無い値を入れてみた範囲では問題なくINSERTできていたので、多分「よしなに」処理してくれているのではないかなと・・・。
varcharなどの文字列フィールドに、制御コードなどCSVでの処理が怪しくなりそうな文字が入ってくるシステムの場合、ご想像の通り、まずCSVのexportで「文字化け」します。byte[], blobならexportまでなら少なくともbase64エンコードしてくれるのですが、varcharの場合は普通にStringとして処理され、CSV出力する際に1文字ずつエスケープ処理のフィルタを通過します。その際に、制御コードなどがおかしくなります。CsvDataSetWriter.escape(String) のコードを読んでみてください。
ではどうするか、ですが、色々考えてみたのですが、DataSetのexport/importの既存の仕組みを壊さずに安全に扱おうとしたら、CsvDataSetWriterで escape() するのではなく、もう一律にbase64エンコードしたのを""囲みにしてしまい、CsvProducerの方でbase64をデコードして文字列に直してから使う、というのがどうにか許容出来る内容になりました。
ではCsvDataSetWriter/CsvProducerを継承したクラスを用意して、ポイントとなるメソッドだけoverrideすれば良いのか・・・となりますが、別の問題が潜んでいました。CsvDataSetWriter/CsvProducerでは、重要なメンバの幾つかというか殆どがprivate宣言されていて、継承したクラスから参照できない状況です。メソッドのoverrideに支障が出る状況でした。
この辺りでいい加減ブチ切れて、今回は力技で、CsvDataSetWriter/CsvDataSet/CsvProducerのソースコードをまるっとコピーして、base64エンコード/デコードが必要な箇所のみを書き換えてしまうという暴挙に走らせてもらいました。(だからprivateは嫌いだ・・・)
それが以下のコードになります。
まずCsvBase64BinarySafeDataSetWriterですが、quote()メソッドで以下のように強制的にbase64エンコードしたのを""囲みにするようにしました。
private String quote(String stringValue) throws UnsupportedEncodingException { logger.debug("quote(stringValue={}) - start", stringValue); // encode to base64 string byte[] rawbytes = stringValue.getBytes(CharsetTool.BINARY); String base64encoded = Base64.encodeBytes(rawbytes); // quote surround return new StringBuffer(QUOTE).append(base64encoded).append(QUOTE).toString(); }
これで数値型も日付型も文字列型も、さらに制御コードが混入した文字列も、文字コードがおかしくなる前に、バイナリデータとしてbase64エンコードされます。blob/byte[]型は、既にbase64エンコードされた文字列が入ってきますが、それをもう一度base64エンコードすることになります。
続いて CsvBase64BinarySafeProducer では produceFromFile() のカラム列の処理を以下のように修正しました。NULL以外、全部base64デコードして使うようにしています。
_consumer.startTable(metaData); for (int i = 1 ; i < readData.size(); i++) { List rowList = (List)readData.get(i); Object[] row = rowList.toArray(); for(int col = 0; col < row.length; col++) { if (row[col].equals(CsvDataSetWriter.NULL)) { row[col] = null; } else { byte[] rawbytes = Base64.decode(row[col].toString()); row[col] = new String(rawbytes, CharsetTool.BINARY); } } _consumer.row(row); } _consumer.endTable();
以上のようにカスタマイズしたクラスを使って、実際に0x00 - 0xFFまでの文字列やバイナリデータをexport/importして最終的に戻せたことを確認しているのが、DbUnitCsvUsageTest.javaの customExportAndImportForControlCodeStrings() メソッドになります。
@Test public void customExportAndImportForControlCodeStrings() throws IOException, DatabaseUnitException, SQLException { IDatabaseConnection dbunit_conn = new DatabaseConnection(conn); // cache(save) expected data snap-shot. IDataSet expectedDataSet = new CachedDataSet( dbunit_conn.createDataSet()); // H2DB can store and load binary string to varchar column safely. Set<T4> a = new T4().findAll(conn); for (T4 a2 : a) { assertEquals(a2.stringField, UnsignedByte.create0x00to0xFFString()); } // export t4 using base64 binary safely csv dataset writer. QueryDataSet exportDataSet = new QueryDataSet(dbunit_conn); exportDataSet.addTable("t4"); CsvBase64BinarySafeDataSetWriter.write(exportDataSet, tmpDir); // clear & insert from exported CSV using base64 binary safely csv data producer. IDataSet csvDataSet = new CsvBase64BinarySafeDataSet(tmpDir); DatabaseOperation.CLEAN_INSERT.execute(dbunit_conn, csvDataSet); IDataSet actualDataSet = dbunit_conn.createDataSet(); Assertion.assertEquals(expectedDataSet.getTable("t4"), actualDataSet.getTable("t4")); }
0x00-0xFFが混入していることを意識させない、他のDataSet処理と同じ仕組を使って違和感のないコードに仕上がっています。
TestNGのデフォルトのAssertionは、 org.testng.Assert クラスにstaticメソッドとして用意されてます。
import staticして使う形になると思います。
primitive型, Object, byte[], Object[], Collection, Set, Map などひと通り揃っています。
デフォルトのCollectionのassertは順番に依存します。順番がどうなるか分からないけど、要素だけ一致しているか確認したい場合は、一度Object[]に直した上でassertEqualsNoOrder()を使えば良いと思います。
自前のクラスのインスタンスを使ってる場合も、ちゃんとhashCode()やequals()を実装しておけば一致チェックしてくれる筈です・・・多分・・・。hashCode(), equals(), toString()はEclipseの自動生成を使うと楽ちんです。
というわけで、ざっくりとassertionを色々と呼び出してみたサンプルが以下になります:
上記の"TestAssertionSamples.java"に含めていますが、基本的にはお好みの一致チェックメソッドを作成して、一致に失敗したら org.testng.Assertのfaile()メソッドを呼び出します。
(原理的には java.lang.AssertionError をthrowしても同等に思えるのですが、試してみたらEclipse上のTestNGプラグインやコンソールのスタックトレースが一個前のassertEquals()になってしまいうまく動かなかったため、fail()の方を使ってます。github上のTestNGのソースを読めば、fail()も最終的にはAssertionErrorをthrowしてます)
void assertCalendarYearIs(Calendar actualCalendar, int expectedYear) throws AssertionError { int actualYear = actualCalendar.get(Calendar.YEAR); if (actualYear != expectedYear) { fail(String.format("expected [%d] but found [%d] in [%s]", expectedYear, actualYear, actualCalendar.toString())); } } @Test public void assertCalenderAndCustomAssertion() { Calendar ac = Calendar.getInstance(); ac.set(2000, 1, 2, 3, 4, 5); Calendar ec = Calendar.getInstance(); ec.set(2000, 1, 2, 3, 4, 5); assertEquals(ac, ec); assertCalendarYearIs(ac, 2000); }
Compositeデザパタを適用したりした、複雑なオブジェクトの場合は、上記のような技法で専用のassertionを用意する必要があるかもしれません。
あるいは、単に一致しているかどうかだけでなく、値が範囲内に収まっているかとか、Collectionに値が含まれているかなどドメインに特化したタイプのassertionの作成にも使えそうです。
assertionのカスタマイズとなりますと、JUnitの文化圏ではhamcrestとの組み合わせがメジャーな状況(2013/06現在)ですが、TestNGでは地雷を踏まない形でのhamcrestとのintegrationの例やドキュメントがなかなか見当たりません。決して「英語のようにスラスラ読める」訳ではありませんし、どちらかと言うと泥臭い技法になりますが、上記のようなカスタムassertionの作成は選択肢の一つに入れておいて損はないと思います。
アラカルト
GitHubに6.8が上がってました。マイナーバージョンアップもぼちぼち勧められてて、プロジェクトが止まってる感じは全くしません。
assertのカスタマイズを考えてたのですが・・・なんか、あんまり複雑な連携してなくて、メッセージでっち上げたjava.lang.AssertionErrorをthrow
してるだけっぽい?
Cayenne参考
Javaで時々、
new Foo().setAbc("xxxx").setDef("yyyy").getGhi()....
みたいなコーディングができるライブラリ、あるいはサンプルコードを見ますが、"Fluent Interface"と呼ばれる技法のようです。デザパタ・・・と
まで言えるのかはわかりませんが。
実装としては、setterとかをJavaBeanでしたら
void setAbc(...) { this.abc = ...; return; }
とするところを
Foo setAbc(...) { this.abc = ...; return this; }
として自分自身を返すようにしてるようです。
参考:
わりと、Javaでも「あ〜、それってアリだよね〜」とコンセンサスを得つつあるようです。
とかいう話になるので、そりゃ難しいわ・・・。
GroovyのPowerAssert便利なので、なんとかJavaからも・・・つまりJUnitとかTestNGとかから使えないもんかな~と前から思ってたんですが、結論から言うと無理ぽで、テストフレームワークとしてSpock使うしか無さそうでした。
そもそもPowserAssertはAST変換でかなり頑張って色々組み換えてやってくれてるみたいなので、単純なAPI呼べばOK、みたいな実装にはなってないようです。
ということで、Spock使えでFinal Answerっぽいです。
で、なんかSpockをEclipseで弄る時はPowserAssertの出力を等幅フォントで表示するよう調整したほうがよさそうでした。
お仕事の方では、データパターンのサポート重視でTestNGに転んだのですが、Spockにしたほうが正解だったかなぁ・・・と若干後悔してます。
こんなん調べるくらいなら一行でもコード書いたほうが価値があるとか言われると泣きそうなので勘弁して下さい。
Play! FrameworkではNetty使ってるっぽい情報を小耳に挟んだのでどうかなと、ちょっと調べなおしてみました。
重要なのは、Maven/Ivy/Grape/Graldeエコシステムから地雷を踏まずに使える点ですね。やっぱりGroovyの"@Grape"で導入出来るのが良いです。
もともとNetty自体は汎用的な通信サーバ作るためのフレームワークっぽいので、HTTPサーバくらいまでは用意してくれてますが、J2EEのServletContainerの実装まではNettyには含まれてない・・・っぽい?ざっとggった程度なので断言出来ず。
ただ、こんなふうにわざわざNettyとは別にServletContainerとのつなぎ込みやってる人たちがいるくらいですので、多分そのままではServletContainerとしては使えないのかな~と。
Netty自体も十分複雑なので、この時点で早々に"@Grape"によるお手軽アプローチは諦めました。
やっぱり軽量で他のプロダクトへの組み込みもやりやすいServletContainerとなるとJettyが挙がるわけですが、Maven/Gradleエコシステムならシームレスに使えるものの、Jetty8-9辺りの依存関係に入ってくるアーティファクトか何かの末尾の".orbit"が、Ivy/Grapeエコシステムでトラブルを誘発するため、地雷回避が必須の状況です。
コレさえ無ければ非常に使いやすいのですが・・・。
長らく開発が停滞していた軽量ServletContainerのWinStoneですが・・・
GoogleCodeで、オリジナルの作者に許可を貰った上で有志により開発が再開された模様です。
ただ、まだMaven CentralリポジトリにはUPされてないため、なかなか"@Grab"使ったGroovyソースファイル一本でお手軽アプローチは難しそうです。
こういうのも見つかりました。
これもwinstoneと同様、まだMaven CentralリポジトリにはUPされてないため、なかなか"@Grab"使ったGroovyソースファイル一本でお手軽アプローチは難しそうです。
これは全く毛色の違う話ですが、EasyGSPというGroovyのWebアプリフレームワークがあるようです。
ちょっと良くわかりませんが、ある程度大きめのフレームワークっぽいです。
"Tomcat"という組み込み用のクラスが用意されてて、割りと簡単にTomcatを組み込みで起動とか出来るようにしてくれてるっぽいです。
参考:
emobileのGL07Sを購入しました。
使わないプリインストールアプリをサクサク削除したり無効化出来るのが気持ちいいです。
スクリーンショットは「通知パネルのショートカット」から「スクリーン」を選ぶと保存出来ました。
USB接続すると、「USBでパソコンに接続」という画面が表示されて、「メディアデバイス」として接続するか「カメラ」として接続するか選択するようになってます。(SDカードが内蔵されてるようではないので、そのへんの兼ね合い?)
→「メディアデバイス」として接続することで、外部ストレージ領域全体をWindowsからアクセス出来ます。
→「カメラ」として接続すると、デジタルカメラのフォルダにしかアクセスできなくなります。
最後にHTTP(S) Proxyについてですが、WiFi接続設定にHTTP(S) Proxyを設定できる欄が有り、アプリによってはroot化しなくともその設定だけでHTTP(S) Proxyを利用するようになってくれました。
するとWiFi接続を編集出来ますので、「プロキシ設定」を「手動」にして、外部のProxyホストとポートを設定します。
これにより、以下のアプリが自動的にHTTP/HTTPSでプロキシを利用してくれました。HTTPSについてはBurpのPortSwigger証明書を外部ストレージ経由でインストールし、証明書検証をpassするようにしてます。
HTTPSについてですが、確実にBurpでHTTPS通信を取れたのは標準ブラウザとFacebook、Instapaperまでです。それ以外については、HTTPS通信するような操作まではしてないので、HTTPS通信が発生してなかったっぽい。
Twitterアプリ(公式)については、通信エラーになってしまい確認出来ませんでした。ただ、通信エラーの原因がHTTPSによる証明書関連の検証エラーだとすれば、もしかしたらProxyを利用していることはしているのかもしれません。独自に証明書検証してるとかで、Android側にインストールされたPortSwiggerのCA証明書が弾かれた可能性があります・・・が、特にadbでログチェックしてみたわけでもないのでそこは良くわかりません。
さしあたり、root化が必要なレベルのアプリは入れる予定がありませんので、ひとまずこれで十分です。