DIVE INTO S2 .HACK : INCLUDE PART II+

Seasar2に慣れ親しむためにS2Containerをプチハックしてみる第2.5弾。 (^^;;;

  • カスタムPathResolverを使って、S2Container間の依存関係(include)をDEPENDENCY-LOOKUPからDEPENDENCY-INJECTIONにしてみる。
  • てゆうか、既存のdiconファイルの設定を変更することなく、一部のdiconファイルを差し替える。 (^^;;;

ResourceResolverを使用してやってみる予定だったのですが、PathResolverをいじらずに、前回とおなじようなことをするためには、XmlS2ContainerBuilderにも変更を加える必要があったので、前のPathResolver版に蛇の足を付けてお茶を濁す
今回は、既存のdiconファイルを、さらに上位のdiconファイルでインクルードすることで、diconツリーの一部のdiconを差し替えてみたり…なんか、視点が、DEPENDENCY-LOOKUPとかINJECTIONとかじゃなくなってきたような…orz

    • 想定する既存のdiconファイル群は、こんな感じです。*1
    • ごく普通のdiconファイルたち。

■org/seasar/framework/container/factory/PathAliasResolverTest2.Node0a.dicon





component0
component1
component2

"Here is node0a container."

■org/seasar/framework/container/factory/PathAliasResolverTest2.Node1a.dicon




"Here is node1a container."

■org/seasar/framework/container/factory/PathAliasResolverTest2.Node2a.dicon



"Here is node2a container."

■org/seasar/framework/container/factory/PathAliasResolverTest2.Node2b.dicon



"Here is node2b container."

    • で、これらの上位に位置させて、既存のdiconを差し替えるためのルートdiconファイルがこちら。
    • 1目のincludeで、Node2aをNode2bに差し替えている。

■org/seasar/framework/container/factory/PathAliasResolverTest2.dicon



<include path="org/seasar/framework/container/factory/PathAliasResolverTest2.Node2a.dicon
=org/seasar/framework/container/factory/PathAliasResolverTest2.Node2b.dicon"/>


    • テストコードは、こんな感じで。

■org/seasar/framework/container/factory/PathAliasResolverTest2.java


package org.seasar.framework.container.factory;

import java.util.List;

import junit.framework.TestCase;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.util.ClassUtil;

/**
* @author hidebsd
*/
public class PathAliasResolverTest2 extends TestCase {

private static final String BASE = ClassUtil.getResourcePath(PathAliasResolverTest2.class).replaceAll("\\.class$", "");

private static final String S2CFG = BASE + ".Configuration.dicon";

private static final String PATH0 = BASE + ".Node0a.dicon";

private static final String PATH1 = BASE + ".dicon";

public void setUp() {
S2ContainerFactory.configure(S2CFG);
}

public void testDefaultSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH0);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
assertEquals("node" + i, "Here is node" + i + "a container.", list.get(i));
}
}

public void testOverrideSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH1);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
String node = "node" + i + (i != 2 ? "a" : "b");
assertEquals("node" + i, "Here is " + node + " container.", list.get(i));
}
}

}

    • で、S2コンテナカスタマイズ用diconはこんな感じで。PathResolverインターフェースを実装しているクラスがあると差し替えられるので、これでOK。
    • ちなみに、s2container.dicon というファイル名でクラスパスのルートに置けば、明示的に S2ContainerFactory.configure(…) しなくても、自動的にそのファイルを読み込んで、S2コンテナがカスタマイズされます。

■org/seasar/framework/container/factory/PathAliasResolverTest2.Configuration.dicon





    • で、本命のPathResolverインターフェースを実装したPathAliasResolverなるクラスを作成。
    • delimiterは、正規表現パターンの一部となるので、指定する際には、場合によりエスケープが必要。

■org/seasar/framework/container/factory/PathAliasResolver.java


package org.seasar.framework.container.factory;

import java.util.HashMap;
import java.util.Map;

import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.StringUtil;

/**
* @author hidebsd
*/
public class PathAliasResolver implements PathResolver {

private static final String TRIM = "[\\n\\r\\t ]*";

private static final String dummyConfigPath = ClassUtil.getResourcePath(
PathAliasResolver.class).replaceAll("\\.class$", "Dummy.dicon");

private final Map aliasDefinitions = new HashMap();

private String delimiter = "=";

public PathAliasResolver() {
}

public PathAliasResolver(String delimiter) {
this.delimiter = delimiter;
}

public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}

