2009年12月30日水曜日

@Runwith(Parameterized + JMock): 解

前回、Parameterized と JMock が併用できずに困ったところで終わったが、併用する案を考えてみた。

Parameterized の使い方と JMock の使い方を知っていれば、コードの意味は自明だと思う。
@RunWith(ParameterizedJMockRunner.class)
public class ATest {
   @Parameters public static Collection<Object[]> params() {
      return Arrays.asList(new Object[][]{
            {-1, -2}, {0, 0}, {1, 2}, {2, 4}});
   }
   Mockery ctx = new JUnit4Mockery();
   int param;
   int expected;
   public ATest(int param, int expected) {
      this.param = param; 
      this.expected = expected;
   }
   @Test public void testCallB() {
      final B b = ctx.mock(B.class);
      ctx.checking(new Expectations(){{ oneOf(b).foo(expected); }});
      new A(b).callB(param);
   }
}


冒頭の RunWith で指定している ParameterizedJMockRunner は以下のようなもの。
public class ParameterizedJMockRunner extends Parameterized {
   public ParameterizedJMockRunner(Class<?> klass) throws Throwable {
      super(klass);
   }
   @Override
   protected List<Runner> getChildren() {
      List<Runner> original = super.getChildren();
      List<Runner> result = new ArrayList<Runner>(original.size());
      for (Runner each : super.getChildren()) try {
         result.add(new ChildRunner(each));
      } catch (InitializationError e) {
         throw new RuntimeException(e);
      }
      return result;
   }
}


オーバライドした getChildren() は、基底クラスの Parameterized が持っている子 Runner を、自前の Runner である ChildRunner にすり替えている。

ChildRunner は、以下のようなコードにした。
class ChildRunner extends BlockJUnit4ClassRunner {
   private Field mockeryField;
   private final int parameterSetNumber;
   private final List<Object[]> fParameterList;

   public ChildRunner(Runner runner) throws InitializationError {
      super(Util.getJavaClass(runner));
      try {
         parameterSetNumber = Util.parameterSetNumberOf(runner);
         fParameterList = Util.parameterListOf(runner);
         mockeryField = Util.findMockeryField(runner);
      } catch (Exception ex) {
         throw new RuntimeException(ex);
      }
   }
   @Override
   public Object createTest() throws Exception {
      return getTestClass().getOnlyConstructor().newInstance(computeParams());
   }
   private Object[] computeParams() throws Exception {
      return fParameterList.get(parameterSetNumber);
   }
   @Override
   protected String getName() {
      return String.format("[%s]", parameterSetNumber);
   }
   @Override
   protected String testName(final FrameworkMethod method) {
      return String.format("%s[%s]", method.getName(),
            parameterSetNumber);
   }
   @Override
   protected void validateZeroArgConstructor(List<Throwable> errors) {
      // constructor can, nay, should have args.
   }
   @Override
   protected Statement classBlock(RunNotifier notifier) {
      return childrenInvoker(notifier);
   }
   @Override
   protected Statement methodInvoker(
         final FrameworkMethod method, final Object test) {
      return new InvokeMethod(method, test) {
         @Override public void evaluate() throws Throwable {
            try {
               method.invokeExplosively(test);
               assertMockeryIsSatisfied(test);
            } catch (InvocationTargetException e) {
               //省略: Test.expected の処理
               throw e;
            }
         }
      };
   }
   private void assertMockeryIsSatisfied(Object testFixture) {
      mockeryOf(testFixture).assertIsSatisfied();
   }
   protected Mockery mockeryOf(Object test) {
      try { return (Mockery) mockeryField.get(test); }
      catch (Exception ex) { throw new RuntimeException(ex); }
   }
}
コードの内容は、JMock クラスのコードと Parameterized の 内部クラスTestClassRunnerForParameters のコードを混ぜたような感じ。methodInvoker のコードが JMock 系で、それ以外が TestClassRunnerForParameters系。

なんだかダラダラしたコードになったが、これでも例外処理とか手を抜いている。本当は Parameterized.TestClassRunnerForParameters コードを 継承したかったが、private なクラスなので駄目だった。

Util.xxx() とあるのは、ベタなリフレクションコードを別クラスに移動したもの。これは、さほど類推が難しくないだろうから、コードは割愛した。

こんな感じで JMock とParameterized を併用できる。まあ前ポストの、共通コードを別メソッドに括り出す方式に比べて、それ程優れてるとは言えない気もするが・・・

今回は pure Java で書いたが、AspectJ を使えばもっと簡潔に書けると思う。

2009年12月29日火曜日

@Runwith(Parameterized + JMock)

以下のような 2クラスがあるときの、A のテストを考える。
public interface B { void foo(int s); }

public class A {
   final B b;
   public A(B b) { this.b = b; }
   public void callB(int s) { b.foo(s * 2); }
}
コードの通り、クラス A はクラス B への 依存を持ち、「 A は、callB() の引数を 2倍して、B の foo() を呼び出す」というコラボレーションの仕様がある。

この A の B とのコラボレーションを検証したいが、assertX メソッドだとややこしくなるので、普通はモッキングの手法を使うことになる。
@RunWith(JMock.class)
public class ATest {
   Mockery context = new JUnit4Mockery();
   @Test public void testCallB() {
      final B b = context.mock(B.class);
      context.checking(new Expectations(){{ oneOf(b).foo(2); }});
      new A(b).callB(1);
   }
}
ここでテスト値が 1 だけなのは不十分なので、-1, 0, 2 での検証も追加したくなったとする。以下のように共通メソッドにくくり出して、呼び出しを並べても良いが・・・
@Test public void testCallB() {
   final B b = context.mock(B.class);
   assertCallB(b, -1, -2);
   assertCallB(b, 0, 0);
   assertCallB(b, 1, 2);
   assertCallB(b, 2, 4);
}
void assertCallB(final B b, int param, final int expected) {
   context.checking(new Expectations(){{ oneOf(b).foo(expected); }});
   new A(b).callB(param);
}
・・・ JUnitでは Parameterized という Suit Runner 派生クラスによる、Parameterized Test が標準でサポートされていて、こっちも活用してみたい。

ところが、アノテーションの仕組み上 RunWith を二重に指定する事はできない(できたとしても Runner の振る舞いがどう合成されるかは自明ではないが)ので、困ったことになる。
@RunWith(Parameterized.class)
@RunWith(JMock.class) // 認められない
public class ATest {
   @Parameters public static Collection<Object[]> params() {
      return Arrays.asList(new Object[][]{{-1, -2}, {0, 0}, {1, 2}, {2, 4}});
   }
   Mockery context = new JUnit4Mockery();
   final int param;
   final int expected;
   public ATest(int param, int expected) {
      this.param = param; 
      this.expected = expected;
   }
   @Test public void testCallB() {
      final B b = context.mock(B.class);
      context.checking(new Expectations(){{ oneOf(b).foo(expected); }});
      new A(b).callB(param);
   }
}
これを後で考えてみる。(←考えた[12/30])

2009年12月28日月曜日

WASCE/ActiveMQ/MDB

前回、WASCE で JMS(ActiveMQ)の動作を確認した。今度は、その勢いで MDB をやってみる。 ■ hello-mdb
  • [New]/[EJB Project]でEJBプロジェクトの作成を開始
    • プロジェクト名を hello-mdb とか適当に指定。
    • 以下を確認
      Target runtimeIBM WASCE v2.1
      EJB Module version3.0
      ConfigurationDefault Configuration for ~
    • Geronimo Deployment Plan のページまで進む
    • groupId 等を適当に入力。ここでは以下のようにした
      groupIdnet.yasuabe.studies.wasce
      artifactIdhello-mdb
      version1.0
      Artifact Typeejb
  • こんな感じで Message Driven Bean を書く
    package sample;
    
    import ・・・略
    
    @MessageDriven(name = "MDB1", activationConfig = {
          @ActivationConfigProperty(
                propertyName = "destinationType", 
                propertyValue = "javax.jms.Queue"),
          @ActivationConfigProperty(
                propertyName = "destination", 
                propertyValue = "HelloQueue") })
    public class MDB1 implements MessageListener {
       public void onMessage(Message message) {
          try {
             System.out.println(((TextMessage) message).getText());
          } catch (JMSException ex) {
             throw new RuntimeException(ex);
          }
       }
    }
  • こんな感じで openejb-jar.xml を編集する
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <ejb:openejb-jar
       xmlns:app="http://geronimo.apache.org/xml/ns/j2ee/application-2.0"
       ・・・略
       xmlns:web="http://geronimo.apache.org/xml/ns/j2ee/web-2.0.1">
       <dep:environment>
          <dep:moduleId>
             <dep:groupId>net.yasuabe.studies.wasce</dep:groupId>
             <dep:artifactId>hello-mdb</dep:artifactId>
             <dep:version>1.0</dep:version>
             <dep:type>ejb</dep:type>
          </dep:moduleId>
          <dep:dependencies>
             <dep:dependency>
                <dep:groupId>console.jms</dep:groupId>
                <dep:artifactId>HelloResourceGroup</dep:artifactId>
                <dep:version>1.0</dep:version>
                <dep:type>rar</dep:type>
             </dep:dependency>
          </dep:dependencies>
       </dep:environment>
       <ejb:enterprise-beans>
          <ejb:message-driven>
             <ejb:ejb-name>MDB1</ejb:ejb-name>
             <name:resource-adapter>
                <name:resource-link>HelloResourceGroup</name:resource-link>
             </name:resource-adapter>
          </ejb:message-driven>
       </ejb:enterprise-beans>
    </ejb:openejb-jar>
■ デプロイ
  • Eclipse で [Export]/[EJB JAR File]を実行して適当な場所に jar を作る
  • WASCE の管理コンソールで"Applications"/[Deploy New]
  • Archive でjar を指定して[Install]実行
  • The application was successfully deployed (または redeployed)と表示されることを確認
■ 確認
  • 前回作った JMSTest の testSend() を文言だけ書き換えて、Outline ビューから個別実行
    @Test
    public void testSend() throws Exception {
       QueueSender queueSender = session.createSender(queue);
       TextMessage message = session.createTextMessage(
          String.format("Hello, MDB!: %1$tH:%1$tM:%1$tS", new Date()));
       queueSender.send(message);
    }
  • コンソールにHello, MDB!: 05:07:29といったメッセージが表示されるのを確認
■ 所感
  • openejb-jar を正しく書くのがちょっと難しくてけっこう試行錯誤した。
  • アノテーションだけで済むだろうと何となく楽観してたら、かなりXML作業があって面倒くさい。
  • 後、Geronimo が不便+不慣れで、とにかく手間がかかって気が滅入る。アンインストールしたつもりでもファイルが残っていて、再デプロイがコケたりするので、何度も手作業で{WASCE_HOME}以下のファイルを掃除したりする羽目になる。ネット上の情報も余り多くないし、かなり扱いにくい・・・。

2009年12月27日日曜日

WASCE/ActiveMQ/JMS

