DOWNLOAD
 Oracle WebLogic Server
 Ant

   TAGS
java, weblogic, All

開発者:Java

Antを使用したOracle WebLogic Workshop 10の拡張

著者:John Liptak

Antの生成済みビルド・ファイルとカスタム・ビルド・ファイルを組み合わせて、 Oracle WebLogic Workshopの組込み機能を拡張する方法について学習します。

2008年7月公開

Oracle Workshop for WebLogic (Oracle WebLogic WorkshopまたはOracle Workshop)は、バージョン10で躍進を遂げました。とくに、設計-構築-実行という開発サイクルが大幅に改善されています。Oracle Workshopの公開機能を使用すると、生産性を向上できるため、開発プロセスの質も向上します。Oracle WebLogic Portalなどの高機能製品は、使用できたとしても、IDEがなければ非常に使いづらいものです。

アジャイル・ソフトウェア開発によって有名になったユニット・テスト(JUnit)、継続的インテグレーション、コード・カバレッジ・ツールは、多くの開 発手法における業界のベスト・プラクティスとみなされるようにまでなりました。

コマンドラインのAnt機能をカスタマイズして組み込むことは、多くの開発チームにとってもはやオプションではなく、必須です。1つのIDEを使用するた めに、これらのきわめて有用なツールをあきらめる必要はありません。どちらかを選ぶのではなく、両方の技術を使えばよいのです。

この記事では、Oracle WebLogic Workshopの組込み機能を拡張して、Antの生成済みビルド・ファイルとカスタム・ビルド・ファイルを組み合わせて使用する方法を紹介します。両方 のビルド・ファイルを最大限活用することは難しいことではありません。必要なのは、ほんの少しの作業と計画を追加し、Antの新機能を利用することだけで す。

次に、コマンドラインを使用するための最低条件をあげます。

  • CruiseControlを使用できること。

  • ユニット・テストのターゲットが、1つのプロジェクトだけでなくアプリケーション全体であること。このターゲット設定は CruiseControlで必要とされますが、それだけでなく、ソース・コード・コントロールの共有ブランチにチェックインする前に、すべてのユニッ ト・テストを実行するという開発習慣を続けるためにも重要です。

  • 生成済みビルド・ファイルまたはカスタム・ビルド・ファイルを確認する必要がないこと。

  • テスト・ビルドと本番ビルドを作成する際、専用の統合マシン上でコマンドラインを使用して実行すること-開発者個人のワークス テーションを使用しないこと。ここで使用するビルド・マシンにはOracle WebLogic WorkshopのIDEがインストールされておらず、実行時のサポート・ファイルのみが含まれています。

  • 環境固有ファイルを生成できること。アウトソーシングされた運用環境を使用している社内開発者にとって、新規ソフトウェア・ バージョンのインストールを自動化することは大切なことです。

Oracle WebLogic Workshop 10へのアップグレード

われわれが、Oracle WebLogic Workshop 10を使用した最初のアプリケーションに着手したとき、以前の8.1アプリケーションからの変更が大きかったため、ビルド・プロセスを統合する新しい方法 を見つける必要がありました。CruiseControl、とくに junitタスクは難題になりそうでした。

Oracle WebLogic Workshop 8.1の場合、 junitタスクの CLASSPATHはとても簡単に設定できました。 ./.workshop/output/<subproject>と すべてのjarファイルをCLASSPATHに追加すれば、すべては順調に動作したのです。

Oracle WebLogic Workshop 10では、CLASSPATH構造がはるかに複雑になりました。図1に、CLASSPATHを構成する各種コンポーネントがどのように組み合わされている かを示します。

Classpath Components
図1:CLASSPATHコンポーネント

この新しい構造では、機能性が格段に向上しています。この構造では、プロジェクトごとに、APP-INF/lib内にjarファイルの コピーを1つ格納できます。また、共有ライブラリのコレクションをWLSライブラリとしてインストールすることもできます。

この問題に対しては、以下の単純な解決策がありますが、それぞれに短所があります。

  • " Oracle Workshopによって実行された複雑なCLASSPATH生成のリバース・エンジニアリングをおこなう。これは、最善の選択肢とはいえません。なぜな ら、プロジェクトやファセット、またはライブラリを追加するたびに、自分の知らないCLASSPATH生成が追加されてしまう可能性があるからです。ま た、新機能を使い始めるたびに再設計を行わなければならないとしたら、IDEの生産性が著しく下がってしまうでしょう。

  • " 生成済みのAntスクリプトを編集して、独自のターゲットを追加する。このソリューションの問題点は、 build.xml ファイルをエクスポートするたびに、このファイルを再編集する必要があることです。

何かもっとよい方法はないでしょうか。じつは、あります。しかも、基本的テクニックを身につければ、あとはとても簡単です。最初のステップとして、 Oracle Workshopを使用して、Antの build.xml ファイルの基本セットを生成します。ここでは、手順を詳しく確認しながら、各自のファイルを生成して作業を進めることができます。

Oracle Workshop Antスクリプトのエクスポート

プロジェクトをエクスポートして、一連の Antビルド・ファイルを生成する方法については、 こちらを参照してください。簡単に言うと、エクスポートする必要があるのは、アプリケーション全体(1つのearプロジェクト内)のビルド・ファ イル、従属関係のあるライブラリとWebプロジェクトのビルド・ファイル、そしてメタデータ・ファイルの3つです。

earプロジェクトに対して生成された build.xml ファイルにより、サブ・プロジェクトの各 build.xml ファイルがコールされます。これにより、必要なサブ・プロジェクトすべてが正しくビルドされ、引き続き、earファイルがアセンブルされます。

ここまでで、すべての build.xml ファイルが生成されました。ビルド・ファイルを拡張するための単純な解決策を選択したくない場合は、どうすればよいのでしょうか。次の項では、Antの <import>パ ターンを使用する方法について説明します。

ワークスペースへのアプリケーション・プロジェクトの追加

コマンドラインAntとOracle Workshop IDE方式の両方をサポートする際の2番目のステップは、ビルド・ファイルが互いに重複していないことを確認することです。われわれのチームでは、アプリ ケーション・プロジェクトと称するプロジェクトを、ワークスペースのearプロジェクトの"上位に"作成することにしました。

独立したアプリケーション・プロジェクトを作成する理由は、おもに2つあります。1つ目に、Oracle Workshopではこのプロジェクトに対する build.xml ファイルは生成されないことがあげられます。つまり、Oracle Workshopで、このプロジェクトに対して競合するビルドが生成される心配はありません。次に、このプロジェクトは、構成ファイル、WLSTドメイン の作成スクリプトと構成スクリプト、プロパティ・ファイル、デプロイメント・プランなどを格納するために最適です。

われわれのチームでは、さまざまなバージョンのOracle Workshopに対して、さまざまなアプリケーションを開発しているため、 build.xml ビルド・スクリプトを呼び出すようにCruiseControlを設定しています。Oracle Workshopのアプリケーションごとに構成を変え、従来のEclipseベースのOracle Workshopを使用しなくても、アプリケーション・プロジェクトのビルド・スクリプトを使用することで、この競合を解決する新たな方向性が生まれてき ます。

Antの <import> ソリューション

Antは広く一般に使用されているビルド・ツールであり、そのほかのツールと同様に発展を遂げてきました。バージョン1.6.5で導入された <import> 機能を使用すると、既存のビルド・ファイルを 拡張できます。一般に言われるこのタスクのメリットは、ビルド・ファイルをリファクタリングし、共有かつ不変の定義、プロパティ、タスクを共有ファイルに 格納できることにあります。この結果として、他のビルド・ファイルからこれらを再利用できるようになります。

