2010年1月28日木曜日

andLinux/Hudson/Maven2/Subversion

andLinux に入れた Hudson に、Subversion と Maven2 を連携させてみた。参考URL

Subversion リポジトリの hooks/post-commit に以下を追記。(prj1 が対象プロジェクト名)
/usr/bin/wget -o /dev/null http://localhost:8080/job/prj1/build

Subversion に変更をコミットして、Hudson の画面を見るとすぐにビルドが始まって結果が出る。簡単にコミット後フックがかけられた。

以下、上手く連携しないときの確認ポイント
  • アドレスを確かめる
    • Hudson 管理画面の "Build Now" リンクのアドレスと比較する
    • ドメインだけ変えてブラウザで開いてみる
  • 追加したコマンドを直に実行してみる。
  • post-commit スクリプトの確認(post-commit の後の2つのパラメータは、リポジトリとリビジョン。post-commit 内の他のコマンドで使用)
    $ env - ./post-commit /home/xad/repos/svn/prj1/ 10

2010年1月27日水曜日

andLinux に Hudson を入れてみた

つい最近、存在を知った CIサーバ Hudson を andLinux に入れてみた。

ここに書いてるとおりにインストール。コマンド4行くらいで、ほとんど手間要らず。

$ sudo /etc/init.d/hudson start
とすると、ポート8080 ですぐ立ち上がる。ホストのブラウザから、http://192.168.11.150:8080/ でトップページが開く。

まだ何もプロジェクトとか作っていない状態で、いろいろメニューから開いて眺めているだけで、なんだか物凄く便利そうな気配。とっくに使い始めてる人から見たら、何を今更って感じだろうけど、なんか良い感じ。ひさびさにテンション上がる。

とりあえず Hudson から、Maven プロジェクトをビルドできるようにしてみたい。

まずプロジェクトを準備する。
  • 適当に Maven プロジェクトを作成する。my-app とした。
  • テストメソッドを2本書いて、1本は失敗させるようにしておく。
  • これを Subversion に入れる。ここでは /home/svn/myproject というリポジトリにした。

次に、Hudson 側で Maven2 を 設定する
  • [Manage Hudson]/[Configure System]を開く
  • MAVEN_HOME に /usr/share/maven2/ あたりを指定。name は適当。

できたら Hudson から、
  • "New Job"をクリックして、適当にJob Name を決めて、"Build a maven2 project"を選択して、[OK]
  • Source Code Management にSubversion、"Repository URL"に file:///home/svn/myproject/my-app を設定
  • Build Triggers とかその他いろいろ設定できるようだが、とりあえずそのままで[Save]。
  • [Build Now]で実行。
  • "Build History"に1行追加されるので、中を見るとテストメソッド2本中1本失敗しているのがわかる。

今度は、
  • 2本ともテストメソッドが成功するように修正してコミット。
  • 再度、[Build Now]でビルド実行。
  • "Build History"に、青い丸で1行追加される。

さらに、そもそもの目的である Continuous Integration をやろうとして、定期的にビルド実行するよう設定しようと "Configure"を開いてみたら、なんか「nightly ビルドとか古臭いから止めときましょう」的なヘルプテキストが書いてある。変更通知をフックしなさいと。なるほど、その通りだ。

Hudson 側で HTTP の受け口が用意してあって、Subversion のフックからリクエストを送るような感じになるらしい。これは、ちょっと一手間かかるみたいなので、明日にしよ。

2010年1月26日火曜日

andLinux の root partition の リサイズ

andLinux を入れなおしたついでに、最初から余裕持たせて、パーティションをデカくしておくことにした。

以前に、残り 300MB になってから慌てて付属の toporesize を使ったときは、なんだかさっぱりうまく動かずに頭をかきむしるような思いをしたので、今度は別のやり方を調べる。

colinux の wiki のエントリ "ExpandingRoot" に、いくつかの方法が書いてあるが、faster かつ safer だという「Copy whole filesystem into new image」というやり方でやってみた。

環境は Win XP SP3 に andLinux を入れて、apt-get update/upgrade した直後の状態。手順はリンク先にある通りなので割愛(step 1~14 をやるだけ)。

さすがにコマンド一発とか二発で済む感じではないが、特別なツールとかは使わずに、Windows と Linux の基本的な操作だけで終わるので、結局このやり方が一番いいかも。

2010年1月25日月曜日

CI のアンチパターン

Continuous Integration のアンチパターンというのを見つけた。
Automation for the people: Continuous Integration anti-patterns

Infrequent check-ins
現象:チェックインの頻度が少ないせいでIntegration が遅れる。Integration が遅れてリアルタイム性が低下すると、後の是正措置の手間が大きくなり、CI の恩恵が減ってくる。
対策:ちょっとずつ細かくチェックインする。少なくとも1日一回、できれば何度も。

Broken builds
現象:ビルド失敗が長時間放置される。その間、他のメンバが更新・チェックアウトできない状態も続く。
対策:コミット前にローカルでビルドする。ビルドの前に一度 update することも忘れずに行う。

Minimal feedback
現象:ビルド失敗通知を設定していなかったり、あるいは通知が地味過ぎて誰も気づかない。
対策:適切なフィードバック手法を使って、ビルド失敗が担当者に確実に認識されるように工夫する。

Spam feedback
現象:Minimal feedback とは逆に、ビルド結果のフィードバックが多すぎて、誰も注意を払わなくなる。
対策:メンバーのロールに応じて通知条件を設定して、無関係な人にスパムメール的な通知が送られないようにする。

Slow machine
現象:マシン性能が低すぎてビルドに時間がかかりフィードバックが遅れる
対策:使えるブツを調達する

Bloated build
現象:一つのビルドプロセスに静的解析やらパフォーマンステストやら、何もかも詰め込みすぎている。そのため時間がかかり過ぎてリアルタイム性が低下している。
対策:ビルドプロセスを段階的に構成し、総ビルド時間の20%でエラーの80%を補足するようなビルドを第一段階に持ってくる。

2010年1月23日土曜日

iBATIS/nested set model

iBATIS で nested set をやってみる。 以下のようなサンプルデータで、とりあえず先祖だけ求めてみる。
mysql> select * from category;
+-------------+-------------+------+------+
| category_id | name        | lft  | rgt  |
+-------------+-------------+------+------+
|           1 | ELECTRONICS |    1 |   10 |
|           2 | TELEVISIONS |    2 |    9 |
|           3 | TUBE        |    3 |    4 |
|           4 | LCD         |    5 |    6 |
|           5 | PLASMA      |    7 |    8 |
+-------------+-------------+------+------+
図式化すると下図のようになる。
ELECTRONICS (1)110
 └TELEVISIONS (2)29
   ├ TUBE (3)34
   ├ LCD (4)56
   └ PLASMA (5)78
以下のようなコードで、LCD (4) を起点にして先祖を求めると、ELECTRONICS、TELEVISIONS が得られる事を確かめてみたい。
Category lcd = dao.getCategoryById(4);
List<Category> ascendants = dao.selectAscendants(lcd);
Category はこんな風なもの。lft、rgt はドメインモデルとは無関係なテクニカルな列なので、エンティティには含めない。
public class Category {
   private int id;
   private String name;
// getter/setter 略
}
で、DAO のselectAscendants()をこんな風に実装して、
@Override
public List<Category> selectAscendants(Category node) {
   return (List<Category>) getSqlMapClientTemplate()
     .queryForList("selectAscendants", node);
}
SQLMap の selectAscendants をこう書けば
<select id = "selectAscendants" 
          parameterClass="studies.spring.ibatis.Category"
          resultMap="result">
  SELECT parent.category_id, parent.name
  FROM category AS node, category AS parent
  WHERE parent.lft &lt; node.lft AND node.lft &lt; parent.rgt
          AND node.category_id = #id# 
  ORDER BY parent.lft
</select>
一発で祖先ノードが得られる。 なかなか iBATIS はシンプルで良いかも。 後は、Nested Set Model の追加時の操作をなんとかしたい。

RPG 環境が見つからない・・・

仕事で AS400 の RPG ソースを読む事になりそうなので、勉強する環境をローカルマシンに作る方法を探してみたが、どうにも情報が乏しい。

10年近く前の海外の BBS から情報を辿って辿って、IBM Rational Developer for System i という Eclipse 環境を見つけた。どうやら RPG も書けそうな雰囲気なので、はちゃめちゃ長時間かけて試用版をダウンロードしインストールしたものの、やはりどうもいかん。最初にプロジェクトを作る時点で、実在する Systemi 環境への接続設定をしないとプロジェクトを作成できない模様。

机上で予習するしかないか・・・

RPG Tutorial
RPG/400, RPG-ILE Tutorial

2010年1月21日木曜日

Axis2 1.5.1/HttpResponseFactory

Eclipse で Axis2 の Web サービス をやってみたら、以下のようなエラーが出た。

致命的: StandardWrapper.Throwable
java.lang.NoClassDefFoundError: org/apache/http/HttpResponseFactory
at org.apache.axis2.transport.http.SimpleHTTPServer.init(SimpleHTTPServer.java:116)
at org.apache.axis2.engine.ListenerManager.init(ListenerManager.java:74)