前回、WASCE を入れて EJB3 を動かしてみた。管理コンソールを見ていると、ActiveMQ が JMS として使えるようなので、ついでにやってみる。 ■ Queue 設定 まず WASCE で Queue の準備をする。 最初にリソースグループを作る
  • 管理画面に入る。
  • Services/"JMS Resources"を開く
  • "Create a new JMS Resource Group"/"For ActiveMQ"を選択
  • "Resource Group Name" に HelloResourceGroup とか適当 な名前を入力し[Next] 押下。
次に Queue を作る
  • [Add Destination] 押下
  • "JMS Destination Type" で Queue が選択されていることを確認して[Next] 押下。
  • "Message Destination Name" に HelloQueue と入力し、[Next] 押下。
[Deploy Now] 押下で JMS Resources 画面に行くので、そこでちゃんと追加されたことが確認できる。(追加したリソースグループの削除は、このJMS Resources 画面じゃなくて、Applications/J2EE Connector 画面かららしい。ちょっと分かりづらいが・・・) ■ クライアントコード この JMS Queue を使うテストコードを書いて、JUnit4 でメソッドを個別実行してみる。
  • Java Project 作成。
  • {WASCE_HOME}/repository 下の 以下のjar を、External Jar としてBuild path に追加。
    • activemq-core-4.1.2.jar
    • backport-util-concurrent-2.2.jar
  • Library を Build path に追加。
    • "Server Runtime"/"IBM WASCE v2.1"
    • JUnit 4
  • 適当なパッケージに クラス JMSTestを作成。
    import java.util.Date;
    import ・・・略
    public class JMSTest {
       static final Hashtable<String, String> env = new Hashtable<String, String>();
       static {
          env.put("java.naming.factory.initial",
                "org.apache.activemq.jndi.ActiveMQInitialContextFactory");
          env.put("java.naming.provider.url", "tcp://localhost:61616");
          env.put("queue.HelloQueue", "HelloQueue");
       }
    
       QueueConnection conn = null;
       QueueSession session = null;
       Queue queue = null;
    
       @Before
       public void setUp() throws Exception {
          Context jndiContext = new InitialContext(env);
          QueueConnectionFactory queueConnectionFactory =
             (QueueConnectionFactory) jndiContext.lookup(
                   "QueueConnectionFactory");
    
          queue = (Queue) jndiContext.lookup("HelloQueue");
          conn = queueConnectionFactory.createQueueConnection();
          session = conn.createQueueSession(false,
                Session.AUTO_ACKNOWLEDGE);
       }
       @After
       public void tearDown() throws Exception {
          if (conn != null)
             conn.close();
       }
       @Test
       public void testSend() throws Exception {
          QueueSender queueSender = session.createSender(queue);
          TextMessage message = session.createTextMessage(
             String.format(
                   "Hello, JMS!: %1$tH:%1$tM:%1$tS", new Date()));
          queueSender.send(message);
       }
       @Test
       public void testReceive() throws Exception {
          QueueReceiver receiver = session.createReceiver(queue);
            conn.start();
    
            TextMessage m = (TextMessage)receiver.receive();
            System.out.println(m.getText());
       }
    }
■ 実行
  • Outline ビューから testSend() を実行。正常に実行されることを確認。
  • Outline ビューから testReceive() を実行。正常に実行され、コンソールにHello, JMS!: 22:48:06みたいな感じで1行出力されることを確認。

円周上に7つの点をムラなく並べるには?


円周を12分割して、12時の位置から時計回りに7つの点を並べるとする。

ただし偏りのないようムラなく並べたい。ここで「ムラなく」を、以下のように定義するとする。
  1. 30度が連続しない(そこが密になるから)。
  2. 60度を超えない(そこが疎になるから)。
どんな並べ方があるか。

やってみると14通りの並べ方があるのがわかる。
121314
p091224_01.draw("canvas091224_1", [0, 1, 3, 4, 6, 8, 10]);
p091224_01.draw("canvas091224_2", [0, 1, 3, 5, 6, 8, 10]);
・・⇒・・
p091224_01.draw("canvas091224_3", [0, 2, 4, 6, 7, 9, 11]);
p091224_01.draw("canvas091224_4", [0, 2, 4, 6, 8, 9, 11]);

実は、この円(360度)を音階の 1オクターブと捉えると、7つの点の組み合わせは十二平均律上の 7音の音階と考えることができる。さらによく見ると、14通りとは、長音階(Ionian)を転回した7通りと、旋律的短音階上昇形(Melodic Minor)を転回した7通りの計14である事がわかる。
Lydian AugLydianIonianMelodic Minor
p091224_01.draw2("canvas091226_0", [0, 2, 4, 6, 8, 9, 11]);
p091224_01.draw2("canvas091226_1", [0, 2, 4, 6, 7, 9, 11]);
p091224_01.draw2("canvas091226_2", [0, 2, 4, 5, 7, 9, 11]);
p091224_01.draw2("canvas091226_3", [0, 2, 3, 5, 7, 9, 11]);
Lydian ♭7MixolydianDorianDorian♭2
p091224_01.draw2("canvas091226_4", [0, 2, 4, 6, 7, 9, 10]);
p091224_01.draw2("canvas091226_5", [0, 2, 4, 5, 7, 9, 10]);
p091224_01.draw2("canvas091226_6", [0, 2, 3, 5, 7, 9, 10]);
p091224_01.draw2("canvas091226_7", [0, 1, 3, 5, 7, 9, 10]);
Mixolydian♭6AeolianPhrygianLocrian♮2
p091224_01.draw2("canvas091226_8", [0, 2, 4, 5, 7, 8, 10]);
p091224_01.draw2("canvas091226_9", [0, 2, 3, 5, 7, 8, 10]);
p091224_01.draw2("canvas091226_a", [0, 1, 3, 5, 7, 8, 10]);
p091224_01.draw2("canvas091226_b", [0, 2, 3, 5, 6, 8, 10]);
LocrianAltered
p091224_01.draw2("canvas091226_c", [0, 1, 3, 5, 6, 8, 10]);
p091224_01.draw2("canvas091226_d", [0, 1, 3, 4, 6, 8, 10]);

この14の音階は、以下のように2次元の表の上でグラデーションを構成するように並べる事ができる。

+5p5+4p4M3m3M2m2
Lyd+LydIonMel- 
M7
-Lyd♭7MixDorDor♭2m7
M6
--Mix♭6AeoPhrm6
p5
---Loc♮2Loc-5
p4
----Alt-4
 


円周の図と表を見比べると、表で右上-左下の対角線を挟んで対称の位置にある組が、円周の図でみると逆回転の組になっている。また左回りでも右回りでも対称なのは、Dorian と Mixolydian♭13 で、これらは表の対角線上に来ているのも分かる。

青文字で書いた Ionian の転回形は、一本につながってジグザグに配置され、その両側からMelodic Minor の転回形が挟み込む形に構成される。

なんだか見れば見るほど不思議な気がする。

ちなみに、冒頭の「ムラなく」並べるルールを6音に適用すると Whole Tone Scale になり、8音に適用すると Diminished Scale または Combination Diminished Scale になる。

参考までに書くと、普通のドレミファソラシド(Ionian)を頭の中で思い浮かべることができる人なら(できない人は見たことないが)、上の表で Ionian から始めて少しずつ他のマス目に移動していく形で、割と簡単に14個の7音音階と6音・8音の音階の計17通りの音階を自由に頭の中で鳴らせるようになる。

2009年12月26日土曜日

WASCE/WTP/Java EE 5

前回、WASCE を Eclipse+WTP から動かしてみた。今度は、この環境で Java EE 5 の簡単な疎通確認をやろうと思う。 確認したいのは ブラウザ→ Servlet→ SessionBean→ JPA→ MySQL という連携だけど、まずは Servlet まで通し、続いて SessionBean、最後に JPA+MySQL という段取りで、疎通してみる。 ■ Servlet まで疎通 ◆ hello-ear プロジェクト作成 適当に新規 Enterprise Application Project を作る。以下、入力例。
target runtimeIBM WASCE v2.1
EAR version5.0
ConfigurationDefault Configuration ・・・
Group Idnet.yasuabe.studies.wasce
Artifact Idhello-ear
Artifact Typeear
◆ hello-war プロジェクト作成適当に新規 Dynamic Web Project を作る。以下、入力例。
target runtimeIBM WASCE v2.1
Dynamic Web Module version2.5
ConfigurationDefault Configuration ・・・
Add project to EARcheck
EAR project namehello-ear
Group Idnet.yasuabe.studies.wasce
Artifact Idhello-ear
Artifact Typewar
◆ hello-ear に プロジェクト依存を追加 プロジェクトの Properties を開いて、"Java EE Module Dependencies" で hello-war にチェック ◆hello-war HTTP/GET を受け取って、文字列 "Hello, Servlet!" を返すサーブレットを作る。
  • [New]/[Other...]/[Servlet] を実行し、net.yasuabe.studies.wasce.war.HelloServlet.java を作成。
  • 以下のようにコードを書き換える
    public class HelloServlet extends HttpServlet {
       private static final long serialVersionUID = 1L;
       protected void doGet(
                HttpServletRequest request, 
                HttpServletResponse response) throws ServletException, IOException {
          response.getWriter().write("Hello, Servlet!");
       }
    }
◆ 確認 ・Servers ビューで hello-ear を WASCE に追加して、起動。 ・http://localhost:8080/hello-war/HelloServlet をブラウザで開くと、Hello, Servlet!と表示されるはず。 以上、ブラウザ→ Servlet まで。 ■ SessionBean まで疎通 続いて、文字列 "Hello, SessionBean!" を返すメソッド sayHello() を持つ SessionBean を書く。 ◆ hello-ejb 作成 適当に新規 EJB Project を作成。以下、入力例。
target runtimeIBM WASCE v2.1
EJB Module version3.0
ConfigurationDefault Configuration ・・・
Add project to EARcheck
EAR project namehello-ear
Group Idnet.yasuabe.studies.wasce
Artifact Idhello-ejb
Artifact Typeejb
◆ hello-ear に プロジェクト依存を追加 プロジェクトの Properties を開いて、"Java EE Module Dependencies" で hello-ejb にチェック ◆ HelloService EJB 作成 hello-ejb プロジェクトで [New]/[Session Bean (EJB 3.x)] を実行。以下入力例。
パッケージ名net.yasuabe.studies.wasce.ejb
クラス名HelloService
コードは以下のような感じ。
@Local
public interface HelloServiceLocal {
   String sayHello();
}

@Stateless
public class HelloService implements HelloServiceLocal {
   @Override public String sayHello() {
      return "Hello, SessionBean!";
   }
}
◆ hello-war での呼び出しコード記述
  • プロジェクトプロパティの"Java EE Module Dependencies"で hello-ejb にチェック
  • doGet()定義の上あたりに、新規フィールドを追加
    @EJB HelloServiceLocal helloService;
  • doGet の中身を以下のように書き換える
    response.getWriter().write(helloService.sayHello());
