Quantcast
Channel: Eclipse RCP, RAP Blog
Viewing all 71 articles
Browse latest View live

Windows 8 は思った以上に使いやすかった

$
0
0
長い間使い続けてきた xp を 8 にアップグレードしました。 ダウンロードサイズは 2GB 程度で、インストールも思いの外簡単でした(クリーンインストールは選択しませんでした)。 私はフリーのアプリや zip 展開型のアプリは _apl フォルダに、そして jdk や eclipse などの開発ツールは _tool フォルダにインストールしています。 まだ全部を試したわけではありませんが、それらの多くはそのまま使うことができました。 一部は設定を変更したり、再インストールが必要なものもあります。 最新バージョンに置き換えるためもあって、再インストールしたソフトは次のようなものです。

  •  jdk 
  • evernote 
  • dropbox 
  • firefox 
  • chrome 
  • mysql 
  • adobereader


かかった時間は、おおむね5時間弱でした。

  • ダウンロードおよびインストール:
    1.5 時間程度 
  • アカウントの設定など:
    15 分程度 
  • _apl, _tool 下のソフトの動作確認(Windows 8 の設定や操作方法の確認もかねる) :
    1 時間程度
  • 上記のアプリのインストール(evernote, dropbox の同期や mysqlの設定などを含む) :
    1 時間程度
  • データを新しい環境に合わせる:
    1 時間程度(これはもっと時間をかけてゆっくりとやりたい) 
  • 開発環境を windows 8 環境に移行する:
    未着手


これまで、ブラウザー、メーラ、テキストエディタ、画像ビューア、ビデオビューアなどは Windows の純正ソフト(インストール済みのソフト)を使ったことがありませんでした。 少し試しただけですが、os との連携機能などを考えると純正ソフトをメインにしても良いかなと思います。 

ドキュメントやアプリのフォルダ構成もこれまでは自分の命名規則を適用してきました。 xp でのドキュメント管理は使いやすいとは言えなかったからです。 最近 osx などの os を使い始めて、自分の規則に os を合わせるのが面倒になったのもありますが、osx や windows 7, 8  などの最新の os はその点がかなり柔軟で使いやすくなっています。そろそろ os のドキュメント管理の構成に自分の規則を統合した方が使いやすくなるかもしれません。 

Window 8 の操作についてはインターネットに使いづらいという意見があります。 私の場合、古いマシンをアップグレードしたので、最初からデスクトップモードで使用するつもりでした。 このモードでの使用感は、 xp と変わらないかそれ以上です。 見た目に関してはそれぞれに好みがあるでしょうが、私自身はすっきりしていて気に入っています。 

多くの純正ソフト(メーラやビューアなど)はメトロモードで提供されているため、デスクトップモードから移動する必要があります。 移動するつもりはなくても画像ファイルをクリックした場合などにメトロモードのフォトアプリに移動してしまいます。 そのためデスクトップモードをメインに使いたいと考えていても、ある程度メトロモードとの行き来が生じて、慣れないうちは戻り方がわからずに戸惑います。 しかしディスプレイの4隅にカーソルを移動すればモード選択が可能になる(基本的に左上=デスクトップ、左下=スタート画面、右の上下=設定など)ので、すぐにデスクトップに戻れます。 Windows ボタンを使えばもっと簡単に「デスクトップ」と「スタート画面」を切り替えられます。 デスクトップに戻れば、使い慣れた xp や 7 の環境になります。 

メトロモードの印象は、iOS や osx の全画面モードに慣れているせいか、それほど違和感はありません。 マウスで疑似的にスワイプ等の操作ができないのが残念です(できるかもしれませんが調べていません)。 まだ eclipse による開発作業を始めていないので、少し不安はありますが、ここまでの印象は、コスト(3300円と5時間の作業時間)以上の成果が得られたと思っています。 window7 もいっしょにアップグレードしようかと考えたぐらいです。

いずれ、eclipse での開発についても報告したいと思います。

Ecliplse 4 アプリケーションの作成 (11) メトロ風スタイル

$
0
0
Windows 8 へアップグレード後の環境整備もほぼ終了したので、Eclipse の動作確認もかねて、メトロ風スタイルで遊んでみました。

BLUEテーマ:



新規プロジェクト作成
Eclipse 4 アプリケーションプロジェクトを作成します。
プロジェクト名: e4.win8style

※「Eclipse 4 アプリケーションの作成(1)」も参考にしてください。 

plugin.xml に以下のようなテーマを追加します。
<extension
point="org.eclipse.e4.ui.css.swt.theme">

<theme
basestylesheeturi="css/BLUE.css"
id="BLUE"
label="テーマBLUE">

</theme>
<theme
basestylesheeturi="css/BROWN.css"
id="BROWN"
label="テーマBROWN">

</theme>
<theme
basestylesheeturi="css/default.css"
id="default"
label="既定のテーマ">

</theme>
<theme
basestylesheeturi="css/GREEN.css"
id="GREEN"
label="テーマGREEN">

</theme>
<theme
basestylesheeturi="css/LIME.css"
id="LIME"
label="テーマLIME">

</theme>
<theme
basestylesheeturi="css/MAGENTA.css"
id="MAGENTA"
label="テーマMAGENTA">

</theme>
<theme
basestylesheeturi="css/ORANGE.css"
id="ORANGE"
label="テーマORANGE">

</theme>
<theme
basestylesheeturi="css/PINK.css"
id="PINK"
label="テーマPINK">

</theme>
<theme
basestylesheeturi="css/PURPLE.css"
id="PURPLE"
label="テーマPURPLE">

</theme>
<theme
basestylesheeturi="css/RED.css"
id="RED"
label="テーマRED">

</theme>
<theme
basestylesheeturi="css/TEAL.css"
id="TEAL"
label="テーマTEAL">

</theme>
</extension>

そして、それぞれのテーマに対応する CSS ファイルを プロジェクトの css フォルダに作成します。
※CSS設定については、「Eclipse 4 アプリケーションの作成(4)」も参考にしてください。
default.css :
.MPart {
background-color: #2b599f#5b99d6100%false;
}
.MPartSashContainer {
background-color: #4b79bf;
}
.MPartLabel {
background-color: #5b99d6#2b599f100%false;
color: #ffffff;
font: 'arial'14px;
font-weight: bold;
}
.MTrimBar {
background-color: #2b599f#5b99d6100%false;
}
BLUE.css :
.MPart {
background-color: #1BA1E2;
}
.MPartSashContainer {
background-color: #1BA1E2;
}
.MPartLabel {
background-color: #1BA1E2;
color: #1BA1E2;
font: 'arial'14px;
font-weight: bold;
}
.MPartComposite {
background-color: #1BA1E2;
}
.MTrimBar {
background-color: #1BA1E2#2bb1f2100%;
}

その他の BROWN, GREEN, LIME, ... の各 CSS では BLUE.css の #1BA1E2, #2bb1f2 の部分がそれぞれ以下のように対応します。

  • BROWN : #A05000, #b06010
  • GREEN : #339933, #43a943
  • LIME    : #8CBF26, #9CAF36
  • MAGENTA : #FF0097, #ff10a7
  • ORANGE   : #F09609, #f1a619
  • PINK    : #E671B8, #f681a8
  • PURPLE : #A200FF, #b210ff
  • RED : #E51400, #f52410
  • TEAL : #00ABA9, #10bbb9



設定ダイアログの作成
テーマを変更するための設定ダイアログを作成します。
publicclass PreferenceDialog extends TitleAreaDialog {

private String fThemeId;
private FieldEditorPreferencePage fPage;
private RadioGroupFieldEditor fThemeIdFld;

/**
* ダイアログの作成.
* @param parentShell
*/

public PreferenceDialog(Shell parentShell) {
super(parentShell);
}

/**
* ダイアログ・コンテンツの作成
* @param parent
*/

@Override
protected Control createDialogArea(Composite parent) {
getShell().setText("Windows 8 スタイル設定");
setTitle("Windows 8 スタイル設定");
setMessage("アプリケーションの実行環境を設定してください");

Composite area = (Composite) super.createDialogArea(parent);
GridLayout layout = (GridLayout) area.getLayout();
layout.marginTop = 10;
layout.marginBottom = 10;
layout.marginLeft = 10;
layout.marginRight = 10;
area.layout();

Preferences pref = ConfigurationScope.INSTANCE.getNode(Activator.PLUGIN_ID);
fThemeId = pref.get("theme.id", "default");

fPage = new FieldEditorPreferencePage(FieldEditorPreferencePage.GRID) {
@Override
publicvoid createControl(Composite parent) {
noDefaultAndApplyButton();
super.createControl(parent);
}

@Override
protectedvoid createFieldEditors() {
fThemeIdFld = new RadioGroupFieldEditor(
"pref.themeid",
"テーマを選択してください",
1, new String[][] {
{ "既定値", "default" },
{ "BLUE", "BLUE" },
{ "BROWN", "BROWN" },
{ "GREEN", "GREEN" },
{ "LIME", "LIME" },
{ "MAGENTA", "MAGENTA" },
{ "ORANGE", "ORANGE" },
{ "PINK", "PINK" },
{ "PURPLE", "PURPLE" },
{ "RED", "RED" },
{ "TEAL", "TEAL" }
},
getFieldEditorParent());
setRadioValue(fThemeId);
addField(fThemeIdFld);
}

@Override
protectedvoid updateApplyButton() {
updateButtons(isValid());
super.updateApplyButton();
}

@Override
publicboolean performOk() {
Composite cp = fThemeIdFld.getRadioBoxControl(getFieldEditorParent());
for (Control c: cp.getChildren()) {
if (c instanceof Button) {
Button b = (Button)c;
if (b.getSelection()) {
fThemeId = b.getData().toString();
break;
}
}
}
returnsuper.performOk();
}

privatevoid setRadioValue(String data) {
Composite cp = fThemeIdFld.getRadioBoxControl(getFieldEditorParent());
for (Control c: cp.getChildren()) {
if (c instanceof Button) {
Button b = (Button)c;
if (b.getData().toString().equals(data)) {
b.setSelection(true);
break;
}
}
}
}

};
fPage.createControl(area);
Control pageControl = fPage.getControl();
pageControl.setLayoutData(new GridData(GridData.FILL_BOTH));
return area;
}

/**
* ボタン・バーの作成
* @param parent
*/

@Override
protectedvoid createButtonsForButtonBar(Composite parent) {
createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
true);
createButton(parent, IDialogConstants.CANCEL_ID,
IDialogConstants.CANCEL_LABEL, false);
updateButtons(fPage.isValid());
}

/** ダイアログの初期サイズ */
@Override
protected Point getInitialSize() {
//int height = PluginConstants.isMac() ? 400 : 350;
int height = 460;
returnnew Point(450, height);
}

@Override
protectedvoid okPressed() {
fPage.performOk();
Preferences pref = ConfigurationScope.INSTANCE.getNode(Activator.PLUGIN_ID);
pref.put("theme.id", fThemeId);
try {
pref.flush();
super.okPressed();
} catch (BackingStoreException e) {
}
}

privatevoid updateButtons(boolean isValid) {
Button okButton = getButton(IDialogConstants.OK_ID);
if (okButton != null) {
okButton.setEnabled(isValid);
}
}

public String getThemeId() {
return fThemeId;
}
}

そして、このダイアログをオープンするハンドラも作成します。 
publicclass OpenPreferenceHandler {
@Execute
publicstaticvoid execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell,
IThemeEngine engine,
IEventBroker broker) {
PreferenceDialog dlg = new PreferenceDialog(shell);
if (dlg.open()==Window.OK) {
String themeId = dlg.getThemeId();
if (engine != null) {
engine.setTheme(themeId,true);
}
}
}
}


ビューの作成

以下の内容の ListView を作成します。
publicclass ListView {
private Table table;

public ListView() {
}

@PostConstruct
publicvoid createControls(Composite parent) {
GridLayout gl_parent = new GridLayout(1, false);
gl_parent.verticalSpacing = 0;
gl_parent.marginWidth = 0;
gl_parent.marginHeight = 0;
gl_parent.horizontalSpacing = 0;
parent.setLayout(gl_parent);

table = new Table(parent, SWT.BORDER | SWT.FULL_SELECTION);
table.setFont(SWTResourceManager.getFont("Meiryo UI", 12, SWT.NORMAL));
table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
table.setHeaderVisible(true);

String[] titles = {"名前 ", "住所"};
for (int i=0; i<titles.length; i++) {
TableColumn column = new TableColumn (table, SWT.NONE);
column.setText (titles [i]);
}
TableItem item = new TableItem (table, SWT.NONE);
item.setText (0, "山本太郎");
item.setText (1, "東京都千代田区");

item = new TableItem (table, SWT.NONE);
item.setText (0, "鈴木花子");
item.setText (1, "東京都渋谷区");
for (int i=0; i<titles.length; i++) {
table.getColumn (i).pack ();
}
}

// ...
}


同様に次のような DetailView を作成します。
publicclass DetailView {

public DetailView() {
}

@PostConstruct
publicvoid createControls(Composite parent) {
GridLayout gd1 = new GridLayout(1, false);
gd1.horizontalSpacing = 0;
gd1.verticalSpacing = 0;
gd1.marginWidth = 0;
gd1.marginHeight = 0;
parent.setLayout(gd1);

Composite composite = new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 0, 0));
}

// ...
}
アプリモデルの修正

Application.e4xmi を開いて、2つのパートを追加して(下図)、上記の ListView、 DetailView とリンクします。 


Window Trim に ToolBar と ToolControl を追加して、ToolBar には Direct Tool Item を追加して、メトロ風のアイコンを設定します(下図)。

設定アイテムには先に作成した、ハンドラクラス(OpenPrefereceHandler) をリンクします。

ウィンドウの表示位置 (5, 5) とサイズ (800, 600) も設定します。

アプリケーションの実行
実行前に、プロダクトの Launching タブの "Program Arguments" に "-clearPersistedState" を設定し、Dependencies タブで必須プラグインを追加しておきます。

アプリを実行して、設定ダイアログを開き(下図)、テーマを変更します。


PINK:

ORANGE:



ビデオプレーヤーアプリにメトロ風スタイルを設定して全画面表示する:
全画面表示でカーソルを左下に移動しても、もちろんスタート画面は表示されませんが、それっぽくは見えます。


Vlcj によるDVD, BLU-RAYの再生

$
0
0
MRL(メディア・リソース・ロケーター)の記述方法
vlcj を使って DVD や Blu-Ray を再生するとき、mrl の書き方がわかりにくかったので、整理してみました。 
  •  Windows の場合:
    • ブルーレイ
      bluray:///D:  (ブルーレイドライブを指定)
      bluray:///E:/movie/TANGLED (外付けHDDの /movie/TANGLED フォルダ)
    • DVD
      dvd:///D: (DVDドライブを指定)
    • ビデオファイル
      file:///C:/video/Britney-Sometimes.divx 
  • Mac の場合:
    • ブルーレイ
      bluray:////Volumes/HDUT/movie/TANGLED (外付けHDDの /movie/TANGLED フォルダ)
    • DVD
      dvd:////Volumes/HDUT/movie/XMEN (外付けHDDの /movie/XMEN フォルダ)
    • ビデオファイル
      file:////Users/user/video/Britney-Sometimes.divx
注意:
vlcj の使い方については以前のポスト「vlcj を使ってビデオプレーヤーを作成する」や「Eclipse 4 アプリケーションの作成 (3) ビデオビューの作成」を参照してください。

メディアを選択するハンドラの作成
上記の記述法を参考にして各メディアに対応する mrl を生成し、ビデオを再生するハンドラの例を以下に示します:
publicclass OpenHandler {

privatestaticfinal String[] VIDEO_FILES = {"mp4", "avi", "wmv", "divx", "flv" };

@Execute
publicvoid execute(
@Named("commandparameter.mediatype") String mediaType,
@Named(IServiceConstants.ACTIVE_PART) final MContribution contribution,
@Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {
String mrl="";
if (mediaType.equals("blulay")) {
DirectoryDialog dialog = new DirectoryDialog(shell);
String path = dialog.open();
if (path!=null && new File(path).isDirectory()) {
if (chekBluRay(path)) {
mrl = "bluray:///" + path;
}
}
} elseif (mediaType.equals("dvd")) {
DirectoryDialog dialog = new DirectoryDialog(shell);
String path = dialog.open();
if (path!=null && new File(path).isDirectory()) {
if (checkDVD(path)) {
mrl = "dvd:///" + path;
}
}
} else {
FileDialog dialog = new FileDialog(shell);
String path = dialog.open();
if (path!=null && new File(path).exists()) {
if (checkVideoFile(path)) {
mrl = "file:///" + path;
}
}
}
if (mrl.length()>0) {
VideoView view = (VideoView)contribution.getObject();
view.play(mrl);
}
}

privateboolean checkVideoFile(String path) {
for (String s: VIDEO_FILES) {
if (path.toLowerCase().endsWith(s)) {
return true;
}
}
return false;
}

privateboolean checkDVD(String path) {
File file = new File(path + "/" + "VIDEO_TS");
if (file.exists() && file.isDirectory()) {
return true;
}
return false;
}

privateboolean chekBluRay(String path) {
File file = new File(path + "/" + "BDMV");
if (file.exists() && file.isDirectory()) {
return true;
}
return false;
}
}

上記ハンドラは、mediaType パラメータを調べて、タイプ別に、mrl を生成します。 アクティブパートから VideoView を取得して、ビューの play メソッドに生成した mrl を渡してビデオを再生します。  選択されたディレクトリやファイルがブルーレイやDVDであるかを確認するために簡単なチェックを行います(ここでは想定されるディレクトリの存在のチェックやファイル拡張子のチェックを行っているだけです)。パラメータを持つコマンド、ハンドラーの作成については「Eclipse 4 アプリケーションの作成 (4) CSSテーマ」を参考にしてください。

注意:
日本語のフォルダやファイル名については詳しく調べていません。

音声、字幕の設定
ブルーレイやDVDの場合、音声や、字幕の設定を行わないと英語音声で字幕のない映画を見ることになります。 私の場合、字幕は必要なので表示できるようにします。 ツールバーに"字幕"項目を作成して、ユーザーがこの項目を選択したとき、vlcj の EmbeddedMediaPlayer が提供する getSpuDescriptions() メソッドにより、字幕トラックの情報を表示します。 Eclipse 4 のアプリモデルで、ToolBar に ToolControl を追加する方法は「Eclipse 4 アプリケーションの作成 (10) ツールバーにコントロールを追加する」を参照してください:
publicclass SetTrackToolItemControl {

private ToolItem fSubtitleItem;
private Menu fMenu;

@Inject
EPartService zPartService;

@PostConstruct
publicvoid createControls(final Composite parent) {
GridLayout gl_parent = new GridLayout(1, false);
gl_parent.verticalSpacing = 0;
gl_parent.marginWidth = 0;
gl_parent.marginHeight = 0;
gl_parent.horizontalSpacing = 0;
parent.setLayout(gl_parent);

final ToolBar toolBar = new ToolBar(parent, SWT.FLAT | SWT.RIGHT);

fSubtitleItem = new ToolItem(toolBar, SWT.PUSH);
fSubtitleItem.setText("字幕");

fSubtitleItem.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
Rectangle rect = fSubtitleItem.getBounds();
Point pt = new Point(rect.x, rect.y + rect.height);
pt = toolBar.toDisplay(pt);
getPopup(parent.getShell()).setLocation(pt.x, pt.y);
getPopup(parent.getShell()).setVisible(true);
}
});
}

private VideoView getVideoView() {
MPart part = zPartService.findPart(VideoView.PART_ID);
return (VideoView)part.getObject();
}


private Menu getPopup(Shell shell) {
fMenu = new Menu(shell, SWT.POP_UP);
List<TrackDescription> trackDescriptions =
getVideoView().getPlayer().getSpuDescriptions();
int index=0;
for (final TrackDescription desc : trackDescriptions) {
MenuItem item = new MenuItem(fMenu, SWT.PUSH);
item.setText(desc.description());
item.setData(new Integer(index));
index++;
item.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
MenuItem item = (MenuItem)e.getSource();
getVideoView().setSpu((Integer)item.getData());
}
});
}
return fMenu;
}
}

ユーザーが選択した字幕トラックを設定するには、EmbeddedMediaPlayer の setSpu  メソッドを使用します。

実行例
字幕メニューの表示:

日本語字幕を選択した状態:

EGit によるソースコード管理 (3) ブランチとマージ

$
0
0
以前のポスト「EGit によるソースコード管理(1) ローカルリポジトリの作成」で書いたように、私の場合、自宅、外出先、自宅、外出先・・・のように作業マシンは違っても、一人で同じプロジェクトを順に修正することが大部分なので、Eclipse での EGit の使い方はほとんど以下のような感じです:
  1. 新規プロジェクトを作成(New-PRJ1)
  2. 作成したプロジェクトからローカルリポジトリを作成(New-RIPO1)
  3. New-PRJ1 → add, commit → New-RIPO1
  4. 公開リポジトリ(Github または Dropbox リポジトリ(ベア))を準備 (PUB-RIPO1)
  5. ローカルリポジトリを公開リポジトリへプッシュ(New-RIPO1 → PUB-RIPO1)
  6. 別のユーザー(マシン)が公開リポジトリからクローンを作成 (PUB-RIPO1 → A-RIPO1, B-RIPO1, ...)
  7. A-RIPO1, B-RIPO1 で作業。公開リポジトリへのプッシュ、プルを繰り返す。
