目次へ

Download NetBeans! 使ってみる?

アクシス・ボンバーもうイッパツ!

NetBeansのおかげでAxisの勉強が止まらなくなってきた。他の仕事いいのかのに子!最近あまりページをでかくするとダウンロードがクソ遅くなる御迷惑に気づいて別ページにしました。上のほうほど新しいトピックが来る積み上げ方式です。

/TCPMonで見るドキュメントのやりとり/とにもかくにも、得られた結果/名前空間ってなんなんだヨ〜/ドキュメント中心サービスのWSDD/請求書発行サービスプログラム/注文書発行クライアントプログラム(抜粋。ゴメソ)/まずテストプログラム/請求書XMLの作成/注文された品の金額の計算/正体がわかっている文書のDOM解析/受け皿Javaクラス/もすこしマシな受注システムを「ドキュメント中心サービス」で/TCPMonだモーン/SOAPヘッダに手を加える/参考文献ももう一回/

プログラムの詳細 記事中に何度も「別ページ」と書いてあるそのページを別窓で開けます。

アクシス・ボンバー、以前の記事


TCPMonで見るドキュメントのやりとり

じゃあいったいどんなものがSOAP文書として流れているのか、やっぱり見てみたいではないか。TCPMonを使ってみてみる。

==== Request ====
POST /noniveg/services/getOrderService HTTP/1.0
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.1
Host: 127.0.0.1
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: ""
Content-Length: 556

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body><order id="20031026-A" xmlns="Order">
<orderfrom>
<name>usako</name>
<address>UsagiHouse</address>
</orderfrom>
<ordercontents>
<item itemname="tomato" quantity="5"/>
<item itemname="onion" quantity="3"/>
<item itemname="cabbage" quantity="8"/>
</ordercontents>
</order> </soapenv:Body>
</soapenv:Envelope>

==== Response ====
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Connection: close
Date: Tue, 28 Oct 2003 09:49:58 GMT
Server: Apache Tomcat/4.0.6 (HTTP/1.1 Connector)

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body><invoice id="20031026-A"><billto><name>usako</name><address>UsagiHouse</address></billto><contents><item itemname="tomato" quantity="5" subtotal="500" unitprice="100"/><item itemname="onion" quantity="3" subtotal="240" unitprice="80"/><item itemname="cabbage" quantity="8" subtotal="384" unitprice="48"/><total price="1124"/></contents></invoice> </soapenv:Body>
</soapenv:Envelope>
==============

なーるほど。Bodyエレメントに、そのまま注文書・請求書のドキュメントが埋め込まれているわけですな。
考えてみれば、こっちのほうがラクかもしれない。いちいちパラメータオブジェクトを配列にまとめて、それらを引数にサービスメソッドを組んで、ということをしなくていいわけですからな。なんかWebサービスらしくなってきた感じ〜。よくわかんないままやってるけど〜。

2003年10月30日

ページ先頭に戻る


とにもかくにも、得られた結果

だが、とにかく結果は得られた。コンソール上にうなぎのようにズラズラと文字列が流れてきた。見にくいので適当に改行してお見せします。

<?xml version="1.0" encoding="UTF-8"?>
<invoice id="20031026-A">
<billto>
<name>usako</name>
<address>UsagiHouse</address>
</billto>
<contents>
<item itemname="tomato" quantity="5" subtotal="500" unitprice="100"/>
<item itemname="onion" quantity="3" subtotal="240" unitprice="80"/>
<item itemname="cabbage" quantity="8" subtotal="384" unitprice="48"/>
<total price="1124"/>
</contents>
</invoice>

ちゃんとできているようだ。 でもなんでsubtotalがunitpriceの前に来てるんだろう。アルファベ順?・・・

2003年10月30日

ページ先頭に戻る


名前空間ってなんなんだヨ〜

さてしくみもよくわからないまま、クライアントプログラムを実行してみた。すると、こんなエラーメッセージが出た。

Cannot invoke Call with null namespace URI for method order

実は、このときorder.xmlはこんな書き方をしていた。

<order id="20031026-A">
<orderfrom>
.......

これで、テストプログラムまではうまく行っていたのだ。Webサービスに載せて初めてこのエラーが出た。
対処法に悩んだあげく、こうやってみたわけだ。

<order id="20031026-A" xmlns="Order">
<orderfrom>
........

