2011年3月6日日曜日

Spring Roo 入門: 第 2 回 Spring Roo を使ってアプリケーションを開発する

作業開始

サンプルのカンファレンス・アプリケーションを拡張するには、第 1 回のアプリケーションを再作成する必要があります。それには、前回の記事で説明した手順に従うか、Roo の script コマンドを使用してください。script コマンドは、リソース・ファイルに指定されたすべてのコマンドを実行します。第 1 回の手順に従っていれば、Roo によって log.roo という名前のファイルが作成されているはずです。このファイルに、Roo シェルで起動されるすべてのコマンドが含まれています。log.roo ファイルを実行して、アプリケーションを再作成します。

  1. このファイルは、サンプル・コードに含まれています。ファイルの名前を conference.roo に変更してください。
  2. conference というディレクトリーを新規に作成し、その中に conference.roo をコピーします。
  3. オペレーティング・システムのコマンドライン・シェルを開きます。
  4. 前の手順で作成した conference ディレクトリーに移動します。
  5. script --file conference.roo コマンドを実行します。

Maven リポジトリーに必要な JAR があれば、script コマンドによって数秒間でアプリケーションが再作成されます。必要な JAR がない場合には、このコマンドがすべての JAR をダウンロードしなければならないため、もう少し時間がかかります。script コマンドは、Spring が管理するプロジェクトを作成するためのテンプレートとして利用できるという点で、便利なコマンドです。

作業を進める前に、Maven プロジェクトを STS にインポートしてください。STS は Maven Eclipse プラグインにあらかじめバンドルされています。Maven プロジェクトをインポートするには、「File (ファイル)」 > 「Import (インポート)」 > 「Maven」 > 「Existing Maven Projects (既存の Maven プロジェクト)」の順に選択し、Maven プロジェクトのディレクトリーを選択します。このプロジェクトを STS にインポートする理由は、後でカスタム・コードを作成するからです。

作成した Web アプリケーションは今のところ機能するので、Speaker エンティティーと Talk エンティティーを手動で作成、読み取り、更新、削除してみることで、アプリケーションをテストすることができます。けれども、この手動のプロセスを自動化できたら素晴らしいと思いませんか?

Web アプリケーションのテストの自動化

そこで登場する Spring Roo の機能が、Selenium テスト・サポートです。Selenium は、Web ベースのアプリケーションの自動テストを迅速に作成できるようにする堅牢なツール・セットです。Selenium テスト・サポートをアプリケーションに追加するには、以下のコマンドを実行します。

	selenium test --controller ~.web.SpeakerController 	selenium test --controller ~.web.TalkController	 

上記の selenium test コマンドによって、Speaker および Talk コントローラーを対象とした Selenium テストが作成されます。このコマンドには必須属性として、Selenium テストを作成するコントローラーの名前を指定する、controller という属性があります。また、このコマンドには、Selenium テストの名前を指定する name、Web アプリケーションが使用可能なサーバーを指定する serverUrl という 2 つのオプション属性もあります。さらに、selenium test コマンドを実行すると、Spring Roo によって Selenium Maven プラグインも追加されます。

上記のコマンドでコントローラーの Selenium テスト・ケースは作成できましたが、これらのテスト・ケースを実行する前に、Spring Roo で作成される Selenium テスト・スイートの小さな欠陥を修正しなければなりません。サンプル・アプリケーションの Speaker エンティティーには、年齢が 25 歳から 60 歳の間でなければならないという制限を設定してありますが、このテスト・スイートは、この制限を考慮しないからです。年齢の値として 1 が使用されているため、テストは失敗することになります。そこで、test-speaker.xhtml という名前のファイルを修正して、リスト 1 に示すセクションを更新する必要があります。


リスト 1. test-speaker.xhtml の修正
		<tr> 			<td>type</td> 			<td>_age_id</td> 			<td>1</td> 		</tr> 

上記のセクションを以下のように変更します。

		<tr> 			<td>type</td> 			<td>_age_id</td> 			<td>26</td> 		</tr> 