一方で、私は、Springで依存性注入を使用するために古いコードをリファクタリングしていたとき、import機能を使えば生成済みの build.xml をビルド・プロセスに注入できることに気づいたのです。

次に、実際に行った作業を示します。

  • 2つ目のビルド・ファイルを作成する

  • " Oracle Workshopにより生成されたビルド・ファイルを <import> する

              <import file="./build.xml" />
                                          

  • " ターゲットとして、生成済みビルド・ファイルから任意のターゲットを使用する

    <target name="unitTest" depends="build">
                                          
    <echo>Did the imported build. Moving on...</echo>
    ...
  • " Oracle Workshopによって作成された任意のプロパティとタスク・タイプを使用する

earプロジェクト

以下に、earプロジェクトに対して生成されたファイルのビルド・ターゲットの内容を示します。ターゲット名 unitTestと Antファイル名buildCustom.xml以外は、すべてそのままです。
...
                                  
<for-each-project-in-build-order>
<if>
<not><equals arg1="${.project.name}"
arg2="${project.name}"/></not>
<then>
<antex
antfile="${.project.dir}/buildCustom.xml"
dir="${.project.dir}"
target="unitTest"
inheritUserProps="false"
inheritAll="false"
inheritRefs="false">
<property name="wl.home"
value="${wl.home}"/>
<property name="patch.home"
value="${patch.home}"/>
<property name="workspace"
value="${workspace}"/>
<property name="echo.metadata"
value="${echo.metadata}"/>
<property name="init.typedefs.executed"
value="true"/>
<propertyset>
<propertyref regex="${path.vars.regex}"/>
</propertyset>
</antex>
</then>
</if>
</for-each-project-in-build-order>
...

このタスクが実行する内容は、見てのとおりです。earと従属関係のあるすべてのプロジェクトをループして、そのプロジェクトのユニッ ト・テストを実行します。

サブ・プロジェクト

earのサブ・プロジェクトも同じようなパターンに従います。ターゲットとして unitTest がコールされるため、これを定義しておく必要があります。

<target name="unitTest" depends="build">
                                  
<echo level="info" >
Did the imported build.
Moving on...
</echo>
<for-each-java-src-path>
<if>
<available file="${.java.src.dir}"/>
<then>
<echo level="info" >
JUnit tests for ${.java.src.dir}
built to directory ${.java.src.output}
</echo>
<junit printsummary="yes"
haltonfailure="on"
reloading="false">
<classpath>
<path refid="java.classpath"/>
</classpath>
<formatter type="plain"
usefile="false"/>
<formatter type="xml"/>
<batchtest
todir="../wls10app/build/unitTest">
<fileset
dir="${.java.src.output}"
includes="**/*Test.class"/>
</batchtest>
</junit>
</then>
</if>
</for-each-java-src-path>
...

アプリケーション・プロジェクトに戻って、以下のとおりに、Oracle Workshop生成済みのビルド・ターゲットをコールします。

    <target name="build" depends="prepare">
                                  
<ant dir="${earProject.dir}"
antfile="build.xml"
target="build" inheritRefs="false" />
</target>

新しく作成したカスタム・ターゲットは、次のようになります。

    <target name="unitTest" depends="prepare">
                                  
<mkdir dir="./build/unitTest" />
<ant dir="${earProject.dir}"
antfile="buildCustom.xml"
target="unitTest" inheritRefs="false" />
</target>

CruiseControlを使用した継続的インテグレーション

CruiseControl は、広く使われているオープンソースの 継続的インテグレーション ・フレームワークです。CruiseControlは、ソース・コード・コントロール・システムを監視し、ビルドとテストを実行し、各チェックイン後の結 果を公開します。これにより、誰でもソース・コードのステータスを確認できるようになるため、開発プロセスが劇的に改善されます。また、肯定的な面と否定 的な面の両方からプロセスをコントロールする役割を果たします。コードのチェックイン、ユニット・テストの書込み、ビルドのブレーキング、ビルドの修正な ど、作業している開発者が誰であるか一目瞭然です。コードにチェックインしておらず、ユニット・テストも行っていない開発者が、タスクは8割完了している などと主張することは、これからはできなくなります。

