09

intra-mart Accel Collaboration のスケジュール権限周辺を調べて見た

intra-mart Accel Platform (iAP) の権限周りに関しては、 ワタシハ認可(im-Authz)チョットデキル んだけど、intra-mart Accel Collaboration (iAC)の権限管理については疎かったので、マニュアルとAPIドキュメントベースだけどちょっと調べた。

version 7.x 以前のグループウェアとして提供されていたintranet Start Pack(iSP)と比べると、だいぶ違っててちょっと驚いた。

まず機能的な面でいうと、

  1. iSPでは管理者が設定する権限とは別に個人設定として、自分のスケジュールを誰に見せるとか見せないとか設定できたのだけど、それがなくなっている
  2. iSPでは更新可能/参照可能 見たいな権限レベルの並びに秘書権というレベルがあったのをiACでは代理という機能で補っている

が、肝心の権限設定においても概念的に以下のように異なっている。っぽい。

  • iSP:AがBのスケジュールを変更・参照する権限を付与(一方向)
  • iAC : AとBがスケジュールを共有する(双方向)

基本的にスケジュールの権限というのは共有グループというのを作って行う事になっていて、そのグループ内でデータは共有される(データ個別で非公開や登録者以外変更不可などの設定はあるものの、基本誰が触っても良い)という考え方。っぽい。見る限り。 この辺はIMBoxやワークスペースにその思想が色濃く出ているけれど、Accel Platform世代のアプリケーションの設計がトップダウンで一方通行なコントロールよりボトムアップで双方向なコラボレーションを意識して設計されているからなんだろう、と勝手に思っている。

しかしこういった違いがあるので、iSPでは設定できたけどiAPでは設定できないという領域は思ったより大きくなるかもしれない。とはいえ実用上問題になるかは別な話なんで、それが悪いかは分からない。 顕著に問題になるのはおそらく、 iSPの権限設定をなんとかしてiACに移行しようとすると、単純にはできないというところ。まあ、その需要は現時点では割と高いんだろうけど。

ClojureでHello World

前口上

基本的にJava屋で、たまにpythonを使っている。.NET は C#よりVB.NETの方が面白いと感じている。なぜかといえば、 なんとなく文法が似ていてJava -> C#という流れに魅力を感じない。 同じ理由でScalaも辛い。界隈の人が声高にコンパイル遅いと叫んでいる事に、まだ手を出すべき時期じゃないかなあ感を抱いているというのもあるけど。

VisualBasicは.NETの機能を手に入れているというのに熟練者にはあまり好かれない空気があって残念だな。可能性を突き詰めれば今と違う世界か見えそうな気がするんだけど。まああのへんの好きなひとはF#にいってるのかねえ。

というわけで、 Clojureをやってみようと言う気になった。 教科書はコレだ

プログラミングClojure 第2版

プログラミングClojure 第2版

今回のテーマは Hello World

なぜ Hello World か

ハマったからである

今回の環境
  • Mac OS X Mavericks.
  • JDK 1.8.0
Closure のインストール

LeiningenというMaven的なことをやってくれるツールが、Clojureのインストールもしてくれるらしい。

https://github.com/technomancy/leiningen

レイニンゲン?人の名前かな。元ネタはこちらのようだ http://en.wikipedia.org/wiki/Leiningen_Versus_the_Ants

REPL の起動(できない)

REPLがあるらしい。Leiningenを使って起動するようだ。

$ lein repl

でREPLが起動してClojureの勉強を開始できる・・・らしいが、以下の例外が出て起動できない。

Exception in thread "Thread-3" java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    ...

REPLがいきなり例外はいて死ぬってちょっとハードル高いな。

ググってみるとGithubのIssueが立っている https://github.com/technomancy/leiningen/issues/1321

どうも内部的にはクライアントサーバ型になっていて、サーバだけ起動するとか、サーバを指定してクライアントを指定するとか色々できるようだ。普通に起動すればサーバとクライアントを起動してプロンプトを出すのだろう。 しかしそこで接続に問題が発生している。理由としてはどうもlocalhostが何に解決されるかで違うんじゃないかとかWifi切れば起動できるとかJava8だと発生するけどJava7なら大丈夫とか、色々あるみたい。自分の場合、ホスト名を設定してやる事でクリアできたようだ。

