Hatena::Diary

seraphyの日記 このページをアンテナに追加 RSSフィード

2007-01-06 JAVAセキュリティメカニズムのメモ

[][] JAVAセキュリティメカニズムのメモ このエントリーを含むブックマーク

セキュリティマネージャとポリシーの設定

  • System.getSecurityManager()がnullの場合、セキュリティメカニズムは機能しない。つまり、どのようなセキュリティポリシーであれ、それは全く検証されず全権で動作する。*1
  • 一部でもセキュリティメカニズムを使いたい場合は、セキュリティマネージャを有効にする必要がある。
  • セキュリティポリシーはPolicyクラスによって保持される。
    • Policyは、システムプロパティ「-Djava.security.policy=XXXX.policy」のように指定してポリシーファイルを読み込むことができる。
    • 明示的にPolicyを作成する場合、PolicyクラスはAbstractであるが全てのメソッドが実装済みのテンプレートクラスとなっている。getPermissions(CodeSource)をオーバーライドしてコードソースに対応するパーミッションコレクションを返せばよい。
  • ポリシーは、コードソースに対するパーミッションコレクションの集合である。
  • 全てのパーミッションは許可を示す。拒否を示すことはできない。権限が存在しないことが拒否を表す。全権があることを示すには、AllPermissionクラスを用いる。
    • ポリシーで有効な権限が1つもなければ、それがサンドボックスを意味する。
    • パーミッションは追加できるが削除できない。ClassLoaderは、クラスをロードするときにプロテクションドメイン(ProtectionDomain)に対して権限を追加しマージすることができる。しかし、すでに付与されている権限を減ずることはできない。
      • クラスがすでにロードされている場合、そのプロテクションドメインは不変である。もはやパーミッションを変更することはできない。
      • つまり、アプリケーションコードの中でアプリケーション自身のパーミッションを設定しようと思っても手おくれである。可能なのは、新しいクラスローダが、これからロードするクラスに対して、追加の権限を与えることだけである。

コードソース(Codesource)の役割

  • すべてのクラスはコードソースをもっている。
    • コードソースは、クラスの実体が置かれているロケーションと、そのコードの署名をもっている。ロケーションは、ローカルシステム上のアプリケーションであればファイルシステムの位置を表すURLであり、それが署名付きJARであれば、それが署名も保持する。(署名がなければnull)。このペアがコードソースである。
    • 実行中のクラスのコードソースは、Class#getProtectionDomain()からProtectionDomain#getCodeSource()で取得可能である。
  • コードソースに対するパーミッションコレクションのペアがプロテクションドメイン(ProtectionDomain)である。
      • nullクラスローダから読み込まれるシステムクラスのプロテクションドメインでは、コードソースを持たずnullとなるが、パーミッションコレクションは有効であり、全権があてられている。
  • セキュリティポリシーでは、そのコードソースに対して権限を割り当てる。
    • つまり、明示的に指定された特定の場所にあるコードのみが特定の権限をもち、それに該当しない場合は、すべてサンドボックス上で動作する。
  • パーミッションの検査は、現在実行されている呼び出し履歴の中の、すべてのコードソースで許可される場合のみ許可される。いいかえると、呼び出し履歴の中の、もっとも低い権限で実行される。

特権(AccessController.doPrivileged)の意味

  • 権限の低いレベルのコードソースが、権限の高いレベルのコードソースを呼び出した場合には、呼び出し履歴の規則によって、高いレベルのコードソースであっても権限は低いほうに限定される。
  • 低いレベルから高いレベルのコードソースが呼び出された場合でも、そのコードソースがもっている権限を行使したい場合に「特権」を行使する。つまり、呼び出し元がどうであれ、本来、自分がもっている権限で実行したい場合が「特権」の意味である。
    • つまり、ライブラリなど外部から利用される可能性があるコードにおいて、そのコードがセキュリティ上の権限を必要とする操作を行う場合には、特権コードとして実行するように記述しておかないと、実行時にセキュリティが足りない事態になる可能性がある。
      • 逆に特権を行為する特別な意図がなければ、単に特権を明示しなければ最小限の権限で動作できる。
    • 特権を指定しても、本来、そのコードソースが許可されている以上の権限は行使できない。

結論

サンドボックスを実装するために必要なコードは0行である。つーか、コードでサンドボックスを書くことは無理。諦めれ。*2

URLClassLoaderを使ってアプリケーションから読みこむアドインにセキュリティをかけたい場合には単に、自分以外のコードソースに権限を与えないようなポリシーを読ませておけば、それで十分。それ以上やる意味なし。

*1:自前のコードに限っていえばSecurityManagerを使わずAccessControllerを使ってセキュリティチェックすることは可能である。しかし、システムクラスは、そのような実装になっていないし、アプリケーションの書き方としても推奨されていない。

*2:JSE6の新しいブートストラップのメカニズムを使ったり、WASやSJSASといったJ2EEサーバのような、もっと手のこんだ方法を考えればいけるのかもしれないが、もう、やる気が起きない。できたとしても、多分、割に合わない。標準のメカニズムで十分だ。

2006-12-13 はじめてのS2Container

[]はじめてのS2Container このエントリーを含むブックマーク

経緯

最近、あちこちで「S2Container + S2Struts + Mayaa」を使った案件があるんだけど…、っていう話を聞くようになってしまった。Springって話は聞かない。S2はSpringの類似品だと思っていたのに違うのか。

  • 「どんなもの、それって簡単?」って聞いたら、「EJBをやってた人はドツボにはまるよ」といわれてしまった。マジですか。
  • S2を使っているエンジニアが「セッションタイムアウトの時間をどうしようか。1時間、2時間?」とか悩んでいて「セッションタイムアウトは短いほうがいいんじゃない?」といったら、「S2Strutsではアクションマップをセッションに入れていてタイムアウトするとログインしなおしなんですぅ。」とか言う。マジですか!?

なんだか、いろんな意味で不安になってきた。これは試しておかないとマズイんじゃないか。

道のりは長いが、まずは簡単なところから。

とりあえず、スタンドアロンでDIコンテナAOPを試す。

S2Containerの設定ファイルはXML形式で記述するけれど、拡張子はdiconと書く。Dependency Injection CONtainerの略だと思うが、どうやら、これは「ダイコン」と読むらしい…。