私のチームでは、CruiseControlをバージョン1の頃から使用しています。また、すべてのビルド情報を組み合わせるとコード・ベースにおける変 更内容の一時的な概要がわかるように、ビルドのラベル・パターンとして <release name>-<iteration number>-<build number>を採用しています。この 同じ情報は、CruiseControlに組み込まれたレポート・サーバーからも取得できます。

上述の記事を書いたあとでCruiseControlが拡張されたため、ここで、プロジェクトのプラグインを使用して複数アプリケーションを統合する構成 ファイルの例を示します。これを使用すると、ソース・コード・コントロールにおける変更のチェック、ビルド、テスト、ビルド・プロセスの結果の公開に対し て、汎用プロセスを一度に定義できます。あとは、各アプリケーションで異なる点を明示するだけで、複数アプリケーションをコントロールできます。

<cruisecontrol>
                                  
<property environment="env" toupper="true" />

<property name="reportdir"
value="${env.CCDIR}/webapps/cruisecontrol"/>
<property name="projectdir"
value="/projects/javaapps/${project.name}"/>
<property name="testdir"
value="${projectdir}/build/unitTest"/>
<property name="logdir"
value="/projects/javaapps/buildlogs/${project.name}" />

<property name="cvs_tag"
value="HEAD"/>

<property name="builder.name"
value="John Liptak" />
<property name="builder.email"
value="John.Liptak@companyname.com" />
<property name="builder2.email"
value="Other.Person@companyname.com" />
<property name="builder3.email"
value="Other.Person2@companyname.com" />

<plugin name="cvs"
cvsroot="your cvs root here"
module="${project.name}"
property="fileschanged"
propertyondelete="filesdelted"
tag="${cvs_tag}" />

<plugin name="ant"
antscript="/projects/javaapps/app2/buildcc.bat"
antWorkingDir="${projectdir}"
target="CCbuildAndTest"
uselogger="true"
usequiet="true">
<property name="cvs_tag" value="${cvs_tag}" />
</plugin>

<plugin name="htmlemail"
buildresultsurl="http://yourCCserver/${project.name}"
mailhost="mailgate.qintra.com"
returnaddress="${builder.email}"
returnname="${builder.name}"
subjectprefix="[BUILD ${project.name}]"
defaultsuffix="@companyname.com"
reportsuccess="always
spamwhilebroken="true"
xsldir="${reportdir}/xsl"
css="${reportdir}/css/cruisecontrol.css" />

<plugin name="currentbuildstatuslistener"
file="${logdir}/buildstatus.html"/>

<plugin name="labelincrementer" classname="net.sourceforge
.cruisecontrol.labelincrementers.CVSLabelIncrementer"/>

<plugin name="project.apptype1.type"
classname="net.sourceforge.cruisecontrol.ProjectConfig">
<bootstrappers>
<cvsbootstrapper
localWorkingCopy="/projects/javaapps/${project.name}"
file="build.xml" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener/>
</listeners>
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${testdir}"/>
</log>
<modificationset quietperiod="60">
<cvs/>
</modificationset>
<schedule interval="3600">
<ant/>
</schedule>
<publishers>
<htmlemail logdir="${logdir}" >
<always address="${builder.email}"/>
<failure address="${builder.email}"/>
<always address="${builder2.email}"/>
<failure address="${builder2.email}"/>
</htmlemail>
</publishers>
</plugin>