◆ 確認 再び、http://localhost:8080/hello-war/HelloServlet を開いて、今度は「Hello, SessionBean!」と表示されるのを確認する。 これで EJB3 に届いた。 ■ JPA+MySQL まで疎通 JPA による データベース・アクセスを以下の流れでやってみる。
  • まずテーブルを用意
  • データソースの設定
  • JPA によるアクセスコード
◆ テーブルの準備
mysql> use test;
mysql> CREATE TABLE t1 (c1 int primary key, c2 varchar(20));
mysql> insert into t1(c1, c2) values(1, 'Hello, JPA!');
mysql> select * from t1;
+----+-------------+
| c1 | c2          |
+----+-------------+
|  1 | Hello, JPA! |
+----+-------------+
◆ データソースの準備
  • WASCE が ドライバの jar を使えるように、"Services"/Repository から mysql-connector を登録する。内部的には以下のようにファイルが置かれる。
    {WASCE_HOME}/repository
      └ mysql
        └ mysql-connector-java
          └ 5.1.10
            └ mysql-connector-java-5.1.10.jar
  • WASCE 管理画面で、Database Pools >Using the Geronimo database pool wizardの順に選択し、以下のように入力
    Name of Database Pooljdbc/HelloDS
    Database TypeMySQL
    Database Nametest
◆ HelloEntity 作成 以下、hello-ejb プロジェクトでの作業。 まず、hello-ejb プロジェクトに以下のようなクラスを書く
package net.yasuabe.studies.wasce.entity;
@Entity(name="t1")
public class HelloEntity {
   @Id @Column(name="c1") private int id;
   @Column(name="c2") private String message;
   ...getter/setter 略
}
次に、META-INF 下にpersistence.xml を作り、以下のように記述する。
<?xml version="1.0" encoding="UTF-8"?>
<persistence    xmlns="http://java.sun.com/xml/ns/persistence"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"
                xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="HelloUnit" transaction-type="JTA">
        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <jta-data-source>jdbc/HelloDS</jta-data-source>
    </persistence-unit>
</persistence>
◆ geronimo-application.xml に依存追加 hello-ear プロジェクトの geronimo-application.xml に以下のように依存を追加する。
<dep:dependencies>
   <dep:dependency>
      <dep:groupId>console.dbpool</dep:groupId>
      <dep:artifactId>jdbc_HelloDS</dep:artifactId>
      <dep:version>1.0</dep:version>
      <dep:type>rar</dep:type>
   </dep:dependency>
</dep:dependencies>
これは Geronimo Deployment Plan Editor の Deployment タブから、Server Dependencies の [Add] で追加することもできる。 この記述が無いと、「Unable to resolve reference "JtaDataSourceWrapper"」という文言のエラーが出て、気づくまでかなり悩むことになったりする。plan というものに慣れるのが、ツボらしい。 ちなみに groupId, artifactId は WASCE の管理コンソールから確認することができる。(Database Pools ページの、jdbc/HelloDS 行 Actions 列から Usage を選択) ◆ 実行 再び、http://localhost:8080/hello-war/HelloServlet を開いて、今度は「Hello, JPA!」と表示されるのを確認する。 これでプレゼンテーション層からデータ層まで疎通できた。 ==== ◆ 手間取ったところ
  • Deployment Plan というのが最初分かりにくくて、geronimo-application.xml への追記を発見するまで手間取った。
  • MySQL の場合、データソースを追加するに先立って、予め repository に データベースドライバを含む jar を置いてないと駄目らしく、気付くまでちょっと悩んだ。他のいくつかの DB は Out-of-the-box で使える。

2009年12月25日金曜日

WASCE/Galileo/WTP

WASCE (WebSphere Application Server Community Edition)を Eclipse で使い始める レシピ。

■ 使うもの
  • Eclipse 3.5 Galileo
  • WTP 3.1.1
  • WASCE V2.1.1.3
  • Windows XP Professional SP 3


■ 準備
  • WASCE をダウンロードする。
  • 適当なところにインストールする。(・・・/IBM/WebSphere/AppServerCommunityEditionといったパスのディレクトリが作られるので、以下、これを{WASCE_HOME}とする。)


■ Eclipse での サーバ設定
  • Eclipse から[File]/[New]/[Other]/[Server]
  • New server ダイアログが開くので、download additional server adapters をクリック
  • Install New Extension ダイアログでWASCE v.2.1 Server Adapter を選択し、アダプタがインストールされる間しばし待つ

  • 一旦、再起動して、再び [File]/[New]/[Other]/[Server]
  • 今度は"IBM WASCE v2.1 Server"がリストアップされるので、選択する
  • Application Server Install Directory に {WASCE_HOME}を指定して、[Finish]。
以上で設定終わり。Eclipse のServers ビューに設定したサーバが表示され、これをダブルクリックすると設定画面が開くので、チラッと観察しておく。

■ 動作確認
  • Servers ビュー でWASCE サーバを選択し、[Start]を実行する。
  • ブラウザで http://localhost:8080/を開く。以下のようになる。
  • Administrative Console を選択すると、ユーザ名パスワードを聞かれるので system/manager と入力。管理画面に入れる。

以上、Eclipse からの起動から管理画面まで。

2009年12月24日木曜日

spring/iBATIS/Eclipse/Maven2

巷の案件情報を調べてみると、意外と iBATIS を使っている開発プロジェクトが多い。Java 業界では、もうとっくに Hibernate × JPA で決着がついたような気がしていたけど、気のせいだったか。 というわけで、復習ついでに Getting Started 的レシピを書いてみる。 ■ 前提
  • Eclipse 3.5 Galileo
  • m2eclipse 0.98 (Maven 2.2.1)
  • MySQL 5.0.45
■ データベースの準備 MySQL の`test`データベースに以下のようにテーブル`t1`を作り、データを入れる。
mysql> use test;
mysql> create table t1 (c1 int primary key, c2 varchar(20));
mysql> insert into t1(c1, c2) values (1, 'Hello, world!');
mysql> select * from t1;
+----+---------------+
| c1 | c2            |
+----+---------------+
|  1 | Hello, world! |
+----+---------------+
■ プロジェクト作成
  • maven-archetype-quickstart 1.0を用いて、Eclipse で Maven Project を作成。artifactId 等、適当でかまわないが、ここでは以下のようにした。
    groupIdnet.yasuabe.studies.spring
    artifactIdspring-ibatis
    version0.0.1-SNAPSHOT
    packagenet.yasuabe.studies.spring.spring_ibatis
  • pom.xml に以下の依存を追加(バージョン等は今日の日付で最新のもの)。
    groupIdartifactIdversion
    org.springframeworkspring-orm3.0.0.RELEASE
    org.springframeworkspring-ibatis2.0.8
    commons-dbcpcommons-dbcp1.2.2
    mysqlmysql-connector-java5.1.10
    JUnit 3.8 を dependency から削除し、src/test/java 下の AppTest.java も削除する。
  • 新規ソースフォルダとして、src/main/resources を作成する。ここに *.xml を置く。
以上、spring + iBATIS の大体の共通作業。 ■ プログラムを書く 続いて上記設定の疎通確認ができるようなプログラムを書いてみる。
  • こんなエンティティクラスに、RDB テーブル`t1`のレコードをマッピングする。
    package net.yasuabe.studies.spring.spring_ibatis;
    
    public class T1 {
       private int c1;
       private String c2;
       ・・・ getter / setter 略
    }
  • また、RDB テーブル`t1`への操作を以下のように T1Dao で宣言する。
    package net.yasuabe.studies.spring.spring_ibatis;
    
    import java.util.List;
    
    public interface T1Dao {
        public List<T1> selectAll();
    }
  • これらのマッピングと操作を以下のように T1.xml にて記述する。
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">
    
    <sqlMap>
        <typeAlias type = "net.yasuabe.studies.spring.spring_ibatis.T1" alias = "t1"/>
        <resultMap class = "t1" id = "result">
            <result property = "c1" column = "c1"/>
            <result property = "c2" column = "c2"/>
        </resultMap>  
        <select id = "selectAll" resultMap = "result">
            select c1, c2 from T1
        </select>
    </sqlMap>
  • この T1.xml を使うことを iBatis に知らせるために、以下のように SqlMapConfig.xmlを書く
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE sqlMapConfig
    PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
    
    <sqlMapConfig>
        <sqlMap resource="./T1.xml" />
    </sqlMapConfig>
  • この SqlMapConfig.xml と、iBatic および Spring を関連付けるために、以下のように spring-ibatis.xml を書く。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
          <property name="driverClassName">
             <value>com.mysql.jdbc.Driver</value>
          </property>
          <property name="url">
             <value>jdbc:mysql://localhost/test</value>
          </property>
       </bean>
       <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
          <property name="configLocation">
             <value>./SqlMapConfig.xml</value>
          </property>
       </bean>
       <bean id="t1Dao" class="net.yasuabe.studies.spring.spring_ibatis.T1DaoImpl">
          <property name="dataSource">
             <ref local="dataSource" />
          </property>
          <property name="sqlMapClient">
             <ref local="sqlMapClient" />
          </property>
       </bean>
    </beans>
    この spring-ibatis.xml では、SqlMapConfig.xml の指定の他に、データソースの指定と、t1Dao 実装クラスへのそれらのインジェクションも記述している。
  • bean定義 t1Dao の class に指定されている T1DaoImpl は、T1Dao の実装クラスで以下のようなものになる。
    package net.yasuabe.studies.spring.spring_ibatis;
    import ・・・略
    
    public class T1DaoImpl extends SqlMapClientDaoSupport implements T1Dao {
       @Override
       public List<T1> selectAll() {
            SqlMapClientTemplate template = getSqlMapClientTemplate();
            return (List<T1>)template.queryForList("selectAll");
       }
    }
    (プロパティ dataSource と sqlMapClient は、基底クラス SqlMapClientDaoSupportで定義されている)
これでエンティティと Dao の準備ができたのでクライアントコードを書いてみる。アーキタイプ が既に App.java を作っているはずなので、その main() を書き換える。
public static void main(String[] args) {
   Resource resource = new ClassPathResource("/spring-ibatis.xml");
   BeanFactory beanFactory = new XmlBeanFactory(resource);

   T1Dao t1Dao = (T1Dao) beanFactory.getBean("t1Dao");
   for (T1 t1: t1Dao.selectAll()) {
      System.out.printf("c1:%d c2:%s%n", t1.getC1(), t1.getC2());
   }
}
■ 実行 こんなコンソール出力を得る。
c1:1 c2:Hello, world!
割と簡単だった。

2009年12月23日水曜日

Nested Set Model と Java のマッピング例

前ポストの続きで、RDB の Nested Set Model と POJO との対応を考えてみる。 Nested Set モデルに従って電化製品の種別を表す、下のようなテーブル `category`を、クラス 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 |
+-------------+----------------------+------+------+
こんな風に書いて、、、
public static void main(String[] args) {
  ・・・ 略
  EntityManager em = emf.createEntityManager();
  Category node = em.find(Category.class, 4);
  someBusinessLogic(node);
  ・・・ 略
}