S2Containerの特徴は、どうやら以下の2点のようだ。

  • 積極的に名前なしでコンポーネント同士でバインドさせる
  • EL式みたいな方法 (OGNL)で値を操作

ってところですか。

必要なもの

http://s2container.seasar.org/ja/ から、S2Containerのzipファイルをもらってくる。

もってきたのは、S2.3.15.zipだった。

DIとAOPを試すだけなら、以下のものだけをクラスパスに通せばよい。

log4jも同梱されているので、これを使ってもよいのだが、とりあえずCommons LoggingのSimpleLogを使ってみる。

SimpleLogを使うために、クラスパス上に以下のファイルを配置する。

Commons-Loggingのログ実装をSimpleLogに選択する

commons-logging.properties

org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

SimpleLogの設定

simplelog.properties

org.apache.commons.logging.simplelog.defaultlog=trace

実験コード

Main.java

package jp.seraphyware.test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public final class Main {

    private static final Log log = LogFactory.getLog(Main.class);
    
    static {
        // SingletonS2ContainerFactoryは静的メソッドしかないユーテリティクラスである。
        // 初回initの呼び出しで、S2ContainerFactory.create("app.dicon")を実行し、
        // そのインスタンスを保持する。
        SingletonS2ContainerFactory.init();
        
        final Thread shutdownThread = new Thread() {
            @Override
            public void run() {
                // コンテナに終了することを通知する。
                // これにより、コンテナは登録されているオブジェクトに対して
                // 破棄メソッドの呼び出しを行う。
                SingletonS2ContainerFactory.destroy();
            }
        };
        Runtime.getRuntime().addShutdownHook(shutdownThread);
    }

    public static void main(final String[] args) throws Exception {
        // 初期化されたコンテナを取得する。
        // 初期化されていない場合はエラーになる。
        final S2Container container = SingletonS2ContainerFactory.
            getContainer();
        
        // 登録されたオブジェクトを取得する。
        final MyComponent obj1 = (MyComponent) container.
            getComponent(MyComponent.class);
        
        // MyComponentに対してAspectを指定しているため、
        // インターセプトが埋め込まれた新しいクラスが生成されている。
        // (つまり、コンテナで作成されたときにのみAOPが埋め込まれる。)
        log.info("クラス名: " + obj1.getClass());
        
        // 自動的に設定されているオブジェクトを取得する。
        final MySubComponent sub1 = obj1.getSubComponent();
        log.debug("name=" + sub1.getName());
        
        // 同じ名前で、もう一度取得する。
        // singletonで宣言されている場合、同じインスタンスを返す。
        // prototypeで宣言されている場合、異なるインスタンスを返す。
        final MyComponent obj2 = (MyComponent) container.
            getComponent(MyComponent.class);
        log.debug("obj1とobj2は等しいか? " + (obj1 == obj2));
        
        final MySubComponent sub2 = obj2.getSubComponent();
        
        // コンテナからインターフェイス名でサブコンポーネントを取得してみる。
        final MySubComponent sub3 = (MySubComponent) container.
            getComponent(MySubComponent.class);
        
        // MySubComponentImplは、singletonとしてコンテナに登録されているため
        // いずれも同じインスタンスを返す。
        log.debug("sub1とsub2は等しいか? " + (sub1 == sub2));
        log.debug("sub1とsub3は等しいか? " + (sub1 == sub3));
    }
    
}

コンテナで管理する雑多なクラスの定義

public interface MyComponent {

    MySubComponent getSubComponent();

}

public class MyComponentImpl implements MyComponent {

    private MySubComponent _subcomp;
    
    public MyComponentImpl(final MySubComponent v_subcomp) {
        if (v_subcomp == null) {
            throw new IllegalArgumentException();
        }
        this._subcomp = v_subcomp;
    }
    
    public MySubComponent getSubComponent() {
        return this._subcomp;
    }
}

public interface MySubComponent {

    void setName(String v_name);

    String getName();
}

public class MySubComponentImpl implements MySubComponent {
    
    private static final Log log = LogFactory.getLog(MySubComponentImpl.class);
    
    private String _name;
    
    public String getName() {
        return this._name;
    }
    
    public void setName(String v_name) {
        this._name = v_name;
    }
    
    public void close() {
        log.info("MySubComponentImpl#close()");
    }

    @Override
    public String toString() {
        return this._name == null ? "(null)" : this._name;
    }
    
}

メソッドインターセプタの定義

package jp.seraphyware.test;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;

public class MyInterceptor extends AbstractInterceptor {

    private static final long serialVersionUID = -8723170966433962705L;

    public Object invoke(MethodInvocation v_invocation) throws Throwable {
        final Class clz = super.getTargetClass(v_invocation);
        final Log log = LogFactory.getLog(clz);
        
        log.trace("begin: " + v_invocation.getMethod());
        try {
            final Object ret = v_invocation.proceed();
            log.trace("end (result=" + ret + ")");
            return ret;
        }
        catch (Throwable e) {
            log.error(e);
            throw e;
        }
    }

}

今回の主役である、app.dicon (その1)

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

    <!-- 
        S2Containerでは登録されたオブジェクトを名前ではなくクラス名で索引することが推奨される。
        (ただし、同型のクラスが複数ある場合は名前でなければ索引できない。)
        instance属性がsingletonの場合、一度作成されたあとコンテナは同じオブジェクトを返す。
        この属性をprototypeとすると、オブジェクトをコンテナから取得する毎に新しいオブジェクトを返す。
        デフォルトはsingletonである。
     -->
    <component class="jp.seraphyware.test.MySubComponentImpl" instance="singleton">
        <!-- 
            プロパティ、setName()に対して自動的に設定する。
            コンストラクタに指定する場合は<arg>タグを用いる。
         -->
        <property name="name">"hello, world!"</property>

        <!-- 
            シングルトンインスタンスの場合、コンテナが破棄(destroy)されたときに
            任意のメソッドを呼び出すことができる。
         -->
        <destroyMethod name="close"/>
    </component>

    <!-- 
        アスペクトをコンテナに登録する。
     -->
    <component name="trace" class="jp.seraphyware.test.MyInterceptor" />
    
    <component class="jp.seraphyware.test.MyComponentImpl" instance="prototype">
        <!-- 
            MyComponentクラスのコンストラクタはMySubComponentインターフェイスを引数にとるが、
            argタグを省略した場合は、コンテナに登録されている同型のオブジェクトが1つだけあれば、それを
            自動的に適用するため、省略できる。
         -->
         <!-- <arg></arg> -->
         
         <!--
             メソッドの呼び出しについてAOPによるフックをかけられる。
             アスペクトは、AbstractInterceptorから派生する。
             pointcutにはカンマ区切りでメソッド名を指定できる。メソッド名には正規表現が使える。
             pointcutを省略した場合、そのクラスが実装しているインターフェイスの全てのメソッドに
             適用される。(インターフェイスをもっていることが必須である。)
           -->
         <aspect pointcut="get.*Component">trace</aspect>
    </component>

</components>

実行結果

[INFO] Main - クラス名: class jp.seraphyware.test.MyComponentImpl$$EnhancedByS2AOP$$b82368
[TRACE] MyComponentImpl - begin: public jp.seraphyware.test.MySubComponent jp.seraphyware.test.MyComponentImpl.getSubComponent()
[TRACE] MyComponentImpl - end (result=hello, world!)
[DEBUG] Main - name=hello, world!
[DEBUG] Main - obj1とobj2は等しいか? false
[TRACE] MyComponentImpl - begin: public jp.seraphyware.test.MySubComponent jp.seraphyware.test.MyComponentImpl.getSubComponent()
[TRACE] MyComponentImpl - end (result=hello, world!)
[DEBUG] Main - sub1とsub2は等しいか? true
[DEBUG] Main - sub1とsub3は等しいか? true
[INFO] MySubComponentImpl - MySubComponentImpl#close()
実験コード2

app.diconだけ差し替えて、自動登録をさせてみる。

app.dicon (その2)

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

    <!--
         コンポーネントの自動登録。
         一括で登録するため、個々にプロパティやコンストラクタの指定、destroyMethodの指定などはできない。
         ただし、コンストラクタやプロパティは自動バインドされるので、コンテナ上に、その引数に合致する型が
         登録されている場合、それを適用することができる。
     -->
    <component class="org.seasar.framework.container.autoregister.ComponentAutoRegister">
        <!-- 
            登録されるコンポーネントのinstance属性を指定する。
         -->
        <property name="instanceDef">
            @org.seasar.framework.container.deployer.InstanceDefFactory@SINGLETON
        </property>
        <initMethod name="addReferenceClass">
            <!-- クラスを検索するクラスローダを発見するために同一クラスローダで読み込まれるクラス名を明示する -->
            <arg>@jp.seraphyware.test.Main@class</arg>
        </initMethod>
        <initMethod name="addClassPattern">
            <!-- パッケージ名 -->
            <arg>"jp.seraphyware.test"</arg>
            <!-- クラス名。クラス名は正規表現を指定できる。 -->
            <arg>"My.*ComponentImpl"</arg>
        </initMethod>
    </component>
    
    <!-- 
        アスペクトをコンテナに登録する。
     -->
    <component name="trace" class="jp.seraphyware.test.MyInterceptor" />

    <!--
         アスペクトの自動登録。
         アスペクトの登録は、コンポーネントの登録後でなければならない。
     -->
    <component class="org.seasar.framework.container.autoregister.AspectAutoRegister">
        <!-- 適用するアスペクト名 -->
        <property name="interceptor">trace</property>
        <initMethod name="addClassPattern">
            <!-- パッケージ名 -->
            <arg>"jp.seraphyware.test"</arg>
            <!-- 適用するクラス名。正規表現を指定できる。 -->
            <arg>"My.*ComponentImpl"</arg>
        </initMethod>
    </component>
    

</components>

実行結果

[INFO] Main - クラス名: class jp.seraphyware.test.MyComponentImpl$$EnhancedByS2AOP$$1960f05
[TRACE] MyComponentImpl - begin: public jp.seraphyware.test.MySubComponent jp.seraphyware.test.MyComponentImpl.getSubComponent()
[TRACE] MyComponentImpl - end (result=(null))
[TRACE] MySubComponentImpl - begin: public java.lang.String jp.seraphyware.test.MySubComponentImpl.getName()
[TRACE] MySubComponentImpl - end (result=null)
[DEBUG] Main - name=null
[DEBUG] Main - obj1とobj2は等しいか? true
[TRACE] MyComponentImpl - begin: public jp.seraphyware.test.MySubComponent jp.seraphyware.test.MyComponentImpl.getSubComponent()
[TRACE] MyComponentImpl - end (result=(null))
[DEBUG] Main - sub1とsub2は等しいか? true
[DEBUG] Main - sub1とsub3は等しいか? true

なんというか、簡単だが…。

むしろ、簡単にするために自動バインドで型を積極的に使うことになっているような感じであろうか。


とりあえず、JTAとJ2EE的宣言トランザクションを使ってみる

必要なもの

前述のjarに加えて、以下を追加。

  • s2-extension-2.4.6.jar
  • geronimo-jta_1.0.1B_spec-1.0.jar apache geronimo
    • または、geronimo-j2ee_1.4_spec-1.0.jar (JTAを含む)
  • derby.jar (JDK6付属のもの) apache derby
実験コード

Main.java

package jp.seraphyware.test;

import java.util.List;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class Main {

    static {
        SingletonS2ContainerFactory.init();
        final Thread shutdownThread = new Thread() {
            @Override
            public void run() {
                SingletonS2ContainerFactory.destroy();
            }
        };
        Runtime.getRuntime().addShutdownHook(shutdownThread);
    }
    
    public static void main(final String[] args) throws Exception {
        final S2Container container = SingletonS2ContainerFactory.getContainer();

        // DAOを取得
        final TestDAO dao = (TestDAO) container.getComponent(TestDAO.class);
        
        // DAOの実行
        final List<TestDTO> results = dao.select("select * from TESTTBL");
        for (final TestDTO result: results) {
            System.out.println(result);
        }
    }
}

まともな実装はせず、単にS2がどのよう設定で、どのように動くのか、ログで見るだけ。

DAO、DTOの雑多なコード

public interface TestDTO {
    // 省略
}

public class TestDTOImpl implements TestDTO {
    // 省略
}



/**
 * Testテーブルに対するデータアクセス
 */
public interface TestDAO {