こうすると解決した。
  • ここから適当なバージョン(最新のGA)の HttpCore をダウンロード
  • 展開した中身の httpcore-xxx.jar を Tomcat の lib 下に置く
  • 参考URL

■ 製品バージョン等
・Eclipse 3.5 Galileo + WTP
・axis2-1.5.1
・Tomcat v6.0.20

GlassFish/JCA/Jackrabbit

GlassFish v3 と Jackrabbit の連携を Tools Bundle For Eclipse で試してみる。

■ 使うもの
  • GlassFish Tools Bundle For Eclipse
    ※{eclipses}の下にインストールしたとする
  • jackrabbit-jca-1.6.0.rar と jcr-2.0.jar

■ 準備
Eclipse から GlassFish を起動して、管理コンソールから以下の設定
  • {eclipses}\GlassFish-Tools-Bundle-For-Eclipse-1.2\glassfishv3\glassfish\domains\domain1\lib\ext 下に jcr-2.0.jarをおく
  • GlassFish 管理画面から以下の設定
    • "Applications"/[Deploy] jackrabbit-jca-1.6.0.rarを指定
    • "Resources"/"Connectors"/"Connector Connection Pools"/[New]
      Namejackrabbit-connection-pool
      Resource Adapterjackrabbit-jca-1.6.0
      Connection Definitionjavax.jcr.Repository
      ConfigFileclasspath:repository.xml
      HomeDirc:/devel/tmp/jackrabbit
    • "Resources"/"Connectors"/"Connector Resources"/[New]
      JNDI Namejcr/jackrabbit
      他はそのまま

■ 構築・実行
前ポストと同様にプロジェクトを作ってデプロイ。ほぼ同じ URLで、ポートだけ GlassFish の HTTPポート(たぶん 8084)に合わせる。

■ 所感
とにかく変更の反映が速くて全然待たされない。前にやった JBoss も悪くないが、GlassFish に比べるとかなりのんびり。

開発者の体感としてはかなり快適だし、最新仕様への対応状況も言うまでもなくダントツで、もう少し普及してくれると嬉しい。(ただ、自分の環境だけかもしれないが、Eclipse から起動できなくなる事があったりするのがちょっと残念。)

2010年1月20日水曜日

JBoss/JCA/Jackrabbit

JBoss と Jackrabbit の連携 以前、Tomcat から Jackrabbit を使うやり方を調べたが、今度は JBoss で似たような事をやってみる。ここでは リソースアダプタ版の jackrabbit を使って JCA 経由で試してみる。 ■ 使うもの ・JBoss 5.1 GA ・Eclipse 3.5 Galileo ・jackrabbit-jca-1.6.0.rar [download] ■ 準備
  • リポジトリ用のフォルダを作る。ここでは C:/devel/tmp/jackrabbit とした
  • JBoss の lib 下に、jcr-2.0.jar を置く(server/defaultとかの方)
  • JBoss のdeploy 下に jackrabbit-jca-1.6.0.rar をおく
  • 同ディレクトリに以下のファイルを作って、上の rar を使うように JBoss に指示する。
    〔jcr-ds.xml〕
    <?xml version="1.0" encoding="UTF-8"?>
    <connection-factories> 
        <tx-connection-factory>
          <jndi-name>jcr/local</jndi-name> 
          <xa-transaction/>
          <rar-name>jackrabbit-jca-1.6.0.rar</rar-name>
          <connection-definition>javax.jcr.Repository</connection-definition>
          <config-property name="homeDir" type="java.lang.String">C:/devel/tmp/jackrabbit</config-property>
          <config-property name="configFile" type="java.lang.String">classpath:repository.xml</config-property>
          <config-property name="bindSessionToTransaction" type="java.lang.Boolean">true</config-property>
        </tx-connection-factory>
    </connection-factories>
    もともとは ここから落としてきたものを必要箇所修正したもの。JBoss 4x 用のようだけど問題ないらしい。
  • Eclipse で JBoss の Server Runtime と Serverを構成しておく
■ 構築
  • Dynamic Web Project 作成。ここではプロジェクト名=hello-jcr-warとした。
  • [New]/[Other...]/[Servlet] で HelloJcaServlet を作成。
    public class HelloJcaServlet extends HttpServlet {
       private static final long serialVersionUID = 1L;
    
       @Resource(mappedName = "java:jcr/local")
       Repository repo;
    
       protected void doGet(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
          Session session = null;
          try {
             session = repo.login(new SimpleCredentials(
                   "username", "password".toCharArray()));
             Node root = session.getRootNode();
    
             if (!root.hasNode("hello/world")) {
                root.addNode("hello").addNode("world")
                   .setProperty("message", "Hello, World!");
                session.save();
             }
             Node node = root.getNode("hello/world");
             response.getWriter().println(
                   node.getProperty("message").getString());
    
          } catch (Exception ex) {
             response.getWriter().println(ex.getMessage());
          } finally {
             if (null != session) session.logout();
          }
       }
    }
■ 実行
  • hello-jcr-war を JBoss サーバにデプロイして起動
  • ブラウザで http://localhost:8080/hello-jcr-war/HelloJcrServlet を開く
  • 「Hello, World!」が表示されるのを確認
■ 補足 @Resource の使用箇所で、本当は mappedName ではなく「@Resource(name="jcr/HelloJCR")」のように論理名にしたかったが、jboss-web.xml や web.xml でマッピングを指定しても効果がなく、どうしても「mappedName じゃなきゃ嫌だ」とJBoss がゴネ続ける。ファイル名をベタ書きしてるみたいな感じでまずい気がするが、未解決。

2010年1月19日火曜日

andLinux/Scala lift

andLinux で Scala Lift を使う準備。

  • maven が入ってなかったら入れとく。Java とか無くても、つられてインストールされる。
    $ sudo apt-get install maven2

    バージョンを見ると Java も Maven もちょっと古いけど、まいっか。
    $ mvn --version
    Maven version: 2.0.9
    Java version: 1.6.0_0
    OS name: "linux" version: "2.6.22.18-co-0.7.4" arch: "i386" Family: "unix"
  • scala も入ってなかったら、入れる
    $ sudo apt-get install scala
    バージョン 2.7.3 が入った。
  • maven2 のアーキタイプでスケルトンをつくる (参考)
    $ mvn archetype:generate -U \
    > -DarchetypeGroupId=net.liftweb \
    > -DarchetypeArtifactId=lift-archetype-basic \
    > -DarchetypeVersion=1.0 \
    > -DremoteRepositories=http://Scala-tools.org/repo-releases \
    > -DgroupId=demo.helloworld \
    > -DartifactId=helloworld \
    > -Dversion=1.0
  • 実行する
    $ cd helloworld
    $ mvn jetty:run
  • ホストのブラウザで、http://192.168.11.150:8080/ を見てみる。なんかシュっとした感じのきれいな helloworld のトップ画面が表示される。

2010年1月18日月曜日

SCMにSOAを適用したという役立ち記事

2005年の雑誌記事からの出典で、かなり昔のものだけど勉強になる記事を見つけた。
SOAで変化に強いSCMシステムを作る(1)

実際に開発された SOA ベース SCM システムについての事例解説。当該システムは2005年半ばの稼動開始というから、開発開始は 2003年前後だろうか。

ビジネスプロセスを無理に一本化しないで、なるべく既存の慣行を尊重しながら、システムの側で多様性を吸収するというアプローチを採ったとのこと。また、そのための設計方針として、「トップダウンアプローチ」と「多層アーキテクチャ」が挙げられていて、2010年時点でも参考になる。

内部構成が示されている記事中のブロック図を見ると、
  • データマッピングフレームワーク: アダプタを介して企業システムと接続
  • ビジネスモジュール: コラボレーショを実現するサービスを実行
  • ebXML通信サーバー: ebXML MS で外部と接続
といった構成らしい。今なら ServiceMix のような ESB 製品や、Mule のような EIP フレームワークを使って低リスク低コストで済ませられそうな気がする。

orchestration か choreography かという視点で考えると、複数企業を含むスケールから choreography に親和性が高そうな感じだけど、受発注者、入出荷拠点という形でプレイヤーをある程度固定的に捉えたら orchestration でいいんだとわかる。

2010年1月17日日曜日

Java Project -> (Dynamic Web && Maven) Project

Eclipse で Java Project を作って、Dynamic Web Project かつ Maven Project にするやり方。

  • [New]/[Java Project]
    • Project Name: を適当に指定して [Next]
    • src をソースフォルダから削除
    • src/main/javaをソースフォルダに追加
    • Default output folder をプロジェクト名/target に変更して[Finish]
  • Navigator ビューを開いて、.project ファイルの<natures>にorg.eclipse.wst.common.project.facet.core.nature を追加
  • プロジェクトのプロパティを開く
    • Project Facets で Java と Dynamic Web Module にチェックし、適当な Server Runtime にチェックを入れる。
    • いったんプロパティを閉じる
  • フォルダ src/main/webapp/WEB-INFを作成
  • WebContent/WEB-INF/web.xml を src/main/webapp/WEB-INF 下にコピー
  • WebContent を削除
  • .settings/org.eclipse.wst.common.component ファイル中の /WebContent を /src/main/webapp に変更
  • プロジェクトのコンテキストメニューから[Maven]/[Enable Dependency Management]を選択し、groupId 以下を適当に入力
  • プロジェクト・プロパティの Java EE Module DependenciesでMaven Dependencies にチェック

以上。

★ ここまでやったら、先に進む前に webapp 直下に index.jsp を置いて確認しとくと無難。

2010年1月16日土曜日

Spring 3.0 REST/JAXB

Spring 3.0 の REST を試してみた。

■ やること
Spring 3.0 で REST がサポートされたというのでやってみる。ただし、余りサンプルを見かけない PUT をやってみる。あとJAXB のバインディングのやり方も確認しておきたい。

以下のような、なんちゃって仕様を実装する。

  • http://localhost:8888/hello-rest 以下にリソース Foo がある
  • Foo は id で識別され、プロパティを name 持つ。
  • たとえば、id=100 の リソース Foo をリクエストの内容で更新する操作は以下のようになる
    URLhttp://localhost:8888/hello-rest/foo/id/100
    methodPUT
    内容<foo><name>hoge</name></foo>
  • 実際にリソース更新する変わりに、操作の内容を標準出力に書き出す

■ 使うもの

  • Eclipse 3.5 Galileo + WTP + m2eclipse
  • Maven 2.2.1
  • Tomcat v6.0

■ サーバ

  • Java Project を作って、Dynamic Web Project 且つ Maven Project にする。
  • POM には 以下の依存を記述
    groupIdartifactIdversion
    org.springframeworkspring-webmvc3.0.0.RELEASE
    org.springframeworkspring-oxm3.0.0.RELEASE
    javax.servletservlet-api2.5
  • リソース Foo は以下のようなクラスで表現する(以下、パッケージはどれも springmvc.web とした)
    @XmlRootElement
    public class Foo {
       String name = "";
       public Foo() {}
       public Foo(String name) { this.name = name; }
       public void setName(String name) { this.name = name; }
       public String getName() { return name; }
    }
  • コントローラはこんな風になる。
    @Controller
    @RequestMapping("/foo")
    public class FooController {
       @RequestMapping(
             value = "/id/{fooId}", method = RequestMethod.PUT)
       public View handle(
             @RequestBody Foo foo, 
             @PathVariable int fooId) throws IOException {
          System.out.printf(
                "name of Foo(id=%d)  --> %s%n",
                fooId, foo.getName());
          return new StatusOK();
       }
    }
  • StatusOK はこんなふうにした。(合ってるかどうか、ちょい不安)
    class StatusOK implements View {
       public String getContentType() { return null; }
       public void render(Map model, HttpServletRequest request,
             HttpServletResponse response) throws Exception {
          response.setStatus(200);
       }
    }
  • これらのクラスをこんな設定ファイルで協調させる。
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">
       <context:component-scan base-package="springmvc.web" />
       <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
          <property name="messageConverters">
             <list>
                <bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
                   <property name="unmarshaller" ref="restXmlMarshaller" />
                </bean>
             </list>
          </property>
       </bean>
       <bean id="restXmlMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
          <property name="contextPaths">
             <list><value>springmvc.web</value></list>
          </property>
       </bean>
    </beans>
  • web.xml はほぼ自明なので省略。ただし DispatcherServlet の url-pattern は "/" とする。
  • Foo.java と同じフォルダに jaxb.index ファイルを置く。中身は単に、一言 Foo とだけ書く。
できたら、Tomcat サーバに hello-rest プロジェクトを[Add] して、エラー無くデプロイされる事を確認。

■ クライアント
クライアント用の Java プロジェクトを作って、

  • Build Path に サーバ側プロジェクトへの依存も含め、
  • POM で org.springframework の spring-webmvc の 3.0.0.RELEASE への依存を指定する
クライアントはこんなコードにする。
public class App {
   public static void main(String[] args) {
      final String uri = "http://localhost:8888/hello-rest/foo/id/100";
      RestTemplate template = new RestTemplate();
      template.put(uri, new Foo("hoge"));
   }
}
POM の<dependencies>には org.springframework の spring-webmvc の 3.0.0.RELEASE だけ指定. また、Build Path に サーバ側プロジェクトへの依存も含めておく。

実行すると、サーバ側のConsole ビューに "name of Foo(id=100) --> hoge" と 表示される。

ちなみに TCPMon で見るとこんな感じ

request
PUT /hello-rest/foo/id/100 HTTP/1.1
Content-Type: application/xml
User-Agent: Java/1.6.0_13
Host: 127.0.0.1:8888
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 83

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <foo>
      <name>hoge</name>
   </foo>
response
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Language: ja-JP
Content-Length: 0
Date: Thu, 14 Jan 2010 13:18:45 GMT

■ 参考サイト
この辺り

  • http://www.developer.com/java/web/article.php/10935_3854986_2/Get-RESTful-with-Spring-30.htm
  • http://grzegorzborkowski.blogspot.com/2009/03/test-drive-of-spring-30-m2-rest-support.html

2010年1月15日金曜日

Servlet/jMock

サーブレットのユニットテストのやり方。

次のようなサーブレトの doGet() が、JavaDoc の仕様どおりに動作する事を、JUnit で検証するにはどうするか。

public class HelloServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;

   @EJB
    private HelloBean helloBean;

   /**
    * req から 取得した GET パラメータ user を、helloBean の 
    * sayHello()メソッドに渡し、その戻り値を res から取得した
    * PrintWriter に書き出す。
    */
   protected void doGet(
         HttpServletRequest req, 
         HttpServletResponse res) 
         throws ServletException, IOException {

      String greeting = helloBean.sayHello(req.getParameter("user"));
      response.getWriter().println(greeting);
   }
}


以下の事がポイントになる。
  • テスト対象外のコードまでカバレッジに含まれてしまわないように、doGet() 以外のコードパスにはなるべく触れない。
  • doGet の仕様には、入出力のみに着目したブラックボックス処理の仕様というより、むしろ、サーブレットと関連オブジェクト群との協調を記述しているという側面がある。
  • APサーバ上で動かす場合、helloBean には 実装クラス HelloBeanImpl がインジェクトされるが、HelloServlet から見えているのは I/F だけであり、実装クラスを意識する必要はないし、それを前提としたテストを書くべきでもない。
  • HttpServletRequest など他の関連するオブジェクトについても、I/F を介したオブジェクトの協調にのみ注意を集中したい。

これらを踏まえてテストコードを書くと以下のようになる。
package hello.servlet;

import hello.ejb.HelloBean;

import java.io.PrintWriter;
import java.lang.reflect.Field;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class HelloServletTest {
   HelloServlet target;
   Mockery context = new JUnit4Mockery() {{
        setImposteriser(ClassImposteriser.INSTANCE);
   }};
   @Before
   public void setup() {
      try {
         target = new HelloServlet();
         Field f = HelloServlet.class.getDeclaredField("helloBean");
         f.setAccessible(true);
         f.set(target, helloBean);
      } catch (Exception ex) {
         throw new RuntimeException(ex);
      }
   }
   @Test
   public void testDoGet() throws Exception {
      final String USER = "ABC";
      final String GREETING = "DEF";
      HelloBean helloBean = context.mock(HelloBean.class);
      HttpServletRequest request = context.mock(HttpServletRequest.class);
      HttpServletRequest response = context.mock(HttpServletResponse.class);
      PrintWriter writer = context.mock(PrintWriter.class);
      context.checking(new Expectations(){{
         oneOf(request).getParameter("user");will(returnValue(USER));
         oneOf(helloBean).sayHello(USER);will(returnValue(GREETING));
         oneOf(response).getWriter();will(returnValue(writer));
         oneOf(writer).println(GREETING);
      }});
      target.doGet(request, response);
   }
}

以上、jMock の使い方の一例として Servlet を引き合いに出してみた。

昔、jMock とか無くて、ユニットテストの意味もよく分かってなかった頃は、データベースにテストレコードまで用意してからダミーのリクエストとレスポンスで doGet()を実行して、レスポンスのバッファに貯めた文字列を検証したりしてたっけ・・・。

testDoGet() をたった一発実行しただけで、驚くほどのカバレッジがでるけど正味のユニットテストの実態とはかけ離れてるみたいな・・・。あと DummyRequest みたいなクラスを毎度毎度書いていたり・・・

Mule/CXF

Mule の CXF を試してみる。 とりあえず ルーティングとか変換は置いといて、まずは単なる一個の Service Component をWebサービスとして呼んでみたい。 ■ 前提 ■ 仕様
  • Mule + CXF でサーバ時刻を得る Web サービス を実装
  • クライアントから与えられた書式で時刻をフォーマットして返す