static void someBusinessLogic(Category node) {
  for (Category a: node.ancestors()) {
     System.out.printf("%s %s%n", a.getId(), a.getName());
  }
}
、、、以下のような出力を得たい。
1 ELECTRONICS
2 TELEVISIONS
someBusinessLogic() では EntityManager を触っていない事に留意しつつ、どんな風に書けるか考えてみる。(話を簡単にするため、Nested Set への操作は祖先ノードを検索する ancestors() メソッドのみとする。) ■ 実装例 ◆エンティティクラス まず、RDBのテーブルにマッピングされる Category エンティティはこんな風にした。
@Entity(name="category")
public class Category {
   static aspect UseNestedSet extends NestedSetIdiom<Category>{}

   @Id()
   @Column(name="category_id")
   private int id;
   private String name;

   @Transient
   NestedSet<Category> nestedSet;

   ・・・id と name のゲッタセッタ

   public List<Category> ancestors() {
      return nestedSet.ancestors();
   }
}
ここで、フィールド nestedSet は Category を要素として持つ Nested Set への ファサード・オブジェクトで、以下のインターフェイスを持つ。
public interface NestedSet<T> {
   List<T> ancestors();
}
interface 定義の中では、Nested Set というデータ集合への操作を契約として表現するのみで、JPA など下位レイヤのAPIにはタッチしない。 さて、実装クラスについては、次のことが問題になる。
  1. いつ、どのようにして NestedSet インスタンスと Category を関連付けるか。
  2. 実際の DB へのアクセスは、NestedSet 実装クラスが受け持つ事になるが、そこで用いる EntityMangaer をどう関連付けるか。
これらの問題を解決するために、AspectJ を使ってみた。上記 Category クラス定義冒頭の内部アスペクトはそのためのもので、抽象アスペクト NestedSetIdiom を具象化している。 ◆アスペクトコード 抽象アスペクト NestedSetIdiom は以下のようなもので、NestedSetUtil.injectNestedSet() を使って、NestedSetImpl、EntityManager、および T の間の関連付けを行う(この例では T=Category )。
public abstract aspect NestedSetIdiom<T> 
      extends EntityCreationWormhole<T> {

   protected void postEntityCreated(EntityManager em, T entity) {
      NestedSetUtil.injectNestedSet(em, entity);
   }
}
class NestedSetUtil {
   public static <T> void injectNestedSet(EntityManager em, T entity) {
      try {
         for (Field f: entity.getClass().getDeclaredFields()) {
            if (NestedSet.class.equals(f.getType())) {
               f.set(entity, new NestedSetImpl<T>(em, entity));
               return;
            }
         }
      } catch (Exception e) ・・・略
   }
   ・・・略: その他メソッド
}
postEntityCreated() は、
  • 基底抽象アスペクト EntityCreationWormhole で定義されたタイミングで呼ばれ、
  • Wormhole を通じて渡されてきた EntityManager で NestedSet 実装クラスを初期化し、
  • これを T entity のフィールドとして挿し込む。
アスペクト EntityCreationWormhole の定義は以下のようになる。
public abstract aspect EntityCreationWormhole<T> {
   pointcut caller(EntityManager em): 
      call(public * EntityManager.*(..)) && target(em);
   pointcut callee(): execution(T+.new(..));
   pointcut wormhole(EntityManager em) : cflow(caller(em)) && callee();

   after(EntityManager em, T entity): wormhole(em) && this(entity) {
      postEntityCreated(em, entity);
   }
   protected abstract void postEntityCreated(EntityManager em, T entity);
}
これで上記の問題1と2が解決できた。あとは NestedSetImpl の ancestors()実装で、EntityManager を使った先祖 Category 検索コードを書くだけ。 ◆ NestedSet 実装クラス これは適当にどんな書き方でも良いが、例えば以下のように書ける。
class NestedSetImpl<T> implements NestedSet<T> {
   public static final String SELECT_ANCESTORS = 
      "select p.* from %1$s as p, %1$s as c " +
      " where c.%2$s = ?1 " +
      "  and p.lft < c.lft and c.rgt < p.rgt";

   private final T entity;
   private final EntityManager entityManager;

   NestedSetImpl(EntityManager em, T entity) {
      this.entityManager = em;
      this.entity = entity;
   }
   public List<T> ancestors() {
      String query = String.format(
         SELECT_ANCESTORS, 
         NestedSetUtil.tableNameOf(entity), 
         NestedSetUtil.idColNameOf(entity));
      return (List)entityManager.createNativeQuery(query, entity.getClass())
         .setParameter(1, NestedSetUtil.idOf(entity))
         .getResultList();
   }
}
ここで使っている NestedSetUtil の tableNameOf(), idColNameOf(), getIdOf() はベタなリフレクションコードなので割愛するが、以下のような仕様になる。
  • tableNameOf(T entity): T についた@Entity のname属性を返す。
  • idColNameOf(T entity): T のフィールドのうち、型 int で @Id アノテーションがついたものを見つけて、その @Column の name属性を返す。
  • idOf(T entity): T のフィールドのうち、型int で@Id アノテーションがついたものを見つけて、その値を返す。
とりあえず上記のようなコーディングで、JPA コードを排除した Java ビジネスロジックコードから、Nested Set Model 準拠 テーブルにある情報にアクセスできるようになる。 Nested Set への他の検索操作(例えば子孫要素の一括取得など)も NestedSet で宣言してNestedSetImplで実装する形で追加できる。 ■ 制約と課題 上述の解は、Proof-of-concept 目的のサンプルコードで、いくつか制約がある。また AspectJ や Nested Set Model に関連する本質的な課題もある。 以下、単純にコードを書き足すだけでどうにでもできそうな制約。
  • NativeQuery を作る際、@Entityや@Columnで表名・列名が明示されていることに依存しているが、本来はクラス名とフィールド名からデフォルト名を得ることもできるようにすべき。
  • ネイティブ SQL Where句中の、自分の位置を決めるため等式 c.%2$s = ?1で、単一の int 列を id として決め打ちしているが、JPA でIDとして許容されるものは複合キーでもその他の型でも、なるべく対応できたほうがいい。
以下は、ちょっと研究が必要な課題。
  • ancestor で集められた 各祖先 Category オブジェクトには、NestedSet オブジェクトが関連付けられていない。これは上例のサンプルが、EntityManager のメソッド実行から連なる Categoryインスタンス生成を joinpoint としている一方、祖先 Cateogry の生成は Query.getResultList() 呼び出しから始まるものでコールスタックに EntityManager のメソッドが無いため。 これは EntityCreationWormhole アスペクトの callee ポイントカットに Query のメソッド実行を書き足せば、何とかなりそう。
  • Category のクラス定義で、抽象アスペクトを具象化しているが、できれば エンティティ・クラスは Java にしたい。本当は、こんな風に
    public interface NestedSet<T> {
       static aspect UseNestedSet extends NestedSetIdiom{}
       List<T> ancestors();
    }
    書きたかったが、文法的にだめらしい。
  • Adjacency List Model(自テーブルを再帰参照するやつ)との共存。
以下は、難しいのがわかっている事柄。
  • 追加・削除。

2009年12月22日火曜日

Nested Set をJavaにマッピングしたいが・・・