    /**
     * データベースから条件でレコードを検索する。
     * @param v_condition 条件
     * @return レコードを格納した{@link Test2DTO}のリスト
     * @throws SQLException 失敗
     */
    List<TestDTO> select(String v_condition) throws SQLException;
}


/**
 * Testテーブルに対するデータアクセスの実装
 */
public class TestDAOImpl implements TestDAO {

    /**
     * ログ
     */
    private static final Log log = LogFactory.getLog(TestDAOImpl.class);

    /**
     * データソース
     */
    private final DataSource _dataSource;
    
    /**
     * データソースを指定してDAOを構築します。
     * @param v_dataSource データソース
     */
    public TestDAOImpl(final DataSource v_dataSource) {
        if (v_dataSource == null) {
            throw new IllegalArgumentException("データソースが指定されていません。");
        }
        this._dataSource = v_dataSource;
        log.debug(v_dataSource);
    }
    
    /**
     * データベースから条件でレコードを検索する。
     * @param v_condition 条件
     * @return レコードを格納した{@link Test2DTO}のリスト
     * @throws SQLException 失敗
     */
    public List<TestDTO> select(String v_condition) throws SQLException {
        // AOPによる「j2ee.xxxTx」インターセプタを使わない場合は、
        // j2ee.diconによりコンテナに登録されるUserTransactionを明示的に取得することができる。
        // final UserTransaction userTx = (UserTransaction)
        //      container.getComponent(UserTransaction.class);
        
        final List<TestDTO> results = new ArrayList<TestDTO>();
        final Connection con = _dataSource.getConnection();
        try {
            // 省略
        }
        finally {
            con.close();
        }
        return results;
    }
}

app.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN"
    "http://www.seasar.org/dtd/components23.dtd">
<components>
    <!--
        JTAやJDBCのコンテナ、データソースを利用するためにはj2ee.diconが必要。
        ここでUserTransactionなどの登録が行われる。
        j2ee.diconはs2-extentionのjarに含まれているため修正する必要ないが、
        このj2ee.diconは、更にjdbc.diconを要求する。
        jdbc.diconはデータベースの設定とコネクションプール、データソースの設定を行ったものを
        クラスパス上に配置しておく必要がある。
    -->
    <include path="j2ee.dicon" />
    
    <!-- xxxDAOImplの自動登録 -->
    <component class="org.seasar.framework.container.autoregister.ComponentAutoRegister">
        <initMethod name="addReferenceClass">
            <arg>@jp.seraphyware.test.Main@class</arg>
        </initMethod>
        <initMethod name="addClassPattern">
            <arg>"jp.seraphyware.test"</arg>
            <arg>".*DAOImpl"</arg>
        </initMethod>
    </component>
    
    <!--
        AOPにより、xxxDAOの全てのメソッドに対して、
        j2ee.requiredTx のコンテナ管理トランザクションを設定する。
        これにより、メソッドの開示時にJTAに対してbeginされ、終了時にcommitされる。
        例外が発生した場合はrollbackされる。
    -->
    <component class="org.seasar.framework.container.autoregister.AspectAutoRegister">
        <property name="interceptor">j2ee.requiredTx</property>
        <initMethod name="addClassPattern">
            <arg>"jp.seraphyware.test"</arg>
            <arg>".*DAOImpl"</arg>
        </initMethod>
    </component>
    
    <!-- xxxDTOImplの自動登録 -->
    <component class="org.seasar.framework.container.autoregister.ComponentAutoRegister">
        <property name="instanceDef">
            @org.seasar.framework.container.deployer.InstanceDefFactory@SINGLETON
        </property>
        <initMethod name="addReferenceClass">
            <arg>@jp.seraphyware.test.Main@class</arg>
        </initMethod>
        <initMethod name="addClassPattern">
            <arg>"jp.seraphyware.test"</arg>
            <arg>".*DTOImpl"</arg>
        </initMethod>
    </component>
</components>

TestDAOImplクラスのコンストラクタは、DataSourceを引数として受け取るのだが、app.diconの中では指定していない。

これは、明示的に指定しなくても、コンテナ内に登録されているDataSource型のオブジェクトが1つだけあれば、自動登録するときに勝手にバインドする為である。

app.diconの中でj2ee.diconをincludeすると、jdbc.diconが必要とされるので、これは自分で設定する。

jdbc.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
    "http://www.seasar.org/dtd/components21.dtd">
<components namespace="jdbc">
    <include path="jta.dicon"/>

    <!-- Derbyのコネクション -->
    <component name="xaDataSource"
        class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
        <!-- ドライバクラス名 -->
        <property name="driverClassName">
            "org.apache.derby.jdbc.EmbeddedDriver"
        </property>

        <!-- 接続文字列 -->
        <property name="URL">
            "jdbc:derby:testdb;create=true"
        </property>
        
        <!-- ユーザ/パスワード -->
        <property name="user">"user1"</property>
        <property name="password">"user1"</property>
    </component>

    <!--
         コネクションプール。
         コネクションプールは、プールするだけでなく、
         データソースとJTAを連携するためにも必要となる。
      -->
    <component name="connectionPool"
        class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
        <!--
             データベースのコネクション。
             コンテナにXADataSource型が1つしかない場合は省略可能。
          -->
        <property name="XADataSource">xaDataSource</property>

        <!-- プールが枯渇した場合のタイムアウト -->
        <property name="timeout">600</property>

        <!-- 最大コネクション数 -->
        <property name="maxPoolSize">10</property>

        <!-- JTAなしにコネクションをオープンすることを許可するか? -->
        <property name="allowLocalTx">false</property>

        <!-- コンテナが終了したときに呼び出されプールの後始末をする -->
        <destroyMethod name="close"/>
    </component>

    <!--
        アプリケーションからアクセスするデータソース。
        このデータソースを通じてコネクションを取得すると、プールからコネクションが
        取得されると同時に、JTAの分散トランザクションにも参加する。
      -->
    <component name="dataSource"
        class="org.seasar.extension.dbcp.impl.DataSourceImpl">
        <!--
            プールを指定する。
            コンテナにConnectionPool型が1つしかない場合は省略可能。
        -->
        <arg>connectionPool</arg>
    </component>

