2013年6月29日土曜日

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

前のポスト「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"
    )
})
public class Contact {
    static final 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 を作成します:
public class 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);
    }

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

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

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

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

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

JPA コンテキストを保存するヘルパークラスを作成します。
rapbinding.db.LocalJPA:
public class LocalJPA {
    
    private static ThreadLocal<EntityManager> entityManagerHolder = new ThreadLocal<EntityManager>();
    private static ThreadLocal<EntityTransaction> transactionHolder = new ThreadLocal<EntityTransaction>();

    private static EntityManagerFactory entityManagerFactory;

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

    public static void setEntityManagerFactory(String pu, Map<String, Object> properties) {
        entityManagerFactory = new PersistenceProvider().createEntityManagerFactory(pu, properties);
    }
    public static void setEntityManagerFactory(EntityManagerFactory emf) {
        entityManagerFactory = emf;
    }
    
    /** フォルダから生成済みのエンティティマネージャを取得する.*/
    public static EntityManager getCurrentEntityManager() {
        return entityManagerHolder.get();
    }

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

    /** エンティティマネージャ・フォルダとトランザクション・フォルダを空にする.*/
    public static void 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);
    }

    /** 実行中のトランザクションのコミットとエンティティマネージャのクローズを行う.*/
    public static void 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);
    }

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

    /** トランザクションの取得または生成&開始.*/
    private static 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:
public class 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 インスタンス
     */
    static public 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:
public class 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
            public void widgetSelected(SelectionEvent e) {
                System.out.println(fContact.toString());
            }
        });
        btnSave.setText("保存");
        sashForm.setWeights(new int[] { 1, 1 });
        setContact(new Contact());
    }

    private void 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();
        }
    }

    private void 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
            public void widgetSelected(SelectionEvent e) {
                int i = fContactTable.getSelectionIndex();
                if (i < 0) return;
                setContact((Contact)fContactTable.getItem(i).getData());
            }
        });
    }

    private static 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;
    }

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
    private void 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);
        }
    }

    public void 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:
public class 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
    protected void 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 環境へアップロードするだけです。 これは次回に説明します。


0 件のコメント: