BT

Ready for InfoQ 3.0? Try the new design and let us know what you think!

ケーススタディ: CAS Software AGにおけるEclipse Rich Ajax Platform

| 作者 Craig Wickesser フォローする 0 人のフォロワー , 翻訳者 近藤 寛喜 - (株)チェンジビジョン フォローする 0 人のフォロワー 投稿日 2009年5月6日. 推定読書時間: 25 分 |

原文(投稿日:2009/2/1)へのリンク

イントロダクション

CAS Software AG は1986年に設立されたドイツのカルルスルーエにある企業です。彼らはCRM(顧客関係管理)の領域についての専門家であり、特に中小企業を対象とした分野に特化しています。CAS Softwareでは長年にわたりCRM市場において、教育や自動車販売、メンバーを基にした組織や協会などのためのCRM製品を提供することで、認識されるようになりました。

最近におけるCAS Softwareが手がけた製品は、CAS PIA(PIA:個人のための情報アシスタント)と名づけられた製品で、CASからは初めてSaaSで提供される製品です。CAS PIAはいくつかの技術から成り立っています。後ほど詳細に述べていきましょう。このケーススタディではEclipse RAP(Rich Ajax Platform)がCAS PIAのアーキテクチャにどう適用されているかに焦点を当てます。CASがRAPを使う中で学んだ興味深い使い方を幾つか紹介します。そして彼らの製品の今後の方向性についても焦点を当てていきます。

ドメイン

CAS Software AG はフリーランサーやS中小企業のためのCRM(顧客関係管理)ソフトウェアの開発を行っています。読者のみなさまの多くはCASが現在ターゲットとしているCRMに特化したツールを使ったことのない方が多いのではないでしょうか。そうではなく、マーケティングや顧客とのやりとりを記録するためにツールを組み合わせている方が多いのではないでしょうか。しかしCASは、顧客の情報を中心に置き、簡単にアクセスできる事で、顧客情報を管理しやすいソフトウェアがとても求められていると信じています。既に特定の技術の上で動作するデスクトップアプリケーションはありましたが、CRMとしては非常に狭い領域の機能だけに限られている傾向がありました。Eclipse RAPやEclipse Equinoxといった技術を利用することでCAS PIAは標準的なCRMの機能だけではなく、またマーケティングのキャンペーンやe-mailのマージ、個人宛のダイレクトメールなどをモジュールとして整理、区分けし、パッケージとして提供しています。

Eclipse RAPを用いて作成する意味はユーザーインターフェースのためです。CAS PIAはユーザーが快適に使えるよう、デスクトップのような使用感を提供します。ユーザーインターフェースは認知人間工学に基づきデザインされ、よく知られたウィジェットで構成されています。ドラッグ&ドロップのようなWebアプリケーションでも期待されるような振る舞いも実現されています。加えて、オンラインで有効な機能としてすべての同僚に対し、ビジネスの範囲内においてアポイントメントやタスク、ドキュメントの管理を許可できます。それと同じくらい重要な機能であるワークフローやアドレスの移行やルート選定など提供されます。以下の節では、私たちはCAS PIAが開発したアーキテクチャを探り、オープンソースがどのように極めて重要な役割として使われているかを述べます。

ソリューションの概要

CRMのソリューションはデスクトップでもオンラインでもいくつもあり、最近は機能もリッチな物が増えてきました。ユーザーはレポート機能やセキュリティ、ルックアンドフィールなど素早く反応するユーザーインターフェースなど、いくつかの機能や特性などにおいて基本的なもの以上のものを期待してます。CRMのソリューションとしてユーザーから期待されたたくさんの機能や要求のために、CASは基盤となる環境として、オープンソースソフトウェアを採用する事を選びました。

CAS PIAはWebアプリケーションとして負荷を分散し、冗長性を提供するために同じような構成のサーバーを利用することで、簡単にスケールできるように設計されています。どのサーバーもアプリケーションサーバーとしてApache Tomcatのインスタンス上で動作し、RAPベースのアプリケーションレイヤーとサーバーのコアを含んでいます。アプリケーションレイヤーはユーザーインターフェースを表示したり、ユーザーからの入力への反応の責務を負います。対してサーバーのコアが供給するのはデータベースアクセスや、ビジネス機能を供給します。