</components>
実行結果
[DEBUG] TestDAOImpl - org.seasar.extension.dbcp.impl.DataSourceImpl@1542a75
[DEBUG] TransactionImpl - トランザクションを開始しました
[DEBUG] ConnectionPoolImpl - 物理的なコネクションを取得しました
[DEBUG] DataSourceImpl - 論理的なコネクションを取得しました
[DEBUG] ConnectionWrapperImpl - 論理的なコネクションを閉じました
[DEBUG] TransactionImpl - トランザクションをコミットしました
[DEBUG] ConnectionWrapperImpl - 物理的なコネクションを閉じました

ちゃんとJTAのメカニズムが動いているようだ。

一応、ソースをみて確認したが、たしかにorg.seasar.extension.dbcp.impl.DataSourceImplでコネクションを取得したとき、コネクションプールを経由してトランザクションマネージャに対しenlistするように要求している。

また、j2ee.requiredTxのインターセプタもソースを確認したところ、単純に、トランザクションマネージャに対して、まだトランザクションが開始されていなければbeginし、メソッドを呼び出して、トランザクションを自分が開始していればcommitする、という動きになっていて、呼び出し元はトランザクションに対して意識することなく、AOPによって自動的に呼び出されるため、たしかにCMT(Container Managed Transaction)的である。

今回はDAOにRequiredを指示しているが、現実的な利用ではDAOはMandatoryで、ビジネスロジックに対してRequiredかRequiredNewをかけることになるだろう。

J2EEサーバを使わずにEJB的な宣言的トランザクションが使えることは大きなメリットだと思う。

結論

S2Container単体、およびJTA/CMT的なサポートは非常によく出来ていると思う。

JavaSEのレベルで、これが普通に使えるのはうれしい。

普通にファンになっちゃったよ。


だが、S2StrutsやらS2Daoが、本当に使いやすいのかは分からない。

これを調べるのが大変なのだと思うのだが。まだまだ先は長いな…。

2006-12-01 JTAとJNDIの仕組み

[]JTAとJNDIの謎を追う このエントリーを含むブックマーク

動機

今日は道草、寄り道。

50%:50%の合弁会社なのでシステム部門も3つあったりする会社での仕事。

相手先がウチよりもレベルが高い(CMMレベル5)ことを鼻にかけて「キミんところで無理なようなら、うちが全部やってあげるよ」とか言われたらしくて、その場にいた一同、ムカッときたらしい。

いや、やってくれるのはかまわないのだが、その見下した態度はどうなのか。

私は、その場に居なかったのでいいんですけど。

実力ないのは事実だし。

final InitialContext ctx = new InitialContext();
final UserTransaction userTx = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
final DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myDB");

この見慣れたコードの断片はWeblogic6.1Jの公式ガイドブックに書かれていたものを見て以来、なにやってるんだろうなぁ、と思っていた。

まず、JNDIの「java:」って、どうゆうメカニズム? という疑問。

java:comp/env/... で、どこからでも今実行しているアプリケーションごとの設定を見れるんだよ。

実行する場所が変われば同じコードでも違う設定が見れるし、位置透過性があるというか。

もう1つは、なんでDataSourceからコネクションを取得するだけでJTAでトランザクションが管理できるの?

javax.transaction.TransactionManagerからjavax.transaction.Transaction#enlistResource()で、XAResourceを分散トランザクションに参加させられることは分かったけれど、

DataSourceからコネクションを取得すると、如何して、このトランザクションに参加することになるのかよ?

と、不思議に思っていたわけだ。

だが、この2つは調べてみてもJ2EEの奥深さに畏敬の念をより強く感じさせられるだけで、まあ、Weblogicとかよろしくやってくれているから、まあ、いいか、と断念して1年が経過してしまった。

最近になって「今回のプロジェクト、TomcatでJTAを使うらしいよ」と聞いて、そんなことできるのか*1と驚いて、あらためて、いろいろ調べてみた。

DataSourceは、いつトランザクションに参加するのか。

JOTMとTomcatの組み合わせ例とか見ながら、まずは、JOTMのソースを見てみる。

うむ…、やはりenlistするタイミングは、依然不明じゃないか。

だが、server.xmlの中に指定するDataSourceに不思議な違和感を感じた。

なぜか、データソースにJOTMのクラスを指定している…。もしや、これはWrapperなのか、と気がつく。

あとは、それらしきものを頼りにGoogleでさまようこと1時間。途中で眠くなったり、もうやめようかと思ったりしながら、ようやく、たどり着いた。

http://www-128.ibm.com/developerworks/java/library/j-jtp0410/

Every J2EE container can create transaction-aware pooled DataSource objects, but the J2EE specification doesn't show you how, because it's outside the spec. If you browse the J2EE documentation, you won't find anything on how to create JDBC data sources. You'll have to look in the documentation for your container instead. Depending on your container, creating a data source might involve adding a data source definition to a property or configuration file, or might be done through a GUI administration tool.

Each container (or connection pool manager, like PoolMan) provides its own mechanism for creating a DataSource, and it is in this mechanism that the JTA magic is hidden. The connection pool manager obtains a Connection from the specified JDBC driver, but before returning it to the application, wraps it with a facade that also implements Connection, interposing itself between the application and the underlying connection. When the connection is created or a JDBC operation is performed, the wrapper asks the transaction manager if the current thread is executing in the context of a transaction, and automatically enlists the Connection in the transaction if one exists.

幽霊の正体見たり枯尾花(?)。

マジックも種を明かされれば、そんなものか、と思ったり。

コンテナがRDBMSのDataSourceなどをラップして、コネクションを取得するときにトランザクションに参加させているだけのことだったのだ。

しかも、それはJ2EEの仕様に、そうしろとは書かれていないらしい。(自明だから、ですかね?)

JNDIプロバイダは、どうやって拡張されるのか。

あと、JNDIのほうも、なんか分かった気がした。

実装できるレベルじゃないけど、ためしにlookup("seraphyware:comp/env")みたいにできるものを作ってみた。

InitialContextの実装で対処するのか、もしそうなら、2つ以上のプロバイダをインストールしようとしたらかち合うんじゃないか、とか、いろいろ余計な心配をしていたが、そうではなかった。

InitialContextからContextを取得したあとlookupするときに、名前の先頭が「スキーマ付き」だと特別扱いになって、xxxURLContextFactoryというクラスを呼び出すだけなんだ。

xxxは、そのままスキーマ名。スキーマは小文字が普通だから、クラス名も、なんと小文字で始まるという異例さ。