そのため、EGit の操作は add, commit, push, pull だけでもほとんど事が足りるのですが、実験的な機能の追加や、大規模なリファクタリング時に直接 master ブランチを修正するのは危険なので、ブランチとマージもよく使います。今回はEgit でのブランチとマージの使い方を説明します。

task1: 公開リポジトリの作成
すでに公開リポジトリ([Dropbox]/__git/egit/egittest.git) が作成されている状態から始めます。このリポジトリのプロジェクトは、以下のような内容の mainclass.txt を持つだけのものです:
mainclass {
main1m1() {
}
}

task2: (1)公開リポジトリからクローンを作成 (自宅PC)
リポジトリビューを表示して、ビューメニューの "Clone a Repository" を選択して、上述の公開リポジトリからクローンを作成します(クローン作成については「EGit によるソースコード管理(2) Dropbox にリポジトリを構築」を参照してください)。このとき、インポートされたプロジェクト(C:/_egit/egit/egittest/egittest) の内容は、初期公開プロジェクトの完全なコピーです(下図):


※:リモート名
クローン作成時にリモート名を設定できます(デフォルトは origin)。公開リポジトリを Dropbox に作成する場合に、例えばリモート名を dropbox にできます。

task2: (2) 実験用ブランチ sample1 を作成
リポジトリビューの egittest ノードを右クリックして、ポップアップ > Switch To > New Branch を選択します。

表示されたダイアログ(上図)で、ブランチ名に sample1 を入力して終了します。

task2: (3) subclass1.txt を追加
subclass1.txt を追加した後のローカルリポジトリと egittest プロジェクトの状態は以下のようになります。



テストと修正を繰り返して実験的な機能が完成したらコミットします。 コミット後のプロジェクトとローカルリポジトリの状態と履歴は以下のようになります:

task3: (1) master ブランチの修正(ノートPC)
同じ自宅PCで、上記の状態から master ブランチに戻るには、 egittest リポジトリを右クリックして、ポップアップ > Switch To > master を選択すれば良いのですが、この作業は外出用のノートPCで行う事にします。この場合ノートPCのリポジトリビューでビューメニューの "Clone a Repository"  を選択します:
表示されたウィザード(上図)で、URI を選択して、Next を押します:


次のページで、URIとして、上述の公開リポジトリを指定して、Next:
必要なブランチは master だけなので、master だけチェックして(上図)、次のページへ移ります。

次のページではクローンを作成するディレクトリを指定して、オプションの"Import all existing projects after clone finishes" をチェックして終了します。 この後のローカルリポジトリと、インポートされたプロジェクトの状態は以下のようになります:

task3: (2) mainclass に main1m2() を追加する
mainclass.txt を修正して、main1m2() を追加してコミットおよびアップストリームへのプッシュを行った後のプロジェクトとリポジトリ、履歴の状態は以下のように変わります:


task4: (1) master と sample1 をマージ (自宅PC)
再び自宅PCで作業を行います。 自宅PCで、ブランチを master に切り替え、公開リポジトリからプルした状態は、ノートPCの最後の状態と同じです。この状態で、リポジトリビューの egittest を右クリックして、ポップアップ > Merge... を選択します。次のダイアログが表示されます:


sample 1を選択して、Merge ボタンを押します。次の結果確認のダイアログが表示されます:


マージ後のプロジェクトとローカルリポジトリ、履歴の状態は次のようになります:

HEADは、sample1 をマージした masterブランチを指します。 アップストリームへプッシュすると、orgin/master にもマージが反映されます。 不要になった sample1 ブランチを削除したい場合は、リポジトリビューで egittest/Branches/Local/sample1 を選択してポップアップ > Delete Branch を選択することができます。

EGit によるソースコード管理 (4) マージコンフリクトの解決

$
0
0
前回のポストでは新規ブランチとして sample1 を作成し、これを master ブランチにマージしました。 この例ではコンフリクトは起きませんでした。 今回はマージコンフリクトについて説明します。

task5: 公開リポジトリからプル (ノートPC)
自宅PCで行ったマージ結果をノートPCに取り込みます。リポジトリビューで egittest を選択。ポップアップメニュー > Pull を選択します。以下のダイアログが表示されるのでOKを押します:
この結果、ローカルリポジトリとプロジェクト、履歴の状態は以下のようになります。これは自宅PCで sample1 ブランチを削除した場合と同じです:

task6: (1) 新規ブランチ sample2 を作成
前回と同様に、egittest ノードを選択。ポップアップ > Switch To > New Branch... を選択して、新規ブランチ sample2 を作成します。ローカルリポジトリ、プロジェクト、履歴の状態は次のように変わります:

task6: (2) subclass2.txt を追加
subclass2.txt を追加して、コミットを行ったあとの状態は以下のようになります:


task7: (1) master ブランチにスイッチする
リポジトリビューで、egittest/Branches/Local/master をダブルクリックして、master ブランチに切り替えます:

task7: (2) subclass2.txt を追加
sample2 での変更とコンフリクトを起こすような変更を master に加えます。 subclass2 を追加して、コミットした後の状態は以下のようになります:


task7: (3) sample2 をマージ
リポジトリビューで master ブランチを選択します。ポップアップ > Merge... を選択して、マージするブランチとして、sample2 を選択して、Merge ボタンを押します:
上のダイアログの結果(1行目)はコンフリクトです。OKボタンを押すと、リポジトリ、プロジェクトの状態は以下のようになります:

この例のような簡単なコンフリクトの場合、手作業で直接対象ファイルを編集する事もできますが、マージツールを使って編集することもできます。コンフリクトを起こしているトップレベルのリソースを選択して、ポップアップ > Team > Merge Tool... を選択します:
"Use HEAD ..." を選択して OKボタンを押します:

マージエディタが開き、左ペインに作業ツリーバージョンが表示され、右ペインにマージされるバージョンが表示されます。 作業ツリーバージョンを正しい状態に修正してください。修正が完了したら、プロジェクトを選択して、Team > Add で解決済みであることを EGit に知らせます。Team > Commit により、コンフリクトの解決をコミットします。この結果、ローカルリポジトリ、プロジェクト、履歴の状態は以下のようになります:

全てが期待通りなら、余分なブランチを削除して、アップストリームにプッシュすることができます。

スクリーンショットが多くて複雑そうに見えますが、操作はいたって簡単です。

Tycho を使って Eclipse 4 アプリをビルドする(1) 最少のプロジェクト構成

$
0
0
以前のポスト「Maven と Tycho を使ってビルドを自動化する(1)(2)(3)」では、Tycho により 3.x RCPをビルドする方法について書きました。 tycho は最新バージョンが 0.17.0 になり、Eclipse も 4.3 M6 がリリースされ、どちらもさらに完成度が高くなっています。今回は、最新の Eclipse と tycho を使った Eclipse 4 RCP のビルドを説明します。3.x RCP のプロジェクト構成は以下のようなものでした(テストプロジェクトは除いてます):
  • com.itrane.mycontact
  • com.itrane.mycontact.db
  • com.itrane.mycontact.pdf
  • com.itrane.mycontact.z_product
  • com.itrane.mycontact.z_feature
  • com.itrane.mycontact.z_platform
  • com.itrane.mycontact.z_site
  • com.itrane.mycontact.zbuild

4.x RCP では少し簡単にします(とりあえず、DBとPDF機能とテストは除きます):
  • e4contact       (プラグイン:E4アプリケーション)
  • e4contact.zbuild    (ビルド用)
  • e4contact.zfeature   (フィーチャー)
  • e4contact.zrepository (リポジトリ)

親プロジェクト e4contact.zbuild の作成
Eclipse のメニューから File > New > Project...  General/Project を選択して一般プロジェクトを作成します。

作成されたプロジェクトを選択して、ポップアップメニューから Configure > Convert to Maven Project を選択します。そして表示されるダイアログで次のように入力して Finish を押してください。

作成された pom.xml を開いて、すべてのモジュールで使う共通設定を追加します。  修正された pom.xml は以下のようになります:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<!-- すべてのモジュールに対する共通設定 -->
<properties>
<tycho-version>0.17.0</tycho-version>
</properties>

<repositories>
<!-- ビルドのための p2 リポジトリの設定 -->
<repository>
<id>eclipse-platform-m6</id>
<layout>p2</layout>
<url>http://download.eclipse.org/eclipse/updates/4.3milestones/S-4.3M6-201303141330</url>
</repository>
</repositories>

<build>
<plugins>
<plugin>
<!-- Tycho ビルド拡張を有効にする -->
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-maven-plugin</artifactId>
<version>${tycho-version}</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>

この後作成するそれぞれのプロジェクト(サブプロジェクト)はこの親プロジェクトの pom.xml (親 POM)から情報を継承します。

Eclipse 4 アプリケーションプロジェクト e4contact の作成
メニューから File > New > Project... > Eclipse 4/Eclipse 4 Application Project を選択して、Next をクリックします。以下の画面で、プロジェクト名とロケーションを設定して、Next をクリックします。


次のページはデフォルト設定のまま Next をクリックします。そして以下の画面で、"Create sample content" をチェックして Finish を押します。


作成されたプロジェクトに、次のような pom.xml を追加します:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../e4contact.zbuild/</relativePath>
</parent>

<artifactId>e4contact</artifactId>
<packaging>eclipse-plugin</packaging>

</project>


フィーチャープロジェクト e4contact.zfeature の作成
メニューから File > New > Feature Project を選択して、以下のように設定して Next をクリックします。

次のページで、参照プラグインとして、e4contact を設定して Finish をクリックします。


作成されたフィーチャープロジェクトに、次のような pom.xml を追加します:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../e4contact.zbuild/</relativePath>
</parent>

<artifactId>e4contact.zfeature</artifactId>
<packaging>eclipse-feature</packaging>

</project>


リポジトリプロジェクト e4contact.zrepository の作成
メニューから File > New > Project... > General/Project を選択して一般プロジェクトを作成します。

作成したプロジェクトを選択して、ポップアップメニューから New > Other... > Plug-in Development/Category Definition を選択して、category.xml を作成します。 category.xml を開いて、"New Category" ボタンをおして、新規カテゴリを追加し、"Add Feature..." ボタンを押して、追加したカテゴリに、e4contact.zfeature を追加します(下図):

e4contct プロジェクトの e4contat.product を e4contact.zripository に移動します。移動した製品構成を開いて、Confguration タブで Start Levels セクションに、以下のプラグインを追加して、それぞれのスタートレベルと自動スタートを設定します:

  • org.eclipse.equinox.common (2, ture)
  • org.eclipse.equinox.ds         (2, true)
  • org.eclipse.core.runtime      (0, true)


Overview タブで、ID に e4contact.product を設定し、製品構成をフィーチャーベースに変更します:


Dependencies タブで、フィーチャーに e4contact.zfeature と org.eclipse.rcp を追加して、"Add Required" ボタンを押します。 その結果、追加フィーチャーは以下のようになります:


他のモジュールと同様に、このプロジェクトに以下のような pom.xml を追加します:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../e4contact.zbuild</relativePath>
</parent>

<artifactId>e4contact.zrepository</artifactId>
<packaging>eclipse-repository</packaging>

<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-director-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<!-- p2 ディレクターを使ってプロダクトをインストール -->
<id>materialize-products</id>
<goals>
<goal>materialize-products</goal>
</goals>
</execution>
<execution>
<!-- インストールされたプロダクトから zip ファイルを作成 -->
<id>archive-products</id>
<goals>
<goal>archive-products</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

この pom.xml には、プロダクトのインストールとアーカイブを作成するためのビルドを設定します。

親POMに各サブプロジェクトを追加
e4contact.zbuild/pom.xml に 要素を追加して、このプロジェクトのサブプロジェクトを指定します。最終的に親POMは以下のようになります:
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<modules>
<module>../e4contact</module>
<module>../e4contact.zfeature</module>
<module>../e4contact.zrepository</module>
</modules>

<!-- すべてのモジュールに対する共通設定 -->
<properties>
<tycho-version>0.17.0</tycho-version>
</properties>

<repositories>
<!-- ビルドのための p2 リポジトリの設定 -->
<repository>
<id>eclipse-platform-m6</id>
<layout>p2</layout>
<url>http://download.eclipse.org/eclipse/updates/4.3milestones/S-4.3M6-201303141330</url>
</repository>
</repositories>

<build>
<plugins>
<plugin>
<!-- Tycho ビルド拡張を有効にする -->
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-maven-plugin</artifactId>
<version>${tycho-version}</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>

ビルドの実行
e4contact.zbuild/pom.xml を選択して、ポップアップメニューから Run As > Maven Build... とし、ゴールとして、clean install を入力します。
・・・
[INFO] e4contact.zbuild .................................. SUCCESS [0.492s]
[INFO] e4contact ......................................... SUCCESS [3.400s]
[INFO] e4contact.zfeature ................................ SUCCESS [0.275s]
[INFO] e4contact.zrepository ............................. SUCCESS [22.645s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 45.048s
・・・

上に示すようにビルドが成功し、e4contact.zripository の target/products フォルダ下のビルドプラットフォームに対応したフォルダ(この場合 win32/win32/x86)にプロダクトがインストールされます。そして、そのプロダクトから zip アーカイブが生成されます(下図):

プロダクトの eclipse.exe を実行すると、IDE から実行した場合と同様に以下の画面が表示されます。








Tycho を使って Eclipse 4 アプリをビルドする(2) データ処理プラグインの追加

$
0
0
前回は最小構成でのビルドを説明しました。 今回は、データ処理プラグインを追加した場合について説明します。

データ処理プラグイン e4contact.db プロジェクトの作成
メニューの File > New > Plug-in Project を選択します。 "Project name"、"Location" を以下のように設定して、Next をクリックします:


次のページで "Genarate an activator ..." をチェックして、Finishをクリックします:


作成された e4contact.db プロジェクトに以下の内容の pom.xml を追加します:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../e4contact.zbuild</relativePath>
</parent>

<artifactId>e4contact.db</artifactId>
<packaging>eclipse-plugin</packaging>
</project>

サブモジュールとして e4contact.db を追加するために親POM の e4contact.zbuild/pom.xml の modules 要素は次のように変更します:
<modules>
<module>../e4contact.db</module>
<module>../e4contact</module>
<module>../e4contact.zfeature</module>
<module>../e4contact.zrepository</module>
</modules>

データ処理機能の実装
ここでは本題が Tycho によるビルドなので、非常に簡単な機能を追加します。

(ビルドパスに Eclipselink ライブラリと Dervy DB を追加)
プロジェクトのルートフォルダに lib フォルダを作成して次の JAR ファイルを追加します。
  • derby.jar
  • eclipselink.jar
  • javax.persistence_x.x.x.x.jar
  • validation-api-x.x.x.jar
MANIFEST.MF を開き、Runtime タブの Classpath セクションで、上記の JAR を追加します。Eclipselink の使い方については以前のポスト「eclipselink を使う :(1)エンティティの作成」も参考にしてください。 

(モデルクラスの作成)
e4contact.db パッケージに、Contact クラスを作成します:
@Entity
@NamedQueries({
@NamedQuery(name="findContactBySimei",
query="SELECT e FROM Contact e WHERE e.simei=:simei"
),
@NamedQuery(name="findContactLikeYomiOrMail",
query="SELECT c FROM Contact c WHERE c.yomi LIKE :like OR c.mail LIKE :like"
)
})
publicclass Contact {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;

@Version
@Column(name="OPTLOCK")
private Integer version = 0;

@NotNull @Size(min=1, max=30) @Column(nullable=false, length=30)
private String simei;
@NotNull @Size(min=1, max=30) @Column(nullable=false, length=30)
private String yomi;
@NotNull @Size(min=0, max=40) @Column(nullable=false, unique=true, length=40)
private String mail;

public Contact() {
this("","","");
}
public Contact(String simei, String yomi, String mail) {
this.simei = simei;
this.yomi = yomi;
this.mail = mail;
}

public String getSimei() {
return simei;
}

publicvoid setSimei(String simei) {
this.simei = simei;
}

public String getYomi() {
return yomi;
}

publicvoid setYomi(String yomi) {
this.yomi = yomi;
}

public String getMail() {
return mail;
}

publicvoid setMail(String mail) {
this.mail = mail;
}
public Long getId() {
return id;
}
publicvoid setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return version;
}
publicvoid setVersion(Integer version) {
this.version = version;
}
@Override
publicboolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Contact other = (Contact) obj;
if (id == null) {
if (other.id != null)
return false;
} elseif (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return"Contact [id=" + id + ", simei=" + simei + ", yomi="
+ yomi + ", mail=" + mail + ", version=" + version +"]";
}
}

(データ処理クラスの作成)
e4contact.db パッケージに、Contact の CRUD 操作を行うための ContactHome クラスを作成します: 
/**
* JPA 版の連絡先ホーム.
*/

publicclass ContactHome {

public ContactHome() {
}

/** 指定クエリーに合致する連絡先取得. */
public List<Contact> findByQuery(String query) {
@SuppressWarnings("unchecked")
List<Contact> list = LocalJPA.getEntityManager().createQuery(query)
.getResultList();
return list;
}

/** 全件取得. */
public List<Contact> findAll() {
return findByQuery("SELECT c FROM Contact c ORDER BY c.id");
}

@SuppressWarnings("unchecked")
public List<Contact> findBySimei(String simei) {
return LocalJPA.getEntityManager()
.createNamedQuery("findContactBySimei")
.setParameter("simei", simei)
.getResultList();
}

@SuppressWarnings("unchecked")
public List<Contact> findLikeYomiOrMail(String like) {
return LocalJPA.getEntityManager()
.createNamedQuery("findContactLikeYomiOrMail")
.setParameter("like", like)
.getResultList();
}

/** 指定IDに合致するエンティティを取得. */
public Contact findById(Long id) throws Exception {
return LocalJPA.getEntityManager().find(Contact.class, id);
}

/** エンティティを追加する. */
publicvoid persist(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
LocalJPA.getCurrentEntityManager().persist(entity); //DBテーブルへ追加

}
publicvoid persistAndCommit(Contact entity) throws Exception {
persist(entity);
LocalJPA.commitAndClose();
}

/** 更新. */
public Contact update(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
return LocalJPA.getCurrentEntityManager().merge(entity);
}
publicvoid updateAndCommit(Contact entity) throws Exception {
update(entity);
LocalJPA.commitAndClose();
}

/** 削除. */
publicvoid remove(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
EntityManager em = LocalJPA.getCurrentEntityManager();
em.remove(em.merge(entity)); //DBテーブルから削除

}
publicvoid removeAndCommit(Contact entity) throws Exception {
remove(entity);
LocalJPA.commitAndClose();
}
}

同じく、e4contact.db パッケージに、ContactHome で利用するユーティリティクラスの LocalJPA クラスを作成します:
publicclass LocalJPA {

// TODO システムにあわせて変更.
privatestatic ThreadLocal<EntityManager> entityManagerHolder = new ThreadLocal<EntityManager>();
privatestatic ThreadLocal<EntityTransaction> transactionHolder = new ThreadLocal<EntityTransaction>();

//シングルトン
privatestatic EntityManagerFactory entityManagerFactory;

/** エンティティマネージャ・ファクトリを取得.*/
publicstatic EntityManagerFactory getEntityManagerFactory() {
if (entityManagerFactory==null) {
entityManagerFactory = Persistence.createEntityManagerFactory("testpu");
}
return entityManagerFactory;
}

publicstaticvoid setEntityManagerFactory(String pu, Map<String, Object> properties) {
entityManagerFactory = new PersistenceProvider().createEntityManagerFactory(pu, properties);
}
publicstaticvoid setEntityManagerFactory(EntityManagerFactory emf) {
entityManagerFactory = emf;
}

/** フォルダから生成済みのエンティティマネージャを取得する.*/
publicstatic EntityManager getCurrentEntityManager() {
return entityManagerHolder.get();
}

/** エンティティマネージャを取得する.*/
publicstatic EntityManager getEntityManager() {
EntityManager entityManager = entityManagerHolder.get();
if (entityManager == null || !entityManager.isOpen()) {
entityManager = getEntityManagerFactory().createEntityManager();
entityManagerHolder.set(entityManager);
}
return entityManager;
}

/** エンティティマネージャ・フォルダとトランザクション・フォルダを空にする.*/
publicstaticvoid cleanup() {
EntityManager entityManager = entityManagerHolder.get();
EntityTransaction transaction = transactionHolder.get();

if (transaction != null && transaction.isActive()) {
if (transaction.getRollbackOnly()) {
transaction.rollback();
}
}
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}

entityManagerHolder.set(null);
transactionHolder.set(null);
}