前に、RDB で階層構造を扱うための Nested Set Model というイディオムを試してみた。こんな風に lft 列と rgt 列で包含関係を表現したものだった。[参考URL]
+-------------+----------------------+------+------+
| category_id | name                 | lft  | rgt  |
+-------------+----------------------+------+------+
|           1 | ELECTRONICS          |    1 |   20 |
|           2 | TELEVISIONS          |    2 |    9 |
|           3 | TUBE                 |    3 |    4 |
|           4 | LCD                  |    5 |    6 |
|           5 | PLASMA               |    7 |    8 |
|           6 | PORTABLE ELECTRONICS |   10 |   19 |
|           7 | MP3 PLAYERS          |   11 |   14 |
|           8 | FLASH                |   12 |   13 |
|           9 | CD PLAYERS           |   15 |   16 |
|          10 | 2 WAY RADIOS         |   17 |   18 |
+-------------+----------------------+------+------+
今回はこれとJava コード(JPAベース)とのマッピングを、ちょっと考えてみたい。 上のテーブルの1レコードを、Category クラス(アノテーション付けただけのPOJO)にマッピングする。この際、category_id 列と name 列 は、それぞれ Category クラスの id フィールド、name フィールドに対応付けるが、lft 列と rgt 列は純粋に RDB操作のためのテクニカルな列なので POJO のフィールドにはしない。ただし、lft 列と rgt 列を用いて得られる階層構造へのアクセスは利用したい。 JPA で提供されている普通の方法を使うとこんな感じだろうか
@Entity
@NamedNativeQueries({
  @NamedNativeQuery(
   name="descendants", 
   query="SELECT c.category_id, c.name " +
         "FROM category as p, category as c " +
         "WHERE p.category_id=?1 and p.lft < c.lft and c.rgt < p.rgt " +
         "ORDER BY c.lft",
   resultClass=Category.class),
  @NamedNativeQuery(
   name="ancestors", 
   ・・・省略
})
public class Category {
   @Id()
   @Column(name="category_id")
   private int categoryId;
   private String name;
   ・・・ getter() / setter() 省略
}
名前つきクエリ descendants のクライアントコードはこんな感じになるはず。
Category c = 略

EntityManager em = emf.createEntityManager();
List<Category> descendants = em.createNamedQuery("descendants")
   .setParameter(1, c.getId()).getResultList();
・・・うーん。なんか違う気がする。 先祖要素や子孫要素にアクセスするたびにEntityManager を参照すると、ビジネスロジックとJPA コードの混在で扱いにくくなって、余計な開発/保守コストがかかるはず。 例えば、こんな風に書けたら良いんだけど・・・
@Entity
public class Category {
   @Id()
   @Column(name="category_id")
   private int categoryId;
・・・略

   List<Category> ancestors;
   Tree<Category> children;
   
   public Category parent() {
      return ancestors.empty() ? null: ancestors.get(0);
   }
   public List<Category> ancestors() {
      return ancestors;
   }
   public List<Category> children() {
      return children.elementsAtLevel(1);
   }
   public List<Category> descendants() {
      return children.toList();
   }
}
//クライアントコード
void doSomeBusinessLogic(Category cat) {
   for (Category descendant: cat.descendants()) {
      ・・・ cat の子孫要素 descendant を使って何かやるコード
   }
}
・・・そう簡単にはできないらしい。自明な解は JPA はもちろん Hibernate にも見当たらない。うーん、もうちょっと考えないと。

2009年12月21日月曜日

AspectJ で using 的な構文を模倣してみる (3)

前に AspectJ で using ぽい構文を試してみた。任意のクラスの解放メソッドを自動的に実行するところまでやったが、今回はネストした using ブロックを試してみる。 以下のようなコードで、、、
public class Client {
   public static void main(final String[] args) throws Exception {
      new UsingA() { A r1 = new A("r1"); {
         new UsingB() { B r2 = new B("r2"); {
            new UsingA() { A r3 = new A("r3"); {
               r1.use("x");
               r2.use("y");
               r3.use("z");
            }};
         }};
      }};
   }
}
、、、自動的に r3, r2, r1 の順にリソースが解放されるようにしてみたい。(前回までのコードでは r1 が解放されず r3 が2度解放されてしまっていた。) 以下のように Stack を使って UsingAspect を修正。ついでにマルチスレッドで使えるように percflow(using()) をアスペクト定義に付与。
public abstract aspect UsingAspect<T, U> percflow(using()) {
   protected Stack<T> stack = new Stack<T>();
   pointcut settingField(T o): set (T+ U+.*) && args(o);
   pointcut using(): call(U+.new(..));

   after(T o): settingField(o) {
      this.stack.push(o);
   }
   after(): using() {
      if (!stack.empty()) endUsing(this.stack.pop());
   }
   protected abstract void endUsing(T o);
}
その他のクラスは、ほとんど自明だけど以下のようなものになる。
UsingA
public interface UsingA {
   public static aspect AspectA
         extends UsingAspect<A, UsingA> {
      @Override
      public void endUsing(A o) {
         if (null != o) o.dispose();
      }
   }
}
UsingB
public interface UsingB {
   public static aspect AspectB
         extends UsingAspect<B, UsingB> {
      @Override
      public void endUsing(B o) {
         if (null != o) o.close();
      }
   }
}
A
public class A {
   private final String name;
   public A(String name) {
      this.name = name;
   }
   public void dispose() {
      System.out.printf("%s disposed%n", this);
   }
   public void use(String str) {
      System.out.printf("%s used with \"%s\"%n", this, str);
   }
   public String toString() {
      return String.format("A(\"%s\")", this.name);
   }
}
B
public class B {
   private final String name;
   public B(String name) {
      this.name = name;
   }
   public void close() {
      System.out.printf("%s closed%n", this);
   }
   public void use(String str) {
      System.out.printf("%s used with \"%s\"%n", this, str);
   }
   public String toString() {
      return String.format("B(\"%s\")", this.name);
   }
}
実行すると以下のようなコンソール出力を得る。
A("r1") used with "x"
B("r2") used with "y"
A("r3") used with "z"
A("r3") disposed
B("r2") closed
A("r1") disposed
割と簡単に using のネストができた。一応、だんだんと実用的になってきた気がする。取りあえずテストコードで使い始めて、パフォーマンスなんかを中心にしばらく観察してから、問題が無ければプロダクトコードで使ってみるような流れで良いか。

2009年12月20日日曜日

Spring Web Service/m2eclipse/WTP

Spring Web Service を Eclipse で使い始めるときのざっくりレシピ。 使用プロダクトは以下のようなもの。
  • Eclipse 3.5 Galileo
  • m2eclipse 0.9.8 (Maven 2.2.1)
  • WTP 3.1.1
  • Apache Tomcat v6.0 (Eclipse で server runtime に設定され、さらにサーバプロジェクトに追加されている前提)
■ 手順 (作成)
  • Eclipse で spring-ws-archetype を使って Maven Project 作成。artifactId等は適当。ここでは以下のようにした。
    groupIdnet.yasuabe.studies.spring.ws
    artifactIdex1
    version0.0.1-SNAPSHOT
    Packagenet.yasuabe.studies.spring.ws.ex1
  • .project に <nature>org.eclipse.wst.common.project.facet.core.nature</nature> を追加( Navigator ビューから)。
  • プロジェクトの Properties を開いて、、、
    • Project Facet で Java と Dynamic Web Module にチェックし、Runtime タブで構成済みの Tomcat を指定
    • Java Compiler に 1.6を指定。
    • Property を一旦閉じて開きなおす。
    • Java EE Module Dependencies で Maven Dependencies にチェックを入れる
  • .settings/ 下の org.eclipse.wst.common.componentファイルを開いて、<ws-resource>の @source-pathを、/WebContentから/src/main/webappに変更する
  • WebContent フォルダを削除する。
  • /src/main/webapp/WEB-INF/下に greeting.xsd を作成する
    <?xml version="1.0" encoding="UTF-8"?>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" 
       elementFormDefault="qualified" attributeFormDefault="qualified"
          targetNamespace="http://studies.yasuabe.net/spring/ws/ex1"
          xmlns:tns="http://studies.yasuabe.net/spring/ws/ex1">
    
       <element name="greetingRequest" type="string"/>
       <element name="greetingResponse" type="string"/>
    </schema>
  • /src/main/webapp/WEB-INF/下の spring-ws-servlet.xml に以下の記述を追加
    <bean id="greeting" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
       <property name="schema" ref="schema"/>
       <property name="portTypeName" value="Greeting"/>
       <property name="locationUri" value="http://localhost:8080/ex1/greetingService"/>
    </bean>
    <bean id="payloadMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
       <property name="defaultEndpoint" ref="greetingEndpoint"/>
    </bean>
    <bean id="greetingEndpoint" class="net.yasuabe.studies.spring.ws.ex1.GreetingEndpoint"/>
    <bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema">
       <property name="xsd" value="/WEB-INF/greeting.xsd"/>
    </bean>
  • 新規ソースフォルダに/src/main/java を作成して、以下のクラスを作成
    package net.yasuabe.studies.spring.ws.ex1;
    
    import org.springframework.ws.server.endpoint.AbstractDomPayloadEndpoint;
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Text;
    
    public class GreetingEndpoint extends AbstractDomPayloadEndpoint {
       @Override
       protected Element invokeInternal(Element req, Document doc)
             throws Exception {
            Element responseElement = doc.createElementNS(
                  "http://studies.yasuabe.net/spring/ws/ex1", "greetResponse");
            String name = req.getFirstChild().getTextContent();
            Text responseText = doc.createTextNode(String.format("Hello,%s!", name));
            responseElement.appendChild(responseText);
    
            return responseElement;
       }
    }
■ 手順 (実行)
  • Servers ビューで Tomcat にex1 プロジェクトを追加
  • Servers ビューからTomcat サーバを [start] する
  • soapUI でプロジェクトを作成し、以下のURLのWSDLを追加
    http://localhost:8080/ex1/greetingService/greeting.wsdl
  • リクエストを投げてみて以下のようになることを確認 <リクエスト>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ex1="http://studies.yasuabe.net/spring/ws/ex1">
       <soapenv:Header/>
       <soapenv:Body>
          <ex1:greetingRequest>World</ex1:greetingRequest>
       </soapenv:Body>
    </soapenv:Envelope>
    <レスポンス>
    <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
       <SOAP-ENV:Header/>
       <SOAP-ENV:Body>
          <greetResponse xmlns="http://studies.yasuabe.net/spring/ws/ex1">Hello,World!</greetResponse>
       </SOAP-ENV:Body>
    </SOAP-ENV:Envelope>
■ 所感
  • Spring と CXF の相性を調べている途中で、たまたま Spring Web Service を使ってみた
  • 本来の作法としては、Endpoint でリクエストを処理するのではなく DI で関連付けられた サービスに処理を委譲するのが正しいようだけど、実験の意図と関係ないので割愛した。
  • 期待したほどお手軽ってわけでもない。

2009年12月19日土曜日

Nested Set Model / MySQL

階層構造のデータを RDB で扱うときの定番手法に、Nested Set Model というのがある。[参考:Managing Hierarchical Data in MySQL]

ただ知られ始めたのが割りと最近なためか、まだそれほど普及していなくて、自テーブル上の親レコードを再帰参照する作り(Adjacency List Model)のものが、今でも多い。(確か情報処理技術者試験のテクデでも Adjacency List Model の過去問があった気がする。)また一個のレコードの追加・削除が、他の多くのレコードの更新を発生させる事があるので、データの用途によってはそもそも向いていない場合もある。

ただし、検索メインの用途の場合、Nested Set Model の方が格段に扱い易いので、Adjacency List Model から リファクタする機会もあるかもしれない。そこで lft 列と rgt 列を追加するやり方を考えて、備えておきたい。

■ 準備
題材は冒頭に書いた MySQL サイトの例を引用してみる。DB は MySQL を使用。

まず、こんな SQL を流して、テーブルを用意する。
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL);

INSERT INTO category
VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),
(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);


中身はこんなレコードになる。
+-------------+----------------------+--------+
| category_id | name | parent |
+-------------+----------------------+--------+
| 1 | ELECTRONICS | NULL |
| 2 | TELEVISIONS | 1 |
| 3 | TUBE | 2 |
| 4 | LCD | 2 |
| 5 | PLASMA | 2 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 6 |
| 8 | FLASH | 7 |
| 9 | CD PLAYERS | 6 |
| 10 | 2 WAY RADIOS | 6 |
+-------------+----------------------+--------+


やりたい事は、このテーブルに lft 列とrgt 列を追加し、Nested Set になるよう値を入れること。

■ やり方
  • 以下のように lft 列とrgt 列を追加する。
    alter table category add column lft int;
    alter table category add column rgt int;
  • さらに以下のようなプロシージャを定義する。
    DELIMITER //
    DROP PROCEDURE IF EXISTS fill_RL //
    CREATE procedure fill_RL(IN parent INT, IN lft INT, OUT rgt INT)
    BEGIN
    DECLARE finished INT DEFAULT 0;
    DECLARE pk int;
    DECLARE cur CURSOR FOR
    SELECT category_id FROM category AS c
    WHERE c.parent=parent || (c.parent IS NULL && parent IS NULL);
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;

    OPEN cur;

    for_each: LOOP
    FETCH cur INTO pk;
    IF finished THEN
    LEAVE for_each;
    END IF;

    CALL fill_RL(pk, lft+1, rgt);

    UPDATE category AS c SET c.lft=lft, c.rgt=rgt WHERE c.category_id = pk;
    SET lft = rgt + 1;
    END LOOP for_each;

    CLOSE cur;

    SET rgt = lft;
    END //
    DELIMITER ;


仕組みは再帰呼び出しを使ったもので、子要素は親要素の lft に1を加えたものを 自らの lft とし、親要素は子要素の rgt に1を加えた値を自らの rgt とするという単純なもの。

但し、これを呼び出す前に MySQL のグローバル変数 @@max_sp_recursion_depthを適当な深さに設定する必要がある。
mysql>set @@max_sp_recursion_depth=10;


準備ができたので実行してみると、以下のような結果を得る。
mysql> call fill_RL(null, 1, @tmp);
mysql> select * from category;
+-------------+----------------------+--------+------+------+
| category_id | name | parent | lft | rgt |
+-------------+----------------------+--------+------+------+
| 1 | ELECTRONICS | NULL | 1 | 20 |
| 2 | TELEVISIONS | 1 | 2 | 9 |
| 3 | TUBE | 2 | 3 | 4 |
| 4 | LCD | 2 | 5 | 6 |
| 5 | PLASMA | 2 | 7 | 8 |
| 6 | PORTABLE ELECTRONICS | 1 | 10 | 19 |
| 7 | MP3 PLAYERS | 6 | 11 | 14 |
| 8 | FLASH | 7 | 12 | 13 |
| 9 | CD PLAYERS | 6 | 15 | 16 |
| 10 | 2 WAY RADIOS | 6 | 17 | 18 |
+-------------+----------------------+--------+------+------+
10 rows in set (0.03 sec)

