2006年 7月 26日 水曜日 今回は,前回のエントリで作成したBPELから,前々回のエントリで作成したサービスをWebサービスで呼び出すことにします。
こんなイメージです。
【1. サービスのWebサービス化】
前々回に作成したサービス(BooksDataFacade,FooPriceListFacade,BarPriceListFacade)をWebサービス化します。手順は,前々回にBooksPricingProcessBeanをWebサービス化したときと同様です。Deployして,Webサービスのテスト機能で簡単に動作確認しておくと良いでしょう。
(*) 複数のWebサービスを同じパッケージで作成する場合,異なるクラスであってもWebメソッド名が重複すると上手く動作しないようです。findBooksData, findFooPriceList, findBarPriceListなど,Webメソッドの名前を変えるようにしましょう。
【2. WSDL,XMLスキーマの準備】
前回のBooksPricingProcessWebServiceBeanと同様の手順で,BooksDataWebServiceBean, FooPriceListWebServiceBean, BarPriceListWebServiceBeanのWSDL,XMLスキーマをローカルに保存,編集します。
【3. BPELの編集】
(1) WSDLのインポート,パートナーリンクの設定
編集した3つのWSDLファイルをBPELにインポートします。そして,それぞれのサービスに対応するパートナーリンクを設定します。
(2) アクティビティの配置
アクティビティを配置して,全体のプロセスが見えるようにしましょう。
(3) Assign1の設定
BPELエディタ上でAssign1を選択して,Mapperでコピー方法を設定します。
左側のFindBookPriceIn1から,右側のFindBooksDataIn1, FindFooPriceListIn1, FindBarPriceListIn1に線を引っ張ります。
NavigatorのBPEL Logical Viewで,それぞれのInvokeを右クリック,[Edit]を選択して,3つのサービスを呼び出すように設定します。Input Variable,Output VariableをCreateするのを忘れないようにしましょう。変数名はデフォルト値で構いません。
(5) Assign2の設定
BPELエディタ上でAssign2を選択して,Mapperでコピー方法を設定します。
左側のFindBooksDataOut1から,右側のFindBookPriceOut1に線を引っ張ります。
<copy>
<from variable="FindBooksDataOut1" part="parameters"/>
<to variable="FindBookPriceOut1" part="parameters"/>
</copy>
</assign>
(6) If1の設定
If1のMapperで,FindFooPriceListOut1のpriceをnumberとして評価した値が,FindBookPriceOut1のpriceをnumberとして評価した値より小さいことを条件として設定します。
(7) Assign3の設定
Assign3のMapperで,FindFooPriceListOut1のpriceをFindBookPriceOut1のpriceにコピーするように設定します。
If1とAssign3のBPEL該当箇所はこうなっているはずです。
<if name="If1">
<condition> ( number($FindFooPriceListOut1.parameters/return/price) < number($FindBookPriceOut1.parameters/return/price) ) </condition>
<then>
<assign name="Assign3">
<copy>
<from>$FindFooPriceListOut1.parameters/return/price</from>
<to>$FindBookPriceOut1.parameters/return/price</to>
</copy>
</assign>
</then>
</if>
(8) If2, Assign4の設定
FindBarPriceListOut1のpriceをnumberとして評価した値がFindBookPriceOut1のpriceをnumberとして評価した値より小さければ,FindBarPricListOut1のpriceをFindBookPriceOut1のpriceにコピーするように設定します。
【4. 実行】
BPELを実行してみましょう。BooksDataから取得したデータに加えて,FooPriceListとBarPriceListで安い方の価格が設定されたデータが戻ってくることを確かめます。
【5. BPELでのプロセス並行処理】
BPELではプロセスの並行処理が簡単に実行できます。これはJavaでプロセスを実行する場合に比較してアドバンテージになります。
Assign1の後に,並行処理のためのアクティビティであるFlowをDrag&Dropして,Invoke1, Invoke2, Invoke3を並行処理するようにアイコンを移動させます。これで並行処理が実行されるようになります。
前回のエントリで,SOAのレイヤ構成を想定したJava EE 5開発を試してみました。これにSOAを適用するとどのようなイメージになるのでしょうか?実際に,前回のアプリケーションをベースにSOAを試してみましょう。具体的には,リソースレイヤとサービスレイヤをそのまま使って,プロセスレイヤをBPELで実現します。
ただし,一気にそこまで行くのは大変なので,今回は,ひとまず簡単なプロセスを動かすところまでにしたいと思います。
【0. 前提条件】
必要なのは,前回と同じくNetBeans Enterprise Pack 5.5 EA版だけです。これには,SOA Starter Kitが含まれています。プラットフォームはJBIのオープンソース実装であるOpen ESBです。
【1. プロジェクトの作成,準備】
(1) プロジェクトの作成
[File]-[New Project]を選択すると,[New Project]ダイアログが起動します。
[Service Oriented Architecture]-[BPEL Module]を選択します。
としてプロジェクトを生成します。
PricingProcess_1.bpel, PricingProcess_1.wsdlが生成されます。
(2) WSDL,XMLスキーマの準備
このプロセスのエントリポイントをWebサービスとして定義するためのWSDL,XMLスキーマを準備します。前回のアプリケーションのBooksPricingProxessWebServiceBeanと同じWSDLとXMLスキーマを流用することにしましょう。
前回の最後の手順で使ったWebサービスのテスト画面にWSDLというリンクがあります。これをクリックしてWSDLを表示させてローカルに保存しましょう。(拡張子をwsdlにしましょう。)
こんな感じのWSDLになっているはずです。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://ws/" name="BooksPricingProcessWebServiceBeanService">
<types>
<xsd:schema>
<xsd:import namespace="http://ws/" schemaLocation="http://xxx.xxx.xxx.xxx:8080/BooksPricingProcessWebServiceBeanService/BooksPricingProcessWebServiceBean/__container$publishing$subctx/META-INF/wsdl/BooksPricingProcessWebServiceBeanService_schema1.xsd" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"/>
</xsd:schema>
</types>
<message name="findBookPrice">
<part name="parameters" element="tns:findBookPrice"/>
</message>
<message name="findBookPriceResponse">
<part name="parameters" element="tns:findBookPriceResponse"/>
</message>
<portType name="BooksPricingProcessWebServiceBean">
<operation name="findBookPrice">
<input message="tns:findBookPrice"/>
<output message="tns:findBookPriceResponse"/>
</operation>
</portType>
<binding name="BooksPricingProcessWebServiceBeanPortBinding" type="tns:BooksPricingProcessWebServiceBean">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="findBookPrice">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="BooksPricingProcessWebServiceBeanService">
<port name="BooksPricingProcessWebServiceBeanPort" binding="tns:BooksPricingProcessWebServiceBeanPortBinding">
<soap:address location="http://xxx.xxx.xxx.xxx:8080/BooksPricingProcessWebServiceBeanService/BooksPricingProcessWebServiceBean" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"/>
</port>
</service>
</definitions>
それから,WSDLの最初の方でXMLスキーマをインポートしています。このURLをブラウザのアドレスバーにコピーして,XMLスキーマを表示させ,これもローカルに保存します。(拡張子をxsdにしましょう)
そして,WSDLを今回の環境に合わせて編集します。
編集後のWSDLはこんな感じになります。(変更箇所をBold体で示しています。)
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://ws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://ws/" name="BooksPricingProcessWebServiceBeanService" xmlns:plink="http://schemas.xmlsoap.org/ws/2004/03/partner-link/">
<types>
<xsd:schema>
<xsd:import namespace="http://ws/" schemaLocation="BooksPricingProcessWebServiceBeanService_schema1.xsd" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"/>
</xsd:schema>
</types>
<!-- 中略 -->
<service name="BooksPricingProcessWebServiceBeanService">
<port name="BooksPricingProcessWebServiceBeanPort" binding="tns:BooksPricingProcessWebServiceBeanPortBinding">
<soap:address
location="http://localhost:18181/PricingProcess/PricingProcess_1" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"/>
</port>
</service>
<plink:partnerLinkType name="BooksPricingProcessPartnerLinkType">
<plink:role name="provider" portType="tns:BooksPricingProcessWebServiceBean"/>
</plink:partnerLinkType>
</definitions>
【2. BPELの編集】
(1) WSDLのインポート
PricingProcess_1.bpelのNavigator画面で,[Imports]を右クリック,[Add Import]を選択して,編集したWSDLをインポートします。
(2) パートナーリンクの追加
右のパレットから,Partner LinkをDrag&Dropします。
としてPartnerLinkを追加します。
(3) Receive, Replyの設定
最初のアクティビティとしてReceive, 最後のアクティビティとしてReplyをDrag&Dropします。
BPEL Navigator画面で,Receive1を右クリック,[Edit]を選択すると,ReceiveのProperty Editorが起動します。
とします。
Receive1に関しては,ソースビューで属性としてcreateInstance="yes"を追加します。
<receive name="Receive1" partnerLink="PartnerLink1" operation="findBookPrice" portType="ns1:BooksPricingProcessWebServiceBean" variable="FindBookPriceIn1" createInstance="yes"/>
同様に,Reply1に関しても
と設定します。
(4) Assignの設定
Receive1とReply1の間にAssignをDrag&Dropします。最初から存在する空のアクティビティは削除します。
Assignは,変数に値をコピーするためのアクティビティです。NetBeans Enterprise PackにはMapperという機能があり,GUIでコピー内容を指定できます。メニュー[Window]-[Mapper]を選択すると,Mapperが起動されます。BPELエディタ上でAssign1を選択して,Assignの内容を設定しましょう。
例えば,FindBookPriceIn1のproductIdをFindBookPriceOut1のproductIdにコピーする場合には,Mapperの左から右へ線を引っ張るだけでO.K.です。
固定値を設定する場合には,Mapperのメニューから[string literal]あるいは[number literal]を選択して値を入力,右の該当箇所へ線を引っ張ります。
BPELエディタをソースビューに切り替えると,Assignの内容が確認できます。
<assign name="Assign1">
<copy>
<from>$FindBookPriceIn1.parameters/productId</from>
<to>$FindBookPriceOut1.parameters/return/productId</to>
</copy>
<copy>
<from>39.99</from>
<to>$FindBookPriceOut1.parameters/return/price</to>
</copy>
<copy>
<from>'Java EE and .NET Interoperability'</from>
<to>$FindBookPriceOut1.parameters/return/productName</to>
</copy>
</assign>
BPELやXPathの細かい文法を知らなくても編集できるのでとても便利です。
【3. 実行】
(1) Composite Applicationの作成
[File]-[New Project]を選択,[Service Oriented Architecture]-[Composite Application]を選択します。
として作成します。
次に,プロジェクト"PricingApp"を右クリック,[Add JBI Module]でプロジェクト"PricingProcess"を追加します。
(2) テストの作成
プロジェクト"PricingApp"の[Test]を右クリック,[Add Testcase]を選択します。Input.xmlのproductIdとして"SUNW-001"など適当な値を指定します。
(3) 実行
プロジェクト"PricingApp"をDeployして,テストを実行します。
どうですか?WSDLの準備がちょっと面倒ですが,後はとても簡単です。プラットフォームがJBIであることを意識する必要も無く,BPELを直接編集する必要もありません。
以前のエントリで,SOAにはレイヤ構成が重要だと書きました。例えば,現在は一般的なエンタープライズJavaの開発を行っていたとしても,将来的にSOAを実施する可能性があるようであれば,SOAのレイヤ構成を想定した開発を行っておけば,スムーズに移行できるようになります。
今回は,最近リリースされたNetBeans Enterprise Pack 5.5を使って,SOAのレイヤ構成に従ったJava EE 5開発を試して見ましょう。(チュートリアル風に手順を追っていきますので,Java EE 5やNetBeansを使われたことのない方も是非試してみてください。とても簡単です。)
以下のような雰囲気のアプリケーションを作りたいと思います。
【0. 前提条件】
NetBeans Enterprise Pack 5.5 EA版をダウンロードしてインストールしてください。それだけでO.K.です。
【1. Java DBの準備】
NetBeansにはデータベースとして,Java DBであるDerbyがバンドルされています。今回はこれを使います。
(1) Java DB起動
NetBeansのメニューで,[Tools]-[Java DB]-[Java DB Database]-[Start Java DB Server]を選択します。コンソール"Java DB Database Process"が開始され,以下のようなメッセージが表示されます。
サーバーは、ポート 1527 で接続を受け入れる準備ができました。
接続番号: 1。
(2) 接続の設定
左側のペインで[Runtime]タブを選択,[Databases]-[Drivers]-[Java DB(Net)]を右クリックして,[Connect Using ...]を選択すると,"New Database Connection"ダイアログが開きます。ここで,
を入力すると,あらかじめ構築済みのサンプルDBに接続できるようになります。
(3) テーブル,データの作成
接続先DBの[Tables]-[Execute Command]を選択すると,SQLコマンドを実行するコンソールが立ち上がります。ここで,3つのテーブルを作成します。
create table books_data(
product_id varchar(8) CONSTRAINT book_data_pk PRIMARY KEY,
product_name varchar(64),
price numeric(6,2)
);
insert into books_data values('SUNW-001', 'Java EE and .NET Interoperability',49.99);
insert into books_data values('SUNW-002', 'Core Security Patterns', 59.99);
insert into books_data values('SUNW-003', 'NetBeans IDE Field Guide', 44.99);
insert into books_data values('SUNW-004', 'Core JavaServer Faces', 54.99);
insert into books_data values('SUNW-005', 'Java? Studio Creator Field Guide', 39.99);
create table foo_price_list(
product_id varchar(8) CONSTRAINT foo_price_pk PRIMARY KEY,
price numeric(6,2)
);
insert into foo_price_list values('SUNW-001', 44.99);
insert into foo_price_list values('SUNW-002', 49.99);
insert into foo_price_list values('SUNW-003', 39.99);
insert into foo_price_list values('SUNW-004', 44.99);
insert into foo_price_list values('SUNW-005', 34.99);
create table bar_price_list(
product_id varchar(8) CONSTRAINT bar_price_pk PRIMARY KEY,
price numeric(6,2)
);
insert into bar_price_list values('SUNW-001', 39.99);
insert into bar_price_list values('SUNW-002', 54.99);
insert into bar_price_list values('SUNW-003', 34.99);
insert into bar_price_list values('SUNW-004', 49.99);
insert into bar_price_list values('SUNW-005', 29.99);
作成されたテーブルのアイコンを右クリックして[View Data]を選択して,データの一覧を確認してください。
【2. エンティティ,サービスの開発】
(1) プロジェクトの作成
[File]-[New Project]を選択すると,"New Project"ダイアログが起動します。カテゴリ"Enterprise"から"EJB Module"を選択して,プロジェクト"PricingService"を作成してください。
(2) エンティティの作成
プロジェクト"PricingService"を右クリック,[New]-[Entity Classes from Database]を選択します。
Data Sourceとして"jdbc/sample"を選択します。(もし存在しないようなら,[New Data Source]から作成しましょう。)
"Available Tables"から"BOOKS_DATA"をADDして[Next],Package: persistを入力します。
[Create Persistence Unit]を押して,Persistence Unitを作成します。今回は,テーブルをcreateする必要は無いので,Table Generation Strategy: Noneを選択して[OK]を押します。
[Finish]でエンティティクラスが作成されます。
以下のように,Java EE 5のアノテーションを使ったEntity Objectが生成されます。
@Entity
@Table(name = "BOOKS_DATA")
@NamedQueries( {@NamedQuery(name = "BooksData.findByProductId", query = "SELECT b FROM BooksData b WHERE b.productId = :productId"), @NamedQuery(name = "BooksData.findByProductName", query = "SELECT b FROM BooksData b WHERE b.productName = :productName"), @NamedQuery(name = "BooksData.findByPrice", query = "SELECT b FROM BooksData b WHERE b.price = :price")})
public class BooksData implements Serializable {
@Id
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@Column(name = "PRODUCT_NAME")
private String productName;
@Column(name = "PRICE")
private BigDecimal price;
/** Creates a new instance of BooksData */
public BooksData() {
}
public BooksData(String productId) {
this.productId = productId;
}
public String getProductId() {
return this.productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getProductName() {
return this.productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrice() {
return this.price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public int hashCode() {
int hash = 0;
hash += (this.productId != null ? this.productId.hashCode() : 0);
return hash;
}
public boolean equals(Object object) {
if (object == null || !this.getClass().equals(object.getClass())) {
return false;
}
BooksData other = (BooksData)object;
if (this.productId != other.productId && (this.productId == null || !this.productId.equals(other.productId))) return false;
return true;
}
public String toString() {
//TODO change toString() implementation to return a better display name
return "" + this.productId;
}
}
(3) サービスの生成
プロジェクト"PricingService"を右クリック,[New]-[Session Facade]を選択します。
"Available Entity Classes"から"BooksData"をADDして[NEXT],Package: serviceを入力します。
[Finish]でセッションファサードが生成されます。
こちらも,Java EE 5のアノテーションを使ったStateless SessionBeanです。
@Stateless
public class BooksDataFacade implements BooksDataFacadeLocal {
@PersistenceContext
private EntityManager em;
/** Creates a new instance of BooksDataFacade */
public BooksDataFacade() {
}
public void create(BooksData booksData) {
em.persist(booksData);
}
public void edit(BooksData booksData) {
em.merge(booksData);
}
public void destroy(BooksData booksData) {
em.merge(booksData);
em.remove(booksData);
}
public BooksData find(Object pk) {
return (BooksData) em.find(BooksData.class, pk);
}
public List findAll() {
return em.createQuery("select object(o) from persist.BooksData as o").getResultList();
}
}
(4) 他のテーブルに対するサービスの作成
上記(1)-(3)の手順を,FOO_PRICE_LISTテーブル,BAR_PRICE_LISTテーブルに対しても同様に実施します。
(*)ここまで,ソースコードは一行も編集していません。
【3. プロセスの開発】
(1) ビジネスプロセスを実行するSession Beanの生成
プロジェクト"PricingService"を右クリックして,[New]-[Session Bean]を選択,以下のような値を入力します。
(2) Dependency Injectionの設定
クラスが生成されたら,ソースコードエディタ上で右クリック,[Enterprise Resources]-[Call Enterprise Bean]を選択します。BooksDataFacade, FooPriceListFacade, BarPriceFacadeをそれぞれ選択すると,Injectionされるメンバー変数が生成されますので,以下のようにアノテーションを追記します。
従来のDDに記述していたejb-refと,ソースコードに記述していたInitialContext#lookupをまとめて記述しているとイメージしてください。このように記述するだけで,メンバー変数にEJB参照が自動的に設定された状態でメソッドからアクセスできるようになります。
@EJB(name="ejb/BooksDataFacade", beanInterface=BooksDataFacadeLocal.class)
private BooksDataFacadeLocal booksDataFacade;
@EJB(name="ejb/FooPriceListFacade", beanInterface=FooPriceListFacadeLocal.class)
private FooPriceListFacadeLocal fooPriceListFacade;
@EJB(name="ejb/BarPriceListFacade", beanInterface=BarPriceListFacadeLocal.class)
private BarPriceListFacadeLocal barPriceListFacade;
(3) ビジネスメソッドの追加
ソースコードエディタ上で右クリック,[EJB Methods]-[Add Business Method]を選択します。
としてメソッドを生成します。
生成したメソッド内で実装を行います。ようやくJavaプログラミングの出番ですが,以前のJ2EEに比べると,とても素っ気無いプログラムです。
public BooksData findBookPrice(String productId) {
// 3つのサービスにアクセスします。
BooksData booksData = booksDataFacade.find(productId);
FooPriceList fooPriceList = fooPriceListFacade.find(productId);
BarPriceList barPriceList = barPriceListFacade.find(productId);
// 戻り値用のインスタンスにBooksDataをコピー
BooksData to_return = new BooksData();
to_return.setProductId(booksData.getProductId());
to_return.setProductName(booksData.getProductName());
to_return.setPrice(booksData.getPrice());
// Foo, Barのうち,安い方の価格を設定します。
if(to_return.getPrice().doubleValue() > fooPriceList.getPrice().doubleValue())
to_return.setPrice(fooPriceList.getPrice());
if(to_return.getPrice().doubleValue() > barPriceList.getPrice().doubleValue())
to_return.setPrice(barPriceList.getPrice());
return to_return;
}
(4) Webサービス化
それでは,このプロセスをWebサービスとして呼び出せるようにしましょう。
プロジェクト"PricingService"を右クリック,[New]-[Web Service]を選択します。
を入力,選択,[Finish]でWebサービスが生成されます。
ただし,ただし,このWebサービスにはpublicメソッドが一切定義されていません。
"BooksPricingProcessWebServiceBean.java"のソースコードエディタ上で右クリック,
[Web Service]-[Add Operation]を選択します。
としてメソッドを生成します。そして,3(2)と同様の手順で,Dependency Injectionの設定した上で,BooksPricingProcessBean#findBookPriceに処理を委譲するように実装します。
(5) Webサービスによる呼び出しテスト
[PricingService]-[Web Services]-[BooksPricingProcessWebServiceBean]を右クリック,[Test Web Service]を選択すると,テスト用のWebページが開きます。
プロダクトID(例えば,SUNW-001)を入力,送信すると結果が表示されます。
SunはInterop Tokyo 2006に出展します。
以下の4つのテーマを柱に,デモ,プレゼンテーションが行われます。
Java, Softwareに関しては,Identity Management, SOA(Sun Java CAPS), 開発ツール(NetBeans 5.5, Sun Java Studio Creator 2)などが予定されています。私もSOAのプレゼンテーションを行いますので,興味のある方は是非お立ち寄りください。
The Aquarium日本語版(Java EE 5アプリケーションサーバのOpen Source開発プロジェクトGlassfishに関するブログ)が登場しています。本家と併せてどうぞ。