/** 実行中のトランザクションのコミットとエンティティマネージャのクローズを行う.*/
publicstaticvoid commitAndClose()
throws Exception, SQLIntegrityConstraintViolationException, OptimisticLockException {
EntityManager entityManager = entityManagerHolder.get();
EntityTransaction transaction = transactionHolder.get();
if (transaction != null && transaction.isActive()) {
try {
transaction.commit();
} catch (Exception ex) {
try {
if (transaction.isActive())
transaction.rollback();
} catch (Exception ex2) {
throw ex; // 元の例外をスローする
}
//[EclipseLink-4002] SQLIntegrityConstraintViolationException
//[EclipseLink-5006] OptimisticLockException
//"Error Code: 1062" 重複エントリー
throw ex;
}
}
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}
entityManagerHolder.set(null);
transactionHolder.set(null);
}

/** エンティティマネージャの生成とトランザクションの開始.*/
publicstaticvoid beginOrJoinTransaction()
throws Exception {
getTransaction(getEntityManager());
}

/** トランザクションの取得または生成&開始.*/
privatestatic EntityTransaction getTransaction(
EntityManager entityManager) {
// トランザクションフォルダにトランザクションが存在するかチェックする
EntityTransaction transaction = transactionHolder.get();
if (transaction == null || !transaction.isActive()) {
// もしフォルダになければ、トランザクションを生成して、開始する
transaction = entityManager.getTransaction();
transaction.begin();
transactionHolder.set(transaction);
} else {
//実行中のトランザクションにジョインする
}
return transaction;
}
}

src/META-INF/persistence.xml を作成します。 本番用と単体テスト用の2つのパーシステンスユニットを追加します。 とりあえず、本番用の ddl-genaration も drop-and-create-tables にしておきます:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0"xmlns="http://java.sun.com/xml/ns/persistence">

<persistence-unit name="pu"transaction-type="RESOURCE_LOCAL">
<class>e4contact.db.Contact</class>
<properties>
<property name="javax.persistence.jdbc.driver"value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:derby:c:/_data_derby/ContactDb;create=true" />

<property name="javax.persistence.jdbc.user"value="test" />
<property name="javax.persistence.jdbc.password"value="test" />

<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation"value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode"value="database" />
</properties>
</persistence-unit>

<persistence-unit name="testpu"transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>e4contact.db.Contact</class>
<properties>
<property name="javax.persistence.jdbc.driver"value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:derby:memory:TestContactDb;create=true" />

<property name="javax.persistence.jdbc.user"value="test" />
<property name="javax.persistence.jdbc.password"value="test" />

<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation"value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode"value="database" />
<!-- Logging -->
<property name="eclipselink.logging.level"value="FINE" />
<property name="eclipselink.logging.timestamp"value="false" />
</properties>
</persistence-unit>
</persistence>

デフォルトでは、パーシステンス・ユニットとして "testpu" が使用されるので、"pu" を使うように e4contact.db.Acitvator クラスを以下のように修正します:
publicclass Activator implements BundleActivator {

privatestatic BundleContext context;

static BundleContext getContext() {
return context;
}

publicvoid start(BundleContext bundleContext) throws Exception {
Activator.context = bundleContext;
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("eclipselink.classloader", this.getClass().getClassLoader());
LocalJPA.setEntityManagerFactory("pu", properties);
}

publicvoid stop(BundleContext bundleContext) throws Exception {
Activator.context = null;
}
}

(エクスポートパッケージの指定)
MANIFEST.MF を開いて、Runtime タブで、エクスポートパッケージとして、e4contact.db を追加します。 最終的に、MANIFEST.MF と build.properties は次のようになります。
MANIFEST.MF:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Db
Bundle-SymbolicName: e4contact.db
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: e4contact.db.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: lib/derby.jar,
lib/eclipselink.jar,
lib/javax.persistence_2.0.3.v201010191057.jar,
lib/validation-api-1.0.0.GA.jar,
.
Export-Package: e4contact.db

build.properties:
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
lib/derby.jar,\
lib/eclipselink.jar,\
lib/javax.persistence_2.0.3.v201010191057.jar,\
lib/validation-api-1.0.0.GA.jar


E4アプリでデータ処理機能を使う
e4contact プロジェクトで e4contact.db のデータ処理機能を使えるように以下の作業を行う必要があります。

(e4contact/plugin.xml の修正)
e4contact/plugin.xml を開き、Dependencies タブを表示して、Imported Packages セクションで、インポートパッケージとして、 e4contact.db を追加します。

(SamplePart クラスの修正)
e4contact.parts.SamplePart クラスを次のように修正します:
publicclass SamplePart {

private Label label;
private TableViewer tableViewer;
private ContactHome home;
private List<Contact> contacts;

@PostConstruct
publicvoid createComposite(Composite parent) {
home = new ContactHome();
try {
home.persist(new Contact("山田太郎", "やまだたろう", "yamada@xxx.com"));
home.persistAndCommit(new Contact("山田花子", "やまだはなこ", "hanako@xxx.com"));
} catch (Exception e) {
e.printStackTrace();
}

parent.setLayout(new GridLayout());

label = new Label(parent, SWT.NONE);
label.setText("Sample table");

tableViewer = new TableViewer(parent);
contacts = home.findAll();
for (Contact c: contacts) {
tableViewer.add(c.toString());
}
tableViewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH));
}

@Focus
publicvoid setFocus() {
tableViewer.getTable().setFocus();
}
}

(e4contact.zfeature フィーチャーの修正)
featur.xml を開いて、Plug-ins タブの Plug-ins and Fragments セクションで、e4contact.db プラグインを追加します。

ビルドの実行
e4contact.zbuild/pom.xml を選択して、Run As > Maven build を選択します。 ゴールは clean install とします:
....
[INFO] e4contact.zbuild .................................. SUCCESS [0.439s]
[INFO] e4contact.db ...................................... SUCCESS [4.187s]
[INFO] e4contact ......................................... SUCCESS [0.798s]
[INFO] e4contact.zfeature ................................ SUCCESS [0.296s]
[INFO] e4contact.zrepository ............................. SUCCESS [24.489s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 47.086s

ビルドが成功し、e4contact.zrepository/target 下にプロダクトがインストールされ、zip アーカイブが作成されます。 e4contact.zrepository をリフレッシュして、target フォルダを展開します:


作成された、eclipse.exe をダブルクリックして実行します:

データベースへの連絡先データの保存と取得が行われていることが確認できます。

Tycho を使って Eclipse 4 アプリをビルドする(3) テストプロジェクトの追加

$
0
0
今回は、前回ポスト「Tycho を使って Eclipse 4 アプリをビルドする(2)」で追加したデータ処理機能をテストするプロジェクトを追加して、ビルド時にテストを行う場合を説明します。

テストプロジェクトの作成
File > New > Project > Plug-in Development/Fragment Project を選択します。表示されたウィザードで下画面のように設定して Next をクリックします:

次のページ(下画面)で、Host Plug-in として、e4contact.db を選択して、Finish をクリックします:

pom.xml の作成
作成されたプロジェクト e4contact.db.test に次のような pom.xml を作成します:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>e4contact</groupId>
<artifactId>e4contact.zbuild</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../e4contact.zbuild</relativePath>
</parent>

<artifactId>e4.contact.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
</project>

親POM (e4contact.zbuild/pom.xml)の modules 要素は次のように修正します:
<modules>
<module>../e4contact.db</module>
<module>../e4contact.db.test</module>
<module>../e4contact</module>
<module>../e4contact.zfeature</module>
<module>../e4contact.zrepository</module>
</modules>


テストケースクラスの作成
作成された e4contact.db.test プロジェクトを選択して、File > New > Other... > Java/JUnit/JUnit Test Case を選択します。表示されたウィザードで、
Package : e4contact.db
Name : TestContactHome_find
テスト対象クラス : e4contact.db.ContactHome
など以下の画面のように設定して、Finish をクリックします:


以上の設定で作成されたテストケースクラス TestContactHome_find を以下のように修正します:
publicclass TestContactHome_find {

privatestatic ContactHome home;
private List<Contact> contacts;

@BeforeClass
publicstaticvoid setUpBeforeClass() throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("eclipselink.classloader", TestContactHome_save_delete.class.getClassLoader());
LocalJPA.setEntityManagerFactory("testpu", properties);

home = new ContactHome();
home.persist(new Contact("山田太郎","やまだたろう","yamada@xxx.com"));
home.persist(new Contact("山田花子","やまだはなこ","hanako@xxx.com"));
home.persist(new Contact("岡本太郎","おかもとたろう","okamoto@xxx.com"));
home.persist(new Contact("佐藤一郎","さとういちろう","satou@zzz.com"));
home.persist(new Contact("金田優子","かねだゆうこ","yuko@zzz.com"));
home.persistAndCommit(new Contact("佐々木健児","ささきけんじ","sasaki@xxx.com"));
LocalJPA.cleanup();
}

@AfterClass
publicstaticvoid tearDownAfterClass() throws Exception {
LocalJPA.getEntityManagerFactory().close();
}

@Before
publicvoid setUp() throws Exception {
home = new ContactHome();
}

@After
publicvoid tearDown() throws Exception {
LocalJPA.cleanup();
}

@Test
publicvoid testFindByQuery() {
contacts = home.findByQuery("select c from Contact c order by c.yomi");
assertEquals(6, contacts.size());
assertEquals("岡本太郎", contacts.get(0).getSimei());
}

@Test
publicvoid testFindAll() {
contacts = home.findAll();
assertEquals(6, contacts.size());
}

@Test
publicvoid testFindBySimeiOkamoto() {
contacts = home.findBySimei("岡本太郎");
assertEquals(1, contacts.size());
assertEquals("岡本太郎", contacts.get(0).getSimei());
}
publicvoid testFindBySimeiYamada() {
contacts = home.findBySimei("山田太郎");
assertEquals(1, contacts.size());
assertEquals("山田太郎", contacts.get(0).getSimei());
}
publicvoid testFindBySimeiNone() {
contacts = home.findBySimei("岡本喜八");
assertEquals(0, contacts.size());
}

@Test
publicvoid testFindLikeYomiBeginWithYamada() {
contacts = home.findLikeYomiOrMail("やまだ%");
assertEquals(2, contacts.size());
}
@Test
publicvoid testFindLikeYomiBeginWithKa() {
contacts = home.findLikeYomiOrMail("か%");
assertEquals(1, contacts.size());
}

@Test
publicvoid testFindLikeMailWithXxx() {
contacts = home.findLikeYomiOrMail("%xxx%");
assertEquals(4, contacts.size());
}
@Test
publicvoid testFindLikeMailWithZzz() {
contacts = home.findLikeYomiOrMail("%zzz%");
assertEquals(2, contacts.size());
}
@Test
publicvoid testFindLikeMailBeginWithS() {
contacts = home.findLikeYomiOrMail("s%");
assertEquals(2, contacts.size());
}
}

同じようにして、テストケースクラス TestContactHome_save_delete を作成します:
publicclass TestContactHome_save_delete {

privatestatic ContactHome home;
private List<Contact> contacts;

@BeforeClass
publicstaticvoid setUpBeforeClass() throws Exception {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("eclipselink.classloader", TestContactHome_save_delete.class.getClassLoader());
LocalJPA.setEntityManagerFactory("testpu", properties);

home = new ContactHome();
home.persist(new Contact("山田太郎","やまだたろう","yamada@xxx.com"));
home.persist(new Contact("山田花子","やまだはなこ","hanako@xxx.com"));
home.persistAndCommit(new Contact("佐々木健児","ささきけんじ","sasaki@xxx.com"));
LocalJPA.cleanup();
}

@AfterClass
publicstaticvoid tearDownAfterClass() throws Exception {
LocalJPA.getEntityManagerFactory().close();
}

@Before
publicvoid setUp() throws Exception {
home = new ContactHome();
}

@After
publicvoid tearDown() throws Exception {
LocalJPA.cleanup();
}

@Test
publicvoid testPersistAndCommitValid() throws Exception {
home.persistAndCommit(new Contact("新規氏名","しんきしめい","uniq@xxx.com"));
contacts = home.findBySimei("新規氏名");
assertEquals(1, contacts.size());
assertEquals("新規氏名", contacts.get(0).getSimei());
assertEquals("しんきしめい", contacts.get(0).getYomi());
assertEquals("uniq@xxx.com", contacts.get(0).getMail());
}

@Test
publicvoid testPersistAndCommitNotUniqMail() {
try {
home.persistAndCommit(new Contact("重複メール","ちょうふくめーる","yamada@xxx.com"));
} catch (Exception e) {
assertTrue(e.getMessage().indexOf("[EclipseLink-4002]")>0);
}
}

@Test
publicvoid testPersistAndCommitSimeiIsNull() {
try {
home.persistAndCommit(new Contact(null ,"しめいぬる","test@xxx.com"));
} catch (Exception e) {
assertTrue(e.getMessage().indexOf("[EclipseLink-4002]")>0);
}
}

@Test
publicvoid testUpdateAndCommitValid() throws Exception {
contacts = home.findBySimei("山田太郎");
assertEquals("yamada@xxx.com", contacts.get(0).getMail());
contacts.get(0).setMail("tyamada@xxx.com");
home.updateAndCommit(contacts.get(0));
Contact c = home.findById(contacts.get(0).getId());
assertEquals("山田太郎", c.getSimei());
assertEquals("tyamada@xxx.com", c.getMail());
}

@Test
publicvoid testUpdateAndCommitOptimisticLock() {
contacts = home.findBySimei("佐々木健児");
assertEquals("佐々木健児", contacts.get(0).getSimei());
contacts.get(0).setVersion(contacts.get(0).getVersion()-1);
try {
home.updateAndCommit(contacts.get(0));
} catch (Exception e) {
assertTrue(e.getMessage().indexOf("[EclipseLink-5006]")>0);
}
}

@Test
publicvoid testRemoveAndCommit() throws Exception {
contacts = home.findBySimei("山田花子");
assertEquals("山田花子", contacts.get(0).getSimei());
home.removeAndCommit(contacts.get(0));

contacts = home.findBySimei("山田花子");
assertEquals(0, contacts.size());
}
}

ビルドの実行
e4contact.zbuild/pom.xml を選択して、Run As > Maven build を選択し、ゴール clean install を指定します:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running e4contact.db.TestContactHome_find
....

Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.128 sec
Running e4contact.db.TestContactHome_save_delete
....

Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.274 sec

Results :

Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
....

[INFO] e4contact.zbuild .................................. SUCCESS [0.488s]
[INFO] e4contact.db ...................................... SUCCESS [4.195s]
[INFO] e4contact.db.test ................................. SUCCESS [9.772s]
[INFO] e4contact ......................................... SUCCESS [0.701s]
[INFO] e4contact.zfeature ................................ SUCCESS [0.293s]
[INFO] e4contact.zrepository ............................. SUCCESS [24.030s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 57.301s

テストは成功し、ビルドが成功します。


WindowBuilder を使って JFace Data Binding を行う

$
0
0
JFace Data Binding は、モデルと UI コンポーネントを結ぶ強力な機能ですが、コーディング量が多く読みにくい(特に日本人にはクラス名が直感的にわかりにくい)ので、積極的に使ってきませんでした。GUI ツールの WindowBuilder を使って、コーディングに頼らずにどこまでできるのか試してみました。

プロジェクトの作成
File > New > Project > Eclipse 4 アプリケーションを選択します。以下のように入力して、Next をクリックします:
次のページはそのままスキップして、次のページで "Create sample content" を選択して Finish をクリックします:



ライブラリの追加
プロジェクトのルートに lib フォルダを作成して以下の JAR を追加します。
Eclipselink と DB:
  • derby.jar
  • eclipselink.jar
  • javax.persistence_2.0.3.v201010191057.jar
JSR303 バリデーション:
  • validation-api-1.0.0.GA.jar
  • hibernate-validator-4.1.0.Final
  • log4j-1.2.16.jar
  • slf4j-api-1.6.1.jar
  • slf4j-log4j12-1.7.5.jar

plugin.xml をオープンして、Runtime タブの Classpath セクションで、lib フォルダ内の JAR を追加します。


モデルクラスの作成
e4binding.db パッケージを追加して、以下の内容の Contact クラスを作成してください:
@Entity
publicclass Contact {
staticfinal String SIZE_MSG = "{min}から{max}の間の長さで入力してください";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;

@Version
@Column(name="OPTLOCK")
private Integer version = 0;

@NotNull @Size(min=1, max=10, message = SIZE_MSG)
@Column(nullable=false, length=30)
private String simei;
@NotNull @Size(min=1, max=30, message = SIZE_MSG)
@Column(nullable=false, length=30)
private String yomi;
@NotNull @Size(min=5, max=40, message = SIZE_MSG) @Email
@Column(nullable=false, unique=true, length=40)
private String mail;
private Date seinengappi;
private String seibetu;

public Contact() {
this("","","", Calendar.getInstance().getTime(), "");
}

public Contact(String simei, String yomi, String mail, Date seinengappi,
String seibetu) {
this.simei = simei;
this.yomi = yomi;
this.mail = mail;
this.seinengappi = seinengappi;
this.seibetu = seibetu;
}

...
}


詳細パートの作成
すでに SamplePart が自動生成されていますが、DetailPart を新たに追加します。
  1. Application.e4xmi を開いて、PartSashContainer 要素に新しく Part を追加します:


  2. 上図で、"Class URI" リンクをクリックして、DetailPart クラスを作成します:
  3. 作成された DetailPart クラスを次のように修正します:
    publicclass DetailPart {
    @PostConstruct
    publicvoid createComposite(Composite parent) {
    parent.setLayout(new GridLayout());
    }

    @Focus
    publicvoid onFocus() {
    //TODO Your code here
    }
    }

WindowBuilder を使って UI を構築
上述の DetailPart を選択して Opne With > WindowBuilder Editor を選択して、DetailPart を編集します。WindowBuilder エディタで、Design タブを開いて、Contact の各プロパティに対応するウィジェットを追加します:


データ結合の設定
Design タブで GUI  コンポーネントを追加したら、Bingings タブを選択します。そして、以下の画面のように、(1) ターゲットのウィジェット(下の例では textSimei)の textプロパティと、モデル(下の例ではContact)の対応するプロパティを選択して、(2) バインディング作成ボタンを押します:


これにより、次のダイアログが表示されますが、ここではデフォルトの状態のまま OK ボタンを押します。

同じようにして、よみ、メール、生年月日、性別についてもデータ結合を行います。生年月日の場合は、DateTime ターゲットの selection プロパティを選択します。

結果として、以下のメソッドとそれを呼び出すコードが自動的に生成されます。このときMANIFEST.MF の Require-Bundle には、必要なバンドルが自動的に追加されます。
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
//
IObservableValue observeTextTextSimeiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textSimei);
IObservableValue simeiContactObserveValue = PojoProperties.value("simei").observe(contact);
bindingContext.bindValue(observeTextTextSimeiObserveWidget, simeiContactObserveValue, null, null);
//
IObservableValue observeTextTextYomiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textYomi);
IObservableValue yomiContactObserveValue = PojoProperties.value("yomi").observe(contact);
bindingContext.bindValue(observeTextTextYomiObserveWidget, yomiContactObserveValue, null, null);
//
IObservableValue observeTextTextMailObserveWidget = WidgetProperties.text(SWT.Modify).observe(textMail);
IObservableValue mailContactObserveValue = PojoProperties.value("mail").observe(contact);
bindingContext.bindValue(observeTextTextMailObserveWidget, mailContactObserveValue, null, null);
//
IObservableValue observeSelectionDateTimeSeinengappiObserveWidget =
WidgetProperties.selection().observe(dateTimeSeinengappi);
IObservableValue seinengappiContactObserveValue = PojoProperties.value("seinengappi").observe(contact);
bindingContext.bindValue(observeSelectionDateTimeSeinengappiObserveWidget,
seinengappiContactObserveValue, null, null);
//Ï
IObservableValue observeTextComboSeibetuObserveWidget = WidgetProperties.text().observe(comboSeibetu);
IObservableValue seibetuContactObserveValue = PojoProperties.value("seibetu").observe(contact);
bindingContext.bindValue(observeTextComboSeibetuObserveWidget, seibetuContactObserveValue, null, null);
//
return bindingContext;
}

この結果ターゲットでデータが編集されると、それがモデルの Contact の対応するフィールドに反映されます。それを確認するために、データ表示ボタンが押された場合に、Contact の内容を表示するようにします。 