図式化すると下図のようになる。
ELECTRONICS120
 ├TELEVISIONS29
 │ ├ TUBE34
 │ ├ LCD56
 │ └ PLASMA78
 └PORTABLE ELECTRONICS1019
   ├ MP3 PLAYERS1114
   │ └ FLASH1213
   ├ CD PLAYERS1516
   └ 2 WAY RADIOS1718


元サイトのサンプルと比べると結果が正しいことが分かる。

ちなみに Nested Set の便利な使い方はこんな感じになる。
  • 直下の子だけじゃなくて、子孫要素の一括検索ができる
    SELECT child.name
    FROM category AS child, category AS parent
    WHERE parent.lft < child.lft && child.rgt < parent.rgt
    AND parent.name = 'PORTABLE ELECTRONICS';
  • ルートからのパスも一括で調べられる(深さも同様)
    SELECT parent.name
    FROM category AS node, category AS parent
    WHERE node.lft BETWEEN parent.lft AND parent.rgt
    AND node.name = 'FLASH'
    ORDER BY parent.lft;


====
リファクタリングの流れを考えると、上記作業で Nested Set の列を追加した後、もともとのparent 列を使っているコードを徐々に lft/rgt を使うコードに移行して(もちろんデグレ止めのテストコードを動かしながら)、最終的にparent 列を削除するという運びになると思う。

便利だけど、OR マッパを使う場合もう一手間か二手間かけることになるはず。おいおい考えておきたい。

2009年12月18日金曜日

AspectJ で using 的な構文を模倣してみる (2)

前回のポストで試した using ぽい書法を、改善してみる。 前に書いた叩き台では、リソースクラスに解放用のインターフェイス Disposable を実装させていたが、任意のクラスに対応できるように抽象化する。こんなコードにしてみた。
public abstract aspect UsingAspect<T, U> {
  protected T obj;

  pointcut settingField(T o): set (T+ U+.*) && args(o);
  pointcut using(): call(U+.new(..));

  after(T o): settingField(o) {
    this.obj = o;
  }
  after(): using() {
    endUsing(this.obj);
  }

  protected abstract void endUsing(T o);
}
この Generic な抽象アスペクトを、それぞれのリソース型について以下のように具体的する。
  • 前回の Disposable オブジェクトの例。
    public interface UsingDisposable {
      public static aspect DisposableAspect
          extends UsingAspect<Disposable, UsingDisposable> {
        @Override
        public void endUsing(Disposable o) {
          if (null != o)((Disposable)o).dispose();
        }
      }
    }
  • java.io の Writer に適用する例
    interface UsingWriter {
      public static aspect UsingWriterAspect 
          extends UsingAspect<Writer, UsingWriter> {
        @Override
        protected void endUsing(Writer o) {
          try { if (null != o) o.close(); } 
          catch (IOException ex) { throw new RuntimeException(ex); }
        }
      }
    }
リソースオブジェクトごとに違うのは、つまるところオブジェクトの型と解放方法なので、カスタマイズするのはその部分だけに留めたい。取り敢えず一つの実装方法としてはこんな風なコードになる。( pointcut の定義で
pointcut using(): call(Using<T>+.new(..));
みたいなパラメタライズした書き方ができれば、更に堅牢で経済的なコードになるけど、残念ながら文法的に許されないらしい。) クライアントコードは以下のようになる
public class Client {
  public static void main(final String[] args) throws Exception {
    new UsingWriter(){
        PrintWriter s = new PrintWriter("test.txt"); {
      s.println("one line written");
      s.flush();
    }};
  }
}
デバッガを使うとか、コンソール入力を待つ行を追加するとかして、適当な箇所で止めながら動かすと、無名クラス生成ブロックの中でだけ ファイル test.txt にロックがかかっているのが確かめられる。 あとはマルチスレッドとネストか。 } } 実行すると以下のようなコンソール出力を得る。
A("r1") used with "x"
B("r2") used with "y"
A("r3") used with "z"
A("r3") disposed
B("r2") closed
A("r1") disposed
割と簡単に using のネストができた。一応、だんだんと実用的になってきた気がする。取りあえずテストコードで使い始めて、パフォーマンスなんかを中心にしばらく観察してから、問題が無ければプロダクトコードで使ってみるような流れで良いか。

2009年12月17日木曜日

AspectJ で using 的な構文を模倣してみる

C# の using のような感じで Java でも書いてみたいが、現時点の Java (SE 6) では文法的に無理があるので、AspectJ も併用して、どんな風になるか試してみる。 ■ 概要 要点は、あるコードブロックの冒頭でリソースの使用を宣言すると、そのコードブロックの終わりでリソースが自動的に開放されるという事。さらに、次の2点も実現したい。
  • そのスコープをブロックの内部に限定してリソースを隔離する事
  • try/finally のような感じで、例外発生時にもちゃんとリソースが開放される事。
■ 問題 以下のような模擬リソースクラスがあるとする。
public interface Disposable {
   void dispose();
}

public class SomeResource implements Disposable {
   public void write(String str) {
      System.out.println(str);
   }
   public void throwException() {
      throw new RuntimeException("something wrong");
   }
   @Override
   public void dispose() {
      System.out.println("disposed");
   }
}
この模擬リソースを[概要]の項に書いたような要件のもとで使う場合、普通の Java で書くと例えば以下のようなコードになる。
public class Client {
   public static void main(final String[] args) {
      {
         SomeResource d = new SomeResource();
         try {
            d.write(args[0]);
         } finally {
            if (null != d) d.dispose();
         }
      }
   }
}
これをリソースの扱いを簡略化した以下のようなコードに書き直して、なおかつ等価な動作を実現するような AspectJ コードを考えてみたい。d.write() の代わりに d.throwException()を呼んで例外を発生させても d.dispose() が実行される事にも留意する。
public interface Using {}
public class Client {
   public static void main(final String[] args) {
      new Using() { SomeResource d = new SomeResource(); {
         d.write(args[0]);
      }};
   }
}
■ 解1 一番単純な解の一つは、とりあえずこんな感じになると思う。
public aspect DisposeAspect {
   Disposable disposable;
   pointcut settingField(Object o): set (* Using+.*) && args(o);
   after(Object o): settingField(o) {
      disposable = (Disposable)o;
   }
   after(): call(Using+.new()) {
      disposable.dispose();
   }
}
実行引数として"hello"を与えて実行すると、コンソールに"hello"と"disposed"が表示される。また write() の代わりに throwException() を呼ぶとスタックトレースと"disposed"が表示される。 ■ 課題 上記コードは Proof of concept として書いた叩き台で、実用的なものにするにはさらに以下のような課題がある。
  • 抽象化して使いまわせるようにする事。例えば、Disposable 実装クラスだけじゃなくJDBC の Connection などのような任意のオブジェクトにも使えるようにしたい。
  • マルチスレッドで使えるようにする事。
  • ネストした場合について考えること
おいおい考えてみたい。

2009年12月16日水曜日

AspectJ : 派生した場合の優先順位

抽象アスペクトを extends して派生アスペクトを定義し、両方で同じ pointcut に advice したらどうなるか。以下のようなコードで試してみた。
package p3;

public class App {
   public static void main(String[] args) {
      new Foo().foo();
   }
}
class Foo {
   void foo() { System.out.println("foo"); }
}
abstract aspect AspectX {
   abstract pointcut p();
   before(): p() {System.out.println("before x.p");}
   after(): p() {System.out.println("after x.p");}
}
aspect AspectY extends AspectX {
   pointcut p() : execution(void p3.Foo.foo());
   before(): p() {System.out.println("before y.p");}
   after(): p() {System.out.println("after y.p");}
}
こんな出力になった(色分けは分かりやすくするため)。規定クラスが内側になるらしい。
before y.p
before x.p
foo
after x.p
after y.p

2009年12月15日火曜日

XSLT で 3元連立方程式を解いてみる

XSLT で 3元連立方程式を解いてみる。

3変数の式3本を、こんな風にXMLで表すとする。(例は手元の本とネットの数学記事から持ってきた。)
例1
x + y + z = 1
2x - y + 2z = -1
x + 2y + 3z = 0
<vec-expr>
<x>1</x><x>2</x><x>1</x>
<y>1</y><y>-1</y><y>2</y>
<z>1</z><z>2</z><z>3</z>
<p>1</p><p>-1</p><p>0</p>
</vec-expr>
例2
x + 2y + 3z = 10
4x + 7y + 8z = 12
5x + 6y + 9z = 11
<vec-expr>
<x>1</x><x>4</x><x>5</x>
<y>2</y><y>7</y><y>6</y>
<z>3</z><z>8</z><z>9</z>
<p>10</p><p>12</p><p>11</p>
</vec-expr>

この XML を読み込んで、x, y, z を求める XSLT を定義したい。解き方はクラメールの公式を使うことにする。なので行列式を計算するテンプレートが必要になるが、これを別の XSL ファイル(determinant.xsl)に分けてみる。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="calc-det-recur">
<xsl:param name="a"/>
<xsl:param name="b"/>
<xsl:param name="c"/>
<xsl:param name="n" select="3"/>
<xsl:choose>
<xsl:when test="0 &lt; $n">
<xsl:variable name="rec-result">
<xsl:call-template name="calc-det-recur">
<xsl:with-param name="a" select="$a"/>
<xsl:with-param name="b" select="$b"/>
<xsl:with-param name="c" select="$c"/>
<xsl:with-param name="n" select="$n - 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="plus-term"
select="$a[$n] * ($b[$n mod 3 +1]) * ($c[($n +1) mod 3 +1])"/>
<xsl:variable name="minus-term"
select="$a[$n] * ($b[($n +1) mod 3 +1]) * ($c[$n mod 3 +1])"/>
<xsl:value-of select="$rec-result + $plus-term - $minus-term"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

この XSLT を用いて、x, y, z それぞれの係数および右辺を表すベクトルを組み合わせて、必要な行列式を計算し、各変数の解を求める XML を、以下のように別途記述する。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="determinant.xsl"/>

<xsl:output method="text"/>
<xsl:strip-space elements="*"/>

<xsl:template match="vec-expr">
<xsl:variable name="xyz">
<xsl:call-template name="calc-det-recur">
<xsl:with-param name="a" select="x"/>
<xsl:with-param name="b" select="y"/>
<xsl:with-param name="c" select="z"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="pyz">
<xsl:call-template name="calc-det-recur">
<xsl:with-param name="a" select="p"/>
<xsl:with-param name="b" select="y"/>
<xsl:with-param name="c" select="z"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="xpz">
<xsl:call-template name="calc-det-recur">
<xsl:with-param name="a" select="x"/>
<xsl:with-param name="b" select="p"/>
<xsl:with-param name="c" select="z"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="xyp">
<xsl:call-template name="calc-det-recur">
<xsl:with-param name="a" select="x"/>
<xsl:with-param name="b" select="y"/>
<xsl:with-param name="c" select="p"/>
</xsl:call-template>
</xsl:variable>

