プロジェクトウィザードプラグインについて

Doltengでは、「Chura Project」として、
幾つかのプロジェクトの中から一つをプルダウンメニューから選択する事で、
ある程度、作りこんだブランクプロジェクトを生成する機能があります。


この機能は、他のプラグインから、拡張ポイントに機能を差込む事で、
生成する事が出来るプロジェクトを増やす事が出来ます。
これから、その拡張ポイントに機能を差込む方法について説明します。


前提知識として、eclipseプラグインを作成する方法について
ある程度知っている必要がありますが、
実際的には、org.seasar.dolteng.projectsのコードや設定ファイルの類を見る方が早いとは思います。


確実に理解しなくてはいけない事柄が、幾つかあります。

  • plugin.xml
  • ResouceLoader
  • ResourceHandler
  • プラグインから、生成されるプロジェクトにリソースがコピーされる処理概要


それでは、一つづつ説明します。
理解出来ない部分や、分り辛い部分があれば、
コメントやトラックバックでお知らせ下さい。
出来うる限り返答しますので。

ResouceLoader

jarファイルや、生成されるリソースのテンプレートを、読み出す為のクラスです。
非常に簡単なインターフェースです。

public interface ResouceLoader {

   public URL getResouce(String path);
}

新しくプラグインを作る場合、このインターフェースの実装クラスを作る必要があります。
基本的には、eclipseAPIを使って、リソースを読み出す事を想定しています。
「path」にどの様な文字列が引数となるのかについては、plugin.xmlに記述する内容の一部として後述します。


サンプルの実装としては、いきなり複雑な作りになってしまって申し訳ないのですが、
Doltengがデフォルトで生成できるプロジェクトの為のResouceLoaderは、こんな実装になっています。

public class CompositeResouceLoader implements ResouceLoader {
   protected final List<Bundle> bundles = new ArrayList<Bundle>();
   public CompositeResouceLoader() {
      bundles.add(Platform.getBundle(Constants.ID_PLUGIN));
      bundles.add(Platform
           .getBundle(org.seasar.dolteng.eclipse.Constants.ID_PLUGIN));
      bundles.add(Platform
           .getBundle("org.seasar.dolteng.projects.dependencies"));
   }
   public URL getResouce(String path) {
      URL result = null;
      for (Bundle b : bundles) {
        result = b.getEntry(path);
        if (result != null) {
           break;
        }
      }
      return result;
   }
}

ここでの重要なポイントは、org.osgi.framework.Bundleクラスです。
このクラスについての詳しい説明は話がそれるのでしません。
正しい説明とは程遠いのですが、
Bundleクラスが、プラグインのインストールされているディレクトリを表している…程度に考えて下さい。

例えば、この画像の場合、org.apache.antと言うIDを、Platform#getBundleに引き渡す事で、
Bundleオブジェクトを取得する事が出来ます。
Bundle#getEntryでは、このプラグインがインストールされているディレクトリからの相対パスを引き渡す事で、
リソースのURLを取得する事が出来ます。
最近のeclipseでは、プラグインディレクトリとして展開せずとも動作させる事が可能ですが、
その場合には、jarファイル内のエントリを取り出す事になります。


例えば、この画像の場合には、「lib/ant.jar」と言う文字列をBundle#getEntryに引き渡すと、
jarファイルを表すURLを取得する事が出来ます。
渡したパスにファイルやエントリが存在しないと、nullが返ってきます。

ResourceHandler

plugin.xmlとResouceLoaderの間を橋渡しすると共に、リソースの生成処理を行う為のクラスです。
基本的には、Doltengで提供している実装クラスを使う様に設定すれば良いクラスです。
殆どの場合、DefaultHandlerを使用すれば事足りる筈ですが、
主に、他のプラグインの為の処理を行う際に、実装クラスを作る事になると思います。

DefaultHanlderが行う処理について

DefaultHandlerでは、簡単に言うとファイルのコピーを行います。
コピーする対象が、ディレクトリの場合には、当該ディレクトリを作成しようとします。
対象リソースの種類を判別する基準については、plugin.xmlに記述する内容の一部として後述します。
そのファイルのコピー元となるリソースを読み出す処理自体は、前述したResouceLoaderに委譲します。


又、コピーするファイル名が、以下の正規表現に一致する場合、テキストファイルとみなし、
ファイルの中身を、あるルールに基づいて置換処理します。