order.xmlをそう書き直しただけである。クライアントもサービスもコードには一切変更なし。そして、名前空間"Order"の根拠は全くなし、ただ適当に命名しただけで、他のどこでも使っていない。

でもそうやってクライアントプログラムを実行したら、ちゃんと答えがかえってきた。

おーい、いいのかAxis?そんないい加減なことで?

ちなみに、上のエラーメッセージをまるっと検索キーワードにしてググってみたら、まるっとメッセージごとヒットしたのは一件だけ、どこにもそのメッセージが書いてあるようには見えない全部ロシア語のページだった。ゴースポジ。

2003年10月30日

ページ先頭に戻る


ドキュメント中心サービスのWSDD

これをdeployorder.wsddファイルにより、デプロイする。

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">

<service name="getOrderService" provider="java:MSG" style="document">
<parameter name="className" value="noniveg.services.getOrder"/>
<parameter name="methodName" value="returnInvoiceDoc"/>

</service>

</deployment>

providerの値が確かに"java:RPC"ではなく"java:MSG"になっている。style="document"という項目も追加されている。だからサービスプログラムではDocumentを引数にとっているのだろうか?わかんないけど。 デプロイは相変わらず、AdminClientアイコンを右クリック実行。

2003年10月30日

ページ先頭に戻る


請求書発行サービスプログラム

サービスプログラムは/noniveg/WEB-INF/classes/noniveg/services/に置く。
クライアントから受け取るのは、結局はDocumentのようだ。そこんとこどういう処理が行われているのかは、ちょっとわからない〜。
とにかく、注文書DocumentからOrderインスタンスを生成して、それをもとにInvoicePublisherで請求書Documentを作っている。

public class getOrder {

public Document returnInvoiceDoc(Document indoc) throws Exception{

MessageContext msgctxt=MessageContext.getCurrentContext();

DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();

OrderAnalyzer analyzer=new OrderAnalyzer(indoc);
Order order=analyzer.createOrder();
InvoicePublisher publisher=new InvoicePublisher(order);
Document returnDoc=publisher.createInvoiceDoc();

return returnDoc;
}

}

2003年10月30日

ページ先頭に戻る


注文書発行クライアントプログラム(抜粋。ゴメソ)

どうやらイケそうなので、 クライアントプログラムを作ってみることにした。でも、後半は「スケ本」ほぼまるうつしなので、悩んだんだけどやはり掲載を控えさせていただくことにしました。
ちなみに、ソフトバンク パブリッシングのサイトから、サンプルコードはダウンロードできるヨン。

例jによってnonivegclienttestパッケージに作る。サービス名はgetOrderServiceにしてデプロイするつもり。
Documentまではクライアントで作る、と言ったが、スケ本ではDocumentじゃなくてInputStreamでやりとりする方法がとられていた。

package nonivegclienttest;

import java.io.*;
import org.apache.axis.encoding.*;
import org.apache.axis.message.*;
import org.apache.axis.client.*;
import org.apache.axis.*;
import org.apache.axis.utils.*;
import java.net.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;