<xsl:text>x=</xsl:text><xsl:value-of select="$pyz div $xyz"/>
<xsl:text>&#010;</xsl:text>
<xsl:text>y=</xsl:text><xsl:value-of select="$xpz div $xyz"/>
<xsl:text>&#010;</xsl:text>
<xsl:text>z=</xsl:text><xsl:value-of select="$xyp div $xyz"/>
<xsl:text>&#010;&#010;</xsl:text>
</xsl:template>
</xsl:stylesheet>
冒頭の例を入力XMLとして与えて実行すると、以下のような結果を得る

例1例2
x=1
y=1
z=-1
x=-9.5
y=-1.2
z=7.3

2009年12月14日月曜日

jMock/JDave/unfinalizer

メソッドの final 修飾子を外す jdave-unfinalizer というツールを使ってみた。

メソッドやクラスについている private とか final は、余りむやみに外すべきじゃないけど、特にテスト目的でたまに必要になる。ただ、private なメソッドを呼び出すのは簡単だけど、final メソッドのオーバーライドはちょっと難しい。

というわけで、ユニットテストの中で final を外すやり方を試してみる。実験の段取りは
  1. テスト対象とモックされるオブジェクトを書いてみる
  2. 普通の JMock テストを書いて、ちゃんと動かす。
  3. モックされるオブジェクトのメソッドを final にして、テストが失敗することを示す。
  4. unfinalizerを使うと、final が外れて2と同じ結果が得られる事を確認する。
という運びになる。

■ 1.テスト対象とモック対象
テスト対象は Greeter クラスで、まあいかにも作為的な仕様だけど「文字列 word で初期化された Greeter オブジェクトは、greet() 実行時に Echoer オブジェクトの echo() メソッドに word を渡し、その戻り値をクライアントに返す。」というふうにする。
public class Greeter {
   private final String word;
   public Greeter(String word) {
      this.word = word;
   }
   public String greet(Echoer echoer) {
      return echoer.echo(word);
   }
}
・以下のクラス Echoer は Greeter と協動するクラスで、こちらがモック対象となる
public class Echoer {
   public String echo(String str) {
      return str;
   }
}
■ 2.普通に JMock テストを通す
以下のコードでは、「文字列 "hello" で初期化された Greeter の greet() が呼ばれると、それに伴って Echoer の echo() が"hello"というパラメータとともに、ただ一度呼ばれる。」という事を、モッキングで確かめている。
@RunWith(JMock.class)
public class GreeterTest {
   private Mockery context = new Mockery() {{
      setImposteriser(ClassImposteriser.INSTANCE);
   }};
   @Test
   public void testHello() {
      final Echoer echoer = context.mock(Echoer.class);
      context.checking(new Expectations(){{
         oneOf(echoer).echo("hello");
      }});
      new Greeter("hello").greet(echoer);
   }
}
ここまで書いて、Outline ビューから testHello() をJUnitで実行する。いつもどおりにモックテストが成功するはず。

■ 3.final をつけて失敗させる
ここで Echoer echo() に final をつけて ”public final String echo(String str)”というシグネーチャにして実行してみる。すると、テスト失敗のメッセージからは原因が分かりにくいが、とにかく失敗する。(デバッガで追ってみると、モックじゃない本物のメソッドが実行されいて、それのため一度だけ呼ばれるはずのメソッドが呼ばれていないと認識されている模様。)

■ 4.unfinalizer を使ってみる
  • まず、ここからJDave の zip をダウンロードし、中にある jdave-unfinalizer-1.1.jarをプロジェクト直下に置く。
  • 次に、下図のように GreeterTest.testHello() の JUnit 実行設定で、VM argument に -javaagent:${workspace_loc:/unfinalizer-test1}/jdave-unfinalizer-1.1.jarを指定する。
  • ここで再度 testHello()メソッドを実行してみると、今度は上手くいったことが確認できる。final メソッドのオーバーライドができた。


========
モックテストの中で final メソッドの呼び出しを検証するような状況は、テストファーストを守って開発している場合、多分めったにない。だけどテストコードがないプロダクトで、後付テストを書く場合なんかには、珍しい状況ではない。そんなとき時、やり方がわからないとちょっと困る。

とりあえずメソッドに関しては、これで確認できたし、クラスも同じ事だろう。あとは、enum の列挙値を、テスト実行時に追加できるような仕組みが欲しい場面もたまにある。これも考えておきたい。

AspectJ: declare precedence