.*\.(txt|java|dicon|properties|tomcatplugin|mf|x?html?|m?xml|pref|sql|jsp?)$

つまり、拡張子が、テキストっぽかったら、中身を置換する訳です。
これに一致しないファイルの場合には、バイナリファイルとして、そのままコピーします。


ファイルの中身を置換する処理は、大体以下の様にして行います。
Sysdeo Tomcat Pluginの為の設定ファイルである「.tomcatplugin」を例に説明します。


Doltengが内部に抱えているリソースは、以下の様になっています。

<?xml version="1.0" encoding="UTF-8"?>
<tomcatProjectProperties>
<rootDir>${webAppRoot}/</rootDir>
<exportSource>false</exportSource>
<reloadable>false</reloadable>
<redirectLogger>true</redirectLogger>
<updateXml>true</updateXml>
<warLocation></warLocation>
<extraInfo></extraInfo>
<webPath>/${projectName}</webPath>
</tomcatProjectProperties>

この中で、置換の対象になるのは、
${で始まり}で終わる部分です。
${と}に挟まれた内部の文字列をキーとして、Doltengが内部に抱えるコンテキスト情報から、
値を取り出し、${から}までの部分と置換します。


Doltengが内部で抱えるコンテキスト情報は、以下の様な種類があります。

キー 元になる情報
projectName ウィザードでの入力
packageName ウィザードでの入力
packagePath ウィザードでの入力
jreContainer ウィザードでの入力
libPath src/main/webapp/WEB-INF/lib 固定
libSrcPath src/main/webapp/WEB-INF/lib/sources 固定
testLibPath lib 固定
testLibSrcPath lib/sources 固定
mainJavaPath src/main/java 固定
mainResourcePath src/main/resources 固定
mainOutPath src/main/webapp/WEB-INF/classes 固定
webAppRoot src/main/webapp 固定
testJavaPath src/test/java 固定
testResourcePath src/test/resources 固定
testOutPath target/test-classes 固定

元になる情報が「固定」となっているものは、僕が画面を作るのが面倒でひよったからです。
こらをウィザードから入力出来る様にするつもりはあるのですが、中々気が向きません…。*1


又、やけに項目が多いのは、Maven2構成を前提にしている為です。
更に、このコンテキスト情報は、
プロジェクトに配置されるリソースのパスを決定する際にも、使用されます。
詳しくは、plugin.xmlに記述する内容の一部として後述します。

他のプラグイン用の処理を行うには

殆どのeclipseプラグインは、当該プロジェクトが、自身と関連付いているかどうかを、
プロジェクトの中にある設定ファイルと、Natureによって判断しています。
もし、それ以外の方法で関連を定義しているとすれば、それは、個々に調査するしかありません。
ここでは、生成しようとしてるプロジェクトをSysdeo Tomcat Pluginのプロジェクトにする為に、
Doltengが提供しているResourceHandlerを通して、他のプラグインと連携する方法を簡単に説明します。
これが唯一無二のやり方では無く、対応したいプラグインによっては、もっと複雑な事をしなければ、
ならないケースはかなりあると思います。


以下のコードが、Sysdeo Tomcat Pluginの為に作成したTomcatHandlerです。

public class TomcatHandler extends DefaultHandler {
   public TomcatHandler() {
      super();
   }
   public String getType() {
      return "tomcat";
   }
   public void handle(ProjectBuilder builder, IProgressMonitor monitor) {
      try {
        super.handle(builder, monitor);
        monitor
              .setTaskName(Messages
                   .bind(Messages.ADD_NATURE_OF, "Tomcat"));
        if (Platform.getBundle(Constants.ID_TOMCAT_PLUGIN) != null) {
           ProjectUtil.addNature(builder.getProjectHandle(),
                Constants.ID_TOMCAT_NATURE);
        }
        ProgressMonitorUtil.isCanceled(monitor, 1);
      } catch (CoreException e) {
        DoltengCore.log(e);
      }
   }
}

DefaultHandlerに対して、getTypeメソッド及び、handleメソッドをオーバライドしています。
getTypeメソッドの戻り値は、文字列リテラルを返す様にします。
又、この文字列リテラルは、plugin.xmlにResourceHandlerを定義した時、
そのname属性と一致させると分かり易いと思います。


DefaultHandler#handleメソッドには、二つの引数が定義されいます。

  • ProjectBuilder
  • IProgressMonitor