またモデルの状態が変わった場合に、その結果を UI に反映するための処理も追加します。最終的な DetailPart クラスの内容は次のようになります:
publicclass DetailPart {
private DataBindingContext m_bindingContext;

private Label lblError;
private Text textSimei;
private Text textYomi;
private Text textMail;
private DateTime dateTimeSeinengappi;
private Combo comboSeibetu;

private Button btnShowData;
private Button btnSetData;

private Contact contact;

private ControlDecoration decoSimei;
private ControlDecoration decoYomi;
private ControlDecoration decoMail;
private ControlDecoration decoSeinengappi;

@PostConstruct
publicvoid createComposite(Composite parent) {
GridLayout gl_parent = new GridLayout();
gl_parent.numColumns = 2;
parent.setLayout(gl_parent);

new Label(parent, SWT.NONE);

lblError = new Label(parent, SWT.NONE);

Label lblSimei = new Label(parent, SWT.NONE);
lblSimei.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSimei.setText("氏名");

textSimei = new Text(parent, SWT.BORDER);
textSimei.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoSimei = new ControlDecoration(textSimei, SWT.LEFT | SWT.TOP);

Label lblYomi = new Label(parent, SWT.NONE);
lblYomi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblYomi.setText("よみ");

textYomi = new Text(parent, SWT.BORDER);
textYomi.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoYomi = new ControlDecoration(textYomi, SWT.LEFT | SWT.TOP);

Label lblMail = new Label(parent, SWT.NONE);
lblMail.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblMail.setText("メール");

textMail = new Text(parent, SWT.BORDER);
textMail.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoMail = new ControlDecoration(textMail, SWT.LEFT | SWT.TOP);

Label lblSeinengappi = new Label(parent, SWT.NONE);
lblSeinengappi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSeinengappi.setText("生年月日");

dateTimeSeinengappi = new DateTime(parent, SWT.BORDER);
decoSeinengappi = new ControlDecoration(dateTimeSeinengappi, SWT.LEFT|SWT.TOP);

Label lblSeibetu = new Label(parent, SWT.NONE);
lblSeibetu.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSeibetu.setText("性別");

comboSeibetu = new Combo(parent, SWT.NONE);
comboSeibetu.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
new Label(parent, SWT.NONE);

btnShowData = new Button(parent, SWT.NONE);
btnShowData.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
System.out.println(contact.toString());
}
});
btnShowData.setText("データ表示");
new Label(parent, SWT.NONE);

btnSetData = new Button(parent, SWT.NONE);
btnSetData.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1980);
cal.set(Calendar.MONTH, 6);
cal.set(Calendar.DAY_OF_MONTH, 15);
setContact(new Contact("氏名","よみ","name@xxx.com", cal.getTime(), "男"));
}
});
btnSetData.setText("データ設定");
setContact(new Contact());
}

publicvoid setContact(Contact contact) {
if (m_bindingContext!=null) {
m_bindingContext.dispose();
}
this.contact = contact;
m_bindingContext = initDataBindings();
}

@Focus
publicvoid setFocus() {
textSimei.setFocus();
}
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
//
IObservableValue observeTextTextSimeiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textSimei);
IObservableValue simeiContactObserveValue = PojoProperties.value("simei").observe(contact);
bindingContext.bindValue(observeTextTextSimeiObserveWidget, simeiContactObserveValue, null, null);
//
IObservableValue observeTextTextYomiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textYomi);
IObservableValue yomiContactObserveValue = PojoProperties.value("yomi").observe(contact);
bindingContext.bindValue(observeTextTextYomiObserveWidget, yomiContactObserveValue, null, null);
//
IObservableValue observeTextTextMailObserveWidget = WidgetProperties.text(SWT.Modify).observe(textMail);
IObservableValue mailContactObserveValue = PojoProperties.value("mail").observe(contact);
bindingContext.bindValue(observeTextTextMailObserveWidget, mailContactObserveValue, null, null);
//
IObservableValue observeSelectionDateTimeSeinengappiObserveWidget =
WidgetProperties.selection().observe(dateTimeSeinengappi);
IObservableValue seinengappiContactObserveValue = PojoProperties.value("seinengappi").observe(contact);
bindingContext.bindValue(observeSelectionDateTimeSeinengappiObserveWidget,
seinengappiContactObserveValue, null, null);
//Ï
IObservableValue observeTextComboSeibetuObserveWidget = WidgetProperties.text().observe(comboSeibetu);
IObservableValue seibetuContactObserveValue = PojoProperties.value("seibetu").observe(contact);
bindingContext.bindValue(observeTextComboSeibetuObserveWidget, seibetuContactObserveValue, null, null);
//
return bindingContext;
}
}

結果を確認する
アプリケーションを実行して、textSimei、textYomi に値を入力すると Contact の対応するフィールドにその値が設定されます。「データ表示」ボタンを押してそれを確認する事ができます:


コンソール出力:
Contact [id=null, version=0, simei=aaaa, yomi=bbbb, mail=, seinengappi=Sat May 04 00:04:55 JST 2013, seibetu=]

「データ設定」ボタンを押すと setContact() メソッドにより、新規生成した Contact のインスタンスが contact にセットされます。その後、DataBindingContext#bindValue() によりターゲットとモデルがバインドされると、モデルの値がターゲットにセットされます:
この例のように単純なケースでは、WindowBuilder の操作に迷うことはほとんどありませんでした。もっと複雑なケースでは操作の習得にある程度時間を必要とするかもしれません。

JFace Data Binding で JSR303 バリデーションを使う

$
0
0
前回のポストで WindowBuilder を使って、UI と モデルをバインドしました。今回は前回作成したプロジェクトに、JSR303 バリデーション機能を追加します。

モデル Contact のそれぞれのプロパティには以下の制約アノテーションを設定しています:
    @NotNull @Size(min=1, max=10, message = SIZE_MSG)
private String simei;
@NotNull @Size(min=1, max=30, message = SIZE_MSG)
private String yomi;
@NotNull @Size(min=5, max=40, message = SIZE_MSG) @Email
private String mail;
private Date seinengappi;
private String seibetu;

上で設定しているアノテーションはデフォルトで用意されているものですが、ここで新たに、自分で定義した @BeforeDate(date="1990/04/01") という制約を seinengappi に対して追加することにします。これは指定した日付 "1990/04/01" より以前の日付でなければならないという制約です。まずこの @BeforeDate アノテーションを定義します。
e4binding.validation.BeforDate:
importstatic java.lang.annotation.ElementType.ANNOTATION_TYPE;
importstatic java.lang.annotation.ElementType.FIELD;
importstatic java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

//決まり文句
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
//バリデータの指定
@Constraint(validatedBy = {BeforeDateValidator.class})
public @interface BeforeDate {
String date() ;
Class<?>[] groups() default {};
String message() default"{date}以前の日付を入力してください";
Class<? extends Payload>[] payload() default {};
}

次にこのアノテーションに対するバリデータを作成します:
publicclass BeforeDateValidator implements ConstraintValidator<BeforeDate, Object> {
BeforeDate constraint;
@Override
publicvoid initialize(BeforeDate constraint) {
this.constraint = constraint;
}
@Override
publicboolean isValid(Object object, ConstraintValidatorContext context) {
Date target = (Date)object;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
String sdate = constraint.date();
System.out.println("targe="+target+" sdate="+sdate);
if (sdate == null) {
return true;
}
Date date;
try {
date = sdf.parse(sdate);
} catch (ParseException e) {
return false;
}
return target.before(date);
}
}

バリデータクラスの作成
上に示したカスタムアノテーションも含めて、JSR303制約アノテーションを調べて入力値の検証を行うバリデータクラスを作成します。
e4binding.validation.BeanValidator:
publicclass BeanValidator implements IValidator {
private ValidatorFactory factory = Validation
.buildDefaultValidatorFactory();

privatestaticfinal String VALIDATION_ERRORS = "検証エラー";
private Class beanType;
private String propertyName;
privateboolean multiStatus;
private ControlDecoration decoration;

public BeanValidator(Class beanType, String propertyName,
ControlDecoration decoration, boolean multiStatus) {
this.beanType = beanType;
this.propertyName = propertyName;
this.multiStatus = multiStatus;
this.decoration = decoration;
if (decoration!=null) {
this.decoration.setImage(FieldDecorationRegistry.getDefault()
.getFieldDecoration(FieldDecorationRegistry.DEC_ERROR)
.getImage());
}
}

@Override
public IStatus validate(Object value) {
// 指定ビーンタイプの指定プロパティの値を検証する.
Set<ConstraintViolation> violations = validateConstraints(value);
if (violations.size() > 0) {
decoration.show();
if (multiStatus) {
// エラーのリストを持つ複数ステータスを作成
List<IStatus> statusList = new ArrayList<IStatus>();
for (ConstraintViolation<Object> cv : violations) {
setDecorationMessage(cv.getMessage());
statusList.add(ValidationStatus.error(cv.getMessage()));
}
returnnew MultiStatus("bindcontact", IStatus.ERROR,
statusList.toArray(new IStatus[statusList.size()]),
VALIDATION_ERRORS, null);
} else {
// 最初のエラーを持つ単一のステータスを作成.
for (ConstraintViolation<Object> cv : violations) {
setDecorationMessage(cv.getMessage());
return ValidationStatus.error(cv.getMessage());
}
}
}
decoration.hide();
return Status.OK_STATUS;
}

privatevoid setDecorationMessage(String msg) {
if (decoration!=null) {
decoration.setDescriptionText(msg);
}
}

@SuppressWarnings({ "rawtypes", "unchecked" })
protected Set<ConstraintViolation> validateConstraints(Object value) {
return (Set<ConstraintViolation>) factory.getValidator().validateValue(
beanType, propertyName, value);
}
}

このクラスでは、javax.validation.Validation の buildDefaultValidatorFactory() メソッドにより、ValidatorFactory インスタンスを生成をします。そして生成したファクトリの getValidator().validateValue(beanType, propertyName, value) により指定されたビーンのプロパティの設定値を検証します。

データバインディングで、このバリデータを使うには次のようにします:
        bindingContext.bindValue(observeTextTextMailObserveWidget, mailContactObserveValue,
new UpdateValueStrategy().setAfterConvertValidator(
new BeanValidator(Contact.class, "mail", decoMail, false)), null);

このバリデータを使うように修正した、最終的な DetailPart クラスは次のようになります:
publicclass DetailPart {
private DataBindingContext m_bindingContext;

private Label lblError;
private Text textSimei;
private Text textYomi;
private Text textMail;
private DateTime dateTimeSeinengappi;
private Combo comboSeibetu;

private Button btnShowData;
private Button btnSetData;

private Contact contact;

private ControlDecoration decoSimei;
private ControlDecoration decoYomi;
private ControlDecoration decoMail;
private ControlDecoration decoSeinengappi;

@PostConstruct
publicvoid createComposite(Composite parent) {
GridLayout gl_parent = new GridLayout();
gl_parent.numColumns = 2;
parent.setLayout(gl_parent);

new Label(parent, SWT.NONE);

lblError = new Label(parent, SWT.NONE);

Label lblSimei = new Label(parent, SWT.NONE);
lblSimei.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSimei.setText("氏名");

textSimei = new Text(parent, SWT.BORDER);
textSimei.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoSimei = new ControlDecoration(textSimei, SWT.LEFT | SWT.TOP);

Label lblYomi = new Label(parent, SWT.NONE);
lblYomi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblYomi.setText("よみ");

textYomi = new Text(parent, SWT.BORDER);
textYomi.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoYomi = new ControlDecoration(textYomi, SWT.LEFT | SWT.TOP);

Label lblMail = new Label(parent, SWT.NONE);
lblMail.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblMail.setText("メール");

textMail = new Text(parent, SWT.BORDER);
textMail.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
decoMail = new ControlDecoration(textMail, SWT.LEFT | SWT.TOP);

Label lblSeinengappi = new Label(parent, SWT.NONE);
lblSeinengappi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSeinengappi.setText("生年月日");

dateTimeSeinengappi = new DateTime(parent, SWT.BORDER);
decoSeinengappi = new ControlDecoration(dateTimeSeinengappi, SWT.LEFT|SWT.TOP);

Label lblSeibetu = new Label(parent, SWT.NONE);
lblSeibetu.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSeibetu.setText("性別");

comboSeibetu = new Combo(parent, SWT.NONE);
comboSeibetu.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
new Label(parent, SWT.NONE);

btnShowData = new Button(parent, SWT.NONE);
btnShowData.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
System.out.println(contact.toString());
}
});
btnShowData.setText("データ表示");
new Label(parent, SWT.NONE);

btnSetData = new Button(parent, SWT.NONE);
btnSetData.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1980);
cal.set(Calendar.MONTH, 6);
cal.set(Calendar.DAY_OF_MONTH, 15);
setContact(new Contact("氏名","よみ","name@xxx.com", cal.getTime(), "男"));
}
});
btnSetData.setText("データ設定");
setContact(new Contact());
}

publicvoid setContact(Contact contact) {
if (m_bindingContext!=null) {
m_bindingContext.dispose();
}
this.contact = contact;
m_bindingContext = initDataBindings();
}

@Focus
publicvoid setFocus() {
textSimei.setFocus();
}
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
//
IObservableValue observeTextTextSimeiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textSimei);
IObservableValue simeiContactObserveValue = PojoProperties.value("simei").observe(contact);
bindingContext.bindValue(observeTextTextSimeiObserveWidget, simeiContactObserveValue,
getContactUpdateValueStrategy("simei", decoSimei, false), null);
//
IObservableValue observeTextTextYomiObserveWidget = WidgetProperties.text(SWT.Modify).observe(textYomi);
IObservableValue yomiContactObserveValue = PojoProperties.value("yomi").observe(contact);
bindingContext.bindValue(observeTextTextYomiObserveWidget, yomiContactObserveValue,
getContactUpdateValueStrategy("yomi", decoYomi, false), null);
//
IObservableValue observeTextTextMailObserveWidget = WidgetProperties.text(SWT.Modify).observe(textMail);
IObservableValue mailContactObserveValue = PojoProperties.value("mail").observe(contact);
bindingContext.bindValue(observeTextTextMailObserveWidget, mailContactObserveValue,
getContactUpdateValueStrategy("mail", decoMail, false), null);
//
IObservableValue observeSelectionDateTimeSeinengappiObserveWidget =
WidgetProperties.selection().observe(dateTimeSeinengappi);
IObservableValue seinengappiContactObserveValue = PojoProperties.value("seinengappi").observe(contact);
bindingContext.bindValue(
observeSelectionDateTimeSeinengappiObserveWidget, seinengappiContactObserveValue,
getContactUpdateValueStrategy("seinengappi", decoSeinengappi, false), null);
//Ï
IObservableValue observeTextComboSeibetuObserveWidget = WidgetProperties.text().observe(comboSeibetu);
IObservableValue seibetuContactObserveValue = PojoProperties.value("seibetu").observe(contact);
bindingContext.bindValue(observeTextComboSeibetuObserveWidget, seibetuContactObserveValue, null, null);
//
return bindingContext;
}
private UpdateValueStrategy getContactUpdateValueStrategy(String propName,
ControlDecoration deco, boolean multi) {
UpdateValueStrategy storategy = new UpdateValueStrategy();
storategy.setAfterConvertValidator(new BeanValidator(Contact.class, propName, deco, multi));
return storategy;
}
}

結果を確認する
アプリケーションを実行すると、検証エラーにより次のようになります:


正しい値を入力するか、「データ設定」ボタンを押して正しい値を設定するとエラーは表示されません:



Twilio を使ってブラウザから電話してみる

$
0
0
クラウド電話APIを提供するTwilio(トゥイリオ)が日本でも4月17日からサービスを開始しました(参考:KDDIウェブコミュニケーションズ、クラウド電話API「Twilio」を国内提供)。Twilio を使えば、様々なデバイス(PCやタブレットなど)を電話機として使えます。Twilio 電話番号を購入し、アカウントを取得すれば Twilio を利用することができます。無料で使えるトライアルアカウントを取得することもできるので、簡単に試めすことができます。アカウントの登録や、SDKのダウンロードはこちらのページから行うことができます。

Twilio を利用する様々なシナリオが考えられますが、簡単なシナリオから始めることにします。

シナリオ1:電話機(携帯、固定電話)から Twilio 番号に電話する
購入電話番号にはどんな電話番号からでも電話できますが、トライアルアカウントの場合はベリファイされた電話番号だけが使用できます。取得した Twilio 電話番号ごとに Voice Request URL という入力欄があります。ここに自分が作成したウェブ アプリケーションの URL を設定すると、この電話番号で受信したときにこのアプリを実行することができます。このアプリで Twilioマークアップコマンドをレスポンスとして返すことで様々な処理を行うことができます。

Twilio マークアップレスポンスの例(女性の声で日本語のメッセージを読む):
<Response>
<Say voice="woman"languege="ja_jp">こんにちは これはテストです</Say>
</Response>

このマークアップを出力する Javaサーブレットを作成して、実際に電話に応答するまでを順を追って説明します。

(WEBアプリの作成)
  1. Eclipse で動的Webプロジェクト(twiml) を作成
    RAP で WEB アプリを作成することもできますが、今回は動的WEBプロジェクトを使用します。File > New >  Dynamic Web Project を選択してプロジェクトを作成します。
    プロジェクト名は twiml とします。
  2. java SDK ライブラリの設定
    Java で Twilio API を使うためのライブラリをダウンロードして、プロジェクトの WebContent/WEB-INF/lib フォルダにコピーします。
    SDK は、https://github.com/twilio/twilio-java からダウンロードしてください。
  3. サーブレットの作成
    上述のマークアップコマンドを出力する以下のサーブレットを作成します。
    twilio.twiml.Test1MLServlet:
    publicclass Test1MlServlet extends HttpServlet {

    publicvoid service(HttpServletRequest request, HttpServletResponse response)
    throws IOException {

    response.setContentType("text/html; charset=UTF-8");
    request.setCharacterEncoding("UTF-8");

    // 応答メッセージを話す TwiML の作成
    TwiMLResponse twiml = new TwiMLResponse();
    Say say = new Say("こんにちは これはテストです");
    say.setLanguage("ja-jp");
    say.setVoice("woman");
    try {
    twiml.append(say);
    } catch (TwiMLException e) {
    e.printStackTrace();
    }
    response.setContentType("application/xml");
    response.getWriter().print(twiml.toXML());
    }
    }
  4. web.xmlの作成
    以下の web.xml を作成して、上記サーブレットをマッピングします。
    WebContent/WEB-INF/web.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>twilio ml</display-name>
    <servlet>
    <servlet-name>Test1MlServlet</servlet-name>
    <servlet-class>twilio.twiml.Test1MlServlet</servlet-class>
    </servlet>

    <servlet-mapping>
    <servlet-name>Test1MlServlet</servlet-name>
    <url-pattern>/test1</url-pattern>
    </servlet-mapping>
    </web-app>
  5. war ファイルをエクスポート
    プロジェクトエクスプローラーで twimlプロジェクトを選択して、ポップアップから Export > WAR file を選択して適当なフォルダへ twiml.war をエクスポートします。

(アプリを公開サーバーにデプロイ)
アプリを公開する方法については Twilio ドキュメントにも色々な方法が説明されていますが、私は、CroudBees の Run@cloud を利用しました。 Run@cloud で新しいアプリを作成し、上で作成した twiml.war をデプロイします。

注意:CroudBees については以前のポスト「CloudBees で RAPアプリケーションを開発する(3) デプロイと実行」を参考にしてください。


(アプリURLを電話番号の Voice Request URLに設定)
Run@cloud で作成したアプリのロケーションが http://xxx.zzz.cloudbees.net/ の場合、Test1MlServlet の URL は http://xxx.zzz.cloudbees.net/test1 となります。これを先に説明した twilio 電話番号の Voice Request URL に設定してください。



これで、携帯電話から Twilio 電話番号に電話をかけるとメッセージを聞く事ができます。出力するマークアップを変えればもっと実用的な処理を実行できます。

次はもう少し複雑な例を取り上げます。

シナリオ2: ブラウザから電話する
ここで作成するアプリのおおまかな処理の流れは次のようになります
  1. ブラウザで実行されるアプリの処理内容:
    1. Twilio 登録アカウントの SID と 認証トークンを使って TwilioCapability オブジェクトを作成します。
    2. TwilioCapability#allowClientOutgoing() を使って接続時に実行するアプリ(TWIMLアプリ)の SID を指定します。この SID は TWIMLアプリを作成すると取得できます。(TWIMLアプリの作成については後で説明します)
    3. TwilioCapability#generateToken() でケーパビリティトークンを取得します。
    4. JavaScript オブジェクトでブラウザから Twilio に接続します。
  2. TWIMLアプリ(サーブレット)の処理内容:
    指定された電話番号へダイアルする Twilio マークアップコマンドを出力します。
上記1. のブラウザで実行するアプリはローカルサーバー上で実行することもできますが、2.のTWIMLアプリは公開サーバー上になければなりません。まず 1. のアプリを作成します。

注意:これらのアプリは「Twilio Client Java クイックスタート」のサンプルを参考にして作成しました。詳しい説明はそちらをご覧ください。