public String resolvePath(String context, String path) {
if (path == null) {
throw new IllegalStateException("Specified path was null.");
}
path = trimPath(path);
if (path.contains(delimiter)) {
path = registerPathAlias(path);
}
if (aliasDefinitions.containsKey(path)) {
path = resolvePath(path);
}
return path;
}

private String trimPath(String path) {
path = path.replaceAll("^" + TRIM, "");
path = path.replaceAll(TRIM + "$", "");
path = path.replaceAll(TRIM + delimiter + TRIM, delimiter);
return path;
}

private String registerPathAlias(String path) {
int pos = path.indexOf(delimiter);
String alias = path.substring(0, pos);
if (!aliasDefinitions.containsKey(alias)) {
path = path.substring(pos + delimiter.length());
path = (path.length() != 0 ? path : dummyConfigPath);
aliasDefinitions.put(alias, path);
}
return dummyConfigPath;
}

private String resolvePath(String path) {
String resolvedPath = (String) aliasDefinitions.get(path);
if (resolvedPath == null) {
return path;
}
return resolvedPath;
}

}

    • それから、diconファイルパスの別名を登録する場合、返すパスがないのですが、なにかしらdiconファイルのパスを返す必要があったので、ダミーのdiconファイルを作成。
    • これは、なんとかならないかなぁ…。やっぱりIncludeTagHandlerで対処するしかないかなぁ…、ま、実行に影響はないからいっか…。

■org/seasar/framework/container/factory/PathAliasResolverDummy.dicon



    • では、テスト開始〜!
    • RED RED RED RED RED RED GREEN!
    • Y-A-T-T-A-A !!!
    • さらなる深みを目指して、DIVE INTO S2 !

*1:xml宣言とDTDの部分は省略してあります

IT ARCHITECT SUMMIT 2006 SUMMER