■ プロジェクト作成
  • Mule Project を新規作成。プロジェクト名は current-time とした。
  • conf 下に current-time-config.xml を書く
    <?xml version="1.0" encoding="UTF-8"?>
    <mule xmlns="http://www.mulesource.org/schema/mule/core/2.2"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:cxf="http://www.mulesource.org/schema/mule/cxf/2.2"
      xsi:schemaLocation="
         http://www.mulesource.org/schema/mule/core/2.2 http://www.mulesource.org/schema/mule/core/2.2/mule.xsd
         http://www.mulesource.org/schema/mule/cxf/2.2 http://www.mulesource.org/schema/mule/cxf/2.2/mule-cxf.xsd">
      <model name="currentTime">
        <service name="currentTimeSC">
          <inbound>
            <cxf:inbound-endpoint 
              address="http://localhost:65082/services/currentTimeSC"
              serviceClass="ex1.CurrentTimeService"/>
          </inbound>
          <component class="ex1.CurrentTimeServiceImpl"/>
        </service>
      </model>
    </mule>
  • Java コードは以下のようにした。
    @WebService
    public interface CurrentTimeService {
       @WebResult(name="currentTime")
       public String currentTime(@WebParam(name="pattern") String pattern);
    }
    public class CurrentTimeServiceImpl implements CurrentTimeService {
       @Override
       public String currentTime(String pattern) {
          try {
             return new SimpleDateFormat(pattern).format(new Date());
          } catch (Exception ex) {
             return ex.getMessage();
          }
       }
    }
■ 実行
  • [Run Configurations...]/"Local Mule Server" で以下のように設定
    Namecurrent-time と入力(適当でOK)
    Projectcurrent-time を選択
    Configuration Filescurrent-time-config.xml
  • [Run]押下で実行
◆ soapUI で確認
  • http://localhost:65082/services/currentTimeSC/?wsdl を指定してプロジェクト作成
  • pattern 要素に yyyy/MM/dd HH:mm:ss と指定すると、2010/01/12 16:47:53といった形式で currentTime要素が返される。
  • pattern 要素を変えると、時刻の書式が変わる事も確認できる。
◆ ブラウザ で確認
  • ブラウザ (ここではFireFox 3.5.7を使用)で、URL に http://localhost:65082/services/currentTimeSC/currentTime/pattern/HH:mm:ss を指定すると、以下のような XML が表示される
    <soap:Envelope>
       <soap:Body>
          <ns2:currentTimeResponse>
             <currentTime>16:46:01</currentTime>
          </ns2:currentTimeResponse>
       </soap:Body>
    </soap:Envelope>
    ※REST 風にサービス名、オペレーション名、パラメータ名、パラメータ値 をURLに含めている。

2010年1月14日木曜日

Spring 3.0 MVC

Spring 3.0 の Spring MVC を Eclipse で試してみる。

■ 前提

  • ローカルの Maven リポジトリに Spring 3.0 のjar が登録済み[参考]。
  • Eclipse に WTP と m2eclipse がインストール済み。
  • Tomcat v6.0 がServer および Server Runtime として構成済み。

■ 構築

  • Java プロジェクト "hello-spring"を作って、これを Maven Project にして、さらに Dynamic Web Project にもする
  • POM に以下を追加する
    groupIdorg.springframework
    artifactIdspring-webmvc
    version3.0.0.RELEASE
    groupIdjavax.servlet
    artifactIdjstl
    version1.2
  • src/main/webapp/jsp/下に hello.jsp を作成する
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <html><body><p>${msg}</p></body></html>
  • WEB-INF 下の web.xml を編集
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_ID" version="2.4"
          xmlns="http://java.sun.com/xml/ns/j2ee" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
          http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
       <servlet>
          <servlet-name>springmvc</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet-mapping>
          <servlet-name>springmvc</servlet-name>
          <url-pattern>/</url-pattern>
       </servlet-mapping>
    </web-app>
  • src/main/webapp/WEB-INF/下 に springmvc-servlet.xmlを作成 context:component-scan の記述により、@Contoller の付いたクラスがコントローラとして扱われるようになる。
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/context 
            http://www.springframework.org/schema/context/spring-context-3.0.xsd">
       <context:component-scan base-package="studies.spring30mvc" />
       <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="viewClass"
             value="org.springframework.web.servlet.view.JstlView" />
          <property name="prefix" value="/jsp/" />
          <property name="suffix" value=".jsp" />
       </bean>
    </beans>
  • 以下のようなコントローラクラスを作成
    package studies.spring30mvc;
     
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloWorldController {
     
       @RequestMapping("/{who}")
       public String handle(@PathVariable String who, Model model) {
          model.addAttribute("msg", String.format("Hello, %s!", who));
          return "hello";
       }
    }

■ 実行

  • Servers ビューからTomcat を起動
  • hello-spring プロジェクトを [Add]
  • ブラウザで http://localhost:8080/hello-spring/hello/spring30 を見るとHello, spring30!と表示される
  • URL の hello の後ろを変えたらメッセージも変わる

Spring 3.0/SpEL

Spring 3.0 の SpEL のドキュメントを読んでみた。

Collection の扱いがちょっと面白い。集合の選択と射影がサポートされている。

サンプルに無かったので、プリミティブ型の配列でどうなるか試してみたら、入力としては OKだけど、出力で Integer[] に変換される様子。また Collection 操作をつなげることもできた。

public class App2 {
   public static void print(Object result) {
      System.out.println(Arrays.toString((Integer[])result));
   }
   public static void main(String[] args) {
      ExpressionParser parser = new SpelExpressionParser();

      Expression even = parser.parseExpression(
         "#root.?[0 == #this % 2]");
      Expression doub1e= parser.parseExpression(
         "#root.![#this * 2].?[#this > 5]");

      int[] ints = {1, 2, 3, 4};
      print(even.getValue(ints));
      print(doub1e.getValue(ints));
   }
}

こんな出力が出た
[2, 4]
[6, 8]

あと、Groovy から、Elvis 演算子という三項演算子の短縮形を取り込んだらしい。
 null!=a ? a: b  a ?: b 
名前の由来はプレスリーの髪型とのこと。Groovy 界隈ではだいぶ前からあったらしいが、不勉強で知らなかった。

Spring 3.0/JSR-330/JavaConfig

Spring 3.0 の JSR-330 と JavaConfig をちょっとだけ試してみた。

====

以下のような2クラスを考える。
  ┌───────┐                ┌─────┐
  │   Greeter    │ 《dependency》 │ Greeting │
  │==============│--------------->│==========│
  │greet(person) │                │getWord() │
  └───────┘                └─────┘
ここで
  • Greeting.getWord() メソッドは、挨拶の言葉を返す。
  • Greeter.greet() メソッドは、Greeting.getWord() で取得した挨拶の言葉に、引数 person を連結して返す。
これを Spring 3.0 で書いてみる。 Greeter と Greeting を Spring 管理下に置くための設定は、従来、XML ファイルで書いていたが、GUICE みたいに Java で書けるようになった。
@Configuration
public class AppConfig {
   @Bean public Greeter greeter() {
      return new Greeter();
   }
   @Bean public Greeting greeting() {
      return new Greeting();
   }
}
Greeter は以下のようになる。Greeting への依存は @Inject で表現する。
public class Greeter {
   @Inject
   private Greeting greeting;
   public String greet(String person) {
      return String.format("%s, %s!", greeting.getWord(), person);
   }
}
Greeting は、まあ適当
public class Greeting {
   public String getWord() { return "Hello"; }
}
こんなコードで実行すると、
public class App {
   public static void main(String[] args) {
      ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
      Greeter bean = ctx.getBean(Greeter.class);

      System.out.println(bean.greet("Spring3.0"));
   }
}
以下のような出力を得る
Hello, Spring3.0!
■ Eclipse で動かすには
  • Spring3.0 の dist 下の jar 群をローカル Maven リポジトリに登録する.[参考]
  • javax.inject も リポジトリに登録。[download]
    mvn install:install-file \
    -DgroupId=javax.inject \
    -DartifactId=com.springsource.javax.inject -Dversion=1.0.0 \
    -Dsources=com.springsource.javax.inject-sources-1.0.0.jar \
    -Dfile=com.springsource.javax.inject-1.0.0.jar \
    -Dpackaging=jar -DgeneratePom=true
  • Eclipse で Maven Project を作成する
  • pom に以下の依存を指定
    groupIdorg.springframework
    artifactIdspring-context
    version3.0.0.RELEASE
    groupIdcglib
    artifactIdcglib
    version2.2
    groupIdjavax.inject
    artifactIdcom.springsource.javax.inject
    version1.0.0

Spring 3.0をローカルリポジトリに登録

Spring 3.0 がリリースされたが、まだ巷の Maven リポジトリに見当たらないので、ダウンロードしてローカルリポジトリに登録する事にした。(Spring の EBR というリポジトリを使うやり方はここに詳しい)

Spring 3.0 を展開したフォルダ構成を見ると、dist 下に かなりの数の Spring3.0 関連 jar がある。で、それぞれの pom.xml は、projects 下の個別プロジェクトフォルダ直下においてある。例えば、org.springframework.aop ならこんな位置関係になる。
{spring-install-folder}/
dist/
org.springframework.aop-3.0.0.RELEASE.jar
projects/
org.springframework.aop/
pom.xml