この欠陥は、Spring Roo の今後のリリースで修正されるはずです。

Selenium テスト・ケースを実行するには、Tomcat サーバーを起動する必要があります。それには、Maven コマンド mvn tomcat:run によってサーバーを起動します。Roo を使用して作成したすべての Web アプリケーションには、デフォルトで、Tomcat および Jetty Web サーバー対応の Maven プラグインが備わっています。selenium test を実行するための Maven コマンドは、mvn selenium:selenese です。

このコマンドを実行すると、Firefox ブラウザーが立ち上がり、Selenium テスト・ケースが実行されます。テストの実行中には、画面に図 1 に示すような内容が表示されます。


図 1. Selenium テスト
Selenium テストを実行中のツールを示すスクリーン・ショット。画面の左上には宣言、右上にはテストを実行するためのコントロール・パネル、下部には実行中のアプリケーションが表示されます。 

目下、このアプリケーションには誰もがアクセスすることができ、Speaker と Talk の作成、更新、削除をすることができます。リアルタイムのアプリケーションには、誰がどの操作を実行できるかに関するセキュリティーが適用されます。

Web アプリケーションをセキュアにする

Roo は、Spring Security を使用して、わずか 1 行でアプリケーションにセキュリティーを追加します。Spring Security は、極めて柔軟にカスタマイズできる、強力な認証およびアクセス制御フレームワークであり、Spring ベースのアプリケーションをセキュアにするためのフレームワークとしては、Spring Security がデファクト・スタンダードになっています。

Spring Security を追加する

Spring Security を追加するには、security setup コマンドを実行します。

このコマンドは、必要なすべての Spring Security JAR を追加して、アプリケーションの基本セキュリティーをセットアップします。このコマンドは他にもファイルを作成しますが、その中で重要なファイルは、applicationContext-security.xml です。このファイルには、セキュリティーに関するすべての Bean 定義が含まれます。applicationContext-security.xml は、リスト 2 のような内容です。読みやすくするために、このリストではハッシュ化されたパスワードをドットに置き換えてあります。


リスト 2. applicationContext-security.xml の内容
<beans:beans xmlns="http://www.springframework.org/schema/security"     xmlns:beans="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://www.springframework.org/schema/beans \ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd                         http://www.springframework.org/schema/security \ http://www.springframework.org/schema/security/spring-security-3.0.xsd">  	<!-- HTTP security configurations -->     <http auto-config="true" use-expressions="true">     	<form-login login-processing-url="/resources/j_spring_security_check" \ login-page="/login" authentication-failure-url="/login?login_error=t"/>         <logout logout-url="/resources/j_spring_security_logout"/>                  <!-- Configure these elements to secure URIs in your application -->         <intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>         <intercept-url pattern="/member/**" access="isAuthenticated()" />         <intercept-url pattern="/resources/**" access="permitAll" />         <intercept-url pattern="/**" access="permitAll" />     </http>  	<!-- Configure Authentication mechanism -->     <authentication-manager alias="authenticationManager">     	<!-- SHA-256 values can be produced using \ 'echo -n your_desired_password | sha256sum' \ (using normal *nix environments) -->     	<authentication-provider> 	    	<password-encoder hash="sha-256"/> 	        <user-service> 	            <user name="admin" password="..." authorities="ROLE_ADMIN"/> 		        <user name="user" password="..." authorities="ROLE_USER"/> 		    </user-service>     	</authentication-provider> 	</authentication-manager>  </beans:beans>  

Roo が構成するセキュリティーは汎用セキュリティーであるため、サンプル・アプリケーションを参照しているところはありません。Roo はアプリケーションのセットアップまたは構成をすぐに始められるように支援しますが、最終的なアプリケーションをカスタマイズするのは開発者の責任であることを忘れないでください。この例の場合、Roo は単に Spring Security のテンプレートを提供したにすぎないので、私たちは必要に応じてこのテンプレートをカスタマイズすることになります。

Spring Security をカスタマイズする

サンプル・アプリケーションでは、誰でも Speaker を作成することができますが、Talk を作成できるのは Speaker のみです。したがって、applicationContext-security.xml は以下に示すように変更しなければなりません。リスト 3 には、修正が必要な XML の部分だけを抜粋しています。


リスト 3. applicationContext-security.xml の変更
<http auto-config="true" use-expressions="true">     	<form-login login-processing-url="/resources/j_spring_security_check" \ login-page="/login" authentication-failure-url="/login?login_error=t"/>         <logout logout-url="/resources/j_spring_security_logout"/>                  <!-- Configure these elements to secure URIs in your application -->         <intercept-url pattern="/talks/**" access="hasRole('ROLE_USER')"/>         <intercept-url pattern="/speakers/**" access="permitAll" />         <intercept-url pattern="/resources/**" access="permitAll" />         <intercept-url pattern="/**" access="permitAll" /> </http> 

上記では intercept-url を更新して、RULE-USER をロールとして持つユーザーだけが Talk を作成できるようにし、すべてのユーザーが Speaker として自己登録できるようにしています。

Roo が生成した上記の Spring Security は、<user-service> タグ内に構成されたメモリー内認証プロバイダーを使用します。サンプル・アプリケーションは Speaker エンティティーを管理することから、Speaker データを使用するカスタム認証プロバイダーを作成しなければなりません。認証には Speaker の E メールをユーザー名として使用し、Speaker エンティティーにパスワード・フィールドを追加することにします。このパスワードを、認証パスワードとして使用します。

ここでも Roo シェルを使用して、パスワード・フィールドを Speaker エンティティーに追加します。

field string --class ~.domain.Speaker --fieldName password --notNull --sizeMin 6 –sizeMax 10 

上記では、パスワードがヌルでないこと、そしてパスワードの長さは 6 文字から 10 文字でなければならないことも制約として追加しました。

認証パラメーターには E メールとパスワードを使用することから、指定された E メールとパスワードの Speaker を検索できるようにしたいと思います。Spring Roo では、finder add コマンドを使用して、アプリケーションのファインダーを作成することができます。

finder add --finderName findSpeakersByEmailAndPasswordEquals --class ~.domain.Speaker 

エンティティーのすべてのファインダーを調べるには、finder list コマンドを使用します。finder add コマンドは、ファインダーのコードを Speaker_Roo_Finder.aj ファイルに書き込み、ビュー関連のファイルを作成します。これにより、GUI から Speaker を検索することが可能になります。

カスタム AuthenticationProvider を作成する

AbstractUserDetailsAuthenticationProvider クラスはユーザー名/パスワード方式の認証で機能するため、このクラスを継承してカスタム認証プロバイダーを作成します。AbstractUserDetailsAuthenticationProvider を継承するクラスが実装しなければならないのは、additionalAuthenticationChecks および retrieveUser という 2 つの抽象メソッドです。プロバイダーは retrieveUser メソッドを呼び出し、入力された E メールとパスワードを使って Speaker を認証します。データベースで Speaker を検索するには、前の手順で作成したファイダーが使用されます。Speaker が見つかると、その Speaker には GrantedAuthority ROLE_USER が割り当てられます。このメソッドは最後に、ログインに成功した場合はデータを設定した UserDetails オブジェクトを返し、ログインに失敗した場合には、適切なメッセージと一緒に BadCredentialsException をスローします (リスト 4 を参照)。