ITアーキテクト・サミット 2006 Summer」に行ってきました。場所は大手町。そういえば、20世紀最後の大晦日から21世紀の元旦にかけて、この近くで仕事してたなぁ…。

  • K-1自治体をつなぐSOA基盤構築への取り組み / HARP 若月 誠 氏
    • 遅刻しました…。これ聴きたかったんだよなぁ…日頃の行いが仇に…orz。
  • 【SP-1】サービス指向アーキテクチャの実践 / マイクロソフト株式会社 成本 正史 氏
    • 実際の事例に携わった経験を踏まえたお話は、説得力がありました。また、マイクロソフトっぽくなく (偏_見;;; 、ユーザ本位の姿勢がとても好印象でした。
    • 金融業界を例にした、ケーパビリティのマッピング*1の説明は、分かりやすかったです。「ビジネスケーパビリティ=サービス」という考え方にもとづいて、いい感じの粒度で、サービスコンポーネントに設計ができれば、業務の規模に応じてそれらの組み合わせを変え、変化に強いビジネスアプリケーションの実現も夢ではないのかなぁと思いました。
    • あと、「SOA設計ガイドライン」と「ポリシーのデザイン」はSOAの考え方を取り入れた開発を行おうとした際に、とても参考になりそうです。
    • Windows Vista の画面、チラ見しました。ん〜やっぱ、これからは3Dですね♪
  • 【IB-1】SOA開発を支える技術/製品、その最新動向を探る / 野村総合研究所 城田 真琴 氏
    • 基本的にSOA弱者なので、大変ためになりました。BPMN*2が標準になりつつあるそうな…。
    • ESB…分散、疎結合、標準技術 がキーポイントかぁ…なるほど。
    • XMLアプライアンスXML処理専用機で、負荷の高いXML処理を高速実行するもの?
    • SCA*3とSDO*4は、様々なデータソースに対し、統一的なインターフェースを提供する仕組みのようです。でも、これって、コンセプト的にはADO*5と同じでは…と思ったのは私だけ? ODBCJDBC、ADO→SDO ? … う〜ん、微妙 … ADOは個人的な浅い経験からすると、ODBCの焼き直し?みたいな印象があったのだけれど、果たしてSDOは…? ま、現在はWebサービスSOAの考え方が広まってきている状況があるので、きちんとSOAっぽく利用されるでしょうね。
    • オープンソースBPMプロダクト*6、ESBプロダクト*7ってけっこうあるんですね。
    • まとめ「ビジネスプロセス中心のモデル駆動型の開発はSOAアプリケーション開発の中心になる」に自分で赤線引いてました… ^^;
  • 【SB-2】SOA開発ライフサイクルの生産性とガバナンス向上のために / ソニック ソフトウェア株式会社 武末 徹也 氏
    • ツール*8や、管理画面*9がかなりいい感じでした♪
    • 分散システムでは、テストやデバッグが大変そうです。でも、このツールは、それらにも対応しているようですね。SOAシステムのテストには、このようなツールが必須ですね。
    • 一度使ってみたいなぁ…と思ったり。
  • 【SA-3】事例検証! SOAを成功させるコンポーネントベースモデリングの極意− / 株式会社 日立製作所 桐越 信一 氏
    • SOAがBuzzる前から、コンポーネントベースでSOAチックなシステム構築してました♪ 的なお話でした。実際の経験を元にしたお話にはなかなかの重みがあり、実際の開発の中での問題点やそれをどう解決したかなど、とても参考になることが多かったです。特に開発プロセスやプロジェクト体制についての話はとても参考になりました。
    • 事例の中で、ビジネスプロセスをコンポーネントに分けて、チェインにしていたので、業務的な仕様変更に対し、コンポーネントの処理順を変えるだけで対応できた話などは、まさにSOAxコンポーネント指向の威力だと思いました。
  • 【IB-4】米国SOA最新事情 / 米国マサチューセッツ大学 鈴木 純一 氏
    • (^^ サインもらいました♪ 快く書いて頂きありがとう御座いました。
    • NASAなどにおける、衛星写真の加工/検索システムへのESB適用例をもとにお話されていました。
    • コンポーネントをデプロイメント・ディスクリプタに基づいてフィルターアーキテクチャー構成でデプロイして、システムを実現するというESBのコンセプトが非常に分かりやすかったです。
    • また、デプロイメント・ディスクリプタの記述量が多くなってくると、直接XMLを変更するのは困難になってくるということで、それに対する解決策として、UML拡張を使用して、視覚的にサービス定義やプロセス定義が出来るようにしているということでした。UMLステレオタイプを使用して意味づけしたクラス図から、プロイメント・ディスクリプタを生成する方式で対応しているようです。これぞまさしく、仕様と実装の一体化では!
    • 実装には、Mule ESB を使用しているそうです。
    • サービスの数としては、けっこうな数になるそうです。
    • 衛星からは、X線写真とか、ノイズ除去したものとか、様々な種類の画像が送られてくるそうです。
    • 利用者の要望に応じて、検索された画像を、指定の加工処理を施したのちに届けるというシステムのようです。
    • スケジューリングされたタスクによる処理と、ユーザイベントによる処理の両方に対応しているとのこと。
    • SOAの特色としては、疎結合と処理の隠蔽 という点を強調されていました。
    • アメリカの地方自治体が、高額なものを買う場合、専門の購買組織に申請する必要があるそうで、今はFAX+手作業というワークフローなのですが、エラー率45%(購買組織側の入力ミスとか) !! とのこと。 このワークフローを自動化するために、SOAベースのシステムを提案しているとのこと。購買組織はやる気満々なのだけれど、申請書などの共通化を図るために自治体の現状分析をしに行ったりした人の話だと、今のワークフロー(FAX申請)に、特に不満はないので、なかなか協力的になってくれないとのこと…。人間いずこも同じ?
  • 感じたこと
    • セッションを通して共通項の1つとして思い浮かんだのは「SOAは、よりユーザサイドの視点に近づくこと」ということでした。でも、ユーザとの隙間を埋めるための規格やツールとしては、まだ今2歩くらいかなぁ…という印象でした。でも、相応のスキルと意欲があるユーザと開発者にとっては、SOAな未来はもうすぐそこ*10にありそうです。
    • そのためにも、表現力豊かなツール類*11の充実が大切なのかなぁと思ってみたり…
  • エピローグ
    • ひさびさの東京だったのですが、なんかいろいろビルが建っていて戸惑い…本を買いに八重洲ブックセンターに行こうかなと思ったのですが、北口の近くに丸善があったので、こちらで買い物をしました。以前のJavaWorldのイベントで、Eclipse GMFの話をして下さった河村さんの記事が載ったムック「Eclipseパーフェクトマニュアル vol.6」を購入。あとartonさんのSeasar本も出てた!うぅAmazonにたのんでるけどまだ着いてない… T_T

*1:Capability Mapping … 機能マッピング

*2:Business Process Modeling Notation

*3:Service Component Architecture

*4:Service Data Object

*5:ActiveX Data Object

*6:jBPM、Active BPELApache Agila

*7:Celtix、ServiceMix、Synapse

*8:Eclipse Plgug-in

*9:Sonic Software / Actional

*10:ここ?

*11:ユーザと対話しながら、ユーザが本当に実現して欲しいことを可視化していくもの

DIVE INTO S2 .HACK : INCLUDE PART II

Seasar2に慣れ親しむためにS2Containerのコードをプチハックしてみる第2弾。 v(^^)v

  • カスタムPathResolverを使って、S2Container間の依存関係(include)をDEPENDENCY-LOOKUPからDEPENDENCY-INJECTIONにしてみる。

koichikさんのアドバイスをもとに、PathResolverインターフェースを実装した AliasAwarePathResolverクラス を作ってから、デフォルトのPathResolverを差し替えることで、できちゃいました。これぞまさしく OPEN-CLOSED PRINCIPLE !!!

    • テスト用のdiconファイルの構成は前回と同じで、こんな感じです。*1

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode0.dicon







component0
component1
component2

"Here is node0 container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode1.dicon




"Here is node1 container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode2a.dicon



"Here is node2a container."

■org/seasar/framework/container/factory/AliasAwarePathResolverTestNode2b.dicon



"Here is node2b container."

    • テストコードは、こんな感じです。ポイントは赤い部分で“S2Container自体の振る舞いを変更”している点です。S2Container本体を構成するコンポーネントのうち、差し替えたいコンポーネントを作って*2、それをコンテナ制御用のdiconファイル*3に設定して、S2ContainerFactory.configure("制御用diconファイルのパス") とするだけで、カスタムS2Containerの出来上がりです。素晴らしい!!!

■org/seasar/framework/container/factory/AliasAwarePathResolverTest.java


package org.seasar.framework.container.factory;

import java.util.List;

import junit.framework.TestCase;

import org.seasar.framework.container.S2Container;

/**
* @author hidebsd
*/
public class AliasAwarePathResolverTest extends TestCase {

private static final String []S2CFG = "org/seasar/framework/container/factory/AliasAwarePathResolverConfiguration.dicon"[];

private static final String PATH0 = "org/seasar/framework/container/factory/AliasAwarePathResolverTestNode0.dicon";

private static final String PATH1 = "org/seasar/framework/container/factory/AliasAwarePathResolverTest1.dicon";

public void setUp() {
[]S2ContainerFactory.configure(S2CFG)[];
}

public void testDefaultSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH0);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "a container.",
list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.",
list.get(i));
}
}
}

public void testOverrideSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH1);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "b container.",
list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.",
list.get(i));
}
}
}
}

    • で、入れ替えるクラスを作りました。今回は、PathResolverインターフェースを実装したAliasAwarePathResolverなるクラスを作成。中身は前回のIncludeTagHandlerに追加した処理とほぼ同じ。

■org/seasar/framework/container/factory/AliasAwarePathResolver.java


package org.seasar.framework.container.factory;

import java.util.HashMap;
import java.util.Map;

import org.seasar.framework.util.ClassUtil;

/**
* @author hidebsd
*/
public class AliasAwarePathResolver implements PathResolver {

private Map aliasDefinitions = new HashMap();

private String prefix = "@";

private String delimiter = ":";

/**
* AliasAwarePathResolverを構築します。
*/
public AliasAwarePathResolver() {
}

/**
* プリフィックスとセパレータを指定して、AliasAwarePathResolverを構築します。
*
* @param prefix
* 別名を示すプリフィックス
* @param delimiter
* 別名とパスを区切るセパレータ
*/
public AliasAwarePathResolver(String prefix, String delimiter) {
this.prefix = prefix;
this.delimiter = delimiter;
}

/**
* パスとその別名が指定された場合には、これを登録し、パスまたは別名が指定された場合には、適切なパスを返します。
*
* @param context
* コンテキスト
* @param path
* パス
* @see org.seasar.framework.container.factory.PathResolver#resolvePath(java.lang.String,
* java.lang.String)
*/
public String resolvePath(String context, String path) {

if (!path.startsWith(prefix)) {
return path;
}
int nameSep = path.indexOf(delimiter);
if (nameSep < 0) {
// Resolve path
String name = path.substring(prefix.length());
path = resolvePath(name);
} else if (nameSep > 1) {
// Register alias-path mapping
String name = path.substring(prefix.length(), nameSep);
path = path.substring(nameSep + delimiter.length());
registerPath(name, path);
path = []ClassUtil.getResourcePath(getClass()).replaceAll("\\.class$", "Dummy.dicon")[];
} else {
// 'path' attibute value was specified like "@:....".
throw new IllegalStateException(
"Alias name of S2Container was empty.");
}
return path;
}

private void registerPath(String name, String path) {
if (aliasDefinitions.get(name) != null) {
return;
}
aliasDefinitions.put(name, path);
}

private String resolvePath(String name) {
String path = (String) aliasDefinitions.get(name);
if (path == null) {
throw new IllegalStateException("Alias name '" + name
+ "' was not defined.");
}
return path;
}
}

    • で、S2Containerの挙動制御用diconはこんな感じで。PathResolverインターフェースを実装しているクラスがあると差し替えられるので、これでOK。