<project.apptype1.type
name="app1" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app2" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app2" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app3" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="app4" buildafterfailed="false">
</project.apptype1.type>
<project.apptype1.type
name="wls10app" buildafterfailed="false">
<property name="builder2.email"
value="Another.Person@companyname.com" />
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${projectdir}/appdir/build/unitTest" />
</log>
</project.apptype1.type>
<project.apptype1.type name="app5" buildafterfailed="false">
<property name="cvs_tag" value="branch_name" />
<property name="builder2.email"
value="John.Liptak@companyname.com" />
<log dir="${logdir}" encoding="ISO-8859-1">
<merge dir="${projectdir}/builds/unitTest" />
</log>
</project.apptype1.type>
</cruisecontrol>

Antタスク定義に不可欠な部分は、CruiseControlで想定されている出力先を指定することです。この例では、 ../wls10app/build/unitTest がすべてのユニット・テストに共通の出力先です。

レポートを作成する際、すべてのユニット・テストが含まれるように、CruiseControl構成で、マージ・ディレクトリ ${projectdir}/builds/unitTestの ビルド・ファイルにおける値を一致させることが重要です。これにより、すべてのサブ・プロジェクトのユニット・テストを、まとめて1つの CruiseControlレポートに表示できます。

構成に関して、もう1つの重要な点は、CruiseControlによりコールされるAntターゲット(この例では、 CCbuildAndTest) です。このターゲットに適切な場所を選択することもまた、大切なポイントです。

環境固有ファイルの生成( <macrodef>の使用)

ここでは、2つのステップを実行して、環境固有のデプロイメント・ファイルを生成します。。Antでは、いったん設定されたプロパティ をユーザーが再定義することはできないため、サポートされる環境ごとに一意の接頭辞を付けたプロパティを作成します。たとえば、 buildEnv.test1.JAVA_HOMEに は、 /opt/bea/home10.0.1/jrockit_150_11という値を設定するとします。ビルド・ファイル で、すべてのプロパティ、ファイルセット、およびそのほかの変数の割当てを何度も繰り返すかわりに、Antの <macrodef> 機能を使用します。

<target name="envTokenFilters"
                                  
depends="tokenFilter">
<macrodef name="env.macro">
<attribute name="envFile" />
<attribute name="envSuffix" />
<sequential>
<!-- Define properties per environment -->
<property
name="buildTypeFile.@{envSuffix}"
location="${config.env}/@{envFile}.properties" />
<dirname
property="buildTypeFile.@{envSuffix}.dirname"
file="${buildTypeFile.@{envSuffix}}" />
<basename
property="buildTypeFile.@{envSuffix}.basename"
file="${buildTypeFile.@{envSuffix}}" />
<available
file="${buildTypeFile.@{envSuffix}}"
property="buildTypeFile.@{envSuffix}.present"/>
<property
file="${buildTypeFile.@{envSuffix}}"
prefix="buildEnv.@{envSuffix}" />

<!-- Define token filterset per environment -->
<filterset id="tokenFilter.@{envSuffix}"
begintoken="@" endtoken="@">
<filtersfile
file="${commonBuildPropsFile}"/>
<filtersfile
file="${buildTypeFile.@{envSuffix}}" />
</filterset>

<!-- Define filelist per environment -->
<filelist id="tokenFilterFiles.@{envSuffix}"
dir="${buildTypeFile.@{envSuffix}.dirname}"
files="${buildTypeFile.@{envSuffix}.basename}" />
</sequential>
</macrodef>

<env.macro envFile="devUnix" envSuffix="dev"/>
<env.macro envFile="integration" envSuffix="int"/>
<env.macro envFile="integration2" envSuffix="int2"/>
<env.macro envFile="itv" envSuffix="test"/>
<env.macro envFile="itv2" envSuffix="test2"/>
<env.macro envFile="itv3" envSuffix="test3"/>
<env.macro envFile="e2e" envSuffix="e2etest"/>
<env.macro envFile="production" envSuffix="prod"/>

</target>