CASがWebベースのアプリケーションに取り組んだのは、顧客がインストールや設定にはまる事を避け、ハードウェアのコストを改善し、顧客がデータのセキュリティを気にせずともよいようにするために選びました。そしてCAS PIAがWebベースにしたアプローチは、ユーザーにとってはどこからでもアクセスすることを可能にし、シッククライアントのようなコンピューターにインストールせずに使える事を実現しています。Webアプリケーションのフロントエンドとして、プレゼンテーションレイヤーにはEclipse RAPを用いています。RAPプロジェクトのページではこのフレームワークが開発者にとって下記の利点があると説明しています。:

Ajaxを使ったWebアプリケーションをEclipseの開発モデルを使うことによって、プラグインと良く知られたワークベンチの拡張ポイント、そしてSWTとJFaceなどのウィジェットツールキットなどにより、よりリッチに構築できます。RAPはEclipse RCPととてもよく似ています。しかし、Eclipse RCPはデスクトップで動作しますが、RAPはサーバーで動作し、クライアントからは普通のブラウザでアプリケーションにアクセスすることができます。これはSWTを特別に実装したサブセット版を用いることで実現しています。(http://www.eclipse.org/rap/about.php) (リンク)

下記の図はEclipse RCPとEcplipse RAPにおけるアーキテクチャの違いを簡単にまとめたものです。

CAS PIAのユーザーインターフェースとしてEclipse RAPがプレゼンテーションレイヤーに用いられているいくつかの理由を示しました。:

  • ルックアンドフィール - とてもリッチで人間工学的に優れ、テーマとして見栄えがまとまったインターフェースで構築することができます。それによりシッククライアントと見分けるのを難しくしています。
  • 本質的な開発 - あらかじめ用意されたコンポーネントを使うことで開発者から見てAJAXとJavaScriptを隠すことができます。そして開発者はよく知っているIDEやライブラリ、Javaで開発できます。
  • 柔軟性 - RAPは開発者がJavaScriptやHTML、CSSを書くことから離れることもできるとは言え、カスタムコンポーネントやスタイルを加えることで十分な柔軟性があるので、どんな問題からも逃れることができます。
  • 工学的な品質 - EclipseやEclipseの製品はソフトウェアのデザインとして優れた実践を続けています。これはRAPも例外ではなありません。
  • 単一のコードベース - RAPはRCPアプリケーションとして、またAJAXアプリケーションとしてコンパイルできます。

プレゼンテーションレイヤーがOSGiランタイムを内包することはモジュール性を提供し、他のCASの製品と共有するための再利用性を高めます。CASは彼らの実装をOSGiコンテナに載せるためにEclipse Equinox(リンク)プロジェクトを選択しました。Equinoxとは:

...OSGi R4のコアフレームワーク仕様(リンク)に基づいた実装とOSGiの補助サービスを実装した様々なバンドルが提供され、他のOSGiベースのシステムのインフラとなっています。

...OSGi R4のコアフレームワーク仕様に基づいた実装とOSGiの補助サービスを実装した様々なバンドルが提供され、他のOSGiベースのシステムのインフラとなっています。

Equinoxによるコンポーネントの分離の利点は、CASがコアモジュールを複数のバンドルで成り立つように実装することで、様々なアプリケーションから利用できる一般的なコンポーネントとすることが出来る点です。それぞれのモジュールが提供する様々な拡張ポイントは、開発するアプリケーションに具体的な振る舞いを提供します。例えばユーザー管理のモジュールは、たくさんのアプリケーションで使うことが出来ますが、コンタクト管理モジュールはよりCRMに関連するアプリケーションにより特化するでしょう。OSGiによって提供される柔軟さは、ビルド中やデプロイする段階でモジュールを拡張したり、まとめたり、逆に除いたりすることを簡単にします。

一方CAS PIAにはビジネスロジックがあり、一般的なサーバー側と同じようにサーバーのコアとしてみなされる機能やEIM(企業情報管理)に問合せたりしています。このEIMはCASによって開発されてきたすべてのエンタープライズ製品の精神を元に設計、開発されています。サーバー側のコアはSunのJAX-WS、RMIとRESTサービスなど、Spring Frameworkを使う事で拡張できるすべてのリモートアクセスを提供しています。

永続化レイヤーにはMySQLが使われていますが、CASによってカスタマイズされたコンポーネントも含まれています。このカスタムコンポーネントは拡張可能なデータモデルと、CAS-SQLとして定義したクエリー言語、権限管理のコンポーネントを提供します。権限管理のコンポーネントはACEGIフレームワークによってユーザー認証と、企業認証サービスを実現されています。しかしACL(アクセスコントロールリスト)はすべてのオブジェクトに対してデータベースレイヤーで提供されています。このカスタム権限システムはOracleのOLS(Oracleラベルセキュリティ)とよくにていますが、CASはMySQL以外のデータベースや他の製品でも使えるように考慮されています。

Eclipse RAPにおけるユニットテスト

ユニットテストはどんなソフトウェア開発の取り組みとして、またソフトウェアのクライアントにとっても重要な関心事です。どんなときも、開発者がソフトウェアアプリケーションのユーザーインターフェースの部分のユニットテストをするのは難しいと感じています。典型的な例として、これはプレゼンテーションとアプリケーションロジックの調和がユニットテストを複雑にし、開発とメンテナンスを難しくしているためです。CASの開発者はサーバー上にあるたくさんのロジックを可能な限りユニットテスティングに取り組むことでより堅牢にしてきました。しかしながら、サーバーにあるすべてをテストすることはできず、堅固なUIデザインが重要な要因となっています。

MVCやプレゼンテーションモデル、モデルビュープレゼンターのような一般的なデザインパターンを用いてユーザーインターフェースを実装することで、ビューはロジックから分離され、ユニットテストを簡単なタスクに変えることができるでしょう。しっかりした設計を、直面している問題に対して加えていくことは、RAPのユーザーインターフェースをテストするのと同等です。第一に、RAPのUIコンポーネントはJavaのレイヤーとJavaScriptレイヤーから成り立ちます。それは2つの分野におけるテストがあることを意味します。Qooxdoo(リンク)という、RAPが使っているAjaxアプリケーションフレームワークでは、JUnitとJSUnitに似た機能性を持つユニットテスティングツールを提供しています。CASはCASが作成したカスタムコンポーネントのJavaScriptレイヤーのテストにはこれらQooxdooのツールを、そしてJavaレイヤーのテストにはJUnitを使うことが有利と考えています。CASによって提供されるカスタムコンポーネントの、JavaScriptレイヤーにおけるユニットテストの例を下記に示します。:

*** 文中のコメントも訳しました ***

/**
* メモリリークのテスト.
*        * オブジェクトの作成と廃棄、インスタンスにメモリリークが起きていないか、確認します。
*
* @type member
* @return {void}
*/
testMemoryLeak : function() {

  var ms1 = de.tests.MemoryLeakUtil.getMemorySnapshot();
  // create
  var dc = new de.cas.qx.ui.widget.calendar.datechooser.DateChooser();

  qx.ui.core.Widget.flushGlobalQueues();

  // dispose
  dc.dispose();
  var ms2 = de.tests.MemoryLeakUtil.getMemorySnapshot();

  var msg = de.tests.MemoryLeakUtil.checkMemoryLeak(ms1, ms2);
  this.assertEquals("", msg, "There are some leaking objects!");
},

このテストの例はCASによって開発されたカスタムコンポーネントの一つである、DateChooserのテストです。DateChooserを使うことでメモリーリークが潜在的に引き起こされていないか、探索します。MemoryLeakUtilクラスはCASによって作成されたカスタムユーティリティークラスです。このクラスはQooxdooによって提供された、例えばメモリ中のすべてのオブジェクトを一覧として提供されるようないくつかの関数を用いて作成されました。Qooxdooが提供する機能を使うことで、とても一般的なカスタムJavaScriptコンポーネントの開発の中でもメモリリークによるエラーがないかを簡単にテストできます。二つ目に直面した問題はRAPのユーザインターフェースをテストする時における非同期のハンドリングと、UIの動的な状態遷移です。アプリケーションを使う事でユーザーの操作を記録したり、スクリプトとして保存することで、何度も繰り替えして動作することのできるいくつかのツールがあります。この種類のテストは振る舞いを観察するのを助け、そして本当にユーザーが操作するようにユーザーインターフェースと対話することが出来ます。しかし制限がある傾向があります。彼らは対話できるか焦点を絞ってWebアプリケーションのテスティングツールを簡単にテストしましたが、非同期を扱い、またページ遷移をもたないユーザーインターフェースを扱うことの出来るものを見つけられませんでした。Ajaxベースのアプリケーションではページ遷移せずに内容が動的に読み込まれる事を追記しておきます。

ある領域のユニットテスティングでは、時々サーバーとの通信とデータレイヤー両方、もしくはどちらかに問題がありました。しばしばサーバーとデータベースなど、直接やりとりして実行するような、テストのためにサーバーやデータベースを所有するユニットテストが書かれます。ほとんどの場合この種類のテストでは、遅さに悩まされるというCASの開発者が経験してきた大きな欠点があります。一般的な解としてユニットテストの間に"本物の代役"として本質的に変わらないモックオブジェクトを使う方法があります。Javaのモックオブジェクトのフレームワークには様々な物があります。Mockito(リンク)、EasyMock(リンク)、そしてJMock(リンク) などモックオブジェクトを簡単に作る事ができます。JavaScriptにもJSMock(リンク)とMock4JS(リンク)というモックオブジェクトのフレームワークがあります。

前にカスタムコンポーネントのJavaScriptレイヤーのテストの例を上げましたが、ここでJavaレイヤーの例をご覧ください。Eclipse RAP 1.1現在ではOSGi環境で実行することが要求される本質的にJUnitテストと同じフレームワークを含んでいます。もしテストの実行中にUIを更新するようなテストが必要な場合は、シンプルにorg.eclipse.rap.junit.RAPTestCaseを継承によってできます。しかしながら、ユーザーインターフェースの描画処理を必要としないユニットテストを実行したい場合は、シンプルにJUnitのデファクトであるorg.junit.TestCaseを継承によって実行できます。下記に示すのは、UIとの対話を含んだRAPのテストケースの例です。:

public class RapJUnitTest extends RAPTestCase {
   public void testOpenView() {
     try {
       IWorkbenchPage page = getPage();
       page.showView( "org.eclipse.rap.demo.DemoTreeViewPartI" );
     } catch( PartInitException e ) {
       e.printStackTrace();
     }
     assertEquals( 1, getPage().getViewReferences().length );

     getPage().hideView( getPage().getViewReferences()[ 0 ] );
     assertEquals( 0, getPage().getViewReferences().length );
   }

   private IWorkbenchPage getPage() {
     IWorkbench workbench = PlatformUI.getWorkbench();
     IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
     return window.getActivePage();
   }
} 

この例では実際にUIコンポーネントを使い、表示されているビューの数を検証しています。この例ではRAPベースのアプリケーションのユニットテストの正しい手順を示していますが、ユーザーからの入力を自動化するようなものを含んでいません。ユーザーがボタンを押したり、テキストフィールドに値を入力するような、UIのテストするため、他のよりよいツールを求めてCASは近い将来投資する予定です。

カスタムユーザーインターフェースコンポーネント

RAP Widget ToolkitやRWTと呼ばれる、RAPの形にとらわれずに、SWTで実装されるような、様々なアプリケーションを満足させる多くのコンポーネントのサブセットが提供されています。しかしながら、必要となればカスタムコンポーネントの使用や開発の上でいくつかのEclipse RAPの能力を越えたサポートが必要となります。カスタムコンポーネントの開発で最初によく考えなければならないことは、いくつかを組み合わせる"compound"型にするのか、それともすべて開発する"owner drawn"型にするのか、RAPには二つのはっきりと異なるコンポーネントの実装方法の型があります。"compound"型のコンポーネントはRAPが用意しているコンポーネントを組み合わせ、新たな振る舞いや機能をUIに実装するだけです。一方、"owner drawn"型のコンポーネントは、全てJavaScriptを使ってコンポーネントを作成します。実装には一般的にたくさんの処理を書く必要があったり、いくつかのサードパーティ製ライブラリを使います。RAP開発者ガイドでは"owner drawn"型のカスタムコンポーネントを作成する場合に参考となる、全ての手順をスクリーンショットやコードサンプルで概説したステップ・バイ・ステップ形式のチュートリアルが提供されています。チュートリアルではカスタムコンポーネントの開発手順として4つのメインステップが言及されています。:

  • サーバーで実行される、コンポーネントのJava実装の作成
  • ブラウザーで実行される、コンポーネントのJavaScript実装の作成
  • RAPのライフサイクルによってJavaコンポーネントとJavaScriptのコンポーネントを結びつける、アダプタのJava実装の作成
  • org.eclipse.rap.ui.resources拡張ポイントを使ったJavaScriptファイルの追加

CASの開発者はカレンダーや日付選択、ツールバーやMicrosoft Outlookで見られるものとよく似たアコーディオンナビゲーションコントロールなど、いくつかのカスタムコンポーネントを作成してきました。CAS PIAで使われているカレンダーやツールバーはJavaScriptとCSS,HTMLを組み合わせてRAPのコンポーネントに変換された、コンポーネント作成者が全て実装する"owner drawn"型の例です。Qooxdooは豊富な機能と、簡単な手順によってコンポーネントを開発できる抽象的な枠組みをを提供しています。下記のスクリーンショットは"owner drawn"型で作られたカレンダーとツールバーです。

このカレンダーコンポーネントにはたくさんの機能があり、例えば左上のハンドルではミニカレンダーをナビゲータとして表示し、真ん中のハンドルではカレンダーの詳細をナビゲータとして表示します。ここからアポイントメントを追加することや、全てのアポイント等、カスタムビューによるフィルタリングをすることもできます。この特殊なコンポーネントは2万行前後のコード、たくさんの時間、実装の努力により作成されています。その他、上記のスクリーンショットで示した"owner drawn"型のコンポーネントであるツールバーですが、最近のMicrosoft OfficeやMicrosoft Outlookの中でよく見られる機能が提供されています。

いくつかのコンポーネントが組み合わされた"compound"型のコンポーネントの例として、CAS PIAの中でも使われている時間選択のスクリーンショットを下記に示します。

この"compound"型のコンポーネントはダイアログ(リンク)やボタン(リンク)、コンボ(リンク)などいくつかのコンポーネントを使って一緒に動作するように時間選択のコンポーネントを作成されています。CAS PIAのスクリーンショットの中から注目に当たるもう一方の例は、アプリケーション全体のスタイルやテーマです。RAPではテーマをカスケーティング・スタイル・シート(CSS)と、さらにorg.eclipse.rap.ui.themes拡張ポイントをアプリケーション中のplugin.xmlに記述することで提供します。

カスタムRAPコンポーネントを開発する時は、開発フェーズやデザインについてなど、常に考慮すべき点がいくつかあります。一つ目はHTMLやJavaScript,CSSとQooxdooについて、カスタムコンポーネントを開発するために必要な知識を得るための、開発者にとってはちょっとした学習曲線があります。私は開発者がJavaで書くことができ、カスタムコンポーネントを書く以外、JavaScriptを書くことを避けられることから、これはRAPの価値となると言及しました。二つ目は、開発者は何をするにしてもいくつかのブラウザー上で機能するコンポーネントを提供することが要求されています。RAPに含まれるコアコンポーネントを作成した開発者はブラウザ互換を確かなものとするため、どんなことでもしてきました。そしてQooxdooの未来のリリースでは、コンポーネント開発者からブラウザ互換問題から開放するための重要な強化が期待されています。最後に、意識しなければならない点としてRAPが含んでいるQooxdooのバージョンとQooxdooが提供しているWebサイトからダウンロードしたバージョンが同じではないことです。RAPの開発者は要求された機能を提供し、フレームワークサイズを縮小するためにQooxdooのサブセットだけを含むことを選びました。これは結局RAP開発者のためにQooxdooのAPIのなかから機能、クラスを制限したように見えます。

開発中に見つかった問題

どの技術を使うにしても、開発者にとって新しいものには壁があり、学んでいく途上には問題もあります。それは開発者にとって学習曲線となります。CAS PIAの開発中にも開発チームはパフォーマンスやデプロイに関連したいくつかの問題にぶつかりました。

最初に直面した問題は彼らはパフォーマンスが出ず、またリソースの消費量が高くなるという問題です。これはクライアント、サーバー側共に発生しました。CASはオブジェクトを貯めておくように実装することで可能な限り改善し、GUIコントロールもできる限り作成せずキャッシュを有効に再利用することを繰り替えしてきました。これらCASが自ら行った解決策により、十分にパフォーマンスとリソース消費に関する問題を取り除いていきましたが、Internet Explorerではパフォーマンス問題を十分に満足した形で取り除くことが出来なかったため、CAS PIAはInternet Explorerをサポートをしていません。CASは近い将来Qooxdoo frameworkを改善する自信があり、Internet Explorer の次期リリースであるIE8によって妥当なパフォーマンスが提供される事で、CASはIEのサポートができるとしています。一方、他のブラウザ、とりわけFirefoxでは、最近になっても改善をし続け、クライアント側で使えるくらいパフォーマンスの向上と、リソース消費の改善されています。

もう一つの問題として、ビルドプロセスを継続的に統合するCIを使ったアプリケーションのRAPコンポーネントのビルドやデプロイにおいて時々問題が発生しました。これはCASがEquinox runtimeの中にweb serverを組み込むのではなく、Eclipse EquinoxをTomcatにデプロイする事を選んだために発生した、ただ一つの問題でした。ビルド時の問題は、CASは自動夜間ビルドのためにEclipseが提供するReleng-Toolsを使っていましたが、彼らはほとんどドキュメントを見つけることができず、そして動的にビルドスクリプトを生成するなど、Antを使う上でのたくさんのマジックを使っていました。たくさんのログを監視し、トライ&エラーを繰り返すことで夜間ビルドを実施できるようにしてきました。

またCASは同時にTomcatの中にEquinoxを導入するための問題に当たっていました。この設定について提案された解決策はすべてのビジネスバンドルと、EquinoxとRAPの実行環境を一つのWARファイルにまとめて生成する事でした。しかし、CAS PIAはEIMもまた必要としていました。これはOSGiではないコンポーネントです。アプリケーションの中のRAPの部分と連携する必要がありました。WebサービスやRMIを使わずに直接通信する必要があったのです。この問題を解決するには2つのステップがあります。最初のステップはEIMのサーバーコンポーネントをWARファイルのlibディレクトリに入れることです。二つめのステップはアプリケーションのweb.xmlにサーブレッドブリッジ(リンク)を解決するための特別な設定を記述します。CASは具体的にはRAPコンポーネントとEIMとの連携に必須である"extendedFrameworkExports"(リンク)パラメータをサーブレッドブリッジに作成しました。

このレッスンで学んだこと

CAS SoftwareはEclipse RAP アプリケーションの開発とデプロイを特に活発に行っています。それと共に彼らがEclipse RAPを使い始め、楽しんで仕事をしてきた中でいくつもの課題を経験してきました。彼らはRAPを使うことによって開発者が素晴らしく生産性を上げることを見つけました。なぜならば第一にJava開発者はEclipse IDEのデバッグサポートやコンポーネントをよく使っているためです。

かつてCASはここまでに言及した課題と、そしてほとんどの場合急勾配な学習曲線を、みな楽しんでRAPと共に仕事してきました。CASがRAPの未来に望むただ一つの機能は、サーバーからクライアントに対して、アクションを起こせるようになることです。これまでCAS PIAは理想的な解決策ではなく、自分たちの解決策を使ってきました。しかしCASはRAPの未来について楽観的に見ています。

今後の方向性

CAS Software AGは現在CRM市場における中小企業向けの製品では主要な位置を占めています。そして彼らは2010年までに全ヨーロッパ市場で主要な位置を占めるように計画しています。CAS PIAは市場を理解し始め、2009年の第一四半期から宣伝する準備が整っています。そのゴールはヨーロッパ市場におけるCRMのSaaSプロバイダーとして20位以内に入ることです。

CASはEclipse RAPを使い、支えることに強く同意しつづけるでしょう。またCASはハンガリーにあるセゲド大学にて2009年2月からRAPのトレーニングコースを開設する予定です。

Additional Links

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

あなたの意見をお聞かせください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする
コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

ディスカッション
BT