■org/seasar/framework/container/factory/AliasAwarePathResolverConfiguration.dicon





    • あ、それから、diconファイルパスの別名を登録する場合、返すパスがないのですが、なにかしらdiconファイルのパスを返す必要があったので*4、ダミーのdiconファイルを作成。上記ソースコードの青色部分で使用してます。

■org/seasar/framework/container/factory/AliasAwarePathResolverDummy.dicon



    • では、テスト開始〜!
    • RED RED RED RED GREEN!
    • YATTAA!!!
    • 次回は、ResourceResolverを使ったやり方に挑戦!*5 *6

*1:xml宣言とDTDの部分は省略してあります

*2:特定のインターフェースを実装したクラス

*3:デフォルトはクラスパスルートのs2container.dicon

*4:Alias登録の場合に、前回と同様にnullを返したらおこられちゃいました…

*5:S2ContainerFactory.DefaultProvider を継承したクラスで対応した方が良さげかも…?

*6:なんか カ☆ナ☆リ 楽しくなってきました♪

DIVE INTO S2 .HACK : INCLUDE PART I

Seasar2に慣れ親しむためにS2Containerのコードをプチハックしてみました。 v(^^)v

  • S2Container間の依存関係(include)をDEPENDENCY-LOOKUPからDEPENDENCY-INJECTIONにしてみる。
    • S2Containerは、1つのdiconファイル*1に対し、1つのS2Containerインスタンスが生成される仕組みになっています。そこで、コンポーネント群を機能ブロック毎に複数のdiconファイルに分けて使用する場合*2、includeタグを使って依存する子コンテナを関連付けるわけです。具体的には、依存するdiconファイルのパスを指定することで、関連付けを行います。しかし、この指定方法は、DEPENDENCY-LOOKUPスタイルなので、依存関係を外部からコントロールしようとすると、工夫が必要*3になります。そこで、なんかいい方法ないかなぁ〜と、悶悶と考えた結果“依存関係…依存関係…依存関係の解決…!…DI*4があるぢゃぁないか!”となったわけです。
    • 現状の課題点は、依存関係が直接diconファイルのパスになっている点です。ここを別名にして、別名とパスのマッピングを別に定義する仕組みを作ってやればいいんじゃな〜い? … と、いうわけで、実験君のはじまりはじまり〜。
    • diconファイルとしては、こんな感じの定義で動いたらいいかなぁ〜というのをまず書いてみました。includeタグの表記形式を拡張する方法*5で対応してます。
      • と書いた場合には、定義のみを行う。
      • と書いた場合には、別名を使ってincludeを行う。
      • と書いた場合には、今までと同様に、指定パスでincludeを行う。
      • 同じ別名がincludeで複数定義されていた場合には、includeツリーをルートから辿る順序で、先勝ちのルールとする。
    • Node0 ← Node1 ← Node2a という依存関係。

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverNode0.dicon


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd">

<!-- Alias definition section -->

<!-- Include section -->

<!-- Component section -->

component0
component1
component2
</component>
"Here is node0 container."

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverNode1.dicon


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd">

<!-- Include section -->

<!-- Component section -->
"Here is node1 container."

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverNode2a.dicon


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd">

<!-- Component section -->
"Here is node2a container."

    • で、I'm Test-Driven*6 なので、テストコードを書いてみます。*7

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverTest.java


package org.seasar.framework.container.factory;

import java.util.List;

import junit.framework.TestCase;

import org.seasar.framework.container.S2Container;