ProjectBuilderには、幾つかメソッドが定義されていますが、
ResourceHandlerの実装クラスで使用するのは、以下の二つです。

  • getConfigContext
    • Doltengが内部で抱えるコンテキスト情報の参照をMapとして取得出来ます。
  • getProjectHandle
    • 現在、生成中のプロジェクト参照を取得出来ます。


IProgressMonitorは、ウィザードの下部にあるインジケータを表すオブジェクトです。
setTaskNameを呼出す事で、インジケータの少しにメッセージを出力する事が出来ますが、
setTaskNameを呼出した後の処理量が少ないと、そのメッセージを目視出来るとは限りません。


これは単にテクニックですが、Platform#getBundleを呼出す事で、
他のプラグインがインストールされ、稼動しているかどうか確認する事が出来ます。


ProjectUtil#addNatureを使用すると、プロジェクトにNatureを追加する事が出来ます。
既にかなりメソッドを追加してしまいましたが、
ProjectUtilは、id:masataka_kさん作です。全パクリしました。


複雑なケースを見てみたい人は、作成したプロジェクトをJavaプロジェクトとする為に、
実装されたJDTHandler及び、ClasspathHandlerをみるのが良いと思います。
他のプラグインに対応する為に、おおよそこれより難しい事が必要な事は無いかと思います。

plugin.xml

ここが、最大の山場です。plugin.xmlでは二種類の定義を行います。
一つは、ResourceHandlerの定義。
もう一つは、生成するプロジェクトのディレクトリ構造及び、
コピーするリソースが配置される場所の定義です。

ResouceHandler

これは、もう非常に単純です。

  <extension
      point="org.seasar.dolteng.projects.resourceHandler">
    <resourceHandler
        class="org.seasar.dolteng.projects.handler.impl.TomcatHandler"
        name="tomcat"/>
  </extension>

只、これだけ。拡張ポイントとして、「org.seasar.dolteng.projects.resourceHandler」を定義し、
「resourceHandler」タグのclass属性として、ResouceHandlerのクラス名を定義します。
name属性は、この後のプロジェクト構造の定義の際に参照される名前として使用します。

プロジェクト構造の定義

まずは、どんな感じの定義になるか見て下さい。

  <extension
      point="org.seasar.dolteng.projects.newProject">
   <project id="0" name="Base" jre="1.4"
     root="template/resources,template/licenses,template/resources/data,template/jars/,template/jars/sources"
     resouceLoader="org.seasar.dolteng.projects.loader.impl.CompositeResouceLoader">
       <handler type="default">
         <entry kind="file" path="LICENSE.txt" />
         <entry path="${mainJavaPath}/${packagePath}/dao" />
         <entry kind="file" path="${mainResourcePath}/app.dicon" />
         <entry path="${testJavaPath}/${packagePath}/web" />
         <entry path="${testResourcePath}/" />
       </handler>
       <handler type="classpath">
         <entry kind="output" path="${testOutPath}" />
         <entry output="${webAppRoot}/WEB-INF/classes"
            path="${mainJavaPath}" />
         <entry path="${testJavaPath}" />
         <entry path="${testResourcePath}" />
         <entry kind="con" path="${jreContainer}" />
         <entry kind="file" path="${testLibPath}/junit-3.8.2.jar" />
       </handler>
       <handler type="jdt">
         <entry kind="file" path=".settings/jdt.pref" />
       </handler>
   </project>
   <project id="11" name="S2Dao Only" jre="1.4" root="template/11"
     extends="0" displayOrder="O02"
     resouceLoader="org.seasar.dolteng.projects.loader.impl.CompositeResouceLoader">
       <handler type="classpath">
         <entry kind="file"
            path="${libPath}/geronimo-j2ee_1.4_spec-1.0.jar" />
         <entry kind="file" path="${libPath}/s2-dao-1.0.40.jar" />
       </handler>
</project>

ここでは、二つのプロジェクトについて定義しています。
そのうち、id属性が「0」のプロジェクトは、displayOrder属性が定義されていない為、表示されず、*2
「11」のプロジェクトは、displayOrder属性が定義されている為、ウィザードのコンボボックスに表示されます。


タグは、以下の三種類があります。

  • project
    • 一塊のプロジェクトを表すタグ
  • handler
    • プロジェクトを生成する際に動作するResouceHandlerを表すタグ
  • entry
    • ResouceHandlerが処理する具体的なパスを表すタグ