あとは、どのパッケージから検索するか、であるが、これも「jndi.properties」というプロパティファイルに書いてクラスパス上に置いておくだけらしい。

クラスパス上に複数のjndi.properiesがあることも当然予想されるが、これらは、すべて連結して検索されるから問題ないらしい。

スキーマでContextの実装が振り分けられた後は、そのContextの実装の中で名前をよろしく解析して、それなりにオブジェクトを返すだけでよい。

幽霊の正体見たり枯尾花(?)。

マジックも種を明かされれば、そんなものか、と思ったり。

結論

とりあえず、満足。

なにかをやり遂げた充実感でいっぱいです。(なにもしてないけど。)

*1:つーか、素直にJ2EEサーバ使おうよ、とか思ったり。

2006-09-27 Python2.5でSQLite3を使ってみる。

[][] Python2.5でSQLite3を使ってみる。 このエントリーを含むブックマーク

SQLiteとは

Python2.5には、標準でSQLiteという軽量データベースが含まれている。

SQLiteは、JAVAでいうところのHSQLDBやApache Derbyのような感じのようである。

SQLLiteはデーモンやサービスではなく、スタンドアロンでのみ動作する。(DerbyやHSQLDBは組み込みモードとサーバーモードの両方をもつが。)

また、データベースのストアもディスク上の単一ファイルになる。

Windowsでいうところの、ACCESS95〜2000で使われていた、JETデータベースエンジンのMDBファイルのような感じであろうか。

要するに単なるライブラリである。

ライセンスは、なんとパブリックドメイン(著作権放棄)である。

そのため、Pythonに限らず、C/C++、Perl、PHPなどに広く組み込まれている。

Python2.4まではアドオンとして別途インストールする必要があったらしいが、Python2.5からは標準装備となっていた。

標準ライブラリとして使えることは、大きな意味があると思う。

Python2.5の実行環境さえセットアップすればよいので、データベースを使うスクリプトを作ることも、利用することも、配布することも、どれも大幅に敷居がさがること間違いない。

JAVAXMLパーサをバンドルしてヒットしたように、Derbyあたりをバンドルしてくれればいいのに。(J2EEのSDKにはバンドルされているが。))

そこで、さっそく試してみた。

Python2.5では、sqlite3というモジュールをインポートするだけで準備完了である。

基本的な使い方(DMLとトランザクション処理)

SQLiteは、DerbyやHSQLDBと同様に「インメモリ・データベース」として動作することができる。

揮発性の、コネクションを閉じるとデータも消えてしまうものだが、テストプログラムや、スタンドアロンアプリケーションの中で一時データの集計などを行う場合には便利だと思われる。

この「インメモリ・データベース」で、基本的な使い方を実験してみた。

私自身がPython使いではないので無駄(もしかすれば間違いも)あるかもしれないが、

使ってみた感触では、意外と簡単というか、APIはよく出来ている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Python2.5以降のみ
# 13.13 sqlite3 -- DB-API 2.0 interface for SQLite databases および、
# http://www.python.org/dev/peps/pep-0249/
# を参照のこと。

# SQLiteの利用
import sqlite3

# テーブル内容の一覧表示
def dump(conn):
    cur = conn.cursor()
    try:
        cur.execute("select idx, val from testtbl order by idx")
        for row in cur:
            # カーソルは、fetchone,fetchallなどで取得できるほか、
            # それ自身をレコードのリストとして列挙可能である。
            print row
    finally:
        cur.close()


# インメモリデータベースとしてコネクションを作成する。
conn = sqlite3.connect(":memory:")

# 指定がないばあいは、最初のSQL文実行でトランザクションが開始する。(beginは必要ない。)
# Autocommitにするばあいは、isolation_levelをNoneにする。
try:
    # DDLを実行する。
    # executescript()はセミコロンで区切り複数のSQL文を流し込める。
    # (コネクション・オブジェクトに定義されているexecuteメソッドは、一時カーソルを作成して
    # 実行する、コンビニエンスメソッドである。)
    # SQLite3は、Integer/Real/Text/BLOB(Binary Large Object)のみサポート。
    # それ以外の型は、類推して、いずれかに割り当てられる。
    conn.executescript("""create table testtbl(
        idx integer primary key,
        val varchar2(512));""")

    # 「カーソル」は読み取りに限らず、SQLの実行のすべてを行う。
    # (トランザクションは、コネクションで制御する。)
    cur = conn.cursor()
    finished = False
    try:
        for n in range(1, 100):
            # 「?」はバインド変数で、引数をしてタプルなどの列挙可能なオブジェクトを渡す。
            # (タプルのタプルを渡すと、複数行の操作になる。)
            # SQLite3は「INTEGER PRIMARY KEY」のカラムを空にすると自動で割当てる。(ROWID)
            # http://www.sqlite.org/faq.html#q1
            cur.execute("insert into testtbl(val) values(?)", (str(n),))
        finished = True
        conn.commit()
    except:
        if not finished:
            # commitに失敗した場合は、トランザクションはロールバックされて無効になっている(はず)
            conn.rollback()
        raise
    finally:
        cur.close()

    # 内容の確認
    dump(conn)
except Exception, ex:
    print ex
finally:
    conn.close()

#
# インメモリデータベースをもう一度開いてみる。(テーブルが未定義と言われる = 消えている。)
conn2 = sqlite3.connect(":memory:")
try:
    dump(conn2)
except Exception, ex:
    print ex
finally:
    conn2.close()

マルチスレッドでのデータベースへの書き込みアクセス

次に、実際にファイルに書き出しながらマルチスレッドでアクセスを試してみた。

SQLiteはスレッドセーフであるが、connect単位で独立させる必要があるとのこと。

つまり、データベースファイルには複数スレッドからアクセスしてもよいが、そのコネクションは共有してはならない、ということ。

また、コネクション・オブジェクトそのものがトランザクションを管理しているようである。

このあたりはJDBCも同様であるから、とくに驚きはない。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python2.5以降のみ

import threading
import sqlite3

# データベースファイル名
# SQLiteのデータストアは単一のファイルである。
DBNAME = "threadingtest.db"