(ブラウザで実行するアプリの作成)
  1. シナリオ1で作成したプロジェクトに新しいサーブレットを追加します。
    このサーブレットは登録アカウントの SID と認証トークンを使って TwilioCapability オブジェクトを作成し、それを使って、Twilio 接続時に実行するアプリケーションの設定を行います。そして、 操作画面のJSPビューへリダイレクトします。
    twilio.client.TwiliolServlet:
    publicclass TwilioServlet extends HttpServlet {

    publicstaticfinal String ACCOUNT_SID = "YOUR_ACCOUNT_SID";
    publicstaticfinal String AUTH_TOKEN = "YOUR_AUTH_TOKEN";

    publicvoid service(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException {

    response.setContentType("text/html; charset=UTF-8");
    request.setCharacterEncoding("UTF-8");

    String applicationSid = "YOUR_APP_SID";
    TwilioCapability capability = new TwilioCapability(ACCOUNT_SID, AUTH_TOKEN);
    capability.allowClientOutgoing(applicationSid);
    String token = null;
    try {
    token = capability.generateToken();
    } catch (DomainException e) {
    e.printStackTrace();
    }
    // Forward the token information to a JSP view
    response.setContentType("text/html");
    request.setAttribute("token", token);
    RequestDispatcher view = request.getRequestDispatcher("client.jsp");
    view.forward(request, response);
    }
    }
  2. 上の TwilioServlet から呼ばれる Jsp ビューを作成します。
    この JSP では「call」ボタン、「hungup」ボタン、電話番号入力用のテキストを表示します。
    ユーザーが電話番号を入力して、「call」ボタンを押すと call() 関数が呼ばれ、Twilio に接続します。Twilio はアプリの SID を調べ、対応する URLのTWIMLアプリを実行します。
    WebContent/client.jsp:
    <!DOCTYPE html>
    <html>
    <head>
    <title>Hello Client Monkey 4</title>
    <script type="text/javascript"
    src="//static.twilio.com/libs/twiliojs/1.1/twilio.min.js"></script>
    <script type="text/javascript"
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js">
    </script>
    <link href="http://static0.twilio.com/packages/quickstart/client.css"
    type="text/css" rel="stylesheet" />
    <script type="text/javascript">

    Twilio.Device.setup("${token}", {debug: true});

    Twilio.Device.ready(function (device) {
    $("#log").text("Ready");
    });

    Twilio.Device.error(function (error) {
    $("#log").text("Error: " + error.message);
    });

    Twilio.Device.connect(function (conn) {
    $("#log").text("Successfully established call");
    });

    Twilio.Device.disconnect(function (conn) {
    $("#log").text("Call ended");
    });

    function call() {
    params = {"PhoneNumber": $("#number").val()};
    Twilio.Device.connect(params);
    }

    function hangup() {
    Twilio.Device.disconnectAll();
    }
    </script>
    </head>
    <body>
    <button class="call" onclick="call();">
    Call
    </button>

    <button class="hangup" onclick="hangup();">
    Hangup
    </button>

    <input type="text" id="number" name="number"
    placeholder="Enter a phone number to call"/>

    <div id="log">Loading pigeons...</div>
    </body>
    </html>
  3. web.xml に次のサーブレットとマッピングの定義を追加します。
    WebContent/WEB-INF/web.xml の修正:
    <servlet>
    <servlet-name>TwilioServlet</servlet-name>
    <servlet-class>twilio.client.TwilioServlet</servlet-class>
    </servlet>
    ...
    <servlet-mapping>
    <servlet-name>TwilioServlet</servlet-name>
    <url-pattern>/client</url-pattern>
    </servlet-mapping>
(TWIMLアプリの作成)
  1. 同じプロジェクトに次のサーブレットを追加します。
    このサーブレットはリクエストパラメータとして渡された電話番号にダイアルするマークアップコマンドを出力します。
    twilio.client.TwilioDialServlet:
    publicclass TwilioDialServlet extends HttpServlet {

    publicvoid service(HttpServletRequest request, HttpServletResponse response)
    throws IOException {

    String phoneNumber = request.getParameter("PhoneNumber");
    String callerId = "+815088889999";

    TwiMLResponse twiml = new TwiMLResponse();
    Dial dial = new Dial();
    try {
    if (phoneNumber != null) {
    dial.append(new Number(phoneNumber));
    dial.setCallerId(callerId);
    }
    twiml.append(dial);
    } catch (TwiMLException e) {
    e.printStackTrace();
    }
    response.setContentType("application/xml");
    response.getWriter().print(twiml.toXML());
    }
    }
    注意: callerId は自分の Twilio 電話番号を使用する必要があります。
  2. web.xml に次のサーブレットとマッピングの定義を追加します。
    <servlet>
    <servlet-name>TwilioDialServlet</servlet-name>
    <servlet-class>twilio.client.TwilioDialServlet</servlet-class>
    </servlet>
    ...
    <servlet-mapping>
    <servlet-name>TwilioDialServlet</servlet-name>
    <url-pattern>/dial</url-pattern>
    </servlet-mapping>
(WARファイルのエクスポート)
 シナリオ1の場合と同様にして、twiml.war をエクスポートします。

(WARファイルのデプロイ)
同様に、シナリオ1で作成した Run@cloud のアプリへ twiml.war をアップロードして新バージョンに置き換えます。

(TWIMLアプリの登録)
Twilio の登録アカウントにログインして、ツールタブのTWIML APP タブから新しいアプリを作成します。作成したアプリの Request URL フィールドに上記 TwilioDialServlet の URL(例えば、http://xxx.zzz.cloudbees.net/dial)を設定します。

ブラウザでURLとして、http://xxx.zzz.cloudbees.net/client (Run@cloud のアプリを実行する場合)または http://localhost:8080/twiml/client(Eclipse IDE から実行する場合)を入力してアプリを実行すると次の画面が表示されます。


自分の携帯の番号を設定して「call」ボタンを押すと携帯に電話をかけることができます。
注意: 電話番号が 080-1111-2222 の場合 +818011112222 を入力します。


AWS Elastic Beanstalk + MySQL + JPA で WEB アプリを開発する(1)

$
0
0
今更という感じもしますが、1年間無料利用枠が使えるという宣伝に誘われてアマゾン・ウェブ・サービス(AWS)を使ってみることにしました。 とりあえず SpringMVC + Eclipselink + MySQL  で簡単にアプリケーションが作成できるか試してみたいと思います。

アカウントの作成
こちらのページ から「今すぐ無料アカウントを作成」ボタンを押して、後は画面の指示に従っていけば簡単にアカウントを作成できます(クレジットカード情報と、その場にある電話の電話番号が必要です)。


Eclipse に開発ツールをインストールする
最新の Eclipse (Kepler JEE パッケージ)に更新サイト http://aws.amazon.com/eclipse から AWS Toolkit for Eclipse をインストールします:


AWS Toolkit for Eclipse の設定
インストールが終了したら、環境設定画面をオープンして、以下の設定を行います (より詳しい内容は "Setting Up the AWS Toolkit for Eclipse" を参照してください)。

(セキュリティの設定)
上の画面でアクセスキーとシークレットアクセスキーを設定します。リンクの "find your existing AWS security credentials" をクリックするとブラウザに次のページが表示され、アクセスキーを確認できます:

まだアクセスキーが作成されていない場合は、"新しいキーを作成する" をクリックして、作成します。

(リージョンの設定)
東京のリージョンを選択します。


それ以外は今のところデフォルトのままでかまいません。


AWS Java Web プロジェクトの作成
インストールと設定が終わったら、いよいよプロジェクトを作成します。File > New > Project... を選択して新規プロジェクトウィザード(下の画面)を表示します。AWS Java Web Project を選択して、Next をクリックします。

次の画面で、プロジェクト名(例 awstest01)を入力して、作成タイプとして "Basic Java Web Application" を選択します。

ローカル環境でのテスト
AWS環境へのデプロイはかなり時間がかかります。デプロイする前に必ずローカル環境でテストすべきですが、ここでは説明は省略します。

アプリケーションをAWSへデプロイする
基本的な AWS Java Web プロジェクトを作成してローカル環境で実行を確認したら、AWSへデプロイします。作成された awstest01 プロジェクトを選択してポップアップメニューから Amazon Web Services > Deploy to AWS Elastic Beanstalk を選択します(AWS 開発者ガイドに詳細な説明があります):
上の画面で、"Manually define a new server"を選択して、サーバータイプを選択します。私はローカル環境に合わせて Tomcat 7 を選択しました。ホスト名他を設定して次へ進みます。

初めての場合はまだデプロイ先の環境が作成されていないので、"Create a new application" を選択して、アプリケーション名、環境名を設定します。

"Deploy with a key pair" をチェックして、キーペア(上の例では key-eclipse-1)を選択します。 まだキーペアが作成されていない場合は新規作成ボタンを押してキーペアを作成します。

デプロイするプロジェクト (awstest01) を追加して、"Finish" をクリックします。

しばらくすると、Eclipse の内部ブラウザに次の画面が表示されます。



作成された AWS Elastic Beanstalk 環境を確認する
アマゾンウェブサービスのサイトの "アカウント/コンソール" メニューから "AWS Management Console"を表示します(下の画面):


Elastic Beanstalk を選択して、デプロイ時に作成された AWS Elastic Beanstalk 環境 (app01env)を確認します。


"Edit Configuration" リンクをクリックして、この環境の現在の設定内容を確認したり、変更を行うことができます(下の画面):



MySQL データベースを作成する
上述のAWS Elastic Beanstalk 環境の Edit Configuration ダイアログの "Database" タブを選択して、この環境で使用するデータベースを設定します。 下の画面は設定後の内容です。

アプリケーション作成時に必要となる項目は DBエンドポイント、マスターユーザー名とパスワードです。

まとめ
以上の操作で、AWSの実行環境とデータベース環境、これから機能を追加していく土台となるWEBプロジェクトが作成されました。初めてでも1時間程度で可能だと思います。次回は、データベースにテーブルを作成して、テストデータをインポートしたいと思います。






AWS Elastic Beanstalk + MySQL + JPA で WEB アプリを開発する(2)

$
0
0
前回は、AWS Toolkit for Eclipse を使って、AWSの実行環境、データベース環境、基本プロジェクトを作成しました。データベースアクセス機能を追加する前に、簡単にデータベースにアクセスできるように設定して、テストデータを作成します。

MySQL Workbench でAWS MySQL データベースにアクセスする
MySQL Workbench を実行します。

"New Server Instance" をクリックすると次のウィザードが表示されます。

"Remote Host" を選択して、"Address" に AWS Elastic Beanstalk 環境の URLを設定します。この URL は様々な方法で確認できますが、前回ポストで説明したのAWS Elastic Beanstalk 環境の確認画面からも確認できます。URLを設定したら "Continue" をクリックします。

上の画面で、以下の項目をそれぞれ設定します:
  • 接続方法
    Standard TCP/IP over SSH
  • SSHホスト名  
    AWS Elastic Beanstalk 環境の URL : SSHポート
  • SSHキーファイル
    デプロイ時に設定したキーペアのファイルパス
  • MySQLホスト名
    AWS Elastic Beanstalk 環境に対して作成したDBのエンドポイント
    エンドポイントは前回説明した環境の設定ダイアログから確認できます。
  • ユーザー名
    AWS Elastic Beanstalk 環境に対して作成したDBのマスターユーザー名
  • パスワード
    上のユーザー名に対するパスワード

"Continue" をクリックします。

設定が正しければ、データベースの接続に成功します。 "Continue" をクリックして次に進みます。

上の状態で"Continue" をクリックします。

作成したサーバーインスタンスに名前をつけて、"Finish" を押します。


ローカル環境からデータベースをインポートする
先に MySQL Workbench を実行して、ローカル環境からアプリで使用するデータベースをエクスポートしておきます。 次に上で作成したリモートサーバーインスタンスを選択して、エクスポートしたデータベースをインポートします。次の画面は demo をインポートした状態です 。


これで AWS のデータベースへアクセスする準備が整いました。 次回は Eclipselink を使用してデータにアクセスするアプリを作成します。


AWS Elastic Beanstalk + MySQL + JPA で WEB アプリを開発する(3)

$
0
0
このシリーズの3回目では、Eclipselink を使ってAWSのデータベースサービス(RDS)にアクセスする簡単なアプリケーションを作成します。1回目のポストで作成したプロジェクトをそのまま使用します。

SpringMVC、Eclipselink に必要なライブラリを設定する
Spring を初めて使う人にとって、最初に面倒に感じるのは Spring を使える環境を整えることだと思います。ここではその手間をのぞくために、AWS Java Web Project の作成ウィザードで選択することができるサンプルプロジェクト(Travel Log)を利用します。このプロジェクトを作成すると Spring を使うために必要なライブラリが WebContent/WEB-INF/lib に含まれています(少しバージョンは古いです)。今回のアプリでは使用しないものや余分なものも含まれていますが、AWS の様々なサービスを使うために必要なものもそろっているので、これをそのまま(SimpleDB 関連は除く)コピーして使用します。これに Eclipselink と MySQL 関連の JAR を追加します。

最終的な、awstest01 プロジェクトの WebContent/WEB-INF/lib の内容は以下のようになります。


時間をかけずに、アプリを完成したかったので、以前のポスト「eclipselink を使う :(1)エンティティの作成」や「eclipselink を使う :(2)CRUD操作用クラスの作成」で紹介したエンティティやデータアクセスクラスを使用します。ここで紹介しなかったソースコードはそちらを参照してください

persistence.xml の作成
プロジェクトの src/META-INF にpersistence.xml を作成します:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
>

<persistence-unit name="pu"transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- All persistence classes must be listed -->
<class>awstest01.db.entity.Contact</class>
<class>awstest01.db.entity.Address</class>
<class>awstest01.db.entity.Phone</class>
<properties>
<property name="javax.persistence.target-database"value="MySQL"/>
<property name="javax.persistence.jdbc.driver"value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://XXXXXXXXXX-northeast-1.rds.amazonaws.com:3306/demo" />

<property name="javax.persistence.jdbc.user"value="username" />
<property name="javax.persistence.jdbc.password"value="password" />

<property name="eclipselink.ddl-generation"value="update-tables" />
<property name="eclipselink.ddl-generation.output-mode"value="database" />
<property name="eclipselink.jdbc.read-connections.min"value="1" />
<property name="eclipselink.jdbc.write-connections.min"value="1" />
<property name="eclipselink.jdbc.batch-writing"value="JDBC" />
<!-- Logging -->
<!-- property name="eclipselink.logging.level" value="FINE" / -->
<property name="eclipselink.logging.timestamp"value="false" />
<property name="eclipselink.logging.session"value="false" />
<property name="eclipselink.logging.thread"value="false" />

</properties>
</persistence-unit>

</persistence>

RCP アプリケーション用に作成したものをそのままコピーしたので、WEB環境に合わせて変更すべきですが、データベースの url とユーザー名、パスワード以外はとりあえずローカル環境の場合と同じにしておきます。 URL、ユーザー名、パスワードは1回目のポストで作成したデータベース環境の、DBエンドポイント、マスターユーザー名、パスワードを使用します。

エンティティクラスの作成
今回使用する Contact クラスを awstest01.db.entity パッケージに作成します:
@Entity
@Table(schema="demo", name="contact")
@NamedQueries({
@NamedQuery(
name="findContactByUsername",
query="SELECT e FROM Contact e WHERE e.username=:username"
)
,
...
})
publicclass Contact extends AbstractPersistentEntity {

privatestaticfinallong serialVersionUID = 1L;

private String simei;
private String yomi;
private ContactType contactType;
private String company;
private String seibetu;
private Date seinengappi;

private String title;
private String jobTitle;
private String note;
private String jpegString;

private String username;
private String googleid;

private Map<String, Address> addresses;
private Map<String, Phone> phones;

public Contact() {
contactType = ContactType.None;
seibetu = "";
seinengappi = Calendar.getInstance().getTime();
simei = "";
yomi = "";
company = "";
title = "";
jobTitle = "";
note = "";
username="";
googleid="";
jpegString="";
addresses = new HashMap<String, Address>();
phones = new HashMap<String, Phone>();
}
//..

public Contact(Contact c) {
contactType = c.getContactType();
seibetu = c.getSeibetu();
seinengappi = c.getSeinengappi();

simei = c.getSimei()==null ? "" : c.getSimei();
yomi = c.getYomi()==null ? "" : c.getYomi();
company = c.getCompany();
title = c.getTitle();
jobTitle = c.getJobTitle();
note = c.getNote();
username =c.getUsername();
googleid = c.getGoogleid();
jpegString = c.getJpegString();
addresses = new HashMap<String, Address>();
//addresses = c.getAddresses();
phones = new HashMap<String, Phone>();
//phones = c.getPhones();
}

@OneToMany(cascade=CascadeType.ALL)
@MapKeyClass(String.class)
@MapKeyColumn(table="CONTACT_ADDRESS")
@JoinTable(name="CONTACT_ADDRESS")
public Map<String, Address> getAddresses() {
return addresses;
}

publicvoid setAddresses(Map<String, Address> addresses) {
this.addresses = addresses;
}

@OneToMany(cascade=CascadeType.ALL)
@MapKeyClass(String.class)
@MapKeyColumn(table="CONTACT_PHONE")
@JoinTable(name="CONTACT_PHONE")
public Map<String, Phone> getPhones() {
return phones;
}

publicvoid setPhones(Map<String, Phone> phones) {
this.phones = phones;
}
//... getter, setter

@Column(length=20)
public String getSimei() {
return simei;
}

publicvoid setSimei(String simei) {
this.simei = simei;
}

@Column(length=30)
public String getYomi() {
return yomi;
}

publicvoid setYomi(String yomi) {
this.yomi = yomi;
}

@ObjectTypeConverter(name = "contactType",
objectType = ContactType.class,
dataType = String.class,
conversionValues = {
@ConversionValue(objectValue = "SYS_FAMILY", dataValue = "家族・親戚"),
@ConversionValue(objectValue = "SYS_FRIENDS", dataValue = "友人・知人"),
@ConversionValue(objectValue = "SIGOTO", dataValue = "仕事"),
@ConversionValue(objectValue = "DOURYOU", dataValue = "同僚"),
@ConversionValue(objectValue = "KOKYAKU", dataValue = "顧客"),
@ConversionValue(objectValue = "SEIKATU", dataValue = "生活"),
@ConversionValue(objectValue = "None", dataValue = "")
}
)
@Convert("contactType")
@Column(length=10)
public ContactType getContactType() {
return contactType;
}

publicvoid setContactType(ContactType type) {
this.contactType = type;
}

...

}


データアクセスクラスの作成
Contact クラスを操作するいわゆる DAO クラスの ContactHomeを awstest01.db.home パッケージに作成します:
@Repository
publicclass ContactHome extends AbstractEntityHome<Contact> implements IContactHome {

private AddressHome addressHome;
private PhoneHome phoneHome;

public ContactHome() {
super(Contact.class);
}

public ContactHome(Class<Contact> type) {
super(type);
}

public ContactHome(Class<Contact> type, AddressHome addressHome,
PhoneHome phoneHome) {
this(type);
this.addressHome = addressHome;
this.phoneHome = phoneHome;
}

@Override
publicvoid fireInputChange() {
getListeners().firePropertyChange(INPUT_CHANGE, null, null);
}

@Override
@SuppressWarnings("unchecked")
public List<Contact> findByUser(String username) {
entityList = LocalJPA.getEntityManager()
.createNamedQuery("findContactByUsername")
.setParameter("username", username)
.getResultList();
return entityList;
}

...


}

このアプリで使用するメソッドは findByUser( ) だけです。


コントローラクラスの作成
ビューに対してモデルを受け渡す、コントローラクラス ContactListController を awstest01.web パッケージに作成します:
publicclass ContactListController implements Controller {

protectedfinal Log logger = LogFactory.getLog(getClass());

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

ContactHome home = new ContactHome();
List<Contact> contacts = home.findByUser("username");

returnnew ModelAndView("contactList", "contacts", contacts);
}
}

上のコードでは、spring の DI を使わずに直接 ContactHome インスタンスを作成しています。ContactHome#findByUser( ) メソッドにより、指定したユーザーの連絡先リストを取得します。戻り値として ModelAndView のインスタンスを返します。これにより、このメソッドの実行後、"contactList" 論理名で指定されるビュー(実際は contactList.jsp)を表示することを指定します。さらに第2、第3の引数により、取得した連絡先リスト contacts を contactList.jsp で "contacts" という名前で参照できるようにします。

ビューの作成
WebContent/WEB-INF/jsp フォルダに連絡先一覧を表示する contactList.jsp を作成します:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
<h3>AWS + Spring + JPA サンプル:</h3>
<table border=1width="80%"align=center>
<caption>連絡先一覧</caption>
<tr bgcolor="#eeeeee">
<th>氏名</th>
<th>よみ</th>
<th>種別</th>
</tr>
<c:forEach items="${contacts}"var="contact">
<tr>
<td><c:out value="${contact.simei}"></c:out></td>
<td><c:out value="${contact.yomi}"></c:out></td>
<td><c:out value="${contact.contactType}"></c:out></td>
</tr>
</c:forEach>
</table>
</body>
</html>

cotactList.jsp では items = "${contacts}" でコントローラから連絡先一覧を受け取ります。 そして forEach ループ内でそれぞれの連絡先の氏名、よみ、種別を出力します。

モデル、ビュー、コントローラを連携するための設定を行う
WebContent/WEB-INF/web.xml を次のように修正します:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID"version="2.5">

<display-name>awstest01</display-name>
<servlet>
<servlet-name>springapp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springapp</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

上のサーブレットマッピングで、このアプリケーションの /*.htm のURLパターンは全て DispatcherServlet によって制御されます。

WebContent/WEB-INF/springapp-servlet.xml を作成します:
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="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-2.5.xsd"
>


<!-- the application context definition for the springapp DispatcherServlet -->

<bean name="/contactList.htm"class="awstest01.web.ContactListController"/>

<bean id="viewResolver"class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix"value="/WEB-INF/jsp/"></property>
<property name="suffix"value=".jsp"></property>
</bean>
</beans>

このファイルには DispatcherServlet で使用される bean 定義を記述します。 springapp-servlet.xml という名前は web.xml で、ディスパッチャのサーブレット名を springapp としたことに対応します(SpringMVCの標準ネーミング規約)。上の bean 定義により、/contactList.htm へのリクエストはディスパッチャで処理されて対応する ConatactListController が実行されます。このコントローラーは ContactHome を使って連絡先リストを取得し、それを contactList.jsp へ渡します。ビューリゾルバーはコントローラが指定した論理名 "contactList" から /WEB-INF/jsp/contactList.jsp を決定します。

更新されたアプリを再度デプロイする
プロジェクトを右クリックして、Amazon Web Services > Deploy to AWS Elastic Beanstalk.. を選択します。 今回は初回に作成したアプリケーション環境 app01 を選択します。しばらくして前回と同じ画面 (index.jsp) が表示されます。 リクエストURLとして、・・・/contactList.htm を指定すると次の画面が表示されます。



AWS Elastic Beanstalk へ RAP アプリをデプロイする(1) アプリの作成

$
0
0
前のポスト「AWS Elastic Beanstalk + MySQL + JPA で WEB アプリを開発する」(1) - (3) では Java Web アプリを Elastic Beanstalk 環境(app01env)で実行しました。 今回は RAP アプリケーションを開発して、この app01env で実行してみたいと思います。

まず、以前のポスト「WindowBuilder を使って JFace Data Binding を行う」で作成した RCP アプリを少し修正して RAP アプリを作成します。 それから WAR ファイルをエクスポートして、これを app01env へアップロード&デプロイします。

新規プラグインプロジェクトの作成
New > plugin-project を選択して新規プロジェクトを作成します。
 ・プロジェクト名: rapbinding
 ・テンプレート: Rap Hello World


ライブラリの追加
プロジェクトに lib フォルダを作成して以下の JAR ファイルを追加します。
Eclipselink と DB:
  • mysql-connector-java-5.1.21-bin.jar
  • eclipselink.jar
  • javax.persistence_2.0.3.v201010191057.jar
JSR303 バリデーション:
  • validation-api-1.0.0.GA.jar
  • hibernate-validator-4.1.0.Final
  • log4j-1.2.16.jar
  • slf4j-api-1.6.1.jar
  • slf4j-log4j12-1.7.5.jar

マニフェストファイルをオープンして、Runtime タブの Classpath セクションで、lib フォルダ内の JAR を追加します。

モデルクラスの作成
rapbinding.db.Contact を作成します:
@Entity
@Table(schema="binding", name="contact")
@NamedQueries({
@NamedQuery(name="findContactBySimei",
query="SELECT e FROM Contact e WHERE e.simei=:simei"
),
@NamedQuery(name="findContactLikeYomiOrMail",
query="SELECT c FROM Contact c WHERE c.yomi LIKE :like OR c.mail LIKE :like"
)
})
publicclass Contact {
staticfinal String SIZE_MSG = "{min}から{max}の間の長さで入力してください";

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id = null;

@Version
@Column(name="OPTLOCK")
private Integer version = 0;

@NotNull @Size(min=1, max=10, message = SIZE_MSG)
@Column(nullable=false, length=30)
private String simei;
@NotNull @Size(min=1, max=30, message = SIZE_MSG)
@Column(nullable=false, length=30)
private String yomi;
@NotNull @Size(min=5, max=40, message = SIZE_MSG) @Email
@Column(nullable=false, unique=true, length=40)
private String mail;
@BeforeDate(date="1990/04/01") @Temporal(TemporalType.DATE)
private Date seinengappi;
private String seibetu;

public Contact() {
this("","","", Calendar.getInstance().getTime(), "");
}

public Contact(String simei, String yomi, String mail, Date seinengappi,
String seibetu) {
this.simei = simei;
this.yomi = yomi;
this.mail = mail;
this.seinengappi = seinengappi;
this.seibetu = seibetu;
}
・・・

}

データ操作クラスの作成
rapbindig.db.ConatactHome を作成します:
publicclass ContactHome {

public ContactHome() {
}

/** 指定クエリーに合致する連絡先取得. */
public List<Contact> findByQuery(String query) {
@SuppressWarnings("unchecked")
List<Contact> list = LocalJPA.getEntityManager().createQuery(query)
.getResultList();
return list;
}

/** 全件取得. */
public List<Contact> findAll() {
return findByQuery("SELECT c FROM Contact c ORDER BY c.id");
}

@SuppressWarnings("unchecked")
public List<Contact> findBySimei(String simei) {
return LocalJPA.getEntityManager()
.createNamedQuery("findContactBySimei")
.setParameter("simei", simei)
.getResultList();
}

@SuppressWarnings("unchecked")
public List<Contact> findLikeYomiOrMail(String like) {
return LocalJPA.getEntityManager()
.createNamedQuery("findContactLikeYomiOrMail")
.setParameter("like", like)
.getResultList();
}

/** 指定IDに合致するエンティティを取得. */
public Contact findById(Long id) throws Exception {
return LocalJPA.getEntityManager().find(Contact.class, id);
}

/** エンティティを追加する. */
publicvoid persist(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
LocalJPA.getCurrentEntityManager().persist(entity); //DBテーブルへ追加

}
publicvoid persistAndCommit(Contact entity) throws Exception {
persist(entity);
LocalJPA.commitAndClose();
}

/** 更新. */
public Contact update(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
return LocalJPA.getCurrentEntityManager().merge(entity);
}
publicvoid updateAndCommit(Contact entity) throws Exception {
update(entity);
LocalJPA.commitAndClose();
}

/** 削除. */
publicvoid remove(Contact entity) throws Exception {
LocalJPA.beginOrJoinTransaction();
EntityManager em = LocalJPA.getCurrentEntityManager();
em.remove(em.merge(entity)); //DBテーブルから削除

}
publicvoid removeAndCommit(Contact entity) throws Exception {
remove(entity);
LocalJPA.commitAndClose();
}
}