$ sudo scutil --set HostName kitchen-mac.local

しかし今度は違う例外が発生する

$ lein repl
nREPL server started on port 56857 on host 127.0.0.1 - nrepl://127.0.0.1:56857
REPL-y 0.3.2, nREPL 0.2.0-beta5NoSuchMethodError clojure.tools.nrepl.StdOutBuffer.length()I  clojure.tools.nrepl.middleware.session/session-out/fn--7630 (session.clj:43)NoSuchMethodError clojure.tools.nrepl.StdOutBuffer.length()I  clojure.tools.nrepl.middleware.session/session-out/fn--7630 (session.clj:43)Exception in thread "nREPL-worker-0" java.lang.NoSuchMethodError: clojure.tools.nrepl.StdOutBuffer.length()I

REPLでNoSuchMethodってどんだけハードル高いんだよなどと思いつつ再びGoogle先生に聞くとコレもやっぱりIssuesが立っている

https://github.com/technomancy/leiningen/issues/1625

in non-project REPL とあったので、教科書のサンプルコードのプロジェクトをダウンロードしてみた。 そこで実行する分には問題ないようだ。

$ lein repl
nREPL server started on port 56906 on host 127.0.0.1 - nrepl://127.0.0.1:56906
REPL-y 0.3.2, nREPL 0.2.3
Clojure 1.3.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0-b132
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (println "Hello World")
Hello World
nil

SQL Server Express 2012+ JDBC

SQL Server Express に JDBC でつなごうとしてなぜかつながらずかなり悩んだのでメモ。 SQL Server Express は インストール時にTCP/IPソケットが有効になっていない。なのでこれを有効にしてやる必要がある。この設定はSQLServer の構成マネージャの「SQL Server ネットワークの構成>SQLEXPRESSのプロトコル」を選択するとTCP/IPの項目があるのでプロパティを開き、有効にする。IPアドレスのタブで個別のアドレスに対してポートを割り振る事ができるが、このタブの設定は少々注意して行った方が良いようだ。TCP動的ポートが0になっているとなんらかのポートを自動的に振ろうとして面倒なことになるので、これはとにかく空欄にするのが良いようだ。あとVM等が入っているとやたらネットワークインタフェースが増えるので、どれに設定が必要なのか注意してみていく必要がある。また、一番最後にAllなる項目があり、これを設定すれば全体的に効果を発揮するのかもしれないが、複合的にどういう挙動を示すのか確認できていない。なので今回とにかく固定になるように動的ポートは0を削除して空欄に設定した。

で、ちゃんとポートが開いているかの確認の仕方。 まず構成マネージャで「SQL Serverのサービス」の項目でSQL Server(SQLEXPRESS)のプロセスIDを確認する。これはタスクマネージャでも確認できるのでそっちでも良い。 で、コマンドプロンプトで netstat -ano を実行する。右側にプロセスIDが出てくるので、この中から上記で確認したプロセスIDを探す。左列にローカルアドレスが出ているので、ここで指定したポートになっているかを確認できる。これでポートが開いていて、それでも接続できないならファイアウォールの設定を疑えばいいかな。

当初まさかTCP/IPが無効になっているなどと思わず、なんだかんだで3、4時間浪費してしまった。

どこまで薄くできるのか

前回サーバサイドが大分薄いシステムを作っている話を書いたのだけれども、こんなに薄くて大丈夫なのかなとぼんやり考えていた。

考えてみると、今実装している機能はドメインロジックらしきものがとても薄い。ドメインロジックが複雑な場所ではこんな実装にすることは難しいのかもしれない。大概のシステムではドメインモデルをきちんとレイヤ分割やクラスシステムによって保護できるように設計しつつ、WEBシステムとしての構造に組み込んでいくだろう。ドメインロジックが複雑であればあるほどそのロジックを持つクラス周辺が大きな構造になるが、自分が今実装している機能は他の業務に影響したり影響されたりする要素が少なく、ぶっちゃけて言えば業務フローにおいてはスキップしても構わない機能だ。おもに後続業務の効率化のために存在していて、いわゆるクリティカルパスではない。

