IntakeサービスWebフォームに入力された値の検証と入力データをBeanのプロパティへマッピングするために、 IntakeはXMLによる定義を使います。 言い換えると、Intakeによって、Webアプリケーションが、Webフォームの入力値を取得できるようになり、 その入力値を検証できるようになり、そしてそのデータをオブジェクトにマッピングできるようになります。 Torqueのようなツールは、 オブジェクトをデータベースへマッピングすることに役立ち、 IntakeはWebフォームのデータをオブジェクトにマッピングすることに役立ちます。 IntakeがTurbineのWebアプリケーションに収まっているイメージは次のようになります:
------------------
HTML Form
------------------
Intake
------------------
Business Objects <- Torque Generated
------------------
Peers <- Torque Generated
------------------
RDBMS
------------------
Intakeを使う利点がいくつかあります。 利点の一つは、Intakeはフォームデータを扱うための集中管理システムを提供します。 Intakeは一つのXMLファイルですべて設定されます。 もう一つの利点は、Intakeはフォームデータの検証に適しています。 フォームフィールドに含まれるべきデータが含まれていることを確かめるために、 正規表現マッチングを提供します。 例えば、フォームフィールドに数字だけしか入力できない場合は、 正規表現で検証されます。 最後の利点は、Intakeはエラーメッセージの集中管理を可能にします。 検証が失敗したら、XMLファイルで定義されているエラーメッセージをユーザに表示することができます。 設定# ------------------------------------------------------------------- # # S E R V I C E S # # ------------------------------------------------------------------- # Turbine サービスのためのクラスはここで定義しなければなりません。 # フォーマット: services.[name].classname=[implementing class] # # サービスのプロパティを設定するには次の書式を用います: # service.[name].[property]=[value] services.IntakeService.classname= \ org.apache.fulcrum.intake.TurbineIntakeService # ------------------------------------------------------------------- # # I N T A K E S E R V I C E # # ------------------------------------------------------------------- # 有効な入力値を特定するためのXMLファイルの位置はここです。 services.IntakeService.xml.path=WEB-INF/conf/intake.xml 使用法IntakeはTurbineのサービス及びPullツールとして実行されます。 XMLによる定義はサービスの初期化中に構文解析されます。 そして、リクエストデータを処理しレスポンスデータを生成する間に、 Pullツールによってその定義が使用されます。 IntakeはVelocityコンテキストで $intakeというデフォルト値で利用可能です。 ツール用に設定した値が使用される変数名になります。 例えば、現在の設定は"tool.request.intake"となっていて、 変数名を"foo"に変えたければ、"tool.request.foo"と設定すればよいでしょう。 Intakeは拡張子がjavaのファイルの先頭に次のインポート文を加えることで、 Javaコードでも利用可能になります。 import org.apache.fulcrum.intake.model.*; import org.apache.turbine.tool.IntakeTool; Xmlファイル次の例はScarabから持ってきました。 これらはScarabのintake.xmlからのいくつかのグループです。
<input-data basePackage="org.tigris.scarab.">
<group name="AttributeValue" key="attv"
mapToObject="om.AttributeValue">
<field name="Value" key="val" type="String">
<rule name="maxLength" value="255">Value length cannot be > 255</rule>
<required-message>This module requires data for this
attribute.
</required-message>
</field>
<field name="Url" key="url" type="String" mapToProperty="Value">
<rule name="maxLength" value="255">Url length cannot be > 255</rule>
<rule name="mask" value="^$|http.+">Please enter an url starting with "http"</rule>
<required-message>This module requires a valid url.</required-message>
</field>
<field name="OptionId" key="optionid" type="NumberKey">
<rule name="mask" value="^$|[0-9]+">Please select a valid choice</rule>
<required-message>This module requires that you select an option
for this attribute.
</required-message>
</field>
</group>
<group name="Login" key="login">
<field name="Username" key="u" type="String">
<rule name="minLength" value="1">Please enter an email address</rule>
<rule name="mask" value=".+@.+\..+">Please enter a valid email address</rule>
</field>
<field name="Password" key="p" type="String">
<rule name="minLength" value="1">Please enter a password</rule>
</field>
</group>
</input-data>
一つのgroupタグは一列に揃えられている一組のfieldタグとなっていて、 論理単位を形成しています。 最初のgroupタグはom.AttributeValueビジネスオブジェクト(BO)からのプロパティをいくつか含みます。 これはオブジェクトの中のプロパティ一つ一つがset/getメソッドを持つJava Beanオブジェクトです。 この場合、オブジェクトはScarabのSQLスキーマからTorqueによって自動生成されたものです。 groupタグは、class属性を持ちます。 そのclass属性は、テンプレートやJavaコード中で、 グループを参照するために使われる名前です。 グループには、クエリパラメータで使われるkey属性が含まれます。 keyはどのコードにも参照されませんので、 残りのグループと一意に識別できさえすれば、 短い名前(1文字でさえ)でもかまいません。 グループのフィールドをマッピングする一つのオブジェクトを指定することもできます。 これがデフォルトです。 つまり、グループ内の個々のフィールドは異なるオブジェクトにマッピングできます。 fieldタグは、groupタグのclass属性、key属性と類似の機能を提供するname属性とkey属性を持ちます。 またmapToObjectとmapToPropertyも持ちます。 これらは、ビジネスオブジェクトをあらかじめ設定されたフィールドに関連づけます。 また、検証が成功したあとでフィールドデータをBeanに割り当てます。 フィールドはStringやIntegerのようなシンプルな型を持ちます。 Email、URL、日付のような複雑な型を追加することが予定されています。 これらの型は、単純な正規表現のマスクでは難しかったり不可能な機能を追加します。 フィールドではrule要素を定義することもできます。 基本的なフィールドでは、正規表現マスクの他に、長さや値の最小値、最大値の規則を含みます。 Login の例<group name="Login" key="login"> name="Login"はグループを説明する名前です。 key="login"はWebフォームの中で、グループを特定するために使われる値です。 key=valueは直接的に参照されることはありません。 つまり、あなたが自分のアプリケーションをデバッグしないなら、 それが存在することを知っている必要は無いでしょう。 これらの属性の値は両方とも、XMLファイル内で、 すべてのグループに渡って一意でなければなりません。 さて、グループ内でのフィールドを見てみましょう。
<field name="Username" key="u" type="String">
<rule name="minLength" value="1">Please enter an email address</rule>
<rule name="mask" value=".+@.+\..+">Please enter a valid email address</rule>
</field>
<field name="Password" key="p" type="String">
<rule name="minLength" value="1">Please enter a password</rule>
</field>
name="Username"はフィールドを説明する名前です。 key="u"はWebフォームの中で、フィールドを特定するために使われる値です。 これら二つの属性はグループ内のフィールドのいたる所で一意でなければなりません。 type="String"は、システムがそのフィールドにどんな入力がされるのかを予想していることを明記しています (使用できる値に関してはintake.dtdをご覧ください)。 フィールド内では、一つ以上のルールを特定することが可能です。 これらのルールは、IntakeがどのようにしてWebフォームのデータを検証すれば良いのかを定義しています。 ruleタグにはminLength、maxLength、mask 属性があります。 ruleタグ内部のメッセージはテンプレート内のエラーを表示するのに使われるテキストメッセージです。 現時点では、Velocityテンプレート内でのIntakeの使い方の例を示すことが最もよいことです:
(1) <form action="$link.setPage("Login.vm")" method="POST" name="login" >
(2) <input type="hidden" name="action" value="Login">
(3) #if ($data.Parameters.nextTemplate)
(4) <input type="hidden" name="nextTemplate"
value="$data.Parameters.nextTemplate">
#else
(5) <input type="hidden" name="nextTemplate" value="Start.vm">
#end
<p>
Email Address:
(6) #set ( $loginGroup = $intake.Login.Default )
(7) #if ( !$loginGroup.Username.isValid() )
(8) $loginGroup.Username.Message<br>
#end
(9) <input name= "$loginGroup.Username.Key"
value="$!loginGroup.Username" size="25" type="text">
</p>
<p>
Password:
(10) #if ( !$loginGroup.Password.isValid() )
(11) $loginGroup.Password.Message<br>
#end
(12) <input name= "$loginGroup.Password.Key"
value="" size="25" type="text"
onChange="document.login.submit();">
</p>
(13) <input type="submit" name="eventSubmit_doLogin" value="Login">
(14) $intake.declareGroups()
</form>
上記の例は、Webアプリケーション設計に関するたくさんの異なる概念を示しています。 従って、上から順に少しそれらを分析していきましょう。 簡単に参照できるように重要な行には番号をつけました。
以下は、ページがリクエストされた後にブラウザに送られるHTMLの例です:
<form action="http://foo:8080/scarab/servlet/scarab/template/Login.vm"
method="POST" name="login" >
<input type="hidden" name="action" value="Login">
<input type="hidden" name="nextTemplate" value="Start.vm">
<p>
Email Address:
<input name= "login_0u"
value="" size="25" type="text">
</p>
<p>
Password:
<input name= "login_0p"
value="" size="25" type="text" onchange="document.login.submit();">
</p>
<input type="submit" name="eventSubmit_doLogin" value="Login">
<input type="hidden" name="intake-grp" value="login"></input>
<input type="hidden" name="login" value="_0"></input>
</form>
考慮すべき注意点がいくつかあります:
フォームのサブミットを取り扱うJava Action のコードは以下のように見えます:
public void doLogin( RunData data, Context context ) throws Exception
{
IntakeTool intake = (IntakeTool) context.get("intake");
if ( intake.isAllValid() && checkUser(data, context) )
{
String template = data.getParameters()
.getString(ScarabConstants.NEXT_TEMPLATE,
TurbineResources.getString("template.homepage", "Start.vm") );
setTemplate(data, template);
}
else
{
// 無名のユーザを取り出します。
data.setUser (TurbineSecurity.getAnonymousUser());
setTemplate(data,
data.getParameters()
.getString(ScarabConstants.TEMPLATE, "Login.vm"));
}
}
/**
ユーザの存在と承認を確認します。
*/
public boolean checkUser(RunData data, Context context)
throws Exception
{
User user = null;
IntakeTool intake = (IntakeTool)context
.get(ScarabConstants.INTAKE_TOOL);
try
{
String username = null;
String password = null;
try
{
Group login = intake.get("Login", IntakeTool.DEFAULT_KEY);
username = login.get("Username").toString();
password = login.get("Password").toString();
}
catch ( Exception e )
{
throw new TurbineSecurityException(
"Login information was not supplied.");
}
// ユーザを認証し、オブジェクトを取得します。
user = TurbineSecurity.getAuthenticatedUser( username, password );
...
}
}
Intakeはコンテキストから取り出されて、設定されているすべての入力値について妥当性を検証します。 もし妥当でないなら、ログインフォームが再び現れ、エラーメッセージが表示されます。 データが妥当なら、IntakeのフィールドはBeanオブジェクトに直接マッピングしないので、 この場合、フィールドデータは手動で抽出されます。 次の例では、Intakeのフィールドが group.setProperties() メソッドを使って、 直接Intakeのフィールドデータをそれに合うBeanに割り当てていきます。 属性値の例
<group name="AttributeValue" key="attv"
mapToObject="om.AttributeValue">
name="AttributeValue" は単純にグループを説明する名前です。 key="attv" はウェブフォーム内でグループを識別するために使われる値です。 これらの属性の両方はXMLファイル中のすべてのグループに渡って固有の値です。 mapToObject="om.AttributeValue" はオプションの属性です。 このグループがマッピングするJava Beanオブジェクトを指定します。 もし mapToObject が特定されなければ、 受け皿となるオブジェクトからデータの値を取り出す代わりに、 Intakeを使ってデータの値を直接取り出すことができます。 このことは、これより先で、詳細に扱っていきます。
<field name="Value" key="val" type="String">
<rule name="maxLength" value="255">Value length cannot be > 255</rule>
<required-message>This module requires data for this
attribute.
</required-message>
</field>
<field name="Url" key="url" type="String" mapToProperty="Value">
<rule name="maxLength" value="255">Url length cannot be > 255</rule>
<rule name="mask" value="^$|http.+">Please enter an url starting with "http"</rule>
<required-message>This module requires a valid url.</required-message>
</field>
グループ内のフィールドはWebページ上のフォームフィールドと関連があります。 現時点では、おそらく、fieldタグのそれぞれが何を意味しているのか詳細に説明するよりも、 例を示すのが最適な方法です。 したがって、単純な例の中で上述のフィールドを使用するなら、 URLを編集できるテキストエントリーボックスを持つフォームにしてもよかったかもしれません。 ファイル名は"EditUrl.vm"です。
#set ( $action = $link.setPage("EditUrl.vm").setAction("SaveUrl") )
<form action="$action"
method="post">
#set ( $attributeValue = $issue.AttributeValue("URL") )
#set ( $group = $intake.AttributeValue.mapTo($attributeValue) )
Enter Url:
<input type="text" name="$group.Url.Key" value="$!group.Url.Value">
<input type="submit" name="eventSubmit_doSave" value="Submit>
$intake.declareGroups()
</form>
上述のテンプレートを説明すると、単純に、便宜上、最初の#setが実行されます。 2番目の#setはScarabの一部で、$issueオブジェクトを使って、特定の問題を取得するため、 "URL"AttributeValueオブジェクトを取り出します。 次の#setはIntakeにそのオブジェクトをAttributeValueグループにマッピングするように指示します。 つまり、IntakeにAttributeValueグループオブジェクトを生成するように指示するということです。 AttributeValueグループオブジェクトは$issueオブジェクトから取り出されたAttributeValueにマッピングされます。 このGroupオブジェクトは、XMLファイルの<group>を、Javaオブジェクトとして表します。 この例を更に見ていくと、nameとvalueの属性を持つ<input>フィールドがあります。 $group.Url.KeyはIntakeにフィールドの情報を取り出すように指示します。 これは、"attv_0url"と評価されることになります。 これは、グループキー(attv)、"$intake.AttributeValue.Default"を取り出した結果の"0"、 フィールドキーである"url"の組み合わせです。 value属性は最初は単純に空の文字列として評価されます。 $intake.declareGroups()は、 どのグループがページに加えられるか宣言するhidden inputフィールドを生成する特別なメソッドです。 以下でより詳細に議論していきます。 テンプレートを実行した後のHTMLページ上のソースを見てください。 そうすると、上記のフォームは次のように見えると思います:
<form action="http://server/s/servlet/s/template/EnterUrl.vm/action/EnterUrlAction"
method="post">
Enter Url:
<input type="text" name="attv_0url" value="">
<input type="submit" name="eventSubmit_doEnter" value="Submit>
<input type="hidden" name="attv" value="_0">
<input type="hidden" name="intake-grp" value="attv">
</form>
フォームがサーバにサブミットされた時(ユーザがサブミットボタンをクリックした時)、EnterUrlAction.java内の次のコードが実行されます。
public void doEnter( RunData data, Context context ) throws Exception
{
IntakeTool intake = (IntakeTool)context
.get(ScarabConstants.INTAKE_TOOL);
// フィールドが妥当かどうか確認します。
if ( intake.isAllValid() )
{
// "AttributeValue" グループを取得します。
AttributeValue av = new AttributeValue();
Group group = intake.get("AttributeValue", IntakeTool.DEFAULT_KEY);
group.setProperties (av);
// これで、avにフォームデータが正しく設定されます。
}
}
XMLファイル中のフィールドに定義されている規則に合わず、フォームフィールドが無効の場合、 アクションは何もせず、ページが再表示されます。 フィールドの説明に戻ります。もう一度例を見てください:
<field name="Value" key="val" type="String">
<rule name="maxLength" value="255">Value length cannot be > 255</rule>
<required-message>This module requires data for this
attribute.
</required-message>
</field>
<field name="Url" key="url" type="String" mapToProperty="Value">
<rule name="maxLength" value="255">Url length cannot be > 255</rule>
<rule name="mask" value="^$|http.+">Please enter an url starting with "http"</rule>
<required-message>This module requires a valid url.</required-message>
</field>
単一のフォーム内にある一つのクラスから成る複数のグループこの例では、問題点(バグ)に関連する各種の属性に値を割り当てているScarabからのフォームを使います。 属性には、summary, operating system, platform, assigned toなどがあります。 この中でいくつかは必須ですが、すべてが必須という分けではありません。 テンプレート:
#set ( $action = $link.setPage("entry,Wizard3.vm").setAction("ReportIssue")
.addPathInfo("nextTemplate", "entry,Wizard4.vm") )
#set ($user = $scarabR.User)
#set ($module = $user.CurrentModule)
#set ($issue = $user.ReportingIssue)
<form method="get" action="$action">
<hr><br>Please fill in the following:<br><br>
#foreach ( $attVal in $issue.OrderedModuleAttributeValues )
#set ( $attrInput = $intake.AttributeValue.mapTo($attVal) )
#if ( $attVal.Attribute.AttributeType.ValidationKey )
#set ( $field = $attVal.Attribute.AttributeType.ValidationKey )
#elseif ($attVal.Attribute.AttributeType.Name == "combo-box" )
#set ( $field = "OptionId" )
#else
#set ( $field = "Value" )
#end
#if ( $attVal.isRequired() )
$attrInput.get($field).setRequired(true)
<b>*</b>
#end
$attVal.Attribute.Name:
#if ($attVal.Attribute.AttributeType.Name == "combo-box" )
<font color="red">
#attrValueErrorMsg ( $attVal $field )
</font>
<br>
#attrValueSelect ($attVal $field "")
#else
<font color="red">
#attrValueErrorMsg ( $attVal $field )
</font>
<br>
#if ($attVal.Attribute.AttributeType.Name == "long-string" )
<textarea name= "$attrInput.Value.Key" cols="40"
rows="5">$!attrInput.Value</textarea>
#else
<input name= "$attrInput.Value.Key"
value="$!attrInput.Value" size="20" type="text">
#end
<br><br>
#end
#end
<p>
<input type="submit"
name="eventSubmit_doEnterissue" value="Submit Issue">
$intake.declareGroups()
</form>
ここで新たに付け加えられた大事な点は、 $intakeグループがビジネスオブジェクトにマッピングされるということです。 この方法で使用されるビジネスオブジェクトは、 オブジェクトを一意に認識するStringキーを得るためのメソッドを提供するRetrievalbeインターフェースを 実装することになっています。 アクション:
public void doEnterissue( RunData data, Context context )
throws Exception
{
IntakeTool intake = (IntakeTool)context
.get(ScarabConstants.INTAKE_TOOL);
// Summary が常に要求されます。
ScarabUser user = (ScarabUser)data.getUser();
Issue issue = user.getReportingIssue();
AttributeValue aval = (AttributeValue)issue
.getModuleAttributeValuesMap().get("SUMMARY");
Group group = intake.get("AttributeValue", aval.getQueryKey());
Field summary = group.get("Value");
summary.setRequired(true);
issue.setVocabulary(new Vocabulary(summary.toString()));
if ( intake.isAllValid() )
{
Iterator i = issue.getModuleAttributeValuesMap()
.values().iterator();
while (i.hasNext())
{
aval = (AttributeValue)i.next();
group = intake.get("AttributeValue", aval.getQueryKey());
if ( group != null )
{
group.setProperties(aval);
}
}
if ( issue.containsMinimumAttributeValues() )
{
issue.save();
String template = data.getParameters()
.getString(ScarabConstants.NEXT_TEMPLATE,
"entry,Wizard3.vm");
setTemplate(data, template);
}
}
}
このアクションでは、ビジネスオブジェクトまたは処理によって、 Intakeにフィールドが必須かどうかを知らせる方法を示しています。 また、同じクラス内の複数グループをテンプレートに加える方法も示しています。 そして、情報は対応するBeanに容易に受け渡されます。
Dan Diephouse wrote:
>
> 私は、Intakeを使ってビジネスオブジェクトのプロパティを更新するような
> フォームの妥当性を検証したいと思っています。
> turbine-2リポジトリの最新のcvsを取得して、turbine.jarの新しい
> ディストリビューションを構築しました。
> 私のデータベースに妥当性検証アイテムを追加することは、Intakeを使って
> 簡単にできました。しかし、それらを変更する時に、思わぬ障害と遭遇してしまいました。
> Intakeの妥当性検証ファイル内でビジネスオブジェクトを定義しています。
> アイテムを更新するために次のコードを使っています。
>
> IntakeTool intake = (IntakeTool) context.get("intake");
>
> ParameterParser pp = data.getParameters();
>
> if ( intake.isAllValid() ) {
> Job j = new Job();
> j.setNew(false);
> group.setProperties(j);
>
> JobPeer.doUpdate(j);
>
> Error--> data.getParameters().add("jobid", j.getJobId().toString());
> data.setMessage("Job updated.");
> } else {
> data.setMessage("There was an error updating the job.
> Check below for further information.");
> }
>
> JobIdを取り出すまでは、問題なく動きます。Null Pointer Exceptionが発生します。
> ここで何かおかしなことをしていますか?またはバグですか?
> Intakeに対して新しいオブジェクトではないということを明記する必要があるのでしょうか?
> テンプレートを含めようとしていますが、たくさんのフィールドがあり、非常に長くなっています。
> ここでは、大部分のフィールドと一緒に少し要約して示しています:
>
> #set ( $job = $basecamp.getJob() ) - This gets a job from a pull tool
> #set ( $jobGroup = $intake.Job.mapTo($job) )
> <input type=hidden name="$jobGroup.JobId.Key"
> value="$jobGroup.JobId.Value">
>
> #if ( !$jobGroup.Title.isValid() )
> $jobGroup.Title.Message<br>
> #end
> <input type=text name="$jobGroup.Title.Key"
> value="$!jobGroup.Title.Value" size="50">
> .
> .
> .
> .
> $intake.declareGroups()
>
> Thanks,
>
> Dan Diephouse
これは、私がいつも行っている方法ではありません。しかし、動作すると考えています。
ひとつ間違っている点は、テンプレート内ではmapTo(job)を使用していて、
アクション内ではIntakeTool.DEFAULT_KEYを使用していることです。
もし、job.getQueryKey()が"_0"を返さなかったら、
この組み合わせは動作しないでしょう。
パラメータがどのようになっているか見るために、
アクション内のdata.getParameters().toString()をプリントアウトして下さい。
ここで、イベントの普通の経路を考えて見ます:
1. テンプレート内:
$job = $foo.Job
#set ( $jobGroup = $intake.Job.mapTo($job) )
(このJobは新しいJobであるはずです。または、すでに保存されています。)
2. アクション内:
// 同じJobを取得して下さい。 (同じJavaオブジェクトである必要はありませんが、
正確に同じ属性を持っていなければなりません。)
Job job = foo.getJob()
Group group = intake.get("Job", job.getQueryKey());
group.setProperties(job);
あなたは一つのJobに対して与えられたパラメータを他のJobに
マッピングしようとしているように見えます。
john mcnally
フィールドに対するデフォルト値もしデフォルトが空であるべきでないinputフィールドを使いたければ、 defaultValueフィールドを使って下さい。
<group name="test" key="test">
<field name="Value" key="val" type="String" defaultValue="preset">
</field>
</group>
#if($!dz)
#set ($frm = $intake.test.mapTo($t))
$frm.Mode.
#else
#set ($frm = $intake.test.default )
#end
<FORM NAME="entryForm">
<INPUT type="text" name="$frm.get("val").Key" value="$!frm.get("val")">
</FORM>
その結果、フォームを$tオブジェクトのフィールドか、 valフィールドに対して'preset'文字列であるデフォルト値にマッピングすることができます。 |