JPA コンテキストを保存するヘルパークラスを作成します。
rapbinding.db.LocalJPA:
publicclass LocalJPA {

privatestatic ThreadLocal<EntityManager> entityManagerHolder = new ThreadLocal<EntityManager>();
privatestatic ThreadLocal<EntityTransaction> transactionHolder = new ThreadLocal<EntityTransaction>();

privatestatic EntityManagerFactory entityManagerFactory;

/** エンティティマネージャ・ファクトリを取得.*/
publicstatic EntityManagerFactory getEntityManagerFactory() {
if (entityManagerFactory==null) {
entityManagerFactory = Persistence.createEntityManagerFactory("testpu");
}
return entityManagerFactory;
}

publicstaticvoid setEntityManagerFactory(String pu, Map<String, Object> properties) {
entityManagerFactory = new PersistenceProvider().createEntityManagerFactory(pu, properties);
}
publicstaticvoid setEntityManagerFactory(EntityManagerFactory emf) {
entityManagerFactory = emf;
}

/** フォルダから生成済みのエンティティマネージャを取得する.*/
publicstatic EntityManager getCurrentEntityManager() {
return entityManagerHolder.get();
}

/** エンティティマネージャを取得する.*/
publicstatic EntityManager getEntityManager() {
EntityManager entityManager = entityManagerHolder.get();
if (entityManager == null || !entityManager.isOpen()) {
entityManager = getEntityManagerFactory().createEntityManager();
entityManagerHolder.set(entityManager);
}
return entityManager;
}

/** エンティティマネージャ・フォルダとトランザクション・フォルダを空にする.*/
publicstaticvoid cleanup() {
EntityManager entityManager = entityManagerHolder.get();
EntityTransaction transaction = transactionHolder.get();

if (transaction != null && transaction.isActive()) {
if (transaction.getRollbackOnly()) {
transaction.rollback();
}
}
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}

entityManagerHolder.set(null);
transactionHolder.set(null);
}

/** 実行中のトランザクションのコミットとエンティティマネージャのクローズを行う.*/
publicstaticvoid commitAndClose()
throws Exception, SQLIntegrityConstraintViolationException, OptimisticLockException {
EntityManager entityManager = entityManagerHolder.get();
EntityTransaction transaction = transactionHolder.get();
if (transaction != null && transaction.isActive()) {
try {
transaction.commit();
} catch (Exception ex) {
try {
if (transaction.isActive())
transaction.rollback();
} catch (Exception ex2) {
throw ex; // 元の例外をスローする
}
//[EclipseLink-4002] SQLIntegrityConstraintViolationException
//[EclipseLink-5006] OptimisticLockException
//"Error Code: 1062" 重複エントリー
throw ex;
}
}
if (entityManager != null && entityManager.isOpen()) {
entityManager.close();
}
entityManagerHolder.set(null);
transactionHolder.set(null);
}

/** エンティティマネージャの生成とトランザクションの開始.*/
publicstaticvoid beginOrJoinTransaction()
throws Exception {
getTransaction(getEntityManager());
}

/** トランザクションの取得または生成&開始.*/
privatestatic EntityTransaction getTransaction(
EntityManager entityManager) {
// トランザクションフォルダにトランザクションが存在するかチェックする
EntityTransaction transaction = transactionHolder.get();
if (transaction == null || !transaction.isActive()) {
// もしフォルダになければ、トランザクションを生成して、開始する
transaction = entityManager.getTransaction();
transaction.begin();
transactionHolder.set(transaction);
} else {
//実行中のトランザクションにジョインする
}
return transaction;
}
}

セッションごとの ContactHome を管理するためのクラスを作成します。
rapbinding.SessionSingletonManager:
publicclass SessionSingletonManager  {

private ContactHome contactHome;

private SessionSingletonManager() {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("eclipselink.classloader", this.getClass().getClassLoader());
LocalJPA.setEntityManagerFactory("pu", properties);
}

/**
* セッションベースのシングルトンインスタンスを返す.
* @return SessionHomeManager インスタンス
*/

staticpublic SessionSingletonManager getInstance() {
return SingletonUtil.getSessionInstance(SessionSingletonManager.class);
}

/**
* 連絡先管理ホームを返す.
* @return 連絡先ホームインスタンス
*/

public ContactHome getContactHome() {
if (contactHome==null) {
contactHome = new ContactHome();
}
return contactHome;
}
}


バリデーションクラスの作成
JFace Data Binding で JSR303 バリデーションを使う」を参照してください。

AWS RDS に合わせて persistence.xml を修正する
src/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0"xmlns="http://java.sun.com/xml/ns/persistence">

<persistence-unit name="pu"transaction-type="RESOURCE_LOCAL">
<class>rapbinding.db.Contact</class>
<properties>
<!--
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/binding" />
<property name="javax.persistence.jdbc.user" value="user" />
<property name="javax.persistence.jdbc.password" value="pass" />
-->

<property name="javax.persistence.jdbc.driver"value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url"
value="jdbc:mysql://XXXXXX.ap-northeast-1.rds.amazonaws.com:3306/binding" />

<property name="javax.persistence.jdbc.user"value="user" />
<property name="javax.persistence.jdbc.password"value="pass" />

<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation"value="drop-and-create-tables" />
<property name="eclipselink.ddl-generation.output-mode"value="database" />
</properties>
</persistence-unit>
</persistence>

上の赤字部分(データベースのエンドポイント、マスターユーザー名、パスワード)を AWS のデータベース環境に合わせて変更します。 またテスト用のアプリなので ddl-genelation は "drop-and-create-tables" にしています。

必須プラグインの追加
マニフェストファイルを修正して、以下の必須プラグインを追加します。
Require-Bundle: org.eclipse.rap.rwt;bundle-version="[2.0.0,3.0.0)",
org.eclipse.core.databinding,
org.eclipse.core.databinding.beans,
org.eclipse.core.databinding.observable,
org.eclipse.core.databinding.property,
org.eclipse.rap.jface.databinding,
org.eclipse.equinox.common,
org.eclipse.rap.jface

WindowBuilder を使ってデータバインディングを行うと、自動的にデータバインディング関連の必須プラグインは追加されます。


WindwoBuilder を使って UIを構築する
WindowBuilder を使って JFace Data Binding を行う」と同じようにして、UIクラスを作成します。

rapbinding.BodyComposite:
publicclass BodyComposite extends Composite {
private DataBindingContext m_bindingContext;

private ContactHome fHome;
private List<Contact> fContacts;
private Contact fContact;

private SashForm sashForm;
private Composite fCompoLeft;
private Composite fCompoRight;
private Table fContactTable;
private Label lblErrorMessage;
private Label lblSimei;
private Text textSimei;
private Label lblYomi;
private Text textYomi;
private Label lblMail;
private Text textMail;
private Label lblSeibetu;
private Combo comboSeibetu;
private Label lblSeinengappi;
private DateTime dtmSeinengappi;
private Button btnSave;

private ControlDecoration decoSimei;
private ControlDecoration decoYomi;
private ControlDecoration decoMail;
private ControlDecoration decoSeibetu;

public BodyComposite(Composite parent, int style) {
super(parent, style);
setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
setLayout(new FillLayout());

sashForm = new SashForm(this, SWT.FILL);
sashForm.setSashWidth(15);

fCompoLeft = new Composite(sashForm, SWT.NONE);
fCompoLeft.setLayout(new GridLayout(1, false));

initData();
createTable(fCompoLeft);

fCompoRight = new Composite(sashForm, SWT.NONE);
fCompoRight.setLayout(new GridLayout(2, false));

new Label(fCompoRight, SWT.NONE);
lblErrorMessage = new Label(fCompoRight, SWT.NONE);
lblErrorMessage.setText("");

lblSimei = new Label(fCompoRight, SWT.NONE);
lblSimei.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
false, 1, 1));
lblSimei.setText("氏名");

textSimei = new Text(fCompoRight, SWT.BORDER);
textSimei.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
1, 1));
decoSimei = new ControlDecoration(textSimei, SWT.LEFT | SWT.TOP);

lblYomi = new Label(fCompoRight, SWT.NONE);
lblYomi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false,
1, 1));
lblYomi.setText("よみ");

textYomi = new Text(fCompoRight, SWT.BORDER);
textYomi.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
1, 1));
decoYomi = new ControlDecoration(textYomi, SWT.LEFT | SWT.TOP);

lblMail = new Label(fCompoRight, SWT.NONE);
lblMail.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false,
1, 1));
lblMail.setText("メール");

textMail = new Text(fCompoRight, SWT.BORDER);
textMail.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
1, 1));
decoMail = new ControlDecoration(textMail, SWT.LEFT | SWT.TOP);

lblSeibetu = new Label(fCompoRight, SWT.NONE);
lblSeibetu.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
false, 1, 1));
lblSeibetu.setText("性別");

comboSeibetu = new Combo(fCompoRight, SWT.NONE);
comboSeibetu.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true,
false, 1, 1));

lblSeinengappi = new Label(fCompoRight, SWT.NONE);
lblSeinengappi.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false,
false, 1, 1));
lblSeinengappi.setText("生年月日");

dtmSeinengappi = new DateTime(fCompoRight, SWT.BORDER);
dtmSeinengappi.setFont(SWTResourceManager.getFont("Lucida Grande", 18,
SWT.NORMAL));
new Label(fCompoRight, SWT.NONE);

btnSave = new Button(fCompoRight, SWT.NONE);
btnSave.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
System.out.println(fContact.toString());
}
});
btnSave.setText("保存");
sashForm.setWeights(newint[] { 1, 1 });
setContact(new Contact());
}

privatevoid initData() {
fHome = SessionSingletonManager.getInstance().getContactHome();
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 1980);
cal.set(Calendar.MONTH, 6);
cal.set(Calendar.DAY_OF_MONTH, 15);
try {
fHome.persist(new Contact("山田太郎", "やまだたろう", "yamada@xxx.com", cal
.getTime(), "男"));
fHome.persistAndCommit(new Contact("山田花子", "やまだはなこ",
"hanako@xxx.com", cal.getTime(), "女"));
} catch (Exception e) {
e.printStackTrace();
}
}

privatevoid createTable(Composite comp) {
fContactTable = new Table(comp, SWT.FULL_SELECTION | SWT.BORDER);
fContactTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true,
true, 1, 1));
fContactTable.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
fContactTable.setData(RWT.CUSTOM_ITEM_HEIGHT, Integer.valueOf(50));
GridData tableLayoutData = LayoutUtil.createFillData();
tableLayoutData.verticalIndent = 10;
fContactTable.setLayoutData(tableLayoutData);
fContactTable.setHeaderVisible(true);
fContactTable.setLinesVisible(true);
createColumn(fContactTable, "氏名/よみ", 200, SWT.LEFT);
createColumn(fContactTable, "メール", 180, SWT.LEFT);
createColumn(fContactTable, "生年月日", 120, SWT.LEFT);
createColumn(fContactTable, "性別", 60, SWT.LEFT);

fContacts = fHome.findAll();
createItems(fContactTable, fContacts);
fContactTable.addSelectionListener(new SelectionAdapter() {
@Override
publicvoid widgetSelected(SelectionEvent e) {
int i = fContactTable.getSelectionIndex();
if (i < 0) return;
setContact((Contact)fContactTable.getItem(i).getData());
}
});
}

privatestatic TableColumn createColumn(Table table, String name,
int width, int alignment) {
TableColumn column = new TableColumn(table, SWT.NONE);
column.setText(name);
column.setWidth(width);
column.setAlignment(alignment);
return column;
}

privatestaticfinal SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
privatevoid createItems(Table table, List<Contact> contacts) {
for (Contact c: contacts) {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(0, "<b>" + c.getSimei() + "</b><br/>(" + c.getYomi() + ")");
item.setText(1, "<a href=''>" + c.getMail() + "</a>");
item.setText(2, sdf.format(c.getSeinengappi()));
item.setText(3, c.getSeibetu());
item.setData(c);
}
}

publicvoid setContact(Contact contact) {
if (m_bindingContext != null) {
m_bindingContext.dispose();
}
fContact = contact;
m_bindingContext = initDataBindings();
}

protected DataBindingContext initDataBindings() {
Realm realm = SWTObservables.getRealm(Display.getCurrent());
DataBindingContext bindingContext = new DataBindingContext(realm);
//
IObservableValue observeTextTextSimeiObserveWidget = WidgetProperties
.text(SWT.Modify).observe(textSimei);
IObservableValue simeiFContactObserveValue = PojoProperties.value(
"simei").observe(realm, fContact);
bindingContext.bindValue(observeTextTextSimeiObserveWidget,
simeiFContactObserveValue,
getContactUpdateValueStrategy("simei", decoSimei, false), null);
//
IObservableValue observeTextTextYomiObserveWidget = WidgetProperties
.text(SWT.Modify).observe(textYomi);
IObservableValue yomiFContactObserveValue = PojoProperties
.value("yomi").observe(realm, fContact);
bindingContext.bindValue(observeTextTextYomiObserveWidget,
yomiFContactObserveValue,
getContactUpdateValueStrategy("yomi", decoYomi, false), null);
//
IObservableValue observeTextTextMailObserveWidget = WidgetProperties
.text(SWT.Modify).observe(textMail);
IObservableValue mailFContactObserveValue = PojoProperties
.value("mail").observe(realm, fContact);
bindingContext.bindValue(observeTextTextMailObserveWidget,
mailFContactObserveValue,
getContactUpdateValueStrategy("mail", decoMail, false), null);
//
IObservableValue observeTextComboSeibetuObserveWidget = WidgetProperties
.text().observe(comboSeibetu);
IObservableValue seibetuFContactObserveValue = PojoProperties.value(
"seibetu").observe(realm, fContact);
bindingContext.bindValue(observeTextComboSeibetuObserveWidget,
seibetuFContactObserveValue, null, null);
//
IObservableValue observeSelectionDtmSeinengappiObserveWidget = WidgetProperties
.selection().observe(dtmSeinengappi);
IObservableValue seinengappiFContactObserveValue = PojoProperties
.value("seinengappi").observe(realm, fContact);
bindingContext.bindValue(observeSelectionDtmSeinengappiObserveWidget,
seinengappiFContactObserveValue, null, null);
//
return bindingContext;
}

private UpdateValueStrategy getContactUpdateValueStrategy(String propName,
ControlDecoration deco, boolean multi) {
UpdateValueStrategy storategy = new UpdateValueStrategy();
storategy.setAfterConvertValidator(new BeanValidator(Contact.class,
propName, deco, multi));
return storategy;
}
}


テンプレートで自動生成されたクラスの修正
Rap Hello World テンプレートを使うと、RAPアプリのエントリポイントクラスが作成されます。このクラスを以下のように修正してください。
rapbinding.BasicEntryPoint:
publicclass BasicEntryPoint extends AbstractEntryPoint {

private BodyComposite fBody;

@Override
protected Shell createShell(Display display) {
Shell shell = super.createShell(display);
shell.setData(RWT.CUSTOM_VARIANT, "mainshell");
shell.setMaximized(true);
return shell;
}

@Override
protectedvoid createContents(Composite parent) {
parent.setLayout(LayoutUtil.createGridLayout(1, false, true, true));
fBody = new BodyComposite(parent, SWT.FILL);
}

}