要するに、業務の関係でよそに配慮したり逆にガードしなければならない部分があまり多くない。

業務システムといっても利用する組織規模によって必要な事項は大分違っていて、大規模になればなるほど業務ドメインごとのデータの完全性が重要になりそうだが、小規模の場合はむしろ人による制御の可能性の方が重要視される場合が多いように思っている。一般的に人によるデータに対する制御可能性を高めようとするとある程度データの完全性は妥協しなくてはならない。順序付けて並べられた業務プロセスを順番に完了させていくのではなく、業務の個別のステップは状態が確定せず同時進行的に進むというようなことを業務フローとして許容できるシステムを要求されることがままある。大規模な組織的利用をするシステムなどでこういった設計を適用してしまうと、適切に情報共有されない部署間の問題などで全体として一貫した対応が取れず、コントロールを失ってしまう可能性がありそうだが、中小規模ならば逆にこのような形態の方が都合がいいという場合もある。

まあ、今回のはそんなに規模が大きくない前提というわけだね。

実装の方に話を戻すけれど、そもそもクライアントサイドでデータ構造を作ってJSON/Ajaxで飛ばすことも割と普通になってきている昨今、クライアントから受け取ったデータを静的型のモデルにマッピングする処理は最低限でいい(フレームワークによるサポートも充実している点も理由としてある)し、今作っているシステムにおいてはデータベースから取り出した結果をサーバサイドで処理する要件もあまりない。データベースから取り出した結果から複雑な処理を行いたい場合、型システムのある世界では型をもった値に変換したくなるのが普通ではあるけれど、そうでもなければSQLの結果を逐一モデル型をつくってそれに変換するというのはオーバーヘッドになるだけで意味があまりない。現状として必要になっているのは、クライアントサイドで利用しやすいように若干の整形処理を行うことくらい。

たとえば、HTTPを型付のシリアライズデータ(OData?)などで転送して、クライアント側ではTypeScriptみたいな言語(TypeScriptが値にたいして型を持てる言語かどうか知らないが)が型付で受け取れるとか、つまりサーバサイドの型をクライアントサイドの型に簡単にマッピングでき、かつその利点を生かせるのならばサーバサイドでも厳密に肩を扱うことに意味が出てくるだろう。

しかし現状それはないので、まあ特別型を使うことにこだわる必要がない。

あとまあ、個人的な経験を振り返って、あるデータ型のオブジェクトから、別の似たような(しかし明確に異なる)型のオブジェクトへひたすら値の転記をやるというコードを過去に何度も書いていて、いい加減(コード上だけでなく実処理として)価値がないよなという認識になっていて、そういった処理を極力排除してみたいという欲求があるというのも、こんな実装にしている理由としてはある。

ただまあ、若干テストについては悩んでいる。

既に述べているとおり、HTTPの入り口からデータベースまでのレイヤがとても薄いシステムになっている。データベースを使用したロジックのテストにおいては、データベース側を抽象化しておいて、モックに差し替えるなんてことをやるのが常套なんだろうけども、サーバサイド側の実装がとても薄いと、あまりそういうものを差し込む場所がない。テスタビリティが低いというのかもしれないが、そのために "SQLを発行して連想配列の配列を吐き出す実装" をモック化したとして意味があるようにも思えない。結局SQLが正しく構成されて発行されるかが重要になっているので、そういう意味では、DBに接続してエンドツーエンドで実行するテストを網羅的に用意した方が、テストとして効果が高い気がする。結局DB依存のクエリなどもあるし、今回の案件は既存システムに対する改修なのでALTERTABLEみたいなデータベース変更もあるため、その辺含めてDBMS様的にも問題ないかチェックしてもらえた方がいい。 つまり、モックを使用したテストにあまり時間をかける価値を感じていない。

自動テストでデータベースに接続してスキーマ作ってなんてやっているととても重いテストになってしまうんだけど、最終的に大掛かりなCIなんかを目指すと結局そこはやりたいし、今回の構成でもっとも効果が高いのはそういうテストだと思っている。 ただまあ、環境が.NETということで全く勝手がわからないのだけど。少し時間が出来たら調べてみようと思う。うまく回りそうならまだブログに挙げることにしよう。