# データベースの準備
def init_db():
    conn = sqlite3.connect(DBNAME)
    try:
        # テーブルを作成する
        conn.executescript("""create table testtbl(
            idx integer primary key, tname varchar(128), val integer);""")
    except Exception, ex:
        # 作成に失敗したら、作成済みと想定して、既存データを削除する
        conn.execute("delete from testtbl")
        conn.commit()
    finally:
        conn.close()

# コネクションを操作するスレッド
class MyThread(threading.Thread):
    def run(self):
        # SQLiteは、コネクション単位でスレッドセーフである。
        # つまり、コネクションやカーソルをスレッドをまたいで使うことは出来ない。
        # ロックのタイムアウトは、60秒に設定。
        conn = sqlite3.connect(DBNAME, timeout=60000)
        # 1件づつ暗黙でコミットする。(Autocommit)
        conn.isolation_level = None
        try:
            cur = conn.cursor()
            try:
                for n in range(1, 10):
                    cur.execute(
                        "insert into testtbl(tname, val) values(?, ?)",
                        (self.getName(), n))
            finally:
                cur.close()
        except Exception, ex:
            # タイムアウト時間までにロックを獲得できなかった場合は例外が発生する。
            print "thread=%s /exception=%s" % (self.getName(), str(ex))
        finally:
            conn.close()

# メイン
def main():
    try:
        init_db()
        
        # スレッドを作成する
        threads = []
        for n in range(1, 10):
            t = MyThread()
            t.setName("MyThread:%d" % n)
            threads.append(t)

        # スレッドを開始する
        for t in threads:
            t.start()

        # すべてのスレッドの終了を待機する
        for t in threads:
            t.join()

        print "done."

    except Exception, ex:
        print ex

# 書き込み結果の表示
def verify_test():
    conn = sqlite3.connect(DBNAME)
    try:
        cur = conn.cursor()
        try:
            cur.execute("select count(*) from testtbl")
            cnt = cur.fetchone()[0]
            print "count=%d" % cnt
            cur.execute("""select tname, count(val) from testtbl
                group by tname order by tname""")
            for row in cur:
                print row
        finally:
            cur.close()
    except Exception, ex:
        print ex
    finally:
        conn.close()

# 実行
main()
verify_test()

実際に試した感触では、どうやら、トランザクションが開始されるとジャーナルファイルが作成され、コミットするとジャーナルファイルがデータベースファイルにマージされたあと削除され、このジャーナルが存在する期間は、別のスレッドはブロックされてしまうようなのだ。

SERIALIZEレベルの分離レベル、ということ?

いや、これはテーブルロックどころか、データベース全体をロックしているように見える。

まあ、これは見た感触であって、ただの推論だが。

結論

SQLiteは簡単に使えるうえに簡単に壊れたりすることはないようだが、

実感として、マルチスレッドでの書き込みが頻発する状況では、かなり遅いと考えてよさそうである。

しかし、主要な用途をクライアントサイドのスタンドアロンなアプリケーションのバッキングストアとして利用する分には、なんの問題もないだろう。

(BLOBのサイズ、データベースファイルの上限も、かなりでかい。まず、使い切ることはないだろう。)

そのうえで、共有しても壊れない、ファイルコピーで持ち運べる手軽さは魅力的である。

今後、Pythonでプログラムを書くときは、これを使う選択肢も覚えておくべきだろう。

C/C++バインドであるが、Windows版はDLL一個で動作するらしい。

さらに、msvcrt.dllという、VCのランタイムライブラリ以外に依存しない、というからまったく手軽なものである。

ぜひ、C/C++でSQLiteを使う方法も習得してみたいと思わされた。

これは、まちがいなく優れたデータベースの1つであって、覚えて損はないだろう。

2006-07-16 Cassini

[] Cassiniを知る このエントリーを含むブックマーク

Windows XP Homeでウェブアプリ

 ここ数年でJAVAもしくはDotNETでのウェブアプリという方向性が浸透していた、ということを実感。

 J2EEしかやってなかったからDotNETがどのくらい使われているのか、いまいちわからなかったのだが、思っていたよりも使われているようだ。

 仕事とはべつに、ちょっとしたウェブアプリを作りたいと思っているのだが、J2EEの機能のうちウェブサーバしか提供していないTOMCATでは力不足で、JBossは再配布がめんどくさいし、でかすぎる。

 Sun Java System Application Server 9 Platform Editionはセットアップもデプロイメントも簡単で、この中では、もっとも最適な解に近いと思う。

 しかし、SJSAS9PEをもってしても、デフォルトで起動するデータベースサーバがない。JAVAで軽量で無料のデータベースとしてはApache DerbyやHSQLDBがあるが、アプリケーションサーバの起動と同時に立ち上がるようにするには少し手間がいる。

 そして、なにより、これらのサーバ類を立ち上げている間、そのマシンの物理メモリの100MBぐらいは軽く仮想マシンが占有してしまう。

 本当にサーバ機に入れるのならば何を気にすることもないのだが、入れたいのは一般人が使うWindows XP Homeのようなマシン。使うときだけJ2EEサーバを立ち上げて、使い終わったらJ2EEサーバをシャットダウンして…、って、かなりめんどくさくてユーザフレンドリじゃない。

Windows XP Home + ASP.NETでウェブアプリ

 なるほど、ASP.NETなら仮想マシンに食われるメモリの心配もしないていいし、試した限りでは不活性状態のDotNETアプリは物理メモリをほとんど食わない。

 …と思ったら、よくよく考えてみると、Windows XP HomeにはIISがインストールできない。

 ちょっと検索すると、

 http://www.15seconds.com/issue/020118.htm

 てな記事があって、homeにiisを入れるためにpro版であることを偽装する方法があるようだが、proのライセンスがない場合には非合法だ。

 しかし、VS2005にはmini-IISのようなものがついていて、実はWindowsXP Home Edition上でASP.NETの開発ができるようになっている。もともとASP.NETの構造は従来のASPのようにIISにディペンドしているものではなく、ウェブサーバへのリクエストをASP.NETのインフラに接続する仕組みになっていて、たとえば、SessionやApplicationCacheなどもASP.NETのフレームワーク側がもっている。当初よりIISに依存しないつくりである。実際に、ApacheにASP.NETを接続するためのmodが開発されているらしい。

 まあ…、UnixでASP.NETの意味があるのかわからないが、Windows 2003 ServerでApache + ASP.NETの意味は限りなくゼロだろう。

 しかし、Windows XP Home + Apache + ASP.NET、というのならば話は別だ。それも、アリかもしれない。

 …だが、こんな記事もみつけた。

