前のポスト「
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 環境へアップロードするだけです。 これは次回に説明します。