リスト 4. カスタム認証
package com.dw.roo.conference.security;  import java.util.ArrayList; import java.util.List;  import javax.persistence.EntityNotFoundException; import javax.persistence.NonUniqueResultException;  import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.\ dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.StringUtils;  import com.dw.roo.conference.domain.Speaker;  public class ConferenceAuthenticationProvider extends \ AbstractUserDetailsAuthenticationProvider {      @Override     protected void additionalAuthenticationChecks(UserDetails userDetails, \ UsernamePasswordAuthenticationToken authentication)             throws AuthenticationException {         // TODO Auto-generated method stub      }      @Override     protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken \ authentication) throws AuthenticationException {         String password = (String) authentication.getCredentials();         if (!StringUtils.hasText(password)) {             throw new BadCredentialsException("Please enter password");         }         List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();         try {             Speaker speaker = Speaker.findSpeakersByEmailAndPasswordEquals(username, \ password).getSingleResult();             authorities.add(new GrantedAuthorityImpl("ROLE_USER"));         } catch (EmptyResultDataAccessException e) {             throw new BadCredentialsException("Invalid username or password");         } catch (EntityNotFoundException e) {             throw new BadCredentialsException("Invalid user");         } catch (NonUniqueResultException e) {             throw new BadCredentialsException("Non-unique user, contact administrator");         }         return new User(username, password, true, // enabled                 true, // account not expired                 true, // credentials not expired                 true, // account not locked                 authorities);     } } 

applicationContext-security.xml で、conferenceAuthenticationProvider Bean を定義し、Roo によって生成されたメモリー内認証プロバイダーを conferenceAuthenticationProvider に置き換える必要があります (リスト 5 を参照)。


リスト 5. conferenceAuthenticationProvider
<beans:bean name="conferenceAuthenticationProvider" class="com.dw.roo.conference.security.ConferenceAuthenticationProvider"> </beans:bean>  <!-- Configure Authentication mechanism --> <authentication-manager alias="authenticationManager"> 	<authentication-provider ref="conferenceAuthenticationProvider"/> </authentication-manager> 

ここでサーバーを起動して、Talk を作成してみてください。表示されたログイン画面には、作成済み Speaker の E メールとパスワードを入力してください。このカスタム認証プロバイダーを作成した目的は、理想的な認証プロバイダーを作成することではなく、Roo が行ってくれる作業と、開発者が自分で行わなければならない作業を説明するためです。

E メール通知

このサンプル・アプリケーションでは、Speaker が Talk を作成した場合に E メールが受信されるようにしたいので、今度は E メール・サポートをアプリケーションに追加します。作業の焦点を Roo によって E メールを送信する部分に絞るため、SMTP サーバーには Gmail を使用します。アプリケーションに E メール・サポートを追加するには、以下のコマンドを使用します。

email sender setup --hostServer smtp.gmail.com --username \ <Your email address> --password <Your email password> --port 587 --protocol SMTP 

email sender コマンドによって、プロジェクトに Spring JavaMailSender がインストールされます。E メール関連のプロパティーは、email.properties ファイルで変更することができます。

Talk の作成後に E メールを送信するには、TalkController に E メール・フィールドを追加しなければなりません。E メール・フィールドを追加するには以下のコマンドを実行します。

field email template --class ~.web.TalkController

上記のコマンドによって、MailSender テンプレートと sendMessage メソッドが TalkController に追加されます。今度は、Talk がデータベースに維持された後に sendMessage メソッドがトリガーされるようにしなければなりません。TalkController のコードはすべて TalkController_Roo_Controller.aj ファイルに含まれているため、このタスクを実現する最も簡単な方法は、この .aj ファイルのTalkController クラスに encodeUrlPathSegment メソッドを作成し、さらに create メソッドの talk.persist() 行の後に sendMessageメソッドの呼び出しを追加することです (リスト 6 を参照)。


リスト 6. .aj ファイルの TalkController クラスに encodeUrlPathSegment メソッドを作成する
public class TalkController {  	@Autowired 	private transient MailSender mailTemplate;  	public void sendMessage(String mailFrom, String subject, String mailTo, 			String message) { 		org.springframework.mail.SimpleMailMessage \ simpleMailMessage = new org.springframework.mail.SimpleMailMessage(); 		simpleMailMessage.setFrom(mailFrom); 		simpleMailMessage.setSubject(subject); 		simpleMailMessage.setTo(mailTo); 		simpleMailMessage.setText(message); 		mailTemplate.send(simpleMailMessage); 	}  	@RequestMapping(method = RequestMethod.POST) 	public String create(@Valid Talk talk, BindingResult result, Model model, 			HttpServletRequest request) { 		if (result.hasErrors()) { 			model.addAttribute("talk", talk); 			return "talks/create"; 		} 		talk.persist(); 		sendMessage("spring.roo.playground@gmail.com", "Your talk is created", 				talk.getSpeaker().getEmail(), \ "Congrats your talk is created"); 		return "redirect:/talks/" 				+ encodeUrlPathSegment(talk.getId().toString(), request); 	}  	private String encodeUrlPathSegment(String pathSegment, 			HttpServletRequest request) { 		String enc = request.getCharacterEncoding(); 		if (enc == null) { 			enc = WebUtils.DEFAULT_CHARACTER_ENCODING; 		} 		try { 			pathSegment = UriUtils.encodePathSegment(pathSegment, enc); 		} catch (UnsupportedEncodingException uee) { 		} 		return pathSegment; 	} } 

これにより、Talk が作成された後は、Speaker の指定アカウントに E メールが届くようになります。

国際化サポート

私たちが作成しているのは、インターネット・ベースの Web アプリケーションです。したがって、各地のユーザーがこのアプリケーションを利用できるように、さまざまな言語をサポートすることが重要になります。Spring Roo では、web mvc install language コマンドを使用してアプリケーションに新しい言語をインストールすることによって、国際化サポートを追加します。以下に例として、スペイン語とイタリア語をインストールする場合のコマンドを記載します。

web mvc install language --code es  web mvc install language --code it 

Roo が現在サポートしているのは 6 カ国語です。その他の言語については、アドオン言語を作成できるようになっています。上記のコマンドを実行した後にアプリケーションを実行すると、英国国旗の他に 2 つの国旗 (イタリアとスペインの国旗) が表示されます。これらの国旗のいずれかをクリックすると、Web アプリケーションはその国旗に対応する言語で表示されます。

Web アプリケーションのソーシャル化

今や、ソーシャル・メディアの時代です。現在のアプリケーションには当たり前のように、ソーシャル機能が追加されています。このサンプル・アプリケーションのソーシャル機能としては、講演の様子の動画を追加するのが妥当でしょう。Roo では、YouTube、Vimeo、Viddler、Google Video などにアップロードされた動画を組み込めるようになっています。動画を組み込むには、以下のコマンドを使用します。

web mvc embed video --provider VIMEO --videoId 16069687 

コマンドを実行した後、サーバーを起動してブラウザーでアプリケーションを立ち上げると、上記のコマンドで組み込まれた動画を見ることができるようなります。同じようにして、YouTube や Viddler の動画を追加することもできます。

Roo には、Twitter のメッセージ、文書、証券コード、地図、写真、ビデオ・ストリームをアプリケーションに組み込むオプションも用意されています。これらのコマンドをリスト 7 に記載します。


リスト 7. embed コマンド
web mvc embed document  web mvc embed finances  web mvc embed map web mvc embed photos  web mvc embed stream video  web mvc embed twitter web mvc embed video  

データベース・リバース・エンジニアリング

データベース・リバース・エンジニアリング (DBRE) によって、既存のデータベースをイントロスペクトし、アプリケーションとして公開することができます。DBRE の仕組みを説明するため、これから既存のフィードバック・スキーマからフィードバック・アプリケーションを作成します。データベースとして使用するのは MySQL です。

Roo を起動する前に、MySQL のインストール済み環境にスキーマを作成する必要があります。リスト 8 に記載する SQL スクリプトを実行すると、MySQL データベースにフィードバック・スキーマが作成されます。


リスト 8. フィードバック・スキーマを作成するための SQL スクリプト
create database feedback_schema; use feedback_schema; CREATE TABLE feedback (   id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,   TalkTitle VARCHAR(45) NOT NULL,   SpeakerName VARCHAR(45) NOT NULL,   Feedback VARCHAR(4000) NOT NULL,   PRIMARY KEY (id) ) ENGINE = InnoDB; 

これは、テーブルが 1 つだけあり、リレーションは何も含まれていない単純なスキーマとなっていますが、複数のテーブルとリレーションを使用する複雑なスキーマでも、Roo では問題なくリバース・エンジニアリングを行うことができます。

上記の SQL スクリプトを実行してスキーマを生成した後は、以下の手順に従ってフィードバック・アプリケーションを作成します。

  1. feedback という名前のディレクトリーを作成します。
  2. オペレーティング・システムのコマンドラインから、feedback ディレクトリーに移動します。
  3. roo コマンドを実行して Roo シェルを開きます。
  4. project --topLevelPackage com.dw.roo.feedback と入力して、新規 Maven プロジェクトを作成します。
  5. このアプリケーションでは、MySQL をデータベースとして使用します。以下のコマンドを使用して、アプリケーションに対して永続化のセットアップを行います。
    persistence setup --provider HIBERNATE  --database MYSQL --databaseName feedback_schema    --userName root --password password

    ここでは root ユーザーとしてスキーマを作成したので、上記ではユーザー名として root を使用していますが、皆さんがこのコマンドを実行するときには、スキーマを作成するために使用したユーザー名とパスワードを入力してください。このコマンドは、パーシスタンスに必要なすべての JAR も追加します。

  6. データベース・スキーマをイントロスペクトするには、database introspect --schema feedback_schema を使用します。database introspect コマンドは、データベース・スキーマに関連するメタデータを表示します。この例で使用するコマンドはスキーマのメタデータを Roo シェル・コンソールに表示しますが、--file 属性を使用してメタデータ XML をファイルにエクスポートすることもできます。
  7. データベース・スキーマのイントロスペクションが完了したら、今度はスキーマのリバース・エンジニアリングを行うために、database reverse engineer --schema feedback_schema --package ~.domain を実行します。

    database reverse engineer コマンドには、schema と package という 2 つの必須属性があります。前者はリバース・エンジニアリング対象のスキーマの名前を指定する属性、後者は Roo がソースを生成する先となるパッケージを指定する属性です。ここでは、すべてのエンティティーを com.dw.roo.feedback.domain パッケージ内に作成します。

  8. 次のステップでは、アプリケーションのコントローラーを生成します。それには、controller all --package ~.web を実行します。
  9. アプリケーションを実行する前に、persistence.xml に含まれるプロパティーを多少変更する必要があります。hibernate.ejb.naming_strategy プロパティーが使用している ImprovedNamingStrategy は、MySQL databasesデータベースには機能しないため、mvn clean install tomcat:run を実行すると、例外を受け取ることになります。これを機能させるには、以下のように、hiberate.ejb.naming_strategy を DefaultNamingStrategy に変更してください。
    <property name="hibernate.ejb.naming_strategy"  value="org.hibernate.cfg.DefaultNamingStrategy"/>

  10. これで、Maven コマンド mvn clean install tomcat:run を使用して、フィードバック・アプリケーションを実行できるようになります。

カンファレンス・アプリケーションとフィードバック・アプリケーションのソース・コードはダウンロードすることができます (「ダウンロード」を参照)。

まとめ

これまでの作業で、単純な CRUD ベースの Web アプリケーションは本格的なエンタープライズ・アプリケーションに拡張されました。この手順をとおして、Selenium テスト機能や Spring Security、そして国際化サポートなどの機能を、いかに簡単に追加できるかがわかったはずです。この記事ではまた、Spring Roo のデータベース・リバース・エンジニアリング機能を使って、既存のデータベースからアプリケーションを作成する方法も説明しました。Roo で簡単にアプリケーションに追加できる機能は、JMS、Solr、JSON サポートをはじめ、まだまだたくさんあります。

第 3 回では、このカンファレンス・アプリケーションを Google App Engine にポーティングする方法を説明します。

0 件のコメント:

コメントを投稿