ASP.NET の 永続化層で悩む

現在作成しているシステムは諸事情あってクライアントサイドがAngularJSでサーバサイドがASP.NET(VB) / WebAPI2 になっている。なるべく早く顧客に動きを見せるためにおおざっぱに全体を実装する感じでやっている。クライアントサイドはまあAngularJSで適当にやっているけど、サーバサイドはいわゆるREST API的に実装しているのだけど、ロジックのほとんどがSQLだ。

  1. HTTPリクエスト受付
  2. ⇒SQL生成
  3. ⇒実行
  4. ⇒結果取得
  5. ⇒そのまま1レコードを連想配列とするようなオブジェクトの配列としてJSON変換
  6. ⇒HTTPレスポンス

みたいな流れがほとんど。

今のところまだバリデーションやらセキュリティやらは考慮していないので今後その辺増えていく可能性はあるけれど、サーバサイドがほとんどSQLで済むのならば、サーバサイドレイヤーはほんとに薄くなりそう。

本当はORマッピングレイヤーを何かしら入れるつもりでいたのだけど、いろいろいじってみているうちにどれも.NET初心者(自分)にとって学習コスト含め重すぎるという結論になってやめた。

EntityFramework6

方式がモデルファーストとコードファーストとDBファーストの3種類ある。らしい。モデルファーストは専用のモデルエディタを使ってエンティティの定義を作ってやるとデータベースのスクリプトとそれにマッピングされるモデルクラスを自動生成してくれるようだ。はじめこれを使ってやっていたのだけど、いろいろがちゃがちゃとやっているうちにモデルエディタがうんともすんとも言わなくなってしまって全く原因もわからないのでパスした。他の方法はあまり試してないのだけど、このあたりでEntityFrameworkの評判を聞いてみたところあまりよい反応はなさそうだったので、別な方法を探した方がよさそうな空気になった。

Linq to SQL

EntityFrameworkを使いたかった理由の一つがLinq to Entitiesを使えるからということで調べていたのだけど、それ以外の方法としてLinqをSQLにマッピングするLink to SQLとやらがあるらしいのでそちらの使用方法も調べてみた。しかしどうもこれDeprecated扱いにしたい雰囲気を感じるのと、こちらでも妙なGUIを使ったモデルエディタの力が必要になるっぽいのでパス。

Dapper

SQLはSQLとして発行してその結果をクラスにマッピングしてくれるだけの薄いDBアクセスフレームワークらしい。いいんだけど、正直結果の数だけクラス作るのかなあと思うと、どうせ結果はそのまま画面に出すだけなんだから結果セットをそのままJSONに変換するくらいの勢いでいいんじゃないだろうか、と思い当った。

といった経緯で、SQLの実行結果はそのままクライアントに転送してもいいのではないかと思い始め、単純にSQL編集の補助をしてくれて結果をJson関数に渡せる形で返すというだけのラッパーを書いてみた。現状サーバサイドロジックは大体こんなような書き方になっている。

Using db as new DB()

    dim sql as new SQLBuilder
    sql.Add("select * from hoge_table ")
    sql.Add("where foo=@bar", barValue)

    return Json(db.Q(sql))

End Using

今のところは(プロトタイプであることもあってか)特に問題とはなっていないけれども、エンティティの整合性を保つためにHTTPのインプット側に関してはサーバサイドでも強固なチェックの必要性はありそうかな。 詳細画面なんかだと、情報の構成がいわゆるヘッダー - 明細構成になっているものがあったりして、その場合はSQLの実行結果から、クライアントサイドで使いやすそうなオブジェクトの形へ整形処理を行ったりしている部分も若干ある。

この辺うまくというか、宣言的な方法でわかりやすい記述でやれるといいんだけどなあ。まあその辺はこれから。

あ、あとエラーハンドリングに関してもまだ方針を決めていない。 個人的にはエラーと正常のフラグを持つオブジェクトを一つ作って、その中に適当に結果を入れて返すのがいいかなあと漠然と考えていたのだけど、どうせならHTTPヘッダにそれらしい情報を埋めてしまうのがいいのかもしれない。

YappoLogs: 2014年に向けた JSON API の実装の方向性と X-JSON-Status 改め X-API-Status header のご提案

Python で Aipo にログインして掲示板に何かしらポストする

概要: requests 便利

というわけでとあるグループウェアの掲示板に定期的にポストするというのを自動化したかったので、やり方を調べてみると、requests というパッケージを使うと良いらしい。

http://docs.python-requests.org/en/latest/

今回のグループウェアは Aipo という製品のオープンソース版です。 http://www.aipo.com/

このグループウェアはApache Turbine というフレームワークをベースとしたポータルフレームワークである Jetspeed をベースに日本語化したプロダクトをベースにしたプロダクトで、オープンソースで提供されている。 由来が古いのでいろいろと古い部分もあり、いわゆるRESTful URLのような設計でも無いように見える。なので継続するリクエストパラメータやセッション情報の取り回しなどが重要。自前でHTTP リクエストを飛ばしたりして作るとその辺面倒だったりしますね。しかし今回requests のおかげで大分楽に実装できた感じ。

今回勘違いしてテストサーバにAipo Version 4をインストールしてしまって、それを対象にスクリプトを書いてしまったのだけれども、まあ最新のバージョンでも多分大筋変わらないんじゃないでしょうかね(という希望的観測)。

掲示板投稿するには

Aipoで普通に掲示板に投稿しようと思ったら以下のような手順を踏むことになる。

  1. Aipoにログイン
  2. 掲示板のフォーム(Ajax)を開く
  3. 内容を記入し、投稿

前提としてログインしたアカウントが掲示板に投稿する権限を持っている必要があるけど、それ以外にこの流れでプログラムが気にする必要があるのは以下。

  • ログインし、ログイン状態を保持しなければ投稿できない
  • CSRF対策等でフォームにワンタイムトークンが発行されている可能性

ログイン状態の保持というのは要するにセッション情報という奴だけど、これについてはCookieの取り回しができれば(だいたいの場合は)良いはず。 ワンタイムトークンは一度フォームにアクセスしてみないと分からないので、上記の手順を忠実に守った上で、取得したフォームの情報からトークンを抽出して記事投稿時に一緒に投げるようにしないといけない。

requests ってみる

さて、いきなりセッションのハンドリング。

    s = requests.Session()

これで s に HTTP GETやHTTP POSTのためのメソッドがついてくる。 つまりあとは手順に従って s.get() とか s.post() とか呼べば良いだけ。分かりやすい!

ログインするために以下のようなコードを書いた。

    # Login to Aipo
    login_data = {'action': 'ALJLoginUser',
                  'username': AIPO_USER,
                  'password': AIPO_PASSWD}
    s.post('%s://%s/aipo/portal' % (AIPO_SCHEMA, AIPO_HOST), login_data)

パラメータ名などはそのまんまですね。actionが何なのかよくわからないけど、Jetspeed か Turbine のお作法で操作をこんな形で指定するのだった気がする。 とりあえずこれでログインはOK。次はトークンを取得するために掲示板の投稿フォームをリクエストする。

    # Open posting form
    form_data = {
        'template': 'MsgboardTopicFormScreen',
        'entityid': 'new'
    }
    js_peid = 'P-14398eea627-1000b'
    form_response = \
        s.post('%s://%s/aipo/portal/media-type/html/user/pass/page/default.psml/js_peid/%s?'
               % (AIPO_SCHEMA, AIPO_HOST, js_peid),
               form_data)

Chromeでネットワークのキャプチャを見ながらパラメータを調べたんだけど、templatenew というパラメータで開くフォームを決定しているっぽい。 js_peid は jetspeed由来のパラメータかな。何度かログイン・ログアウトしてみても特にセッションによって変化する値のようでも無かったので、とりあえず固定値で持つ事に。若干ググってみたところ、おそらく、ポートレット固有のIDだと思われるのだけど、確定情報には至らず。