Cygwin で Spring 展開フォルダの下の dist に行って、以下のコマンドを発行する。(本当は一行で)
$ ls|gawk '{print $9}'|perl -e 'while() {s/((\S+)-3.+)$/mvn install:install-file -Dfile=$1 -DpomFile=..\/projects\/$2\/pom.xml/;print $_; }'


以下のような出力が得られる。
mvn install:install-file -Dfile=org.springframework.aop-3.0.0.RELEASE.jar -DpomFile=../projects/org.springframework.aop/pom.xml
mvn install:install-file -Dfile=org.springframework.asm-3.0.0.RELEASE.jar -DpomFile=../projects/org.springframework.asm/pom.xml
・・・略・・・
mvn install:install-file -Dfile=org.springframework.web.struts-3.0.0.RELEASE.jar -DpomFile=../projects/org.springframework.web.struts/pom.xml

これを全部コピぺするなりファイルに書き出すなりして実行すれば、dist 下の jar が全部登録される。

いつもはサクラエディタのキーボードマクロでやっていて、そっちの方が簡単だけど、ちょっと練習のためにやってみた。

systems thinking の本

システム・シンキング入門 (日経文庫)』を読んだ。

====
従来のシステム開発において、何らかの原因(時間が無いとか)でストレスが増えるとテスト実行が減り、テスト実行の減少がさらにストレスを増やし(バグ対応とか)、それが悪循環を引き起こしてどんどん状況が悪化するというのがよくある。

これを図示したものが、Kent BeckのTDD本 から引用した「The "no time for testing" death spiral」という図。(図の矢印線は因果関係を表していて、線上に置かれた丸は原因側のプラスが結果側のマイナスとして作用する事を示している。)

この図は、システム・シンキングで言うところの「因果ループ図」で(Beck の本では "influence diagram" と書かれていたが)、マイナス方向に作用する丸印が偶数個ある事から、「拡張フィードバック・ループ」という事になる。このタイプのループは変化が一方向に進んでいくタイプで、調子が良い時は好循環になり右肩上がりで状況がよくなるが、逆に悪いことが起こるとどんどん悪循環にはまるというもの。

このタイプと別に「バランス・フィードバック・ループ」というものもあり、これはマイナスの因果関係が奇数個のもので、系の中のギャップを縮めて安定させる効果があるという。

テスト駆動開発を導入すると、ストレス増加が必ずしもテスト実行の減少を引き起こさないし、逆に積極的にテストを増やすように対処する事で、以下のように Stress → RunTest の因果関係をプラスにしてバランス・フィードバック・ループに転換できるということになる。最初に TDD を読んだときは意識していなかったが、こうやってテスト駆動開発をシステム・シンキングの言葉で捉えなおす事もできて面白い。(まあ実際の開発現場では、ストレス増加の結果としてテストの「実行」は減らないとしても、テスト品質や網羅率が下がったりするから、上の図はちょっと理想化しすぎている感があるが。)

====
因果ループの他に、メンタルモデルというのも紹介されている。本書によれば、個々の出来事の層、出来事のパターンの層、パターン間の構造の層というシステムを構成する3層の更に下に、暗黙のうちに前提とされているメンタルモデルの層があるとされている。これも開発者なら(特にいろんな企業や業界のシステム開発を渡り歩いている)、ピンと来るものがあるんじゃないだろうか。

昔、Yourdon が著作『デスマーチ』で、デスマーチ属性は個々のプロジェクトじゃなくて組織の属性だという事を看破したけど、つまりそういうことなんだと思う。なんとなく組織の人間に共有されているシステム開発のイメージとか価値観だとか、そういった組織内部の人間には自覚しにくいメンタルモデルが、何度もデスマーチを繰り返すベースになっているという事もあるように見える。

====
短めの新書だけど、他にもシステム開発者にとって考えさせされる事があって面白い本。特に開発プロセスやプロジェクトの運営に興味がある開発者にお勧め。

2010年1月13日水曜日

andLinux + Trac

Trac wiki のエントリ TracInstallUbuntuを見ながら、andLinux に Trac を入れたメモ。

andLinux は Windows XP SP3上で稼動。

基本的にほとんど問題なく作業が進むが、後半の Apache 上の設定について、記述のままだと 仮想ホスト tractest に行かなかった。# invoke-rc.d apache2 reload としても、[warn] NameVirtualHost *:80 has no VirtualHosts なんてメッセージが表示される。

いろいろ試行錯誤して、/etc/apache2/sites-available/tractest の最初の行を <VirtualHost 192.168.11.150> に変えてみると、エラー無く立ち上がった。

立ち上げてから Windows XP 側で hosts ファイルに、
192.168.11.150       tractest
と追記して、ブラウザで http://tractest/をみると、Trac のトップページが開いた。(Windows の hostsは C:\WINDOWS\system32\drivers\etc の下)

reload 時に、"Could not reliably determine the server's fully qualified domain name, using 192.168.11.150 for ServerName" なんてメッセージが出ているが、とりあえず放置。apache2.conf に ServerName を記述すれば無くなるようだけど、今は別にいいや。

http://192.168.11.150/ を指定しても、Apache のトップページ(It works!)が見られなくなったが、これも使うとき考えることにした。

====(2010/01/27追記)
元ネタの wiki には、trac を wget して Python のインストーラを手動で起動するような、ちょい面倒なやり方が書いてあって、このブログを書いたときにはそれに従ったが、単純に apt-get install trac でも行けた。

2010年1月12日火曜日

TPTP/BigDecimal

以前から、BigDecimal を使うとき MathContext をどう選ぶか気になる事がたまにあった。

割り算をするときに、DECIMAL32、DECIMAL64、DECIMAL128のうちのどれかを指定するけど、時間とかメモリ使用量とかどんなトレードオフがあるんだろう。

とりあえずざっと時間を計ってみた。(というか計り方を確認してみたかった。)

以下のようなコードで、1 を 3 で割って 3 を掛けなおす計算を、各 MathContext で 100万回ずつ繰り返すメソッドを呼んでみる。
public class Main {
  private static final BigDecimal three = new BigDecimal(3);
  public static void main(String[] args) {
    repeat(1000000, MathContext.DECIMAL32);
    repeat(1000000, MathContext.DECIMAL64);
    repeat(1000000, MathContext.DECIMAL128);
  }
  private static void repeat(int count, MathContext mc) {
    BigDecimal result = new BigDecimal(1);
    for (int i = 0; i < count; ++i) {
      result = result.divide(three, mc).multiply(three);
    }
    System.out.println(result);
  }
}
この Main クラスを[Profile As]/[Java Application] で実行すると、以下のような結果を出力する裏で、プロファイル情報が収集される。(ここでは「時間」を指定した)
0.9999999
0.9999999999999999
0.9999999999999999999999999999999999
Profiling and Logging パースペクティブの Profiling Monitor ビューから、収集したプロファイル情報を開くと、Execution Statistics ビューがエディタペインに開く。

このビューのCall Tree タブを選択すると、下図のような表示が得られる。
画像下部の[Previous], [Next] ボタンで3つの呼び出しを選択でき、実行時間に占めるパーセンテージが表示される。まとめると以下のようになる。
DECIMAL32約10%
DECIMAL64約24%
DECIMAL128約64%
上の計算パターンのみだと一概には言えないけど、DECIMAL128 でもそうべらぼうに遅くはならないらしい。DECIMAL32 とくらべて 6倍ちょっと(2.5 の2乗?)だから、計算コードを書くときは、まず 一番わかりやすいアルゴリズムで DECIMAL128 を使ってコーディングして、速くする必要が生じたときに、ちゃんと測定してボトルネックを見つけてから、必要なら別の MathContext を考えるという流れでよさそう。

それと計算時間に加えてメモリ使用量についても傾向をつかんでおけば、まあまあ根拠のある設計判断ができそう。後は仕様と相談か。

2010年1月11日月曜日

Motzart/Logic puzzle

だいぶ前にインストールした Mozart という Oz 言語実装を、初めて使ってみた。

Wikipedia でロジックパズルを引くと、こんな簡単な例題が載っている(2010/01/11現在)。これを Mozart で書いてみる。
3人のそれぞれの発言から、それぞれの今日の昼食を当ててください。ただし、カレーライス、ラーメン、そばのうちから3人とも別々のものを食べました。
トンキチ:…。
チンペイ:あいつみたいにそばだったら僕は足りないな。
カンタ:僕はカレーライスもそばも嫌いなんだ。
答え
トンキチ:そば
チンペイ:カレーライス
カンタ:ラーメン