build.properties の修正
次の一行を追加します。
javacDefaultEncoding.. = UTF-8


ローカル環境で実行する
自動生成された rapbinding.launch を右クリックして Run As > Run Configurations.. を選択して、実行構成ダイアログを表示します:

Bundles タブで、"Include optional dependencies when computing required bundles" をチェックして、"Add Required Bundles" ボタンをクリックして、必要なバンドルを追加します。 "Apply" を押して変更を保存し、"Run" を押して実行します。 

左の一覧テーブルから、連絡先を選択すると、右側に詳細が表示されます。

氏名フィールドで、不正なデータを入力すると検証エラーが表示されます。


以上で RAP アプリケーションは完成です。 後はWARファイルをエクスポートして、それを Elastic Beanastalk 環境へアップロードするだけです。 これは次回に説明します。



AWS Elastic Beanstalk へ RAP アプリをデプロイする(2) デプロイと実行

$
0
0
前回ポストでは、RAPアプリを作成して、ローカル環境で実行するところまで説明しました。今回はWARファイルのエクスポートと、Elastic Beanstalk 環境へのデプロイを説明します。


WARファイルのエクスポート
p2 リポジトリ http://download.eclipse.org/releases/kepler/ から WAR Products ツールがインストールされていることを前提に説明します。

(WARプロダクト設定の作成)
rapbinding プロジェクトを選択して New > Other... を選択して次のウィザードを表示します:


"Plug-in Development/WAR Product Configuration" を選択して Next をクリックします。


上の画面で、"Use a launch configuration" を選択して、ローカル環境での実行に使用した実行構成ファイル "rapbinding.launch" を指定します。適当な名前を付けて Finish を押します。

(WARファイルのエクスポート)
作成したWARプロダクト設定をダブルクリックしてオープンします。
Configurations ページを表示して(下の画面)、必要なバンドルを追加してください。


Overview ページへ移動して(下の画面)、エラーが無いか検証して、大丈夫なら WARファイルを適当なディレクトリへエクスポートします。


WARファイルのデプロイ
AWS Managment Console を表示して(下の画面)、Elastic Beanstalk を選択します。

次の画面で青枠のボタンを押します。

アップロードファイルの選択ダイアログが表示されるので、下の画面のように設定します。

"Upload and Deploy New Version" ボタンを押せば、アップロードとデプロイが開始します。

RAPアプリの実行
アプリケーションを実行する前に、MySQL Workbench を起動して、AWSデータベースに binding スキーマを作成しておきます。

ブラウザで、URLとして、"app01env の URL"/hello を入力します。



還暦プログラマーの闘病記

$
0
0
ずいぶんと久しぶりの投稿になってしまいました。今回はこのブログのテーマである技術的な内容ではありませんが、私と同年齢で現役で仕事をされている方も多いと思い、私の体験を投稿することにします。

受診
昨年の秋頃、風邪の症状(熱や咳)がありましたが、市販薬などを飲みながら仕事に行ってました。2週間近くたっても症状は悪くなるばかりなので、病院で診てもらうことにしました。通常の血液検査と胸部レントゲン検査を行って受診を待っていたのですが、検査の結果から次々と別の検査が追加されて、受付は10時頃でしたが、心電図、心臓エコー、大量の血液検査、追加のレントゲン検査、造影剤を使った心臓CT などが終わったときには17時を過ぎていました。

緊急入院
検査後、担当医から入院の必要があると言われ、その日のうちに入院することになりました。診断は肺炎ということですが、さらに検査が必要とのことでした。

  • 1日目:
    通常の食事は出されず、治療薬、補助栄養などの点滴による治療が行われました。
  • 2日目:
    点滴による治療が継続され、この時点では、電話やトイレには歩いて移動することができました。
  • 3日目:
    症状はより悪くなり、3回ほどトイレに行っただけで疲労が激しく、夜からは尿器を使用することになりました。
  • 4〜6日:
    同様の治療と検査が繰り返されましたが、さらに症状は悪化して、尿器を使う体力もなくなり、尿道にバルーンを入れられました。
  • 7日目:
    抗生剤による治療では成果がなく、より設備の整った病院に転院するしかないと伝えられたのです。
転院
午後2時、消防局の救急車が到着し、二人の救急隊員がストレッチゃーでてきぱきと搬送してくれました。頭はしっかりしてましたが、車が激しく揺れて、息が苦しく、もうダメかなと弱気になりました。しかし、車はすぐに到着して、ストレッチャーが降ろされると、冷たい秋の風が顔にあたり、久しぶりの温かい日差しを受けて生き返った気持ちでした。

ER
転院先のERでは、皮膚の状態の撮影、動脈および静脈からの大量の血液採取、胸部レントゲン、胸部CT、像映剤を使った肺のCTなど、再度様々な検査を受けましたが、大勢のスタッフが同時に作業して、1時間半ほどで終了しました。

転院先への入院
  • 1日目:
    前の病院では食事は出されませんでしたが、栄養補給は消化器官を使った方がいいとのことで、嚥下食(喉につまらないように柔らかくしたもの)が出て、1週間ぶりに食べ物を口にすることができました。
  • 2〜3日:
    検査結果などから、間質性肺炎(肺の間質性組織が炎症する疾患)の疑いが濃厚だが、しばらくは抗生剤を中心にした治療が継続して行われました。しかし症状は改善せず、手遅れに成る前にステロイドなどによる集中的な治療が必要だと説明され、3日目の夜、ICUへ移されました。

集中治療室
  • 1〜4日:麻酔により眠っていたため、この間の記憶はほとんどありません。何回か身体の向きを変えられたり、脇や股の付け根に氷をあてられたのをかすかに覚えています。目が覚めて日付を聞かされて初めて4日が経過していたことを知りました。峠は越えましたが、まだ自立呼吸はできず、気管チューブをはじめ、様々な管や針が身体中に刺さっている状態でした。
  • 5〜7日:この時期がもっとも苦しい時期でした。呼吸が苦しい、薬の影響で眠れない、モニターに異常があればすぐに看護婦がやってくる。異常がなくても定期的に痰の吸引や、点滴の交換や採血、ベッドサイドモニターの確認など、ほとんどひっきりなしに看護婦がやってくる。全く眠る事ができず、さらに呼吸が苦しくなり、それがモニターに通知されて警告音が頻繁になります。楽しい事を考え、息を吐く事と吸う事だけに意識を集中しました。それが三日三晩続きました。その間に少しずつ息苦しさが減り、血中酸素濃度(SpO2)が改善していきました。
  • 8〜10日:さらに様々な数値が安定して、10日目ついに挿管チューブが抜かれ、酸素補助で自立呼吸できるようになりました。
  • 11〜14日:その後は順に股動脈の穿刺針、鼻の栄養補給チューブ、Aライン、尿道のバルーン、右腕の点滴針など、身体に刺さっていたほとんどの管や針が取り除かれて、残るのは左腕の点滴針だけになり、やっと一般病棟へ戻ることができました。

リハビリ
使わなければ、1日で筋肉は5%ずつ弱っていくそうですが、4週間ほどベッドに寝たきりだった上、私の場合は間質性肺炎の原因が膠原病(多発性筋炎)によるものだったため、それによる筋力低下も重なって、一般病棟に戻ったときは腕はある程度動かせましたが、足はタオルが乗っているだけで1ミリも持ち上げることができなくなっていました。しばらくはベッドの上で手で支えながら少しずつ足を動かして、手伝ってもらいながら車いすに座れるようになり、自分で立てるようになるには5週間ぐらいかかりました。歩行器を使わずに歩けるようになるにはさらに1週間程度が必要でした。入院から3ヶ月、どうにか退院することができました。

ドクター・ハウス
私は海外ドラマのドクター・ハウスが好きで、全話楽しんで観てました。その中では、私が受けたものよりはるかに過酷な検査や治療が毎回描かれます。今までは、最高の医療チームが病気を推理し患者を助けるストーリーを純粋に楽しめましたが、これから先も同じように楽しめるか少し心配です。今回私は最終的に信頼できる医療チームにより、適切な治療を受ける事ができました。彼らを信頼して、苦しい検査も治療もまかせることができました。本当に大切なのは、そういった医療チームに出会えることだと感じています。

厚生労働省の2012年の推計によれば、肺炎は日本人の死亡原因の3位です。死に至るケースは65歳以上の高齢者がほとんどですが、私のように発見が遅く治療が遅れると、手遅れに成る事もあります。運良く回復しても、社会復帰までに相当な期間が必要になります。くれぐれもただの風邪だと自己診断せず、すぐに医療機関で診てもらうことが大切ですね。

Spring MVC + Thymeleaf による WEB アプリケーション開発(1)プロジェクト作成

$
0
0
このシリーズの1回目では、Spring MVC と Thymeleaf によるWEBアプリケーション開発の雛形となるプロジェクトを作成します。
開発環境は以下の通りです。
  • Maven 3.1.1
  • MySQL 5.6.17
  • Eclipse IDE for Java EE 4.3.2
  • Tomcat 7.0.29

依存ライブラリの主なものは以下の通りです。詳細は、後述する pom.xml を参照してください。
  • Spring MVC 3.2.8.RELEASE
  • Spring Data JPA 1.5.1.RELEASE
  • Thymeleaf 2.1.3.RELEASE
  • Hibernate 4.1.9.FINAL

プロジェクトの作成
eclipse メニューから File > New > Project… で新規プロジェクト作成ウィザードを表示します:

Dynamic Web Project を選択して Next をクリックします:

プロジェクト名:mvcdemo
ターゲットランタイム:Apache Tomcat v7.0
ダイナミックWEBのモジュールバージョン:3.0
を設定して Finish をクリックします。

以下のような構造のプロジェクトが作成されます:



Maven による依存ライブラリの管理
プロジェクトを Maven プロジェクトに変換します。プロジェクトを右クリックして、ポップアップメニューから Configure > Convert to Maven を選択してください。 POMファイル作成ウィザードが表示されます:
上図のように、グループID、アーティファクトIDなどを設定して Finish をクリックします。
以下のような pom.xml ファイルがプロジェクトのルートに作成されます:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>com.itrane</groupId>
<artifactId>mvcdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>mvcdemo</name>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

この pom.xml に properties と dependencies を追加します:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
<name>mvcdemo</name>

<!-- ここに依存ライブラリを指定 -->
<properties>
<!-- Spring / Hibernate version -->
<version.spring>3.2.8.RELEASE</version.spring>
<version.spring.data>1.5.1.RELEASE</version.spring.data>
<version.spring.boot>1.0.0.RELEASE</version.spring.boot>
<version.hibernate>4.1.9.FINAL</version.hibernate>

<version.aopalliance>1.0</version.aopalliance>
<version.thymeleaf>2.1.3.RELEASE</version.thymeleaf>
<version.mysql>5.1.22</version.mysql>
<!-- Logger -->
<version.sl4j>1.7.7</version.sl4j>
<version.logback>1.0.7</version.logback>
<!-- Servlet & taglibs -->
<version.servlet>3.0.1</version.servlet>
<version.jstl>1.2</version.jstl>
<version.taglibs>1.1.2</version.taglibs>
<!-- Test -->
<version.junit>4.11</version.junit>
<version.dbunit>2.4.9</version.dbunit>
</properties>

<dependencies>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>${version.thymeleaf}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring3</artifactId>
<version>${version.thymeleaf}</version>
</dependency>

<!-- Servlet & taglibs -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${version.servlet}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${version.jstl}</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>${version.taglibs}</version>
</dependency>

<!-- JSR-330 -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

<!-- Spring dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${version.spring}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${version.spring}</version>
</dependency>

<!-- Third Party dependencies -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>${version.aopalliance}</version>
</dependency>

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>

<!-- Spring DATA -->
<dependency>
<groupId>org.springframework.data</groupId >
<artifactId>spring-data-jpa</artifactId>
<version>${version.spring.data}</version >
</dependency>
<!-- Hibernate and JPA -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${version.hibernate}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${version.hibernate}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.java-persistence</groupId>
<artifactId>jpa-api</artifactId>
<version>2.0-cr-1</version>
</dependency>

<!-- JSR 303 with Hibernate Validator -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
</dependency>

<!-- MySQL JDBC Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${version.mysql}</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.0.4</version>
</dependency>

<!-- Logger Library -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.sl4j}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${version.logback}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<version>${version.spring.boot}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${version.spring.boot}</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${version.spring}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>${version.dbunit}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
...
</plugins>
</build>
</project>


ログ出力のための設定
ログ出力には、sl4j と logbackを使います(pom.xml の dependencies に追加)。以下のような logback設定ファイルをプロジェクトの src ディレクトリに作成します。
mvcdemo/src/logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE"class="ch.qos.logback.core.rolling.RollingFileAppender">
<prudent>true</prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>

<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT"class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>

<logger name="com.itrane.mvcdemo" level="DEBUG"/>

<root>
<level value="info" />
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>

上記の設定では、com.itrane.mvcdemo パッケージ以下のクラスに対してログレベルをDEBUGに設定しています。

Spring MVC Java 設定
Spring では従来、アプリケーションのコンテキスト XML ファイルを使用して設定を行うのが一般的ですが、このプロジェクトでは JAVA ベースの設定を使用します。AbstractAnnotationConfigDispatcherServletInitializer を拡張して以下のような AppInit クラスを作成します。
com.itrane.mvcdemo.init.AppInit:
/**
* web.xml に変わり、アプリの初期化を行う.
*/

publicclass AppInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
returnnew Class<?>[0];
}

@Override
protected Class<?>[] getServletConfigClasses() {
returnnew Class<?>[]{ WebAppConfig.class };
}

@Override
protected String[] getServletMappings() {
returnnew String[]{ "/" };
}

/*
* 日本語の文字化けを防ぐために エンコーディング・フィルタを設定する
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#getServletFilters()
*/

@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);

returnnew Filter[] { characterEncodingFilter };
}
}

上記イニシャライザーの getServletConfigClasses( ) メソッドで、WEBアプリケーション設定クラスとして、WebAppConfig クラスを指定します。このクラスの内容は以下のようになります。
com.itrane.mvcdemo.init.WebAppConfig:
/**
* Spring フレームワークの設定.
* 注意:この Java 設定クラスを使用するには Spring Framework 3.1 以上が必要
*/

@Configuration
@EnableWebMvc
@ComponentScan("com.itrane.mvcdemo")
@PropertySource("classpath:resources/app.properties")
publicclass WebAppConfig extends WebMvcConfigurerAdapter {

@Resource
private Environment env;

//静的リソースの設定
@Override
publicvoid addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}

//テンプレートリゾルバーの設定
@Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".html");
//NB, selecting HTML5 as the template mode.
resolver.setTemplateMode("HTML5");
resolver.setCacheable(false);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}

//Thymeleaf テンプレートエンジンの設定
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}

//Thymeleaf ビューリゾルバー設定
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[]{"*"});
viewResolver.setCache(false);
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}

//メッセージソースの設定
//WEBページでプロパティファイルを使用できる
//日本語メッセージ:messages_ja.properties
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(env.getRequiredProperty("message.source.basename"));

// trueをセットすれば、メッセージのキーがない場合にキーを表示
// false の場合、NoSuchMessageExceptionを投げる
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
// # -1 : リロードしない、0 : 常にリロードする
messageSource.setCacheSeconds(0);
return messageSource;
}
}

上記設定クラスでは、静的リソースの設定、Thymeleaf のテンプレートエンジン、テンプレートリゾルバーの設定、メッセージソースの設定を行っています。
アノテーションの @Configuration はこのクラスが設定ファイルであることを示します。 @EnableWebMvc は Spring MVC の機能を有効にします。@ComponentScan("com.itrane.mvcdemo") は、com.itrane.mvcdemo パッケージ以下の Spring コンポーネント(@Controller や @Service などの注釈がついたクラス)を検出して登録します。@PropertySource は、設定内容を外部のプロパティファイルから読み込む事を指定します 。
src/resources/app.properties ファイルの内容は以下のようになります:
#message source
message.source.basename=classpath:resources/messages

コントローラの作成
アプリケーションのホームページを表示するための簡単なコントローラを作成します。このコントローラはアプリケーションの URL "http://localhost:8080/mvcdemo/"がリクエストされたとき、ログを出力し、現在の時間を取得して、Model オブジェクトに設定します。表示ビューとして、"home/home.html"を指定します。
com.itrane.mvcdemo.controller.HomeController:
@Controller
publicclass HomeController {

finalstaticprivate Logger log = LoggerFactory.getLogger(HomeController.class);

@RequestMapping(value="/", method=RequestMethod.GET)
public String navbar(Model model) {
log.debug("");
model.addAttribute("today", Calendar.getInstance().getTime());
return"/home/home";
}
}

ビューの作成
ビューフレームワークとして ThymeleafBootstrap を使用します。Bootstrap の使用に必要なファイルをダウンロードして、 WebContent/resources フォルダにコピーしてください:

設定クラスの WebAppConig のテンプレートリゾルバーの設定にしたがって、ビューは WEB-INF/views フォルダ以下に作成する必要があります。アプリケーションのホームページ・ビューをviews フォルダ下の home フォルダーに作成します。
WEB-INF/views/home/home.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:include="fragment/frag01 :: htmlhead" th:with="title='Spring MVC デモ'"></head>

<style>
.contents {
margin: 50px;
}
</style>
<body>

<div th:include="fragment/frag01 :: navbar"></div>

<div class="contents">
<div class="container">

<div class="row">
<div class="col-lg-12">
<h2 class="page-header" th:utext="#{home.title}">Home
<small>(Spring MVC Demo)</small>
</h2>
</div>
</div>

<div class="row">
<div class="col-lg-12">
<p th:utext="#{home.welcome}">Welcome to our mvc sample</p>
<p><span th:text="${today}">2014/05/01</span></p>
<br />
<br />
<hr />
<div th:include="fragment/frag01 :: footer"></div>
</div>
</div>

</div>
</div>
</body>
</html>

(home.html : フラグメントのインクルード)
このビューでは、Thymeleaf のテンプレート機能を使って、共通コンテンツ(HTMLヘッダ、ナビゲーション・バー、フッター)をフラグメントとしてインクルードしています。
HTMLヘッダのインクルード:
<head th:include="fragment/frag01 :: htmlhead" th:with="title='Spring MVC デモ'"></head>
ナビゲーション・バーのインクルード:
<div th:include="fragment/frag01 :: navbar"></div>
フッターのインクルード:
<div th:include="fragment/frag01 :: footer"></div>

th:include では "テンプレートのフォルダ/テンプレート名 :: フラグメント名"により、インクルードするフラグメントを参照します。

アプリケーションの各ビューが参照するテンプレートファイルを作成します。
WEB-INF/fragment/frag01.html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<!-- HTMLヘッダ -->
<head th:fragment="htmlhead">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title th:text="${title}">(title)</title>

<!-- CSS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />

<!-- HTML5 shim, IE6-8で HTML5 要素をサポートする -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

<!-- JavaScript : jquery, bootstrap, custom -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</head>

<body>

<!-- ナビゲーション・バー -->
<div th:fragment="navbar">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/}">Spring MVC デモ</a>
</div>

<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">About</a></li>

<li class="dropdown"><a href="#"class="dropdown-toggle"
data-toggle="dropdown"> 店舗管理 <b class="caret"></b></a>
<ul class="dropdown-menu" role="menu">
<li><a href="#">店舗一覧(ajax)</a></li>
<li class="divider"></li>
<li><a href="#">全件削除</a></li>
<li><a href="#">1000件追加</a></li>
</ul></li>
</ul>
</div>
</div>
</nav>
</div>
<!-- フッター -->
<div th:fragment="footer">
<footer>
<p>&copy; Spring MVC デモ 2013</p>
</footer>
</div>

</body>
</html>

上のファイル中の th:fragment 属性はフラグメントを定義します。フラグメント "htmlhead"では各ビューで共通の HTMLヘッダーを定義します。ここでは Bootstrap の使用に必要な css , javascript を指定しています。

(home.html : 国際化メッセージファイル)
<p th:utext="#{home.welcome}">Welcome to our mvc sample</p>

home.html の上記の部分はウェルカムメッセージを日本語のメッセージに変換します。 #{home.welcom} 式は、メッセージファイル中の識別キー "home.welcome"に対応するメッセージに変換されます。th:utext または th:text は変換結果で、タグのボディ部を書き換えます。home.html と同じフォルダに、メッセージファイル"home_ja.properties"があれば、 Thymeleaf は、このファイルから対応するメッセージを見つけます。
WEB-INF/views/home/home_ja.propeties の内容は次のようになります:
home.title=Spring MVC デモ <small>ホーム</small>
home.welcome=<b>MVCサンプル</b>へようこそ!


ここまでの作業を確認
作成したアプリケーションを実行します:


 実行の結果:
フラグメント(htmlhead, navbar, footer) が正しくインクルードされています。
Bootstrap により、ナビゲーションバーの表示、ページのレイアウトが正しく行われています。
ウェルカムメッセージは、正しく日本語に変換されています。
コントローラーから返された日付が正しく表示されています。