public class orderVeg {


public static void main(String[] args) throws Exception{

String wsUrl="http://localhost:8081/noniveg/services/getOrderService";

BufferedInputStream bis=null;

try{
URL docUrl=new URL("http://localhost:8081/noniveg/resource/order.xml");
InputStream in=docUrl.openStream();
bis=new BufferedInputStream(in);
}
catch(Exception e){
System.out.println("Failed to get fileinput");
e.printStackTrace();
}

・・・・
このあとクライアントプログラムはこのInputStream bisをSOAPリクエストに載せて送信し、、さらにサービスプログラムからのSOAPレスポンスを受け取って、そこから請求書ドキュメントを取り出して表示するところまでやっているのだが、あとは遠慮します。スミマセン。

2003年10月30日

ページ先頭に戻る


まずテストプログラム

これでツールはそろった。だがこれらをサービスにお出ししてもいいものかどうか、まずは毒味してみよう。
テストプログラムを作る。注文書order.xmlという実際のファイルからドキュメントを仕立てるプロセスはこのテストプログラムに置く。というのは、実際のサービスでは、クライアントさんにお手持ちのファイルのほうからドキュメントのほうまではお作りになっていただきたいんですよぅ〜。と言うつもりだから。
結果として、getDocumentElement()というメソッドを使うと、ドキュメントの全文を得ることができる。そういう用途に使うものなのかどうかはわからないが、ここではそれで請求書が書けていることをまあ、確かめた。

public class publishTest {

static final String filename="/export/home/noniko/axisworks/noniveg/resource/order.xml";

public static void main(String[] args) throws Exception{

publishTest pt=new publishTest();
Document indoc=pt.getDocument();//ファイルからドキュメントを生成するメソッド
OrderAnalyzer analyzer=new OrderAnalyzer(indoc);
Order order=analyzer.createOrder();
InvoicePublisher publisher=new InvoicePublisher(order);
Document outdoc=publisher.createInvoiceDoc();

Element outelm=outdoc.getDocumentElement();
System.out.println(outelm);

}

public Document getDocument(){
Document doc=null;
BufferedInputStream bis=null;

try{
File file=new File(filename);
bis=new BufferedInputStream(new FileInputStream(file));
}
catch(FileNotFoundException e){
System.out.println("XML file not Found.");
e.printStackTrace();
}
catch (IOException e){
System.out.println("Failed to get InputStream");
e.printStackTrace();
}
try{
DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance();
DocumentBuilder builder= factory.newDocumentBuilder();
doc=builder.parse(bis);

}
catch(Exception e){
System.out.println("Failed in parsing.");
e.printStackTrace();
}

return doc;
}



}

2003年10月30日

ページ先頭に戻る


請求書XMLの作成

 請求書作成クラスInvoicePublisher(なんか名前がカッコイイけど、アノ会社の製品にも同じような名前のがあった気がして少しナエ) は、やはりOrderクラスを属性に持つ。注文書から作成されたOrderインスタンスを入れてやるのだ。

public class InvoicePublisher {

Order order;

public InvoicePublisher(Order order){

this.order=order;

}

あとはDOMツールによって必要なエレメントを追加していく。まず請求書の宛先=注文書の差出人のエレメントを
<billto>
<name>name</name>
<address>address</address>
</billto>
というカタチで書くためのメソッドを用意する。

public void addBillToElements(Document doc, Element billto){

try{

Element toname=doc.createElement("name");
toname.appendChild(doc.createTextNode(order.getOrderFrom().getName()));
billto.appendChild(toname);

Element toaddress=doc.createElement("address");
toaddress.appendChild(doc.createTextNode(order.getOrderFrom().getAddress()));
billto.appendChild(toaddress);
}
catch(Exception e){
System.out.println("Failed in adding BuillTolElements");
e.printStackTrace();
}

まあ。これも、特定の構造が決まっている場合の作成メソッドである。

注文内容の確認の部分を作成するメソッド。Orderインスタンスから注文商品名と注文個数、アーンドcreateChargeクラスの計算メソッドを使い、小計とか総計を計算して付け加える。だからまずcreateChargeを呼び出しておく。

public void addContentsElements(Document doc, Element contents){

try{

createCharge cc= new createCharge(order);

Orderインスタンスを、再びバラバラに・・・

orderedItem[] items=order.getOrderedItems();

or (int i=0; i<itemlength; i++){

Element itemelm=doc.createElement("item");
String itemname=items[i].getItemName();
itemelm.setAttribute("itemname", itemname);
int quantity=items[i].getQuantity();
itemelm.setAttribute("quantity", Integer.toString(quantity));

のようにして、<item name="...." quantity="..."というカタチを作っていくのだ。
さて、計算だ。

int unitprice=cc.getUnitPrice(items[i]);
itemelm.setAttribute("unitprice", Integer.toString(unitprice));
int subtotal=cc.getSubTotal(items[i]);
itemelm.setAttribute("subtotal", Integer.toString(subtotal));

これで、<item name="...." quantity="..." unitprice="..." subtotal="...."/>というエレメントができあがる。これを、
<contents>...</contents>の中にまとめる。

contents.appendChild(itemelm);

ついでに
total += subtotal;

せっかくcreateChargeクラスに用意したgetTotalメソッドだが、結局使わなかったというわけ〜。とにかく、これをorderedItem[]配列の各要素について行う。

この二つのメソッドを使って、実際に請求書を作るのがcreateInvoiceDocメソッド。戻り値はDocumentだ。

public Document createInvoiceDoc(){

まず、空のドキュメントを用意する。

Document doc=null;

try{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder= factory.newDocumentBuilder();
doc=builder.newDocument();
}
catch(Exception e){
System.out.println("Failed to create a new Document.");
e.printStackTrace();
}

ルートエレメントの名前は<invoice>にする。属性idは注文書のidをそのまま返す。

try{
Element root=doc.createElement("invoice");
root.setAttribute("id", order.getId());
doc.appendChild(root);

あとは、二つのメソッドをかますだけ〜。
Element billto=doc.createElement("billto");
root.appendChild(billto);

addBillToElements(doc, billto);

Element contents=doc.createElement("contents");
root.appendChild(contents);

addContentsElements(doc, contents);


}
catch(Exception e){
System.out.println("Failed to Build Document");
e.printStackTrace();
}

return doc;
}

}

2003年10月30日

ページ先頭に戻る


注文された品の金額の計算

サービスプロバイダでは、まず、注文された品の小計と総計を出してやらなければならない。そのためにcreateChargeクラスを用意する。
これは、Order とvegAnalyzerクラスを属性に持つ。
VegAnalyzerというのは、このプロジェクトを始めたときに作ったクラスで、データベースファイルveg.xmlから、商品名・価格・ストックを属性に持つインスタンスvegetableを作成するためのものだ。前に作ったデータベース関係のファイルやツールも、今作っているコードの詳細ページに再掲したので、前の記事に戻らなくても御覧いただけます。

public class createCharge{

Order order;
vegAnalyzer analyzer;

public createCharge(Order order) throws Exception{
this.order=order;
analyzer=new vegAnalyzer();
}

OrderAnalyzerにより、注文書の内容を反映したOrderインスタンスを作った。これをcreateChargeのコンストラクタの引数に入れてやる。
vegAnalyzer はただリフレッシュするだけ〜。コンストラクタ中でしてもあまり意味ないかも知れないけど。

注文書から作成したOrderインスタンスの、属性orderedItem[] の中に にんじん3本だのきゅうり5本だのと、名前と個数を属性にもつorderedItemが入っている。これを一つ一つ取り出して、getItemName()メソッドで名前を得る。それをvegAnalyzerのfindVegメソッドの引数に入れてやれば、注文書にあった名前に一致する名前を持つvegetableインスタンスを取り出せる。
というのが、createChargeクラスにおけるgetVegegableメソッドである。

public vegetable getVegetable(orderedItem item) throws Exception{
String itemname=item.getItemName();
vegetable veg=analyzer.findVeg(itemname);
return veg;

}

そしたら、その単価を取り出す。自分で何度も混乱しかかったのは、価格は注文書order.xmlから作成されるorderedItemの属性ではなく、商品データベースveg.xmlから作成されるvegetableの属性なのだということ。

public int getUnitPrice(orderedItem item) throws Exception{

return getVegetable(item).getPrice();
}

それに、orderedItemの属性quantityを乗ずれば、そのorderedItemの小計になる。


public int getSubTotal(orderedItem item) throws Exception{

int quantity=item.getQuantity();
int subtotal=getUnitPrice(item)*quantity;
return subtotal;
}

ついでに総計を求めるメソッドgetTotalも作ったけど、注文書作成プログラム中でちゃちゃっと計算してもいいんだよな。コード全文は同じく別ページです。

2003年10月28日

ページ先頭に戻る


正体がわかっている文書のDOM解析

まずこの文書を解析してJavaクラスに収めるということをしなければならない。解析はというと今度はDOMだ。なぜならお手本にしている「スケ本」がDOMっているから他にしようがない。
だが、思い切ってDOMの世界に飛び込んでみたら、文書の正体っていうか構造がわかっているときのDOM解析というのはむしろ涙が出るほど簡単だということがわかった。ここでは当然(威張るなよ)ウサコさんはこちらが予期しないような間違いは絶対しないでくれるということを前提としている。

まず、XMLファイルからDOMドキュメントを得なければならない。これにはもちろんSun様のJAXPのAPIを使うぞッ。などと偉そうに言ってるけど要するにスケ本でやってるんだモン。

それにはjavax.xml.parsers.* 及びorg.w3c.dom.* をインポートする必要がある。そして、

DocumentBuilderFactory factory= DocumentBuilderFactory.newInstance();
DocumentBuilder builder= factory.newDocumentBuilder();
Document doc=builder.parse(bis);

これだけでいーのだ!さて、ここでカッコの中のbisというのは入力ストリームで、こうやって作った。

String filename="/export/home/noniko/axisworks/noniveg/resource/order.xml";
File file=new File(filename);
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file));

parseする対象はInputStreamなものであればいいようなのだが、2バイトコードを使うときはBufferedなものにしたほうがいいという話をよく耳にするので、今から癖をつけておこうという次第である。
Document, それは普通のファイルや入力を、解析のまな板に載せるために支度したもの、ということなのだろうか?

さて文書の構造、それはつまりどんなタグがあるかということである。それがわかればgetElementsByTagNameという冗談のように使えるメソッドがある。だがElementとは何なのか。

今作ったdocに対して、文字列tagnameを引数に、こんなプロセスを含むメインプログラムを適当に組んで走らせてみる。

NodeList nlist=doc.getElementsByTagName(tagname);
int nlistlength=nlist.getLength();
Element[] elementlist=new Element[nlistlength];
for (int i=0; i<nlistlength;i++){
elementlist[i]=(Element)nlist.item(i);
System.out.println(elementlist[i];
}

くだんのorder.xmlについて、tagname="name"を入れてみる。何が出てくるのか?

<name>usako</name>

では、tagname="item"で走らせてみると・・・

<item itemname="tomato" quantity="5"/>
<item itemname="onion" quantity="3"/>
<item itemname="cabbage" quantity="8"/>

どうやら、この.getElementsByTagNameメソッドをdocにかますと、元の文書の階層にかかわらず、とにかく指定のタグを見つけてそれの中身を持ってきてくれるようなのだ。こりゃ便利じゃありませんか。

さらに、

NodeList nlist=elem.getElementsByTagName(tagname);

と、今度は特定の親エレメントの下にあるお子様をひっぱってきてくれる。親エレメント、とは、たとえば

<orderfrom>
<name>usako</name>
<address>UsagiHouse</address>
</orderfrom>

という構造の部分について考えると、

NodeList nlist=doc.getElementsByTagName("orderfrom");

とやって得た elementlistに対して、

Element nameelem=elementlist[0];

としてやればいい。この場合一個しかないわけだから。で、

NodeList nlist=nameelem.getElementsByTagName("name");

とやってさらに得られた新しいelementlistについて中身を書き出させると

<name>usako</name>

が得られるという具合だ。

じゃあこの中からusakoを取り出すにはどうすればよいかというと、

String value=elm.getFirstChild().getNodeValue();

とやればvalue=usakoが得られる。いきなり elm.getNodeValue() と書いたらコンパイルエラーは出なかったが中身はnullだった。

<item itemname="tomato" ...

のカタチのほうが、むしろ簡単で、itemのエレメントitemelmに対して

itemelm.getAttribute("itemname");

でゲットできる。

このようにして文書から値を取り出し、JavaのOrderインスタンスを作成するためのクラスOrderAnalyzerを作る。これはDocumentクラスを属性に持つ。注文書order.xmlから作ったDocumentインスタンスを入れてやる次第だ。

public class OrderAnalyzer{

Document doc;

public OrderAnalyzer(Document doc){
this.doc=doc;
}

エレメントを取り出すメソッドを用意する。ドキュメントから直接、指定したタグ名のエレメントを取り出すには

public Element[] getElements(String tagname){

NodeList nlist=doc.getElementsByTagName(tagname);
int nlistlength=nlist.getLength();
if(nlist ==null || nlistlength==0){
return null;
}
Element[] elementlist=new Element[nlistlength];
for (int i=0; i<nlistlength;i++){
elementlist[i]=(Element)nlist.item(i);
}

return elementlist;

}

エレメントが一個しかないとわかってる場合も多いので、

public Element getTheElement(String tagname){
Element[] elms=getElements(tagname);
return elms[0];
}

親エレメントを指定して、子エレメントを取り出すには

public Element[] getNextElements(Element elem, String tagname){
NodeList nlist=elem.getElementsByTagName(tagname);
int nlistlength=nlist.getLength();
if(nlist ==null || nlistlength==0){
return null;
}
Element[] elementlist=new Element[nlistlength];
for (int i=0; i<nlistlength;i++){
elementlist[i]=(Element)nlist.item(i);
}

return elementlist;
}

これも、一個しかない場合のメソッドを作っておく。

エレメントから実際の値を取り出すには

public String getValue(Element elm){
String value="";

try{
value=elm.getFirstChild().getNodeValue();
}
catch(DOMException e){
System.out.println("Unable to get Nodevalue:");
e.printStackTrace();
}

return value;
}

と、ここまで道具を用意して、さあ料理しよう。

public Order createOrder() throws Exception{

Order order= new Order();

まず、<order id="20031026-A" .... のところから、idを取り出す

Element odElem=getTheElement("order");
order.setId(odElem.getAttribute("id"));

注文書の差出人の情報orderFromインスタンスを取り出す

orderFrom of=new orderFrom();
Element ofElem=getTheElement("orderfrom");
Element nameElm=getTheNextElement(ofElem, "name");
Element addressElm=getTheNextElement(ofElem, "address");
of.setName(getValue(nameElm));
of.setAddress(getValue(addressElm));

で、Orderインスタンスにくっつける

order.setOrderFrom(of);

つぎに、注文内容orderedItemインスタンスを次々に生成し、配列にブチこむ。注文内容全体はorder.xml中で<contents>...</contents>内に囲まれているので、まずそこから出発。

Element contentelm=getTheElement("ordercontents");
Element[] itemelms=getNextElements(contentelm, "item");

itemエレメントは、複数存在する可能性がある。

int itemlength=itemelms.length;
orderedItem[] items=new orderedItem[itemlength];
for (int i=0; i<itemlength; i++){
orderedItem item =new orderedItem();
String itemname=itemelms[i].getAttribute("itemname");
String qStr=itemelms[i].getAttribute("quantity");
item.setItemName(itemname);
item.setQuantity(Integer.parseInt(qStr));
items[i]=item;
}

で、配列をOrderインスタンスにくっつける。

order.setOrderedItems(items);

こうして作成されたOrderインスタンスが、戻り値なのだ。


return order;

コード全文は別ページに示します。

2003年10月28日

ページ先頭に戻る


受け皿Javaクラス

まず、受け皿のほうを用意しなければなるまい。以下のようなクラスを作った。

noniveg.Order フィールド:String id, orderFrom orderfrom, orderedItem[] orderedItems
noniveg.orderFrom フィールド: String name, String address
noniveg.orderedItem フィールド: String itemName, int quantity, int subTotal

どれもめんどくさいからコンストラクタは明示的に作らず、フィールドのSetterとGetterだけ作った。詳細は別ページ。

2003年10月28日

ページ先頭に戻る


もすこしマシな受注システムを「ドキュメント中心サービス」で

ではもう少しマシな発注システムを作ろう。その場限りの問い合わせではなく、きちんと注文者、注文内容を文書にして送る。すると返信は注文者と注文内容の確認に、注文品目の小計や総計を書き加えた文書が送られてくるというものだ。
なーんつーとカッコいいけど、結局「スケ本」がそういうことをやっているので、マネしてみようというのだ。
今までのようなパラメータや戻り値のやりとりを「RPCサービス」というのに対して、「文書」を送ると「文書」が帰ってくる、これを「ドキュメント中心サービス」と言うらしい。わかってねえヤツ。だがそれはつまり、こっちで作った文書をSOAP文書の中にチョクで埋め込むものらしい。文書とはもちろんXML文書で、ここではこのようなものを使用した。

<order id="20031026-A" xmlns="Order">
<orderfrom>
<name>usako</name>
<address>UsagiHouse</address>
</orderfrom>
<ordercontents>
<item itemname="tomato" quantity="5"/>
<item itemname="onion" quantity="3"/>
<item itemname="cabbage" quantity="8"/>
</ordercontents>
</order>

/export/home/noniko/axisworks/noniveg/resource/order.xmlというファイルだ。
結論を言うと、名前空間 xmlns="Order" はかなり意味がない。最終段階でクライアント起動ってときに、ここに書いておかないとAxisくんが「名前空間もねえような文書を扱えると思ってんのカッ」のようにエラーメッセージを出すので、かなりジボージキに適当に書いてみたら動いた、というだけの話である。

ともあれ、ウサギハウスのウサコさんからこのようなご注文をいただいたというわけだ。クライアントプログラムではこのファイルを読み込んでサービスプロバイダに送ることにする。サービスプロバイダのあるディレクトリにクライアントのファイルがおいてあるというのもちょっとアレだが、バラけるのもよくないので。

2003年10月28日

ページ先頭に戻る


TCPMonだモーン

しかしホントにSOAPヘッダはちゃんと書き換えられているのか?やはり見なければなるまい、TCPMonを。さてNetBeans上からコイツは動かせるのか?
やることは同じだ。またAxisのサイトに行ってソースコードをもらってくる。TCPMon.javaと、TCPMon.propertiesももらってくる。
一方axisworks/noniveg/WEB-INF/classes/org/apache/utils/ディレクトリを作って、こいつらをお迎えする。
右クリックでコンパイル。

実行すると、ちゃんとzTCPMonのウィンドウが立ち上がった。横取りポートを8099に指定する。接続待機状態になる。

それから、nonivegClientSPAM.javaのソースコードを書き換える。

String wsUrl="http://localhost:8099/noniveg/services/SPAMFilter";

ポート番号を書き換えたわけだ。実行すると、ホントにデータが出てきた。

内蔵Tomcatのバージョンが結構古いことまで露呈されているぞ!
こいつの詳細は「save」ボタンを押して保存してやればいい。とにかく、この部分だ。

<soapenv:Header><ns1:Sender xmlns:ns1="noniFilter">usako</ns1:Sender> </soapenv:Header>
<soapenv:Body>
<getTheString>
<arg0 xsi:type="xsd:string">????????ソ??/arg0>
</getTheString>
</soapenv:Body>
</soapenv:Envelope>

ちゃんとヘッダにSenderという要素ができている。すごーい。Bodyの送信メッセージは日本語だったから文字化けしてるけど、結局はちゃんと届いてるわけだからエライっす。

2003年10月26日

ページ先頭に戻る


SOAPヘッダに手を加える

Axisはいったい何をしているのか。それはクライアントを実行すればクライアントリクエストをSOAP文書にして発行し、またサービスプロバイダのほうでそれを受け取るとサービスレスポンスをSOAP文書にして発行する仕事だという。
そのSOAP文書というのは基本的にはAxisで決めている。だが、クライアントプログラムやサービスプログラムを書き換えることによりそれに要素を付け加えたり することができるらしい。
SOAPのヘッダというのは、発信者だの優先度だのいう情報を取り扱う部分だそうな。そこに手を加えてみよう。

のに子八百屋には、経営者のに子への簡単なメッセージを受け付けるWebサービスがある。そこには 重要な顧客からの連絡もあれば、トモダチからの挨拶もある。加えて最近は迷惑なSPAMも多い。そこで、これらのメッセージを振り分けることにした。
送られたメッセージはどれも、受信日時を秒単位まで表した名前のファイルとして受信ボックスに保存される。そこで受信ボックスにimportant, normal, SPAMの三つのフォルダを設け、発信者名によって三つのうちにどれかにファイルを保存する。

サービスは非常に単純だ。クライアントから送られたメッセージをただ返すだけ。今までのサービスプログラムとおんなじ場所に置く。

package noniveg.services;

public class getString {

public String getTheString(String input){
return input;
}
}

問題はクライアントプログラムである。必ずしもSPAMではないのだが名前はnonivegClientSPAM。サービスプログラムに送るパラメータはあくまでメッセージだけだが、コマンドライン引数としてもうひとつ送信者名を入れるとそれはリクエスト文書のヘッダに組み込まれる。

package nonivegclienttest;

import org.apache.axis.client.*;
import org.apache.axis.message.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class nonivegClientSPAM{

public static void main(String[] args) throws Exception{

//SPAMFilterという名前でサービスをデプロイしてあるので。
String wsUrl="http://localhost:8081/noniveg/services/SPAMFilter";
String input=args[0]; //メッセージを入れる。
String sender=args[1]; //送信者名を入れる。

DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.newDocument();

Element senderElem=doc.createElementNS("noniFilter","Sender"); //サービスプロバイダのほうで参照するらしい。
senderElem.appendChild(doc.createTextNode(sender));//args[1]の内容が組み込まれた

SOAPEnvelope reqEnv=new SOAPEnvelope();
reqEnv.addHeader(new SOAPHeaderElement(senderElem));//新しいヘッダ要素が組み込まれた
Object[] params=new Object[]{input};

reqEnv.addBodyElement(new RPCElement("","getTheString",params));
//サービスへのパラメータ送信自体はこれまでと変わらない

Service service=new Service();
Call call=(Call)service.createCall();
call.setTargetEndpointAddress(new java.net.URL(wsUrl));
SOAPEnvelope respEnv=call.invoke(reqEnv);

System.out.println("The Process Done.");//今回はクライアントへの応答はなし、送信成功のメッセージだけ。
}

}

ヘッダを扱うのは、以下のハンドラである。

package noniveg.services;

import java.util.Vector;
import org.apache.axis.*;
import org.apache.axis.message.*;
import org.apache.axis.handlers.*;
import noniveg.*;

import java.io.*;
import java.util.Calendar;
import java.util.Date;
import java.text.SimpleDateFormat;

public class nonivegfilterHandler extends BasicHandler{

private Object getParam(Vector params, int index){
return((RPCParam)params.get(index)).getValue();
}

public void invoke(org.apache.axis.MessageContext msgContext) throws org.apache.axis.AxisFault {

try{
Message reqMsg=msgContext.getRequestMessage();
SOAPEnvelope reqEnv= reqMsg.getSOAPEnvelope();
SOAPHeaderElement header= reqEnv.getHeaderByName("noniFilter","Sender");
//クライアントプログラムと統一すりゃいいようだ(いい加減だな!)

if(header !=null){
header.setProcessed(true);
String sender=(String)header.getValueAsType(Constants.XSD_STRING);

RPCElement reqRPC= (RPCElement)reqEnv.getFirstBody();
Vector params=reqRPC.getParams();
String input=(String)getParam(params, 0);

Message respMsg=msgContext.getResponseMessage();
SOAPEnvelope respEnv=respMsg.getSOAPEnvelope();
RPCElement respRPC= (RPCElement)respEnv.getFirstBody();


String result=(String)getParam(respRPC.getParams(), 0);
FilterIt(sender,result);//送信データの振り分けメソッド

}

}
catch(Exception e){
throw new AxisFault("Ha ha! Axisfault in FilterHandler", e);
}
}

//ファイル名として受信日時を取得するメソッド
public String getFileName(){
Calendar cal=Calendar.getInstance();
Date date=cal.getTime();
SimpleDateFormat myformat=new SimpleDateFormat("yyMMddHHmmss");
String logname=myformat.format(date);
String filename=logname+".log";//たとえば20031025192123.logみたくなる。
return filename;

}

//振り分ける。顧客やトモダチとして認定する名前は簡単のためここではひとりずつ。
public void FilterIt(String sender, String result){

String dir;

try{
if (sender.equals("usako")){//重要な顧客はusakoさん
dir="important";
}
else if(sender.equals("cisco")){//フツーのトモダチciscoさん
dir="normal";
}
else{//あとはみんなSPAM扱い
dir="SPAM";
}


String filepath="/export/home/noniko/axisworks/noniveg/noniveglog/"//受信ボックス
+dir+"/"+getFileName();

File file=new File(filepath);
BufferedWriter bf=new BufferedWriter(new FileWriter(file));
bf.write(result);
bf.close();
}
catch(FileNotFoundException e){
System.out.println("The file is not available");
}
catch(IOException e){
System.out.println("Failed to Filter:"+e.getMessage());
}


}


public void undo(MessageContext msgcontext){}
}

wsddファイルはこんなもんだ。

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

<handler name="FilterIt" type="java:noniveg.services.nonivegfilterHandler"/>
<service name="SPAMFilter" provider="java:RPC">
<parameter name="className" value="noniveg.services.getString"/>
<parameter name="allowedMethods" value="*"/>
<responseFlow>
<handler type="FilterIt"/>
</responseFlow>
</service>

</deployment>

こいつをGUIGUIとデプロイする。さて、クライアントプログラムを動かしてみよう。

 見えにくいけど、空白入ってます。

args[0]=耳よりな話、args[1]="usako"である。これで「nonivegClientSPAM」アイコンを右クリックで実行する。
コンソールウィンドウに The Process Done. と出たら、ファイルエクスプローラで該当ディレクトリのアイコンを選んで再表示命令。

他にもいろいろ試した後の結果であるが、22時10分37秒を現すこのファイルの中身は、

で、ちゃんと重要顧客usakoさんからのメッセージは重要ボックスに振り分けられたという次第だ。
なんかすごいたのしーぞ!

2003年10月24日

ページ先頭に戻る


参考文献ももう一回

ページも改まったところで、このページでの勉強の参考にさせていただいている参考書をもう一度上げたいと思う。

完全オープンソースによる(+Java2 SDK)基礎からのWebサービス」(藤田泰徳著、セレンディップ発行、小学館発売)
:略称「藤田本」

JavaによるWebサービス構築」 ソフトバンク パブリッシング(株)発行、本体定価5,800円。
Steave Graham他 著、嶋本 正 他 訳
:略称「スケ本」

2003年10月24日

ページ先頭に戻る