さて、上記で掲示板の投稿フォームが取得できるので、そのレスポンスからトークンらしき物を探してみたところ、secid なるパラメータを見つけた。

正規表現を使ってこれを取り出す。

    # Extract secid(security id?) from response text
    secid = re.search('name="secid" value="([^"]+)"', form_response.text).group(1)

どうせならタグからマッチさせた方が良い。もっと言うならDOMに変換すべきか・・・まあ、今回は置いておきましょう。 あとはこれを使って投稿するだけ。

    # Post main subject
    entry_data = {
        '_name': 'msgboardForm',
        'secid': secid,
        'is_new_category': 'false',
        'mode': 'insert',
        'category_name': '',
        'topic_name': 'こんにちわこんにちわ',
        'folderName': '',
        'eventSubmit_doMsgboard_insert': '追加する',
        'category_id': '2',
        'note': """
        てすててす
        """ ,
        'template': 'MsgboardTopicFormJSONScreen'}
    s.post('http://%s/aipo/portal/media-type/html/user/pass/page/default.psml/js_peid/%s' % (AIPO_SCHEMA, AIPO_HOST, js_peid),
           entry_data)

この辺のパラメータも全部Chromeのネットワークのキャプチャから引っ張ってきただけなんだけど。secidを設定し、topic_name と note に投稿したい内容を設定して s.post() すれば今回の目的は達成と。

まとめ:requests 便利

HTTPのキャプチャは最近ではブラウザ付属のデバッガなどで簡単に取れるようになってきているので、今回使ったrequestsのようなライブラリがあれば手で操作しているような操作を簡単にシミュレートできますね。こんなに簡単ならHTTPベースのソフトウェアテストなんかをそのまま書いてもそんなに面倒じゃないかも。

IM-BISで外部連携のデータソースとしてJAVAの実装を追加する

掲題の件、やり方が分からないと社内で質問されて若干調査したので、その内容をメモ。

といっても基本的には以下の説明に従って実装すれば良い。

IM-BIS で外部連携として利用できるJavaプログラムの仕様(Java連携で呼び出し可能なクラス)

ただ上記の説明は若干理解しにくかった模様。 作成すべきクラスの要件としては

クラスのデフォルトコンストラクタを呼び出すことができること。

多分クラス名から newInstance() か何かでそのままインスタンスを作成するためかな。

クラス内にmainメソッドに該当する呼び出し可能なメソッドを、1つ以上用意してください。

main メソッドに該当というのが何を言いたいのか分からないのだが、単に public なメソッドという事のようだ。別に static でなくていい。というか、試していないけど多分 static ではダメなのではないか。

呼び出し可能なメソッドの引数、および戻り値については、「入出力オブジェクトの仕様」を確認してください。

参照先で述べているのは大体以下のようなこと

  • 引数は Map<String, Object>List<Object> 一つだけ。値の型が決まっているようなら、Objectではなくその型で良いのかもしれない。
  • 戻りは List<Object> のみ。 これも引数と同様。

作成するJavaプログラム内で、intra-martのAPIやサードパーティライブラリを利用して、プログラムを実行することができます。

ま、これはこの通り。

特に何かしらのインタフェースを実装するなどの必要はないので、シンプルなJavaのクラスで、上記のルールを守って書けば良い。作成したらJarにエクスポートして

${imart}/WEB-INF/im_bis/datasource/lib 

に配置。

あと多分、運用環境として構築してると上記のクラスを読み込もうとして失敗してもログとかでないみたいなんで、開発中は単体テスト環境で構築しておくべき。

多分サンプルコードと手順が示されていれば躓かなかったんだろうけど。 今回調査の為に書いた物は以下のようなもの。単純すぎて晒すのもためらうレベルではあるけど。

package test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Test {

     public List<String> test(Map<String,Object> param){
          List<String> v = new ArrayList<String>();
          v.add("Hello");
          return v;
     }
}

どうしても管理画面で読み込めない場合は、再起動してみたりJarをResinのクラスパスの通っているところに複製してみるなどしてみてもいいかもしれない。が、これはあまり本質的ではないはず。

なお試したバージョンは intra-mart Accel Platform 2013 Winter (Felicia)