さらに、eclipse IDE のコンソールビューで、ログ出力を確認することもできます:
2014/06/1313:17:36:829 DEBUG - HomeController.navbar 
2014/06/1313:17:36:848 INFO - TemplateEngine.initialize [THYMELEAF] INITIALIZING TEMPLATE ENGINE
.....

まとめ
Spring フレームワークの Spring MVC を利用して、モデル・ビュー・コントローラ構造のWEBアプリケーションを柔軟に構築できます。Spring MVC とThymeleaf を連携することで、ビューの共通要素をフラグメント化したり、HTML5の機能を利用することが可能です。jQueryBootsrapにより、クールな UI を簡単に実現できます。これらのフレームワークはアプリ要件に応じて他の強力なフレームワークと比較的簡単に組み合わせる事ができます。
※データベースアクセスのための設定は、次回に説明します。

ソースコード
ソースコードは GitHub-branch:mvcdemo01 からダウンロードできます。

Spring MVC + Thymeleaf による WEB アプリケーション開発(2)データベース設定

$
0
0
このシリーズの2回目では、前回(1回目)作成したプロジェクトに、データベース・アクセスのための設定を追加します。

データベース設定
前回作成した app.propertiesを次のように変更します。
src/resources/app.properties:
#message source
message.source.basename=classpath:resources/messages

#DB properties:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/demomvc
db.username=demo
db.password=pass

#Hibernate Configuration:
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
model.scan.package=com.itrane.mvcdemo.model
開発の初期段階では、上記ファイルの  hibernate.hbm2ddl.auto=update を hibernate.hbm2ddl.auto=create-drop にしておけば、アプリの起動時に、作成(または変更)したエンティティに対応するテーブルが、MySQLデータベースに自動的に作成されます(上記の例では、ユーザー:demo 、パスワード: pass でアクセス可能な demomvc スキーマを作成しておく必要があります)。

上記のプロパティ値を使用するデーベース設定クラスを作成します。
com.itrane.mvcdemo.init.DbConfig:
/**
* データベース設定.
* @EnableTransactionManagement : <tx:annotation-driven transaction-manager="transactionManager" />
* @EnableJpaRepositories("..."): <jpa:repositories base-package="..." />
*/

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.itrane.mvcdemo.repo")
publicclass DbConfig {

// プロパティの値は src/resources/app.properties から取得
privatestaticfinal String P_DB_DRIVER = "db.driver";
privatestaticfinal String P_DB_URL = "db.url";
privatestaticfinal String P_DB_USERNAME = "db.username";
privatestaticfinal String P_DB_PASSWORD = "db.password";

privatestaticfinal String P_HB_DDL_AUTO = "hibernate.hbm2ddl.auto";
privatestaticfinal String P_HB_DIALECT = "hibernate.dialect";
privatestaticfinal String P_HB_SHOW_SQL = "hibernate.show_sql";
privatestaticfinal String P_MODEL_SCAN = "model.scan.package";

@Resource
private Environment env;

// データソースの設定。
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();

dataSource.setDriverClassName(env.getRequiredProperty(P_DB_DRIVER));
dataSource.setUrl(env.getRequiredProperty(P_DB_URL));
dataSource.setUsername(env.getRequiredProperty(P_DB_USERNAME));
dataSource.setPassword(env.getRequiredProperty(P_DB_PASSWORD));

return dataSource;
}

//エンティティマネージャ
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean
= new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
entityManagerFactoryBean.setPackagesToScan(
env.getRequiredProperty(P_MODEL_SCAN));

//JPAプロパティの設定
Properties properties = new Properties();
properties.put(P_HB_DDL_AUTO, env.getRequiredProperty(P_HB_DDL_AUTO));
//properties.put("hibernate.hbm2ddl.auto", "create-drop");
properties.put(P_HB_DIALECT, env.getRequiredProperty(P_HB_DIALECT));
properties.put(P_HB_SHOW_SQL, env.getRequiredProperty(P_HB_SHOW_SQL));
entityManagerFactoryBean.setJpaProperties(properties);

return entityManagerFactoryBean;
}

//トランザクションマネージャ
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}

上記のコードで、@EnableTransactionManagement アノテーションは、トランザクション管理を有効にします。@EnableJpaRepositories は、パッケージ "com.itrane.mvcdemo.repo"のクラスを Spring Data の JPA リポジトリーとして処理するように指定します。 また、@Bean アノテーションを使って、データソース、エンティティマネージャ・ファクトリ・ビーン、トランザクションマネージャのSpring Bean を定義します。

次に、このデータベース設定クラスをインポートするためのアノテーション @Import({DbConfig.class}) を WebAppConfig クラスに追加します。WebAppConfig では @PropertySource アノテーションで、app.properties を参照するように設定しているため、DbConfig クラスのビーン定義で使用する設定値を app.properties から読み込むことができます。ここでは WebAppConfig と DbConfig を別のクラスとして作成していますが、1つにまとめることも可能です。修正後の WebAppConfig は次のようになります。
com.itrane.mvcdemo.init.WebAppConfig :
/**
* Spring フレームワークの設定.
* 注意:この Java 設定クラスを使用するには Spring Framework 3.1 以上が必要
*/

@Configuration
@EnableWebMvc
@Import({DbConfig.class}) //データベース設定をインポート
@ComponentScan("com.itrane.mvcdemo")
@PropertySource("classpath:resources/app.properties")
publicclass WebAppConfig extends WebMvcConfigurerAdapter {

@Resource
private Environment env;

//静的リソースの設定
@Override
publicvoid addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}

//テンプレートリゾルバーの設定
@Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".html");
//NB, selecting HTML5 as the template mode.
resolver.setTemplateMode("HTML5");
resolver.setCacheable(false);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}

//Thymeleaf テンプレートエンジンの設定
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}

//Thymeleaf ビューリゾルバー設定
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[]{"*"});
viewResolver.setCache(false);
viewResolver.setCharacterEncoding("UTF-8");
return viewResolver;
}

//メッセージソースの設定
//WEBページでプロパティファイルを使用できる
//日本語メッセージ:messages_ja.properties
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(env.getRequiredProperty("message.source.basename"));

// trueをセットすれば、メッセージのキーがない場合にキーを表示
// false の場合、NoSuchMessageExceptionを投げる
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
// # -1 : リロードしない、0 : 常にリロードする
messageSource.setCacheSeconds(0);
return messageSource;
}
}

次回は、モデル(エンティティクラス)を作成し、Spring Data JPA を使ったデータアクセス機能をテストします。

Spring MVC + Thymeleaf による WEB アプリケーション開発(3)モデルの作成

$
0
0
3回目の今回は、モデル(エンティティクラス)を作成し、Spring Data JPA を使ったデータアクセス機能をテストします。
※このシリーズの1回目2回目も参照してください。

モデルの作成
このアプリのモデルは実用的なものではありません。Spring MVC でのバリデーション機能や、Thymeleaf での form タグや input タグの使い方をチェックできるように、色々なタイプのフィールドを持つものにします。AbstractPersistentEntity を拡張した、com.itrane.mvcdemo.model.Shop クラスを作成します:
@Entity
@Table(name = "shops")
publicclass Shop extends AbstractPersistentEntity {

privatestaticfinallong serialVersionUID = 1L;

@Column(length=20)
@NotNull @Size(min = 1, max = 20)
private String name;

@Column(length=13)
@Size(min = 10, max = 13)
private String phone;

@Column(length=50)
@Size(max = 50)
private String email;

@Column(length=10)
@Size(max = 10) @NotNull @NotEmpty
private String kaitenBi;

@Column(name = "employees_number")
@Min(value=5) @Max(value=100)
private Integer emplNumber;

//-- getter , setter 省略

com.itrane.mvcdemo.model.AbstractPersistentEntity は次のようなものです:
/**
* エンティティのスーパークラス.
*  ・Serializable の実装
*  ・Id フィールド(自動設定)
*  ・version フィールド
*/

@MappedSuperclass
publicabstractclass AbstractPersistentEntity implements Serializable {

privatestaticfinallong serialVersionUID = 1L;

protected Long id = null;
protected Integer version = 0;

@Version
@Column(name="OPTLOCK")
public Integer getVersion() {
return version;
}

publicvoid setVersion(Integer version) {
this.version = version;
}

@Id
@GeneratedValue
public Long getId() {
return id;
}

publicvoid setId(Long id) {
this.id = id;
}

/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/

@Override
publicint hashCode() {
finalint prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
}

リポジトリーの作成
先に作成した Shop エンティティの CRUD 操作を行うため、Spring Data フレームワークの JpaRepository インターフェースを拡張して、ShopRepository インターフェースを作成します。JpaRepository および、そのスーパークラスには findAll( ) をはじめとして、CRUD 処理のための様々なメソッドが用意されています。そのため、それらの継承メソッドを使用するだけなら、ShopRepository では何もする必要がありません。
com.itrane.mvcdemo.repo.ShopRepository:
publicinterface ShopRepository extends JpaRepository<Shop, Long> {

@Modifying(clearAutomatically = true)
@Transactional
@Query(value="delete from shops", nativeQuery=true)
publicvoid deleteAll();

public List<Shop> findByName(String searchStr);
public List<Shop> findByNameLike(String searchStr);

public List<Shop> findByPhone(String searchStr);
public List<Shop> findByPhoneLike(String searchStr);

public List<Shop> findByEmail(String searchStr);
public List<Shop> findByEmailLike(String searchStr);

public List<Shop> findByKaitenBi(String searchStr);
public List<Shop> findByKaitenBiLike(String searchStr);

public List<Shop> findByEmplNumber(Integer searchInt);
public List<Shop> findByEmplNumberGreaterThan(Integer searchInt);
}

サービスの作成
実際のアプリケーションでは、1つのトランザクション内で複数のテーブルにまたがった処理が必要です。インターフェース ShopService と、インターフェースを実装する ShopServiceImpl を作成します。
com.itrane.mvcdemo.service.ShopService
publicinterface ShopService {

public Shop create(Shop shop);
public List<Shop> create(Iterable<Shop> shops);
public Shop delete(long id) throws ShopNotFound;
public List<Shop> findAll();
public List<Shop> findAll(int pageIndex, int rowsOfPage);
public List<Shop> findByPage(int pageNum, int pageSize, String sortDir, String... cols);
publiclong countShops();
public Shop update(Shop shop) throws ShopNotFound;
public Shop findById(long id);
publicvoid deleteAll();
}

com.itrane.mvcdemo.service.ShopServiceImpl:
@Service
publicclass ShopServiceImpl implements ShopService {

@Resource
private ShopRepository shopRepository;

public ShopServiceImpl() {}

public ShopServiceImpl(ShopRepository repo) {
this.shopRepository = repo;
}

@Override
@Transactional
public Shop create(Shop shop) {
Shop createdShop = shop;
return shopRepository.save(createdShop);
}

@Override
@Transactional
public List<Shop> create(Iterable<Shop> shops) {
return shopRepository.save(shops);
}

@Override
public Shop findById(long id) {
return shopRepository.findOne(id);
}

@Override
@Transactional(rollbackFor=ShopNotFound.class)
public Shop delete(long id) throws ShopNotFound {
Shop deletedShop = shopRepository.findOne(id);

if (deletedShop == null)
thrownew ShopNotFound();

shopRepository.delete(deletedShop);
return deletedShop;
}

@Override
public List<Shop> findAll() {
return shopRepository.findAll();
}

@Override
public List<Shop> findAll(int pageIndex, int rowsOfPage) {
Page<Shop> shopPage = shopRepository.findAll(new PageRequest(pageIndex, rowsOfPage));
return shopPage.getContent();
}

@Override
public List<Shop> findByPage(int pageNum, int pageSize, String sortDir, String... cols) {
Sort.Direction dir = Sort.Direction.ASC;
if (sortDir.equals("desc")) {
dir = Sort.Direction.DESC;
}
PageRequest request = new PageRequest(pageNum, pageSize, dir, cols);
Page<Shop> shopPage = shopRepository.findAll(request);
return shopPage.getContent();
}

@Override
publiclong countShops() {
return shopRepository.count();
}

@Override
@Transactional(rollbackFor=ShopNotFound.class)
public Shop update(Shop shop) throws ShopNotFound {
Shop updatedShop = shopRepository.findOne(shop.getId());

if (updatedShop == null)
thrownew ShopNotFound();

updatedShop.setName(shop.getName());
updatedShop.setEmplNumber(shop.getEmplNumber());
return updatedShop;
}

@Override
@Transactional
publicvoid deleteAll() {
shopRepository.deleteAll();
}
}

テストの作成
テストケースを作成する前に、テスト用のデータベース設定クラスを作成します。
test.config.TestDbConfig:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.itrane.mvcdemo.repo")
publicclass TestDbConfig {

@Autowired
private Environment env;

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/testmvc");
dataSource.setUsername("demo");
dataSource.setPassword("pass");
return dataSource;
}

//エンティティマネージャ
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean
= new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
entityManagerFactoryBean.setPackagesToScan("com.itrane.mvcdemo.model");

//JPAプロパティの設定
Properties properties = new Properties();
properties.put("hibernate.hbm2ddl.auto", "create-drop");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
properties.put("hibernate.show_sql", "true");
entityManagerFactoryBean.setJpaProperties(properties);
return entityManagerFactoryBean;
}

//トランザクションマネージャ
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}

前述のリポジトリとサービスの機能をテストするためのテストケースを作成します。test.service.ShopRepositoryTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestDbConfig.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class })
@DatabaseSetup("shopData.xml")
publicclass ShopRepositoryTest {

@Autowired
private ShopRepository repo;

@Test
publicvoid testFindAll() {
System.out.println("testFindAll--------");
List<Shop> shops = repo.findAll();
for(Shop shop: shops) {
System.out.println(shop.toString());
}
assertThat(shops.size(), is(12));
}
@Test
publicvoid testCount() {
System.out.println("testCount--------");
long count = repo.count();
assertThat(count, is(12L));
}

@Test
publicvoid testFindByName() {
System.out.println("testFindByName----------");
List<Shop> shops = repo.findByName("店舗名001");
assertThat(shops.size(), is(1));
assertThat(shops.get(0).getName(), is("店舗名001"));
}
@Test
publicvoid testFindByNameNotFound() {
System.out.println("testFindByNameNotFound----------");
List<Shop> shops = repo.findByName("店舗名001x");
assertThat(shops.size(), is(0));
}
@Test
publicvoid testFindByNameLike() {
System.out.println("testFindByNameLike----------");
List<Shop> shops = repo.findByNameLike("店舗名%");
assertThat(shops.size(), is(12));
shops = repo.findByNameLike("店舗名00%");
assertThat(shops.size(), is(9));
}

@Test
publicvoid testFindByEmplNumber() {
System.out.println("testFindByEmplNumber----------");
List<Shop> shops = repo.findByEmplNumber(5);
assertThat(shops.size(), is(1));
assertThat(shops.get(0).getEmplNumber(), is(5));
}
@Test
publicvoid testFindByEmplNumberNotFound() {
System.out.println("testFindByEmplNumberNotFound----------");
List<Shop> shops = repo.findByEmplNumber(15);
assertThat(shops.size(), is(0));
shops = repo.findByEmplNumber(2);
assertThat(shops.size(), is(0));
}
@Test
publicvoid testFindByEmplNumberGT() {
System.out.println("testFindByEmplNumberGT----------");
List<Shop> shops = repo.findByEmplNumberGreaterThan(3);
assertThat(shops.size(), is(12));
shops = repo.findByEmplNumberGreaterThan(5);
assertThat(shops.size(), is(11));
shops = repo.findByEmplNumberGreaterThan(6);
assertThat(shops.size(), is(4));
}
@Test
publicvoid testFindByEmplNumberGTNotFound() {
System.out.println("testFindByEmplNumberGTNotFound---------");
List<Shop> shops = repo.findByEmplNumberGreaterThan(7);
assertThat(shops.size(), is(0));
}

}

上記コードで、@RunWith アノテーションはテストの実行に SpringJUnit4ClassRunner を使用することを、@ContextConfiguration アノテーションは先に作成した、テスト用データベース設定クラス TestDbConfig を使用することを指定します。テストで DbUnit を使用するために、@TestExecutionListeners アノテーションを追加して、リスナーとして標準的な Spring リスナーと一緒に、DbUnitTestExecutionListener を渡します。さらに、@DatabaseSetup アノテーションにより、DbUnit データセット設定ファイルとして、shopData.xml を使用する事を指定します。 @DatabaseSetup をクラスに付けた場合、各テストの実行前に、データセット使ってデータベースが初期化されます。

test.service.ShopServiceTest:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestDbConfig.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class })
@DatabaseSetup("shopData.xml")
publicclass ShopServiceTest {

@Autowired
private ShopRepository repo;

private ShopService service;

@Before
publicvoid setUp() {
service = new ShopServiceImpl(repo);
}

@Test
publicvoid testFindByPageOrderByIdAsc() {
System.out.println("-------------testFindByPageOrderByIdAsc");
List<Shop> shops = service.findByPage(0, 5, "asc", new String[] {"id"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(1, 5, "asc", new String[] {"id"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(2, 5, "asc", new String[] {"id"});
assertThat(shops.size(), is(2));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
}

@Test
publicvoid testFindByPageOrderByIdDesc() {
System.out.println("-------------testFindByPageOrderByIdDesc");
List<Shop> shops = service.findByPage(0, 5, "desc", new String[] {"id"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(1, 5, "desc", new String[] {"id"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(2, 5, "desc", new String[] {"id"});
assertThat(shops.size(), is(2));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
}

@Test
publicvoid testFindByPageOrderByNameAsc() {
System.out.println("-------------testFindByPageOrderByNameAsc");
List<Shop> shops = service.findByPage(0, 5, "asc", new String[] {"name"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(1, 5, "asc", new String[] {"name"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(2, 5, "asc", new String[] {"name"});
assertThat(shops.size(), is(2));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
}

@Test
publicvoid testFindByPageOrderByNameDesc() {
System.out.println("-------------testFindByPageOrderByNameAsc");
List<Shop> shops = service.findByPage(0, 5, "desc", new String[] {"name"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(1, 5, "desc", new String[] {"name"});
assertThat(shops.size(), is(5));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
shops = service.findByPage(2, 5, "desc", new String[] {"name"});
assertThat(shops.size(), is(2));
for(Shop shop: shops) {
System.out.println(shop.toString());
}
}
}


 上記のテストクラスで指定した DbUnit データセット設定ファイルを作成します。
src/test/servie/shopData.xml:
<dataset>
<shops id="1" OPTLOCK="0" name="店舗名001" phone="03033314444"
email="tenpo001@xxx.com" kaitenBi="2014/01/01" emplNumber="5"/>
<shops id="2" OPTLOCK="0" name="店舗名002" phone="03033325555"
email="tenpo002@xxx.com" kaitenBi="2014/02/01" emplNumber="6"/>
<shops id="3" OPTLOCK="0" name="店舗名003" phone="03033335555"
email="tenpo003@xxx.com" kaitenBi="2014/03/01" emplNumber="6"/>
<shops id="4" OPTLOCK="0" name="店舗名004" phone="03033345555"
email="tenpo004@xxx.com" kaitenBi="2014/04/01" emplNumber="6"/>
<shops id="5" OPTLOCK="0" name="店舗名005" phone="03033355555"
email="tenpo005@xxx.com" kaitenBi="2014/05/01" emplNumber="6"/>
<shops id="6" OPTLOCK="0" name="店舗名006" phone="03033365555"
email="tenpo006@xxx.com" kaitenBi="2014/06/01" emplNumber="6"/>
<shops id="7" OPTLOCK="0" name="店舗名007" phone="03033375555"
email="tenpo007@xxx.com" kaitenBi="2014/07/01" emplNumber="6"/>
<shops id="8" OPTLOCK="0" name="店舗名008" phone="03033385555"
email="tenpo008@xxx.com" kaitenBi="2014/08/01" emplNumber="6"/>
<shops id="9" OPTLOCK="0" name="店舗名009" phone="03033395555"
email="tenpo009@xxx.com" kaitenBi="2014/09/01" emplNumber="7"/>
<shops id="10" OPTLOCK="0" name="店舗名010" phone="03034035555"
email="tenpo010@xxx.com" kaitenBi="2014/10/01" emplNumber="7"/>
<shops id="11" OPTLOCK="0" name="店舗名011" phone="03034135555"
email="tenpo011@xxx.com" kaitenBi="2014/11/01" emplNumber="7"/>
<shops id="12" OPTLOCK="0" name="店舗名012" phone="03034235555"
email="tenpo012@xxx.com" kaitenBi="2014/12/01" emplNumber="7"/>
</dataset>


テストの実行
テストクラスを右クリックして、ポップアップメニューから Run As > JUnit Test を選択します:
まとめ
Spring Data JPA はデータベースアクセスを抽象化したライブラリで、これによりデータベースに依存しないコードを書く事ができます。このデモアプリでは JUnit と DbUnit を使ってリポジトリとサービスの簡単なテストしか行っていません。アプリに求められるサービスを実行時間なども含めてしっかりとテストしておけば、UI実装時に安心して使用できます。


ソースコード
ソースコードは GitHub-branch:mvcdemo02_03からダウンロードできます。


Viewing all 71 articles
Browse latest View live