下のコードを Oz Programming Interface に入力して、C-M-x で読み込ませると、
local Kan Chin Ton
Lanch = [curry ramen soba]
NotIn = fun {$ Ex}
{Filter Lanch fun {$ X} {Not {Member X Ex}} end}
end
in
thread (Ton |_)= {NotIn [Kan Chin]} end
thread (Chin|_)= {NotIn [Kan soba]} end
thread (Kan |_)= {NotIn [curry soba]} end
{Browse [tonkichi#Ton chinpei#Chin kanta#Kan]}
end
Oz ブラウザに
[tonkichi#soba chinpei#curry tanta#ramen] 
と表示される。

うーん、まだよくわからんが Prolog とはだいぶ違う。

====
以下を参考にした

2010年1月10日日曜日

TPTP/エラー IWAT0435E

Eclipse TPTP についての備忘録

今使っている Eclipse 環境で初めて TPTP を使ったら、、、
The launch requires at least one data collector to be selected.
IWAT0435E error occured connecting to the host
なんて事を言ってきた。

Package Explorer でクラスを選択し、コンテキストメニューから[Profile As]/[Java Application]でプロファイルを開始しようとすると、Profile Configuration ダイアログボックスが開くが、ここで上記のエラーメッセージが表示されてしまう。

なんだろうと思ってググってみると、「Agent Controller が要るらしい」とか「いや要らないはずだ」だとか、いろいろと意見が見つかる。

ただ Eclipse のヘルプを読むと、ローカルでのプロファイルなら他のプロダクトに依存しないとはっきり書いてあるので、調べなおすと TPTP 関連のプラグインが、インストールされていなかった。メニューに[Profile As]があるので、インストール済みだと思い込んでいた。間抜けだ・・・

以下のプラグインを入れると、ちゃんと TPTP が動き始めた。
  • TPTP Monitoring Tools
    TPTP Platform Project
  • TPTP Reporting with BIRT
  • TPTP Tracing and Profiling Tools Project


なかなか良い感じ。

2010年1月9日土曜日

CXF/REST/Galileo

CXF で RESTful Web サービスを書いて、Eclipse で動かしてみる getting started。

■ 使うもの
・Eclipse 3.5 Galileo + m2eclipse

■ やりたい事
JSR 311準拠の REST を、CXF を使ってやってみる。

JSR 311: JAX-RS: Java API for RESTful Web Services  疎通確認程度の、なるべくコンパクトなコードにしたいが、GET だけだとアレなので PUT での更新も試してみる。

方針としては、簡易サーバ上のサービスに対して、ブラウザで GET して、HttpClient を使ったクライアントで PUT する。

■ プロジェクト作成
  • archetype-maven-quickstart で Maven Projectを作る。
  • pom.xml に 以下を追加
    org.apache.cxfcxf-rt-frontend-jaxrs2.2.5
    org.apache.cxfcxf-rt-transports-http2.2.5
    org.apache.cxfcxf-rt-transports-http-jetty2.2.5
    commons-httpclientcommons-httpclient3.1
■ サーバアプリ作成
◆ やり取りする REST メッセージは以下の二つ
@XmlRootElement(name = "Message")
public class Message {
  private String text;
  // ・・・ getter/setter 略
  public void setText(String greeting, String name) {
    setText(String.format("%1$s, %2$s!", greeting, name));
  }
}
@XmlRootElement(name = "Greeting")
public class Greeting {
  private String word;
  // ・・・ getter/setter 略
}
◆ サービスクラスはこんな感じ
@Path("/greetingservice/")
public class GreetingService {
  private String greeting = "Hello";
    @GET
    @Path("/greeting/{name}/")
    public Message getMessage(@PathParam("name") String name) {
      Message hello = new Message();
      hello.setText(greeting, name);
      
      return hello;
    }
    @PUT
    @Path("/greeting/")
    public Response updateGreeting(Greeting greeting) {
      this.greeting = greeting.getWord();
        return Response.ok().build();
    }
}
◆ このサービスクラスを動かす簡易サーバ
public class App {
  public static void main(String[] args) throws Exception {
    JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
    sf.setResourceClasses(GreetingService.class);
    sf.setResourceProvider(GreetingService.class,
        new SingletonResourceProvider(new GreetingService()));

    sf.setAddress("http://localhost:9000/");
    sf.create();

    new BufferedReader(new InputStreamReader(System.in)).readLine();
    System.exit(0);
  }
}
◆ 動作確認
GET だけならこの時点でブラウザから確認できる。
http://localhost:9000/greetingservice/greeting/World
こんなXML が返ってくる
<Message>
  <text>Hello, World!</text>
</Message>
あるいは URL がこうならば、、
http://localhost:9000/greetingservice/greeting/Europa
、、このようになる。
<Message>
  <text>Hello, Europe!</text>
</Message>
■ クライアントコード
◆コード
public final class Client {
  public static void main(String args[]) throws Exception {
    PutMethod put = new PutMethod(
        "http://localhost:9000/greetingservice/greeting");
    String content = String.format(
        "<Greeting><word>%s</word></Greeting>", args[0]);
    RequestEntity entity = 
        new StringRequestEntity(content, "text/xml", "UTF-8");
    put.setRequestEntity(entity);
    try {
      HttpClient httpclient = new HttpClient();
      int result = httpclient.executeMethod(put);
      System.out.println("Response status code: " + result);
    } finally {
      put.releaseConnection();
    }
  }
}
◆ 実行
サーバが起動されている状態で、Program Argument に "Good-Bye"を指定して、上記クライアント・コードを実行。正常終了すれば、PUT リクエストが送られたことになる。

ここで再び、ブラウザから http://localhost:9000/greetingservice/greeting/Asia に GET リクエストを送る。
<Message>
  <text>Good-Bye, Asia!</text>
</Message>
今度は Hello ではなくGood-Bye と表示されるのが確認できる。

■ 所感
  • いい感じの EoD で、上のような疎通確認程度ならほとんど勘で充分。
  • 他の CRUD も同じ要領でできそう。
  • 今回、CFX 付属の sample を参考にして、JAXRSServerFactoryBean で Server endpoints を生成する簡易サーバで動かしたが、Web アプリにするやり方も確認したい。

2010年1月8日金曜日

CXF/WTP/m2eclipse/Tomcat

Maven 2 の アーキタイプで作った CXF サンプルの試行を、全部 Galileo 上でやってみる。

■ 使用プロダクト
  • Eclipse 3.5 Galileo + WTP + m2eclipse
  • Tomcat v6.0.20 (Eclipse 上でServer と Server Runtime が構成済みの前提)
  • soapUI 3.0.1

■ プロジェクト作成
  • [New]/[Other..]->[Maven Project]
  • cxf-java-first 2.2.5 アーキタイプを選択
  • 以下の諸元を指定して[Finish]
    GroupIdnet.yasuabe.studies.ws.cxf
    ArtifactIdjava-first
    Version0.0.1-SNAPSHOT
    Packagenet.yasuabe.studies.ws.cxf.java_first
  • .project ファイルを開いて、<natures>にorg.eclipse.wst.common.project.facet.core.nature を追加
  • [Properties]/"Project Facets" で Java と Dynamic Web Module にチェック。Runtime タブで Tomcat にチェック。
  • WebContent フォルダが生成されていたら、削除。
  • [Properties]を開きなおし、Java EE Dependencies/Maven Dependencies にチェック
  • もし src/main/java がソースフォルダになっていなかったら、改めて指定
  • .settings/org.eclipse.wst.common.componentファイルで、/WebContent を/src/main/webapp に変更
  • 以下のような Access restriction が出ていたら、F2を押下して Configure Problem Severity を実行し、Forbidden Reference を Ignore に変更
    Access restriction: The type WebService is not accessible due to restriction on required library C:\Program Files\Java\jdk1.6.0_13\jre\lib\rt.jar


■ 実行
  • Servers ビューで、http-basic プロジェクトを Tomcat に 追加して [Start]
  • ブラウザで http://localhost:8080/java-first/HelloWorld?wsdl を開いて、まずは WSDL が返ってくるのを目視確認
  • soapUI で 上記のWSDL のURLを指定し、新規プロジェクト作成。以下のようにリクエストを送って、レスポンスを確認

■ 補足
Maven プロジェクト を作るとき、アーキタイプの選択で http-basic も選択肢にリストアップされてくるが、これは java-first とほとんど同じ内容だけど、アーキタイプに不備があるらしく、パッケージ名が決めうちになっている模様。修正すればこれも動かせないことは無いが、java-first があるので、あまり意味なし

2010年1月7日木曜日

lossless proration algorithm

按分した数値を丸めて再度合計したときの、元の値とのずれをどう処理するかについて考えてみた。

■ 問題
例えば、10円をなるべく 11:12:27 (計50) に近い比率で按分したいとする。比率を掛けて丸めると以下のようになる
10 * (11 / 50) = 2.2 ≒ 2円
10 * (12 / 50) = 2.4 ≒ 2円
10 * (27 / 50) = 5.4 ≒ 5円
小数部分を合計すると 10円だが、丸めた整数部分を計算すると 9円で 1円消えてしまう。この 1円をどうするか。

◆ ① 誤差を許容する
仕様によっては、これもあると思う。検索してみると、そういう但し書きのある公官庁発表のデータもいくつか見つかる。

◆ ② 誤差を最大値に割り当てる
上例の場合、5 円に誤差 1円を加算して 2円、2円、6円とする。ロジック的には、どこに誤差を散らすか決定するために、余計に一手間二手間かかるはず。
◆ ③ 累積値で丸める
以下のように累積値で四捨五入して、、、
10 * (11 / 50) =  2.2 ≒ 2 →  2 - 0 = 2円
10 * (23 / 50) =  4.6 ≒ 5 →  5 - 2 = 3円
10 * (50 / 50) = 10.0 ≒10 → 10 - 5 = 5円
、、2円、3円、5円となる。

■ 比較
3つの方法で得られた値は、以下のように比較できる
元の比率111227計=50
101025計=45
101030計=50
101525計=50
((元-①)/元)^20.0083 0.0278 0.0055 0.0415
((元-②)/元)^20.0083 0.0278 0.0123 0.0484
((元-③)/元)^20.0083 0.0625 0.0055 0.0763
近いのは誤差を許容した ①のやり方だが、合計値が一致しないので除外する。

累積値で丸める ③は比率を構成する数値の並び方で値が変わってくるはずで、この例だけではなんとも言えないが、計算量は少なくてすみそう。

最大値に誤差を割り当てた ②が、元の比率に近い。直感的には、この ②のパターンが、もとの比率を一番よく反映していそう。細かいことを考えると、この手法を採った場合、同じ比率の場合に、どれに端数を割り当てるかという問題が残る。例えば 3円を2人で分けたり、1円を2人で分けたりする場合など。

■ 実装
使用箇所を Java で書いた場合を想像するとこんな感じか
Proration proration = new Proration(new int[] {11, 12, 27});
int[] actual = proration.prorate(10);
int[] expected = {2, 2, 6};
assertArrayEquals(expected, actual);
まあ「関数型言語だったらアレなのになあ」なんてボヤきながら書くような、ベタな Java コーディングになりそうだ。

単に業務ロジックの実装として書くとしたら、普通に必要なコードだけ書けば良いだろうけど、フレームワークとかライブラリにしようとしたら、以下のような事が考慮するポイントになるかもしれない。
  • 複数の型で generic に使えるようにする。
  • 端数割り当てロジックを実行時に指定できるようにする。
  • ②で、端数の割り当て先選択ロジックを実行時に指定できるようにする。

■ 所感
検索したら、すぐに定番的なやり方が見つかるかと思ってたら、そうでもなかった。

そもそも案分を英語でなんて言うのかわからなかったりしたが、prorate と言う単語を見つけてググると、とりあえずこんなのを見つけた。「lossless」な proration アルゴリズムについての質問とその一個の解答が載ってた。

2010年1月6日水曜日

ANTLR IDE/分数電卓

金額計算に使えそうな外部 DSL を書きたいが、その前に軽くウォームアップ。

分数電卓を作ってみる。コンソールで式の入力を受付けて計算結果を返す。ただし小数は受け付けない。またゼロ割り算はシンプルに例外送出のみとする。

作業環境・実行環境は Eclipse を使用。コンソールビューの入力と出力がこんな風になるようにしたい。
1+2/5
(1+2)/5
1/2-1/2
0/3-1/4
7/5
3/5
0
-1/4


以下のような仕様で、簡単なJava の有理数クラスも要るので用意しておく。自明なのでコードは省略。
  • 分子と分母を long 値のペアで表現する、Rational という名前の不変クラス。
  • コンストラクタで約分する。
  • add, subtract, multiply, divide メソッドは計算結果を新規インスタンスで返す。
  • toString()は、分母が1なら整数、そうでないなら分数での文字列表現とする。
  • 上記のとおりゼロ割りは例外送出。

この Rational クラスを、以下のような ANTLR コードから使う。AST 生成文法とAST 処理文法を別々にした。
grammar Expr;

options {
output = AST;
ASTLabelType=CommonTree;
}

//prog: (stat {System.out.println($stat.tree.toStringTree());})+;
prog: stat+
;
stat: expr NEWLINE -> expr
| NEWLINE ->
;
expr: multExpr (('+'^ |'-'^) multExpr)*
;
multExpr : atom (('*'^ |'/'^) atom)*
;
atom: INT
| '('! expr ')'!
;

INT : '0'..'9'+ ;
NEWLINE: '\r'? '\n' ;
WS : (' '|'\t'|'\n'|'\r')+ {skip();} ;
tree grammar Eval;

options {
tokenVocab=Expr;
ASTLabelType=CommonTree;
}
@header {
import rational.Rational;
}

prog: stat+
;
stat: expr {System.out.println($expr.value);}
;
expr returns [Rational value]
: ^('+' a=expr b=expr) {$value = a.add(b);}
| ^('-' a=expr b=expr) {$value = a.subtract(b);}
| ^('*' a=expr b=expr) {$value = a.multiply(b);}
| ^('/' a=expr b=expr) {$value = a.devide(b) ;}
| INT {$value = new Rational(Long.parseLong($INT.text), 1);}
;

まあ、普通に動くようだ。

流れ的には、まず文法定義を書いてから、JUnit を用いたテストファーストで Rational クラスを書きながら、ANTLR コードと Java コードの結合を手作業で確認した。実戦だと ANTLR コードの検証も自動化したい。

次にやる事
  • 行列の計算。もともとやりたいのは按分計算込みの事務処理用DSLだけど、その前に簡単な行列の計算がいりそうな感じ。
  • 変数と代入式
  • Java とのインターフェイス。コンソールを使わない形で Java から使えるようにする。

2010年1月5日火曜日

Emma Maven Plugin

Maven 2 の Emma プラグインを試してみた。

Eclipse と Maven2 の組み合わせ (m2eclipse)と、Eclipse と Emma の組み合わせ (Eclemma)なら今までもよく使ってきたけど、意外とMaven 2 と Emma の組み合わせは未経験だった。

別に何か準備するわけでもなく pom.xml を編集するわけでもなく、以下のように叩くだけでいきなり動き始める。
mvn emma:emma

終わるとプロジェクトの site フォルダの下に emma フォルダができている。中の html を開くと以下のような感じになっている。


拍子抜けするほどあっさりカバレッジレポートが出来てくるのが、いかにも Maven らしい。煩わしい作業がなんにもいらない。

今後やること。
  • CIサーバ との組み合わせ
  • カスタマイズとか

2010年1月4日月曜日

EJB 3.1/EJB Timer/@Schedule

この間入れたGlassFish の Eclipse バンドルで EJB Timer を動かしてみた。

組み込み済みの GlassFish プラグインのおかげで、動作確認だけならほとんどコードを書かずに試行できる。

こんな風にする。(以下、プロジェクト名、クラス名等は適当)

■ EJB プロジェクト作成
以下の手順
  • [New]/[EJB Project]
  • 以下入力
    Project namehello-ejb-timer
    Add project to an EARチェック
    EAR project namehello-ejb-timerEAR
  • [Finish]
EJBプロジェクト、EJB Client JARプロジェクト、EARプロジェクトと、プロジェクトが3つ作成される。

■ EJB Timer 作成
以下の手順
  • hello-ejb-timer プロジェクトから[New]/[EJB Timer (Java EE 6)]
  • パッケージとクラス名を適当に指定。
  • "Schedule:" のテキストエリアをざっと眺める。デフォルトは以下のような感じで月~金の08時~23時なので、必要なら適当に直す。
    second="*/10", minute="*", hour="8-23", dayOfWeek="Mon-Fri", dayOfMonth="*", month="*", year="*", info="MyTimer"

■ 動かす
  • Servers ビューで GlassFish に hello-ejb-timerEAR を追加
  • Servers ビューから[Start]
  • cygwin などで {Domain Directory}/domain1/logs/server.log を tail で垂れ流しておくと、10秒ごとにメッセージが出力されているのが観察できる。({Domain Directory} の位置は、GlassFish サーバをダブルクリックすると開く設定画面で[Browse]ボタンを押せばわかる。)

■ 所感
  • とても簡単に cron ぽいスケジュール動作が実現できる。
  • GlassFish も Eclipse plugin もすごく良い感じ。コード変更も瞬時に反映される。
  • Servlet のみの Java Web アプリの定番のイディオムに、ServletContextListenerクラスで TimerTask の実行を開始する手法があるけど、EJB(3.1以上)が使える環境なら、やっぱり EJB Timer がシンプルで経済的な感じがする。

Continuum/Maven/Subversion/Windows

Continuous Integration のお試し環境構築レシピ。

ソース管理、ビルド管理、CI(Continuous Integeration)サーバの間の最低限の疎通確認を、できるだけ少ない手順でやってみる。

■ 使ったもの
・ ソース管理:Subversion 1.6.6 [download]
・ ビルド管理:Maven 2.2.1 [download]
・ CI サーバ:Continuum 1.3.4 (Beta) [download]
(ベータだけど気にしない)
・ SMTP サーバ:Radish というのを見つけた [URL]
好き使えるSMTPサーバとか、なんちゃってスクリプトとかあったらそれでも可。

■ 各プロダクトの準備
コマンドプロンプトは Win XP 上の Cygwin を使用

◆ ソース管理
  • Subversion が無かったらインストールしておく。
  • 適当なディレクトリに SVN リポジトリを作る。ここでは c:\devel\tmp\repo とした
    $ svnadmin create c:/devel/tmp/repo

◆ ビルド管理
  • Maven2 が無かったら インストールしておく。

◆ SMTP サーバ
  • Radish をダウンロードしたら適当なところに展開して実行。
  • メニューの[設定]/[STMP/POP3サーバ設定ダイアログ]から、ドメイン名に ci_test.net を指定。
  • メニューの[設定]/[ユーザ]で、ユーザ ci_tester(パスワード:no)を追加
ci_test.netci_tester は任意でも可

◆ CI サーバ
  • 無かったら適当にインストール
  • jetty.xml中、MailSessionReference の設定を以下のように編集
    <Set name="user">ci_tester</Set>


■ Continuum からビルド
  • 適当なディレクトリで、プロジェクト作成
    $ mvn archetype:create \
    > -DgroupId=net.yasuabe.studies.ci \
    > -DartifactId=continuum-test \
    > -DarchetypeArtifactId=maven-archetype-quickstart
    mvn package と叩けばビルドできることが確認できる。

  • pom.xml に以下を追記(dependency の後あたり)
    <scm>
    <connection>
    scm:svn:file:///C:/devel/tmp/repo/continuum-test
    </connection>
    </scm>
  • Subversion にimport する
    $ svn import file:///c:/devel/tmp/repo -m "initial import"
  • Continuum に登録
    • "Add Project"/"Maven 2.0x Project"
    • "Upload POM" でpom.xml を指定して[Add]
  • ユーザを追加
    • "Administration"/"Users"から[Create New User]
    • "User Name"=ci_tester Email Address = ci_tester@ci_test.netで[Create User]
    • ロール等、適当にチェック(continuum-testのProject Administratorなど)
    • Project Group Summary の MembersタブでUsersの表に表示されていることを確認
  • Notifiers を追加
    • "Notifiers"/[Add]
    • Type=Mail でSubmit
    • "Mail Recipient Address"=ci_tester@ci_test.net "Send on Failure"にチェックして[Save]
  • Continuum からビルド
    • "Continuum"/"Show Project Groups"
    • "continuum-test"
    • "Member Projects"/"continuum-test"
    • [Build Now] ボタン押下
  • 確認
    • Builds タブに成功行が追加され
    • Working Copy にソースがチェックアウトされていることを確認

■ ビルド失敗の通知確認
  • AppTest.java の既存テストメソッドを編集してコミット
    public void testApp() {
    assertTrue(false);
    }
  • Continuum で再び [Build Now]
  • "continuum-test" プロジェクトの Builds タブで失敗行が追加されていることを確認
  • Radish の MAILBOX にメールが入っていることを確認
  • メールを開くと、どのテストが失敗したかなど詳細情報が載ってる


以上、CIサーバと Maven、Subversion を連携させて、失敗時にメールを送るところまでできた。

後の作業は以下のような感じだろうか。
  • Continuum のメニュー上の他の設定オプションを試す。
  • Continuum の他の構成を試す。デフォルトはデータベース=Derby、APサーバ=Jetty。
  • 他の環境で試す(現場は Linux 多いかも)。
  • Trac との連携

2010年1月2日土曜日

andLinux

andLinux を起動して su でroot になろうとしたら、パスワードを聞かれるもののインストール時を含めて設定した覚えがない。いろいろ試してみるが一向にログインできない。

ちょっと調べてみると解答があったので、やってみたら成功。以下、備忘録。
xad@andLinux:~$ sudo /bin/bash
[sudo] password for xad:
root@andLinux:~# passwd root
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@andLinux:~# exit
exit
xad@andLinux:~$ su
Password:
[root@andLinux xad]# exit
exit
xad@andLinux:~$

2010年1月1日金曜日

GlassFish v3tools Bundle/Application Client

今月初めにGlassFish v3 が出て、さらにその後、GlassFish と Eclipse が一体になった GlassFish tools Bundle がダウンロード可能になった。
GlassFish Tools Bundle for Eclipse v1.2 (December 17, 2009)
これをちょっと試用してみた(OS は Windows XP SP3)。


やってみたいのは最小の Application Client の作成してリモートデバッガの引っ掛け方を確認する事だけど、その前に簡単な WAR と EJB(SessionBean) を含む EAR をデプロイして動作確認。

まずインストールして起動したら、すでに Server Runtime と Server が GlassFish v3 で構成済み。デプロイもすごく軽快だし、Servlet や EJB への変更も瞬時に反映される。

WASCE を使っていたときには、EAR のデプロイや変更反映の度に、頭をかきむしる様な思いをしつつハチャメチャに長い長い長い時間を待たされたものだけど、GlassFish だと実行した事すら気づかないくらい瞬時に完了する。ちょっと感動。

この GlassFish v3 と専用 Eclipse 環境を使って、以下、個人的にちょっと懸案だった、Application Client を試してみる。

■ ゴール
以下のようなコードを動かして、ちゃんと helloBean がインジェクトされ、正しくメソッドが呼ばれる事を確認したい。
public class Main {
   @EJB static HelloBeanRemote helloBean;
   public static void main(String[] args) {
      System.out.println(helloBean.sayHello());
   }
}
■ EJB と EAR プロジェクトの作成
まず EJB プロジェクトを作る
  • [New]/[EJB Project] で "project name"に hello-ejb と指定
  • "EAR membership"/ "Add project to EAR"にチェック
  • EJB Module のページまで[Next]押下
  • EJB Client JAR で"Create an EJB Client JAR~"にチェックされている事を確認
  • Finish 押下で、以下のプロジェクトができているのを確認
    • hello-ejb
    • hello-ejbClient
    • hello-ejbEAR
次に Session Bean を作る
  • hello-ejb から[New]/[Session Bean(Java EE 6)]
  • 以下の諸元入力
    Java Packagetest.ejb
    Class NameHelloBean
  • "Create business interface"/Remote にチェック
  • [Finish]押下
HelloBean に以下のメソッドを追加
@Override
public String sayHello() {
   return "Hello, Bean!";
}
赤いアンダーラインのところで、Ctl+1 で「Create "sayHello" in super ~」を選択。HelloBeanRemote に メソッド宣言が追加される。

★ ここで一旦、hello-ejbEARをデプロイしてみて、エラー無くデプロイされることを確認。

■ Application Client の作成
  • Package Explorer から[New]/[Application Client Projet]
    • Project Name:hello-client
    • "EAR membership" でhello-ejbEAR を指定
    • Finish
  • Property を開いて以下の操作
    • Java Build Path:hello-ejbClient を追加
    • "Java EE Module Dependencies"/Use EJB client JARs を選択、hello-ejbClient.jar にチェック
  • Mainクラスを冒頭のコードに書き換える
■ 実行する
まず jar を作る。
  • hello-ejbClient から[Export...]でJAR file選択
  • "export destination"に hello-client\appClientModule\META-INF\hello-ejbClient.jar を指定
  • "JAR Manifest Specification"まで遷移して、"Use existing~"にチェックがあることを確認して、[Finish]
  • hello-client から[Export]/[App Client JAR file]実行
  • Destination に、c:\tmp\hello-client.jar のような適当なパスを指定して[Finish]
jar ができたら、コマンドプロンプトから動かす。
  • コマンドプロンプトで GlassFish のホームディレクトリに移動。(こんな感じのパス。 ~\GlassFish-Tools-Bundle-For-Eclipse-1.2\glassfishv3\glassfish )
  • 以下のように入力し、Hello, Bean! が出力されることを確認
    bin\appclient.bat -jar c:\tmp\hello-client2.jar
■ デバッグ実行する
  • 環境変数 VMARGS を設定
    set VMARGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8118
  • 再度実行すると、以下のようなメッセージを出してとまる。ここでEclipse に移動
    Listening for transport dt_socket at address: 8118
  • Main.main()のSystem.out~の行にブレークポイントを設定。
  • Eclipse から[Run]/[Debug Configurations...],
    • Remote Java Applicationから[New]
    • Connection Properties の Port で8118を指定
    • Connection Type が Socket への Attachになっているのを確認して、[Debug] 押下
  • デバッグパースペクティブになり、ブレークポイントで止まるのを確認。
  • [F8]で継続。
  • コンソールにHello, Bean!が表示されることを確認

以上、Java EE Application Client のGetting Started からデバッグまで

■ 補足
  • 実装コードとインターフェイスを同じ JAR にするやり方もあるが、上記作業では ejbClient を 別JAR に分ける方式にした。後者の分ける方式では 通常の Eclipse と GlassFish の連携では上手く機能しないという理由もあって、Eclipse Tools Bundle を使う事にした
  • 上記の不具合は、最新(12月上旬)の GlassFish と Server Adapter では解消されているらしいが、どうしても GlassFish Adapter が最新のものに更新できなかった。共存できないバージョンのプラグインがあるためインストールが無理といったエラーメッセージが出るが、どうしても干渉を解決できず、断念。

    GlassFish とEclipse で作業する場合、通常の Eclipse に Server Runtime を設定してやるより、最初から素直に Tools Bundle を使った方が、やはり無駄な労力が省けるかも。