AspectJ の declare precedence の挙動を確認してみた。 以下のようなクラス App とアスペクト AspectX があるとする。
public class App {
  void foo() {
    System.out.println("hello");
  }
}
aspect AspectX {
  pointcut x(): execution(* *.foo(..));
  before() : x() {
    System.out.println("before x");
  }
  Object around(): x() {
    System.out.println("around(1) x");
    Object result = proceed();
    System.out.println("around(2) x");
    return result;
  }
  after() : x() {
    System.out.println("after x");
  }
}
これを実行すると、以下のような出力になる。
before x
around(1) x
hello
around(2) x
after x
ここで更に、AspectX とほぼ等価な内容の AspectY を追加したらどうなるか。
aspect AspectY {
  pointcut y(): execution(* *.foo(..));
  before() : y() {
    System.out.println("before y");
  }
  Object around(): y() {
    System.out.println("around(1) y");
    Object result = proceed();
    System.out.println("around(2) y");
    return result;
  }
  after() : y() {
    System.out.println("after y");
  }
}
出力は以下のようになる。分かりやすいように AspectX の出力を赤、AspectY の出力を青に色分けした。
before x
around(1) x
before y
around(1) y
hello
around(2) y
after y
around(2) x
after x
上の例では、たまたまAspectX が優先となっているが、この優先順位を明示するには、declare precedenceを記述する。(ここでは AspectY に追加した。)
aspect AspectY {
  declare precedence: AspectY, AspectX;
  pointcut y(): execution(* x.foo(..));
  before() : y() {
  ・・・略
こうすると、以下のように前後関係が逆転する。
before y
around(1) y
before x
around(1) x
hello
around(2) x
after x
around(2) y
after y

2009年12月13日日曜日

Cygwin: Git の起動失敗 ⇒ zlib 入れ替え

Cygwin setup.exe の devel グループ下に git があったので、インストールしてみた。ネット上の記事をチラ見しながら、適当にリポジトリ用のディレクトリを掘って、$ git initとやってみる。

ところが何も起こらない。期待通りなら "Initialized empty Git repository in ~"なんてメッセージが出て、.gitディレクトリが作成されるはずだが、何ともなってない。

という現象の解決策が分かったのでメモしておく。

■ 現象の起きたバージョン
・Cygwin 1.5.25
・Git 1.6.1.2

■ 原因
・Git 実行に必要なバージョンの zlib.dll と、Cygwin のデフォルト(最新)のものが違う($ echo $?で 53 が表示される。)

■ 対策
setup.exe をやり直す。このとき Base カテゴリ下ある zlib(zlib0ではなく)のバージョンを、1.2.3-3 から 1.2.3-2に下げる(他は Keepのまま)。

これでうまく動くはず。やってみると、冒頭に書いたような期待通りの動きになった。

Cygwin: bash の更新失敗

以前、Cygwin を更新しようとして、setup.exe を実行したところ、bash をアンインストールするフェーズでクラッシュした事があった。
またそれ以降、vi を使おうとすると、一応は作業できるものの、起動時に以下のようなメッセージで不調を訴えていたことがあった。なんか vi が 依存している terminfo というものが、bash の更新失敗の影響で、正しくインストールされていない状態になっている模様。
E558: Terminal entry not found in terminfo
'cygwin' not known. Available builtin terminals are:
builtin_riscos
builtin_amiga
builtin_beos-ansi
・・・


しばらく放置していたが、簡単に修正できることが分かったので、メモしておく。

  • /etc/setup/bash.lst.gzを削除する
  • setup.exe をやり直す


これで、bash を含む 更新が全部上手く言って、vi のエラーメッセージも消えた。

XSLT で sin を求める

XSLT で 三角関数を求めてみる。0度から90度までの sin が求まればいいので、定義域は[0, 90)とした。

やり方は、角度とsin値の対応表を作っておいて、その表と与えられた角度を照合し、表にない角度については単純に線形補間する。

線形補間は直線を折り曲げたカクカクしたグラフで大体の値を求めるやり方で、例えば、30度と45度が表にあるときの sin35度の値は以下のように計算する。
sin(35) ≒ sin(30) + ((35 - 30) / (45 - 30)) * (sin(45) - sin(30))
電卓で計算すると、以下のようになる。
左辺 ≒ 0.5736
右辺 ≒ 0.5690

XSLT 的なポイントとしては、RDB のマスタテーブル的な XMLファイルを用意しておいてXSLTから参照する事と、あとはテンプレートの再帰呼び出しのあたりか。

まず sin値の表を以下のように用意する。
<?xml version="1.0" encoding="UTF-8"?>
<sine-table>
<sine digree="0">0</sine>
<sine digree="15">0.259</sine>
<sine digree="30">0.5</sine>
<sine digree="45">0.707</sine>
<sine digree="60">0.866</sine>
<sine digree="75">0.966</sine>
<sine digree="90">1</sine>
</sine-table>

この表に基づいて sin値を計算する XSLT を、次のように定義する。
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<result-list>
<xsl:apply-templates/>
</result-list>
</xsl:template>
<xsl:template match="digree">
<result>
<xsl:value-of select="."/>
<xsl:text> ⇒ </xsl:text>
<xsl:call-template name="sine0-90">
<xsl:with-param name="d" select="."/>
<xsl:with-param name="sines" select="document('sin-table.xml')/sine-table/sine"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="sine0-90">
<xsl:param name="d"/>
<xsl:param name="sines"/>
<xsl:choose>
<xsl:when test="$sines[2]">
<xsl:choose>
<xsl:when test="$d &lt; $sines[2]/@digree">
<xsl:variable name="a" select="$sines[1]/@digree"/>
<xsl:variable name="b" select="$sines[2]/@digree"/>
<xsl:variable name="fa" select="$sines[1]"/>
<xsl:variable name="fb" select="$sines[2]"/>
<xsl:value-of select="$fa + ($fb - $fa) * ($d - $a) div ($b - $a)"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sine0-90">
<xsl:with-param name="d" select="$d"/>
<xsl:with-param name="sines" select="$sines[position() > 1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>


この XSLT に下図左のXML を読ませると、下図右の出力XMLを得る。
<sin-test>
<digree>0</digree>
<digree>30</digree>
<digree>35</digree>
<digree>40</digree>
<digree>45</digree>
</sin-test>
<result-list>
<result>0 ⇒ 0</result>
<result>30 ⇒ 0.5</result>
<result>35 ⇒ 0.569</result>
<result>40 ⇒ 0.6379999999999999</result>
<result>45 ⇒ 0.707</result>
</result-list>

電卓の値と比較すると、だいたい合ってる。上の例は直角をたった6分割しただけの粗いものだけど、細かく分割すれば精度も上がる。

AspectJ design pattern: Participant

Aspectj in Action: 第一版に載ってた、Participant パターンについて。
class ClassA {
   public void foo() {
      System.out.println("ClassA.foo() called");
   }
   public void bar() {
      System.out.println("ClassA.bar() called");
   }
}
public class App {
   public static void main(String[] args) {
      ClassA a = new ClassA();
      a.foo();
      a.bar();
   }
}
上のコードに作用して、以下のように星印をつけてコンソール出力を修飾する Star アスペクトがあるとする。
アスペクト適用なしStar アスペクト適用
ClassA.foo() called
ClassA.bar() called
 ⇒ 
★ClassA.foo() called
ClassA.bar() called
上の例では★がついているのは ClassA.foo() だが、後々の拡張性を考えて、どのメソッドが★印修飾されるかについて、アスペクト定義の側にはハードコードしたくない。また、後で★印修飾を適用するメソッドは、ジョインポイントにマッチさせるためのシグネーチャの制約(例えば命名規約だとか)をつけたくない。こういう状況で、Participant パターンが適用できる。 まず以下のように抽象アスペクトを定義する
public abstract aspect Star {
   public abstract pointcut needStar();
   Object around() : needStar() {
      System.out.print("★");
      return proceed();
   }
}
次に、ClassA のクラス定義内で具象アスペクトを定義する。
class ClassA {
   public static aspect StarForA extends Star {
      public pointcut needStar(): call(void ClassA.foo());
   }
   public void foo() {
   ...略
}
こんな風にして、上図右のような★印修飾がついたコンソール出力が得られる。 さて、後でClassB を追加して、App.main() を以下のように変更したとする
public static void main(String[] args) {
   ClassA a = new ClassA();
   ClassB b = new ClassB();
  
   a.foo();
   a.bar();
   b.baz();
}
ここで、メソッド baz() にも★印修飾を与えたい場合、以下のように書けばよい。
class ClassB {
   public static aspect StarForB extends Star {
      public pointcut needStar(): call(void ClassB.baz());
   }
   public void baz() {
      System.out.println("ClassB.baz() called");
   }
}
こうすれば、Starアスペクトへの変更も、baz() へのシグネーチャ制約も無く、以下のような出力が得られる。
★ClassA.foo() called
ClassA.bar() called
★ClassB.baz() called
■所感
  • GoF の Template パターンでは、変更を与えるポイントが決まっていて、そのポイントでの振る舞いをカスタマイズする。一方、この Participant パターンでは、ある一定の振る舞いの変化を、どこに適用するかについてカスタマイズする。つまり Template パターンとParticipant パターンで再利用部分とカスタマイズ部分が逆になっているが、これが OOP と AOP の典型的な違いのような気がする。
  • 具象側のアスペクトでポイントカットを定義する代わりに、アノテーション を使うやり方もある。上の例では、内部アスペクトを定義してクラス単位のジョインポイント管理にしていたが、アノテーションを使うとメソッド単位で アスペクトに参加するメソッドを宣言することになり、より粒度の細かい管理になる。
    pointcut p() : execution(@Hoge * *.*());

2009年12月12日土曜日

ear, war の読み方

Java の仕事をしていると、WAR(Web ARrchive) をワーと発音する人をみかける。また、EAR(Enterprise ARchive) をアーなんて発音する人も多い。ちょっと気になるので調べてみた。

まずは Youtube で 英語圏の人たちがどう発音しているか観察してみる。ejb だとか Tomcat だとかで検索すると、いろんなのがヒットする。例えば、以下のビデオでは、01分43秒のところで、次のように話しているのが聞き取れる。

http://www.youtube.com/watch?v=wIKdM-d7FiA

We go on a little bit about packaging our application. This is EAR, WAR and JAR files.

普通の英単語と同じように WAR ⇒ /wɔr/、EAR ⇒ /ɪər/、JAR ⇒ /dʒɑr/って言ってる(記号はここ参照)。「war」は、「戦争」という意味でも 「Web Archive」というい意味でも、発音については/wɔr/ で同じらしい。まあ極めて自然。

さて改めて、日本語による WAR の発音を考えてみると、結局「WAR」という英字スペルにマッピングするカタカナの配列を新たに発明するのではなく、普通の英単語「war」、あるいは発音記号 /wɔr/ で表される音列を表すときに、われわれ日本人がこれまでどんなカタカナの組み合わせを使ってきたかを考えてみれば良いのだと思う。

というか、・・・うーん、どうかんがえても「ウォー」以外ないのではなかろうか。「war」で考えても /wɔr/ で考えても「ワー」は無いわ。いや普段から「スター・ワーズ」とか、「ワン・ツー・スリー・ファー」とかいった感じで喋ってる人なら問題ないかもしれないけど。

あと warning を ワーニングって言う人も多いけど、これはある意味 war を「ワー」って言う以上になんか悲しい。WAR は Web Archive の略称でもあるから別の読みがあっても不思議じゃないけど、warning はどこから見ても普通の単語だし。まあ普段から「ハート・ワーミング・ストーリー」とか「ワーミング・アップ」なんて感じで喋ってる人なら、ある意味、一貫性があるけどさ。

ear も同じことで、イアーとイヤーで迷うなら分かるけど、何故「アー」なのか不思議。なんか earth とか heart あたりからインスパイアされたりしてるんだろうか。

ちなみに、false をファルス(男根の意味)と読むのも恥ずかしい。人前で喋るのを生業とする人たちにとって、彼らが「噛んだ」とき、単にいくつかの音節を発音し損ねるだけじゃなく、別の言葉として成立してしまうような噛み方をしてしまうのが最悪だというけど、それにも通じるパターンだ。いや、気付いていないという点で、それ以上かも。

AspectJ: Worker Object Creation pattern

AspectJ デザインパターンの Worker Object Creation というのをやってみる。 下のコードを実行すると、コンソールに1,2,3 と順番に表示されるが、これを3,2,1 と逆順で実行されるようにしてみる。本番の開発では、そんなことしないだろうけど、実験として試してみたい。
public class App {
  void foo() {
    bar(1);
    bar(2);
    bar(3);
  }
  void bar(int number) {
    System.out.println(number);
  }
  public static void main(String[] args) {
    new App().foo();
  }
}
AspectJ In Action (古い版)に Worker Object Creation という技が載っていて、メソッド呼び出しをオブジェクトにくるんで管理したり間接的に実行するパターンが、そう呼ばれている。これを応用すると以下のようなアスペクト定義で、たとえば上のような逆に実行すといった制御も実現できる。
public aspect Reverse {
   static interface Worker { void run(); } 
   Stack<Worker> history = null;
   pointcut callFoo(): execution(void App.foo());
   pointcut callBar(): execution(void App.bar(int));
   before (): callFoo() {
      history = new Stack<Worker>();
   }
   after (): callFoo() {
      while (!history.empty()) history.pop().run();
   }
   void around(): callBar() {
      history.push(new Worker(){
         public void run() { proceed(); }
     });
   }
}
動かしてみると、bar() 呼び出しが逆順に実行され、コンソールに 3,2,1 と表示される。 AspectJ In Action のサンプルコードでは、メソッド呼び出しを Runnable.run()からproceed() して非同期にしている。また同書10章「Authentication and Authorization」の、PrivilegedExceptionAction.run() からproceed() しているイディオムも、worker object creation の一例。おそらくこういった別スレッド化やセキュリティ対応などといったアスペクトが、このパターンの典型的な使い型だろうけど、やればこんな変な事もできるというのを試してみた。

2009年12月11日金曜日

AspectJ design pattern: Wormhole

Aspectj in Action: 第一版に載ってた、デザインパターン。
public class ClassA {
   ClassB b = new ClassB();
   void foo(int count) {
      for (int i = 0; i < count; ++i) b.bar(i);
   }
}
class ClassB {
   void bar(int i) { new ClassC().baz(i); }
}
class ClassC {
   void baz(int i) { System.out.println(i); }
}
上のコードを "new ClassA().foo(3);" のように実行すると、下図左のような出力になるが、これを下図右のように最後の1行だけ修飾したい。
0
1
2
0
1
last -> 2
このためには baz() 呼び出し時に、foo() 呼び出し時に渡された count が参照できなければならないが、bar()、baz() のシグネーチャは汚したくない。こういうときに AspectJ の Wormhole パターンが使える。 もとのコードは変えないまま、こんなアスペクトで上記の結果が得られる。
public aspect AspectX {
   pointcut foo(int count) : execution(void ClassA.foo(int)) && args(count);
   pointcut baz(int i) : execution(void ClassC.baz(int)) && args(i);
   pointcut wormhole(int count, int i): cflow(foo(count)) && baz(i);
   before(int count, int i) : wormhole(count, i) {
      if (count == i + 1) System.out.print("last -> ");
   }
}

2009年12月10日木曜日

SDO 1.1.1/Galileo/m2eclipse

Eclipse 3.5 Galileo で SDO を動かす Getting Started。 クラスパス上のXMLスキーマを読み込み、それに従って XML を解釈し、文字列 "Hello" と"World"を取り出して合成し、コンソールに"Hello, World!"を出力する。 ■ プロジェクトの作成
  • 新規 Maven Project を作成する
  • pom.xml に以下を追加する
    <dependencies>
       <dependency>
          <groupId>org.apache.tuscany.sdo</groupId>
          <artifactId>tuscany-sdo-impl</artifactId>
          <version>1.1.1</version>
       </dependency>
    </dependencies>
  • 新規ソースフォルダ src/main/resources を作成
■ hello world 作成
  • src/main/resources に greeting.xsd を作成する。
    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://sample/sdo-test1" 
        targetNamespace="http://sample/sdo-test1">
      <xsd:element name="greeting" type="GreetingType"/>
      <xsd:complexType name="GreetingType">
        <xsd:sequence>
          <xsd:element name="word"   type="xsd:string"/>
          <xsd:element name="to"   type="xsd:string"/>
        </xsd:sequence>
      </xsd:complexType>
    </xsd:schema>
  • App クラスを書き換える
     public static String xmlDoc = 
            "<?xml version=\"1.0\" encoding=\"ASCII\"?>\n"
          + "<greeting xmlns=\"http://sample/sdo-test1\">\n"
          + "  <word>Hello</word>\n"
          + "  <to>World</to>\n"
          + "</greeting>\n";
    
     public static void main(String[] args) throws Exception {
        HelperContext scope = SDOUtil.createHelperContext();
        XSDHelper xsdHelper = scope.getXSDHelper();
    
        URL url = App.class.getResource("/greeting.xsd");
        InputStream is = url.openStream();
        xsdHelper.define(is, url.toString());
        is.close();
    
        XMLDocument doc = scope.getXMLHelper().load(xmlDoc);
    
        DataObject greeting = doc.getRootObject();
        System.out.printf("%s, %s!%n", 
            greeting.get("word"), greeting.get("to"));
     }
■ 雑感 わりとすんなり動いた。参考にしたのは、tuscany-sdo-1.1.1 の sampleで、これは、コメンタリを出力しながら複数のサンプルが連続して動作するような、ちょっと趣向を凝らしたフレームワーク的なものだけど、ただ、コードの断片だけ参考にしたい時にはちょっと回りくどいかもしれない。