public class IncludeTagHandlerResolverTest extends TestCase {

private static final String PATH = "org/seasar/framework/container/factory/IncludeTagHandlerResolverNode0.dicon";

public void setUp() {
IncludeTagHandler.clear();
}

public void testBasicSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "a container.", list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.", list.get(i));
}
}
}
}

    • じゃ、ハッキングしまぁ〜す。オレンジ色部分が追加コード。

■ org/seasar/framework/container/factory/IncludeTagHandler.java


/*
* Copyright 2004-2006 the Seasar Foundation and the Others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.seasar.framework.container.factory;

import java.util.HashMap;
import java.util.Map;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.env.Env;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.xml.TagHandlerContext;
import org.xml.sax.Attributes;

/**
* @author higa
*
*/
public class IncludeTagHandler extends AbstractTagHandler {

private static final long serialVersionUID = 7770349626071675269L;

/**
* @see org.seasar.framework.xml.sax.handler.TagHandler#start(org.seasar.framework.xml.sax.handler.TagHandlerContext,
* org.xml.sax.Attributes)
*/
public void start(TagHandlerContext context, Attributes attributes) {
String path = attributes.getValue("path");
if (path == null) {
throw new TagAttributeNotDefinedRuntimeException("include", "path");
}
S2Container container = (S2Container) context.peek();
String condition = attributes.getValue("condition");
if (!StringUtil.isEmpty(condition)) {
Map map = new HashMap();
map.put("ENV", Env.getValue());
Object o = createExpression(context, condition).evaluate(container,
map);
if (!(o instanceof Boolean)) {
throw new IllegalStateException("condition:" + condition);
}
if (!( (Boolean) o).booleanValue()) {
return;
}
}

path = resolveOrRegisterPath(path);
if (path == null) {
return; // Alias-path mapping was Registered
}

S2ContainerFactory.include(container, path);
}


private static final Map aliasNameDefinitions = new HashMap();

public static void clear() {
aliasNameDefinitions.clear();
}

private String resolveOrRegisterPath(String path) {
if (!path.startsWith("@")) {
return path;
}
int nameSep = path.indexOf(':');
if (nameSep < 0) {
// Resolve path
String name = path.substring(1);
path = resolvePath(name);
} else if (nameSep == 1) {
// 'path' attibute value was specified like "@:....".
throw new IllegalStateException(
"Alias name of S2Container was empty.");
} else if (nameSep > 1) {
// Register alias-path mapping
String name = path.substring(1, nameSep);
path = path.substring(nameSep + 1);
registerPath(name, path);
path = null;
}
return path;
}

private void registerPath(String name, String path) {
if (aliasNameDefinitions.get(name) != null) {
return;
}
aliasNameDefinitions.put(name, path);
}

private String resolvePath(String name) {
String path = (String) aliasNameDefinitions.get(name);
if (path == null) {
throw new IllegalStateException("Alias name '" + name
+ "' was not defined.");
}
return path;
}
}

    • んで、テスト! RED RED RED RED RED RED RED RED GREEN! いいんじゃなぁ〜い?
    • こんどは、リーフノードのコンテナを交換してみます。
    • Node0、Node1のdiconファイルには、全く手を加えず、Test1で依存関係を変更する方向で…。
    • Test1 ← Node0 ← Node1 ← Node2b という依存関係。
    • 切り替え用*8のルートのdiconファイルはこんな感じ。

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverTest1.dicon


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd">

<!-- Alias definition section -->

<!-- Include section -->


    • そして、切り替えられるリーフノードのdiconファイルがこれ。〜aが〜bになってます。

■ org/seasar/framework/container/factory/IncludeTagHandlerResolverNode2b.dicon


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd">

<!-- Component section -->
"Here is node2b container."

    • そして、I'm Test-Driven*9 なので、またまたテストコードを書いてみます。先のテストコードに以下のコードを追加します。


private static final String PATH1 = "org/seasar/framework/container/factory/IncludeTagHandlerResolverTest1.dicon";