projectタグで定義出来る属性は以下の通りです。

  • id
    • プロジェクトを一意に扱う為の属性。extends属性の値として参照する際に使用します。
  • name
    • ウィザードのプルダウンメニューに表示される名前。displayOrder属性が存在しないprojectタグの場合、使用されません。
  • jre
    • 当該プロジェクトが対応しているJREのバージョン。現状ではDoltengの実装上、「1.4」もしくは「1.5」のどちらかを定義出来ます。
  • root
    • リソースを検索する際に、前置されるパス。「,」区切りで、複数指定する事が出来ます。リソースの検索処理については、後で詳しく説明します。
  • extends
    • 当該プロジェクトが、プロジェクトの定義構造を継承する親を指定します。「,」区切りで、複数指定する事が出来ます。
  • displayOrder
    • ウィザードのプルダウンメニューに表示する際に、順序付けする為の値。この値を比較して、並べ替え処理を行います。又、この属性が定義されていない場合、当該定義は表示されません。
  • resouceLoader
    • プロジェクトを生成する際に、コピーされるリソースを読み出す処理を行うResouceLoaderを指定します。


handlerタグでは、type属性としてResouceHandlerの名前を定義します。
ResouceHandlerの名前は、resourceHandlerタグのname属性です。
Doltengが提供しているResouceHandlerは、以下の通り

名前 説明
default 前述した、DefaultHanlder。
classpath Javaプロジェクトの為の、「.classpath」ファイルを生成します。
dolteng Churaプロジェクト初期設定を行います。
diigu Diigu Pluginの初期設定を行います。
tomcat Sysdeo Tomcat Pluginの為の設定を行います。
h2 H2 Database用の初期データが投入されたデータベースを作成します。
jdt Javaプロジェクトの為の初期設定を行います。
dblauncher DbLauncherの為の初期設定を行います。
flexbuilder flexbuilderの為の初期設定を行います。将来的には、Doltengから無くなり、別なプラグインに移動する可能性があります。


entryタグでは、二つの属性を定義する事が出来ます。
path属性では、プロジェクトの相対パスで、リソースがどこに配置されるか定義します。
path属性の値に対して、DefaultHandlerでのテキストファイルに対する処理と同じ置換処理が行われます。
kind属性では値として、「file」もしくは「path」を指定する事が出来ます。
「file」とした場合、DefaultHanlderでのファイルコピー処理対象となります。
「path」とした場合、DefaultHanlderで、ディレクトリ作成処理が行われます。
kind属性を省略した場合、「path」属性が指定されたとみなします。


例外的に、classpathHandlerの子タグとして、entryタグを記述した場合、
kind属性に上記に加えて、「con」、「output」を指定する事が出来ます。
kind属性を、「con」とした場合、JavaプロジェクトのJREコンテナの設定になります。
「output」とした場合、Javaプロジェクトにおけるソースパスとなります。

プラグインから、生成されるプロジェクトにリソースがコピーされる処理概要

最後に、ファイルをコピーする際のルールについて説明します。
例えば、projectタグが、

   <project id="0"      root="template/resources,template/licenses,template/resources/data,template/jars,template/jars/sources">
     ・・・処理に関係の無い属性、及び子タグは省略
   </project>
   <project id="11" root="template/11" extends="0"
     resouceLoader="org.seasar.dolteng.projects.loader.impl.CompositeResouceLoader">
     ・・・処理に関係の無い属性、及び子タグは省略
   </project>

idが「11」のprojectの中にあるentryタグが

<entry kind="file" path="${mainResourcePath}/app.dicon" />

となっている場合。
ResouceLoaderには、以下の順序でパスが引き渡されます。

  1. template/11/app.dicon
  2. template/resources/app.dicon
  3. template/licenses/app.dicon
  4. template/resources/data/app.dicon
  5. template/jars/app.dicon
  6. template/jars/sources/app.dicon

つまり、

  1. リソースがプロジェクトに配置される際の、パスのうちファイル名部分を切り出す。
  2. 現在処理しようとしているprojectタグのroot属性を、そのファイル名の頭に付けて検索
  3. 見つからない場合、extendsで指定したprojectタグのroot属性を、そのファイル名の頭に付けて検索
  4. 以後、リソースが見つかるか、頭に付けるroot属性がなくなるまでextends属性を辿っていく

と言う風になります。

*1:誰か作ってくれないかなぁ…とか…

*2:結果的に生成不可能になります。