次に、出力ファイルを生成するために、それぞれの環境に対してマッピングをおこなうターゲット(スペースの都合上依存性チェックは省略 していますが、実際のビルド・スクリプトには必要です)が続きます。

<macrodef name="copyPropertyFiles.macro">
                                  
<attribute name="envSuffix" default=""/>
<sequential>
<!-- Perform token replacements -->
<copy overwrite="true"
todir="${dist.config}" >
<fileset
dir="${config.dir}/properties">
<patternset
refid="propertyFiles" />
</fileset>
<filterset
refid="tokenFilter@{envSuffix}" />
<mapper type="glob"
from="*"
to="*@{envSuffix}"/>
</copy>
<!-- A real build file has many copies of this
copy block. -->

</macrodef>

<copyPropertyFiles.macro/>
<copyPropertyFiles.macro envSuffix=".dev"/>
<copyPropertyFiles.macro envSuffix=".int"/>
<copyPropertyFiles.macro envSuffix=".int2"/>
<copyPropertyFiles.macro envSuffix=".test"/>
<copyPropertyFiles.macro envSuffix=".test2"/>
<copyPropertyFiles.macro envSuffix=".test3"/>
<copyPropertyFiles.macro envSuffix=".e2etest"/>
<copyPropertyFiles.macro envSuffix=".prod"/>

このような方法を使用すると、すべての環境固有ファイルを1つのビルドにまとめることができます。1つのビルドですべての環境に対応できるため、結果的に テスト担当者と運用担当者の両者に喜ばれます。

WLSTスクリプトに同じ処理を追加して、各自のドメインの生成と構成をおこなえば、自動インストール・プロセスにきわめて近い結果が 得られるでしょう。SOX監査で問題とならないように、WebLogic、データベース、そのほかの各種資格証明を手動で入力するステップを除けば、手作 業はほとんどなしでインストールを完了できます。

制限事項

このソリューションには、考慮が必要な制限事項がいくつかあります。

  • " 複数のプロジェクトで重複するクラス名を使用している場合、CruiseControlのレポートで混乱が生じます。これは重大な制限ではありません。重 複クラス名は開発者をも混乱させるため、プロジェクトでは禁止しましょう。
  • " すべてのJUnitクラスの末尾に Testを付けること。これは、ナビゲータ・ビューで、定 義のとなりにテスト・クラスが表示されるようにするために採った方法です。同じパッケージにテスト・クラスがあるように見せておいて、本番ビルドからは取 り除くことができるように、ソース・ツリー全体を同じディレクトリ構造で格納することを好む人々もいます。
  • " 現時点では、オラクルはカスタムAntタスクの同時処理をサポートしていません。一部のAntタスクでは、一時ディレクトリに対する排他的アクセスを前提 としているため、ワークスペースやプロジェクトが異なる場合でも、複数のビルドが実行されると処理が失敗します。

結論

この記事では、Antの <import> 機能および <macrodef> 機能を使用して、Oracle WebLogic Workshopにより生成されたビルド・ファイルを、利用しやすいように拡張する方法について説明しました。 <import>を 使用したテクニックでは、 <junit> タスクを使用するために必要なCLASSPATHを生成する方法を紹介しました。このテクニックを使用して、CLASSPATHに基づくそのほかの処理を 組み込むこともできます。

ビルド・プロセスにおいてearプロジェクトの上位にアプリケーション・プロジェクトを作成すると、CruiseControlを統合 できます。また、生成される環境固有ファイル(プロパティ・ファイル、WLSTスクリプト、デプロイメント・プランなど)のための場所が提供されます。

ビルド・プロセスを注意深く設定すれば、コマンドラインAntを介して豊富な機能セットを取り入れることができると同時に、Oracle WebLogic Workshopで提供される高度なIDE機能の恩恵も受けることができます。


John Liptak は、Qwestのシニア・ソフトウェア・エンジニアであり、1997年以来、Javaを使用した開発をおこなっています。