public void testOverrideSettings() throws Exception {
S2Container container = S2ContainerFactory.create(PATH1);
container.init();
List list = (List) container.getComponent("collection");
for (int i = 0; i < list.size(); ++i) {
if (i == 2) {
assertEquals("node" + i, "Here is node" + i + "b container.", list.get(i));
} else {
assertEquals("node" + i, "Here is node" + i + " container.", list.get(i));
}
}
}

    • しょっしゃぁ!、DOテスト! RED RED RED RED RED GREEN! ふぅ。しばしのプログラマーズ・ハイ状態…*10
    • 以上でハック完了であります。
    • それから、このコードの課題としては、以下のようなところでしょうか…ま、プチハックということで。*11
      • diconファイルの別名とパスをstaticなMapに持っているため、リロードの際には、中身をクリアしてあげる必要がある。
      • 別名を定義する際の表記方法がイマイチ*12。includeタグに別の新規属性を追加した方が、きっと自然。
      • IncludeTagHandlerに追加したコードは、別クラス(Innerクラスでもいいかも)にした方がいいだろうなぁ。
      • スレッドセーフとか全く考慮してないです。
    • まだまだ課題はありそうですが、とりあえず、YATTAA!!
    • いや〜、code-readingしたり、こうやって、HACKしてみたりすると、少しずつですがS2の理解が深まる感じがしますね。それに大変勉強になります。S2開発者の方々に感謝!
    • その昔、パソコンサンデーという知っている人は知っている番組があったのですが、それに出てくるドクターパソコン*13の口癖『習うより慣れろ』が頭に浮かびました。真理ですきっと (^^v

あとがき

はじめは、MetaDefに別名とパスのマッピング情報を持たせようとしたのですが、includeタグの処理の時点では、S2Containerのrootとかchildrenが設定されていないようで、かなり試行錯誤+七転八倒したのですが、出来ませんでした…理解不足の可能性大…orz。結局…あれ?、Mapで別に持てばいいんじゃない?ということに気づき、この形になりました。*14 staticフィールではなくて、ルートのS2Containerのインスタンス変数とかに持てると、多少いいかなぁと思うのですが、私の今の実力では、これが限界でした orz. orz. orz. orz.

*1:ちなみにdiconファイル以外のリソースからS2Containerを構築する拡張性もあって、拡張子でコンテナビルダーの振舞いを制御出来るようになっています。

*2:業務アプリケーションなら普通はそうしますよね…^^;;;

*3:最近、この課題の解決策の1つとしてincludeタグに、condition属性が付きました v(^^)v これは便利ですね〜。けっこう待ち望んでいた人いるんじゃないかなぁ〜。

*4:Dependency Injection です念のため ^^;;

*5:初めは と書いたのですがDTD直したりしたくなかったので、姑息な手段を使用しました。正直このやり方はイケテナイと自分でも思います。ま、プチハックなので…と逃げてみる ^^;;;;;

*6:但し、気が向いたときに限る ^^;;

*7:当然、はじめに書いたコードにはバグがありました。IncludeTagHandler.clearが抜けてたり…

*8:というか上書き指定という感じかなぁ…Override?

*9:但し、気が向いたときに限る。っていうかクドイ!

*10:魂が抜け、画面を見つめてニヤニヤしている状態。他人に見られるととってもYABAI。

*11:はい、その通りです、自分の無力さに対する言い訳です。orz

*12:イマ3くらい?

*13:ちょっと「桃や」のキャラクターに似ていた、故・宮永好道さん[シャープ顧問]

*14:これに気づいたときにはかなり脱力しました。 orz

FOLLOWING ECLIPSE GMF

…つづき

  • クイック・スタート
    • もし、GMFが動くところを見たいのであれば、Taipan サンプルプロジェクトを、CVSからチェックアウトしてみて下さい。CVSリポジトリパースペクティブに切り替えて、下図のリポジトリ・ロケーションの設定で取得できます。もし、動かすのは後でよければ、次のセクションに進んで下さい。

    • /HEAD/org.eclipse.gmf/examplesを開いて、3っのorg.eclipse.gmf.examples.taipan.* modulesを選択してから、右クリックでチェックアウトを選択して下さい。もしあなたの使用しているGMFと依存プラグインが最新版でない場合には、そのバージョンに合った Tipan サンプルをチェックアウトできます。重要なのは、使用しているGMF SDKのバージョンと、Taipan サンプルのバージョンが合っていることです。古いバージョンの Taipan をチェックアウトするには、最新版をチェックアウトした後に、プロジェクトのところで、右クリックし、'Team | Switch to Another Branch or Version...' を選択し、それから、'Select the tag from the following list' を選んで、'Add Date...' ボタンを使って GMF 1.0 release (27 June 2006) を入力します。あとは Finish ボタンを押せば、指定したバージョンに設定されます。
    • プラグイン開発・パースペクティブに切り替えて、org.eclipse.gmf.examples.taipan プロジェクトの models フォルダを開いてみましょう。それぞれのモデルを探してみましょう。特に taipan.gmfgraph と taipan.gmfmap モデルと、それらの要素の属性を探してみて下さい。
    • このチュートリアルを進めていく間に、これらのモデルを見ることになりますが、とりあえず、設定が正しいかどうかを確認するために、ランタイム・ワークスペース(単に、'Eclpse Application' を実行した場合のデフォルト設定でOKです)で、実行したほうが良いでしょう。ランタイム・ワークスペースで、新しい空のプロジェクトを作り、そのプロジェクトの新規作成メニューの中の 'Example' を選択します。'New Example' ダイアログを開くと 'Taipan Diagram' が見つかるはずです。好きなファイル名を付けて、Finish ボタンをクリックして下さい。あなたが見たい、生成されたダイアグラム・エディタが開くでしょう。特に注目すべき点は…
      • ツール・パレットとオーバー・ビュー
      • レイアウトと選択ツール
      • ダイアグラム・イメージのエクスポート(svg, bmp, jpeg, gif)
      • タブ対応のプロパティ・ビュー
      • 選択された要素に対するフォントと色のオプション
      • リンクのルートとスタイルのオプション
      • ポップアップ・バーと接続ハンドル
      • ノートと図形
      • アニメーション・ズームとレイアウト
      • ダイアグラム印刷


これで、クイック・スタートは終わりです。さて次は、マインドマップモデリングのところで見てきた、それぞれのモデルについて、より詳細に見ていきます。
…つづけぇ〜

FOLLOWING ECLIPSE GMF

…つづいた

  • セットアップ
    • このチュートリアルは、GMF 1.0 を使用して書かれています。GMFをインストールする前に必要なプラグインについては、ビルドのページに書かれています。これらは、ダウンロードページ、もしくはアップデートマネージャーによりインストール可能です。
    • GMFをはじめる一番簡単な方法は、たぶん、最新の Eclipse Platform 3.2 をインストールして、Callisto*1のアップデートサイトを使用することです。メニューの Help > Updates > Find and Install... Search for new features to install > Callisto Discovery Site にあります。それから、Models and Model Development カテゴリの Graphical Modeling Framework (Callist Edition) を選択し、GMFの依存プラグインが一緒にダウンロードされるように 'Select Required' ボタンをクリックして下さい。簡単でしょ?

    • これから見るサンプル TaiPan は、CVSでメンテナンスされていますが、たとえこのチュートリアルが最新でなくても、ここで説明するGMFの機能は、最新のビルドに含まれているでしょう。

…つづくか?

*1:依存関係を解決するフレームワーク Eclipse 3.2 からの機能

FOLLOWING ECLIPSE GMF

…つづき

    • この図は、GMFベースの開発で使われる主なコンポーネントとモデルを示しています。GMFの核となる概念は、グラフィカル要素定義モデル*1です。このモデルはGEFベースのアプリケーションを実行した際に表示されるグラフィカルな要素に関連する情報を含んでいますが、表示や編集の対象であるドメインモデルへの直接的な関連は含みません。パレットやその他のメニュー、ツールバーなどのデザインには、オプションのツール定義モデルが使われます。
    • グラフィカル要素やツール要素の定義は、複数のドメインで同じように動作することが求められます。例えば、UMLのクラス図には多くの要素がありますが、それらの基本的な外見や構造はどれもよく似ています。GMFの目標は、グラフィカル要素定義を複数のドメインで利用できるようにすることです。これは、グラフィカル要素とツール要素の定義と、指定されたドメインモデルを紐づける、マッピングモデルを別に定義することにより実現されます。
    • 一度、適切なマッピングが定義されれば、GMFはアプリケーション生成フェーズで必要となるGMFジェネレーターモデル(実装の詳細なモデル)を供給します。ジェネレーターモデルにもとづいた編集用プラグインの製作では、最終的なモデル(ダイアグラム・ランタイムモデル*2または“表示”モデル*3 )を目指します。実行時に、ユーザが図を操作すると、表示モデルとドメインモデルが連動し、モデルの永続化と同期も行われます。ここで重要となるのは、実行環境が、EMFとGEFの機能に対して、サービスベースのアプローチをとるということです。そして、実行環境において、まだアプリケーションを生成していない状態で動作させることができるという点です。GMFの基礎はこれで終わりにして、今度は、特定ドメインのグラフィカルな編集画面の開発を通して、GMFの使い方を見ていきましょう。まず最初に、GMFと依存ライブラリをインストールする必要があります。

…つづくかも

*1:graphical definition model

*2:diagram runtime model

*3:notation model