http://msdn.microsoft.com/msdnmag/issues/04/12/ServiceStation/default.aspx

 なんだか、よくわからないが、ASP.NETのインフラに接続するために必要なことは、ほんとうに少ないらしい。

 Apacheである必要性がない。

 さらに調べてみたら、VS2005が使っているmini-IISのような機能は、サンプルサーバのCassiniと呼ばれるもので、ASP.NETのサイトで、マイクロソフトが以前から提供しているものらしい。これはサービスとして起動するわけでもなく、配置できるアプリケーションも1つだけのシンプルで単純なサンプル・ウェブサーバである。

 そして、それをベースに、再配布、アプリケーションの配備を簡単にし、複数のアプリケーションのデプロイとサービスとしての起動を可能にしたフリーのサーバがあるらしい。

 UltiDevCassiniWebServerというものである。

http://www.ultidev.com/Products/Cassini/CassiniDevGuide.htm

 おお、これは大本命かもしれない。

 データベースサーバはSQLServer2005Express Editionを使えばいいし、ウェブサーバも、これでセットアップできるのならば、ASP.NETで開発してもXP Homeの心配はしなくてすみそうだ。

 おまけにSQLServer2005もASP.NETも不活性状態であれば物理メモリに与えるインパクトは少ない。OracleやJ2EEのようなサーバ・アプリがメモリを占有できるリッチなマシンでなくてもいい。他のアプリケーションに与える影響も少なく、一般コンシューマのマシンにもウェブベースのアプリケーションを配布できる感じなっている気がする。

 ちょっと調べてみるかな。

(追伸) 普通に問題なく動いたし、簡単にデプロイできました。あとは、再配布用のセットアップを、どう作るか、ですかね…。

2006-07-06 Vistaは名称変更がいっぱい

[] Windows Vista Beta2 #3 このエントリーを含むブックマーク

VS2005、WindowsSDK(beta2)のインストール完了。

昨日、VS2005がうまくゆかなかったのは、たぶん、評価版の時期かもしれない。今日、うまくいったのは、たけさんからもらったバージョン。同じVS2005で同じバージョンのはずなのだが。昨日失敗したバージョンはProfessionalの180日評価版。

現在、Professionalの評価版をとろうと思うと90日評価版になっている。Team版なら180日だけど、あのころ、それは評価版として提供されていなかった気がする。

微妙にインストール時の動作が違うのかもしれない。

思ったより使えている

VistaはOS添付の標準のソフトウェアの出来は悪くないが、それ以外のソフトでは微妙に小さなところで不具合がある。

つまり、互換性問題が多いようだ。

ドライバやセキュリティモデルの変更に伴う積極的な互換性問題もあるが、それは、そうゆうものだと割り切る必要があろう。x64版でドライバがなかなかそろわなかったケースと同じだと思う。

しかし、そうではない部分での小さな互換性問題がチマチマとあって、これが問題になりそう。来年までにつぶせるのか少し疑問。たとえば、Google ToolbarをIE7+にインストールすると、問題なく動くのだけど、画面上で右クリックしてコンテキストメニューを開いてもマウスに反応しなくなる。(キーボードでは操作はできるところが、いかにもタチの悪い不具合っぽさがある。)

Becky!2のツリービューが崩れる問題も、使えないことはないけれど些細な互換性問題がたくさんあることを示唆しているのだろう。

それと、シェルの動きが、まるで3.1時代か、X-Windowのような動きに退化してしまっている部分がある。どこかのウィンドウでメッセージのハンドリングが滞ると、他のウィンドウにも影響を与えてしまうかのような動きをしているように見える。

どこかのプロセスがロック待ちしているから、他のアプリを先行して操作しておこう、とか思っても、なかなか、うまく動いてくれず、動いたころには待っていた側のロックも外れている、みたいな感じの動きをする。

従来のWin32系のWindowsの仕組みからすれば、ありえないと思うが、16ビット時代のように、1つのリソースを取り合うような仕組みに逆戻りしているのか。

ブルーバックは1件のみ

ブルーバックは今朝の事例が1回あったのみ。

とりあえずMS謹製(?)のカーネルモードで動くドライバの出来は安定性の面では実用レベルにあるのじゃないかな。

パフォーマンスは度外視されているけど。

あと、電源ボタンのように見えるスタートメニューにあるボタンを押して、やけに電源切れるのが早いな、と思いつつハードディスクを抜いてブルーバックになったのは、これは当たり前だが。これはサンペンドだったらしい。

まだ見えないところではあるが。

強化された部分については、まだ体感できていないが、さまざまな部分で名称変更を伴う「革新」が提供されているようだ。

WinFXは「DotNET Framework 3.0」と名称変更された。

明らかに、これはXPにもインストール可能になることを意味していると思う。

現行の「DotNET Framework2.0」をコアにして、その上で動作する3つのフレームワークを総称して3.0と呼ぶらしい。

Vistaのもっさり感はDotNETのもっさり感からきているのか。

J2SEにエンタープライズ系のライブラリをくっつけたのがJ2EEであるように、2.0の言語や基礎ライブラリの上に、GUI32にかわる、あたらしいGUIレイヤーと、P2Pをはじめとする通信インフラ、くわえてタスク処理系の新しい仕組みをつけたのが、DotNET Framework3.0ということらしい。

また、Windows Script Host(WSH)は、Windows PowerShellと名称が変更された。これも、DotNET Framework3.0の一部と考えてよいらしい。具体的に、どう強化されたのかわからないが、3.0のワークフロー系のフレームワークと関連があるのか。

一般ユーザで目に付くところは新しいGUIだろう。3.1の時代にはコンボボックスやリストボックスはあったが、エクスプローラで使われているようなツリービューやリストビューは95から。ウィザード画面も95からだった。Vistaでは、あたらしくタブ型のメニューなど、いくつかの新しい操作性が提供されることになるらしい。

標準ファイルダイアログ(コモンダイアログ)も刷新されたし、たしかに以前から実装はされていたIExtractIcon系のファイルコンテンツのプレビュー機能などを本格的に積極的に使うようになっている。

結論

なかなか興味深い。現行のXPまでのWindowsとは確かに「革新」を目指した感じがある。