2013年4月27日土曜日

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

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

データ処理プラグイン 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"
    )
})
public class 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;
    }

    public void setSimei(String simei) {
        this.simei = simei;
    }

    public String getYomi() {
        return yomi;
    }

    public void setYomi(String yomi) {
        this.yomi = yomi;
    }

    public String getMail() {
        return mail;
    }

    public void setMail(String mail) {
        this.mail = mail;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Integer getVersion() {
        return version;
    }
    public void setVersion(Integer version) {
        this.version = version;
    }
    @Override
    public boolean 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;
        } else if (!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 版の連絡先ホーム.
 */
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();
    }
}

同じく、e4contact.db パッケージに、ContactHome で利用するユーティリティクラスの LocalJPA クラスを作成します:
public class LocalJPA {
    
    // TODO システムにあわせて変更.
    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;
    }
}

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 クラスを以下のように修正します:
public class Activator implements BundleActivator {

    private static BundleContext context;

    static BundleContext getContext() {
        return context;
    }

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

    public void 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 クラスを次のように修正します:
public class SamplePart {

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

    @PostConstruct
    public void 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
    public void 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 をダブルクリックして実行します:

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

0 件のコメント: