« 2013年1月 | トップページ | 2013年3月 »

2013年2月

2013年2月25日 (月)

google-cord-prettify 導入

google-cord-prettify というのを導入して,ブログのコードのキーワードなどに色がつくようにしてみた。

pvt message broadcaster

OpenDolphin-1.3.0 での受付システムは,受付は orca で行い,orca から受付情報を claim で OpenDolphin の受付サーバ(pvt server)に送る仕組みになっている。送られた受付情報は,pvt server で pvt 情報として記録されるが,OpenDolphin クライアントは pvt  情報が更新された時点では,それを知ることができない。そのため,クライアントは pvt  情報を一定時間毎(当院カスタマイズでは 5〜30秒毎)にサーバに取りに行って,取ってきた情報をクライアントの受付画面に表示していた。

 この方式では,受付があってからクライアント画面に表示されるまでに,必然的に最大 30秒のタイムラグが生じてしまう。また, pvt  情報更新があってもなくても,クライアントはサーバに常に情報を取りに行くことになるので, 無駄なサーバアクセスが生じてしまう。最新の OpenDolphin では,pvt 情報更新があった時点でクライアントにその情報が送られるようになっているらしいが,当院のシステムは jndi なレガシーシステムで最新方式は使えない。

 まあしょうがないかな,と思っていたのであるが,ふと「サーバが pvt 情報に変化があった時点で,LAN 全体に broadcast して,それをクライアントが勝手に拾って,拾った時点で pvt 情報を取りに行ったらいいのでは」と,素人くさいアイデアを思いついた。これなら簡単に実装できるので,早速プログラムしてみたところ,とりあえずうまく動いた。

 受付リストがほぼリアルタイムで更新されるようになってよかったのであるが,もう少し素人くさくない方法はないかと思って調べたら,jms (java message service) という,まさにこれという仕組みがあることがわかった。そして,それは増田先生が既に 1.4m 時代に導入されていた方法であることもわかった。orz

 増田先生のコードを参考にさせていただいて,無事 jms を導入して,受付リストのリアルタイム更新ができるようになった。しかし,デバッグの段階で,健康保険情報関連の LazyInitializationException が出て困ったので,検索パネルと同じく,健康保険情報はカルテを開くときに初めて fetch するように書き換えたところ,受付リストの表示が劇的に速くなり,Exception も出なくなった。

 

サーバで jms 設定

  1. jms は standalone/configuration/standalone-full.xml で設定する。standalone.xml と同じ設定をした上から,さらに以下の設定をする。
    <hornetq-server>
      <persistence-enabled>true</persistence-enabled>
      <security-domain>openDolphin</security-domain>
      :
      <security-settings>
        <security-setting match="#">
          <permission type="send" roles="user"/>
          <permission type="consume" roles="user"/>
          <permission type="createNonDurableQueue" roles="user"/>
          <permission type="deleteNonDurableQueue" roles="user"/>
        </security-setting>
      </security-settings>
      :
      <jms-destinations>
        <jms-topic name="pvt">
          <entry name="topic/pvt"/>
          <entry name="java:jboss/exported/jms/topic/pvt"/>
        </jms-topic>
      </jms-destinations>
    </hornetq-server>
    
    ダウンロード standalone-full.xml (21.9K)
  2. bin/standalone.conf default 変更
    if [ "x$JAVA_OPTS" = "x" ]; then
       JAVA_OPTS="-Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000"
       JAVA_OPTS="$JAVA_OPTS -Djboss.modules.system.pkgs=$JBOSS_MODULES_SYSTEM_PKGS -Djava.awt.headless=true"
    #   JAVA_OPTS="$JAVA_OPTS -Djboss.server.default.config=standalone.xml"
       JAVA_OPTS="$JAVA_OPTS -Djboss.server.default.config=standalone-full.xml"
       JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8"
    
    ダウンロード standalone.conf (2.3K)
  3. client の依存性に org.jboss.as : jboss-as-jms-client-bom 7.1.3.Final を加える

RemotePvtServiceImpl.java

addPvt,updatePvt で pvt 情報を送信する

private void broadcast(PatientVisitModel pvt) {   
  TopicConnection connection = null;
  
  try {
    InitialContext ic = new InitialContext();
    TopicConnectionFactory cf = (TopicConnectionFactory) ic.lookup("/ConnectionFactory");
    Topic topic = (Topic) ic.lookup("topic/pvt");
    
    connection = cf.createTopicConnection();
    TopicSession session = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
    
    MessageProducer publisher = session.createProducer(topic);
    connection.start();
          
    ObjectMessage message = session.createObjectMessage(pvt);
    publisher.send(message);
    
  } catch (NamingException ex) {
    logger.error("RemotePvtService broadcast error: " + ex.getMessage());
  } catch (JMSException ex) {
    logger.error("RemotePvtService broadcast error: " + ex.getMessage());
  } finally {
    if (connection != null) {
      try {
        connection.close();
        //logger.info("TopicConnection closed");
      } catch (JMSException ex) {
        logger.error("RemotePvtService broadcast error: " + ex.getMessage());          
      }
    }
  }
}

WatingListImpl

RemotePvtServiceImpl.java で送信された pvt を受け取り,テーブルを更新する。

private void startPvtMessageReceiver () {
  try {
    // ログイン情報
    String pk = Project.getFacilityId() + ":" + Project.getUserId();
    String password = Project.getUserPassword();
    String host = Project.getHostAddress();
    int port = Project.getHostPort();

    // InitialContext 取得
    Properties env = new Properties();
    env.put(InitialContext.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
    env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");
    //env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHAHISMS", "JBOSS-LOCAL-USER");
    env.put(InitialContext.PROVIDER_URL, String.format("remote://%s:%d", host, port));
    env.put(InitialContext.SECURITY_PRINCIPAL, pk);
    env.put(InitialContext.SECURITY_CREDENTIALS, password);

    InitialContext ic = new InitialContext(env);

    // Lookup the connection factory        
    TopicConnectionFactory cf = (TopicConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
    // Lookup the JMS topic
    Topic topic = (Topic) ic.lookup("jms/topic/pvt");        
    // Create the JMS objects to connect to the server and manage a session
    topicConnection = cf.createTopicConnection(pk, password);
    TopicSession session = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);

    MessageConsumer subscriber = session.createConsumer(topic);

    // 通知された pvt を処理する
    subscriber.setMessageListener(new MessageListener(){
      @Override
      public void onMessage(Message msg) {
        try {
          ObjectMessage message = (ObjectMessage) msg;
          PatientVisitModel hostPvt = (PatientVisitModel) message.getObject();

          // 送られてきた pvt と同じものを local で探す
          PatientVisitModel localPvt = null;
          int row = -1;

          for (int i=0; i<pvtTableModel.getObjectCount(); i++) {
            PatientVisitModel p = (PatientVisitModel) pvtTableModel.getObject(i);
            if (p.getPatient().getId() == hostPvt.getPatient().getId()) {
              localPvt = p;
              row = i;
              break;
            }
          }
          int changedRow = -1;
          if (localPvt != null) {
            // localPvt がみつかれば,それは state 更新である
            if (localPvt.getState() != hostPvt.getState()) {
              localPvt.setState(hostPvt.getState());
              changedRow = row;
              //logger.info("pvt state updated at row " + row);
            }
            // byomeicount の更新
            if (localPvt.getByomeiCount() != hostPvt.getByomeiCount()) {
              localPvt.setByomeiCount(hostPvt.getByomeiCount());
              changedRow = row;
              //logger.info("byomeiCount updated at row " + row);
            }
            // byomeicounttoday の更新
            if (localPvt.getByomeiCountToday() != hostPvt.getByomeiCountToday()) {
              localPvt.setByomeiCountToday(hostPvt.getByomeiCountToday());
              changedRow = row;
              //logger.info("byomeiCountToday updated at row " + row);
            }

          } else{
            // localPvt がなければ,それは追加である
            row = pvtTableModel.getObjectCount();
            // 番号付加
            hostPvt.setNumber(row+1);
            pvtTableModel.addRow(hostPvt);
            changedRow = row;
            //logger.info("pvt added at row " + row);
            // 患者数セット
            setPvtCount(row+1);
          }

          // changeRow を fire,ただしカルテが開いていたら fire しない
          if (changedRow != -1 && !ChartImpl.isKarteOpened(localPvt)) {
            pvtTableModel.fireTableRowsUpdated(changedRow, changedRow);
          }

        } catch (JMSException e) {}
      }
    });
    topicConnection.start();

  } catch (NamingException e) {
    e.printStackTrace(System.err);
  } catch (JMSException e) {
    e.printStackTrace(System.err);
  }
}

2013年2月19日 (火)

文書履歴の自動調節

設定したカルテ検索期間内にカルテが1件もない場合,見つかるまで自動的にさかのぼって,最低1件は履歴に表示するようにした。例えば,5年以上前に1度受診した患者さんが久しぶりに受診した場合など,カルテ検索期間を5年にしておいても,それ以前のカルテが自動的に検索されて履歴に表示される。

DocumentHistory.java

private void updateHistory(List newHistory) {
  // ソーティングする
  if (newHistory != null && !newHistory.isEmpty()) {
 :
  } else {
    // カルテが見つかるまで抽出期間を自動的に延ばす
    int selected = extractionCombo.getSelectedIndex();
    if (selected < extractionCombo.getItemCount() - 1) {
      extractionCombo.setSelectedIndex(++selected);
      //System.out.println("extraction period extended to " + extractionObjects[selected]);
    } else {
      // 最後まで見つからない=初診カルテ
      countField.setText("0 件");
    }
  }

2013年2月18日 (月)

検索リニューアルの技術的背景

検索の高速化

以前のコードで「乾癬」を全文検索してみたところ,PatientModel で 217件のヒットがあり,結果の表示までに15秒程度かかってしまった。どこで時間をとっているか調べてみたところ,最終受診日と健康保険情報の取得のところで大量のクエリが発生しており,ここで 14.2秒を消費していた。
 そこで,最終受診日を取得するクエリを 1回ですますように工夫し,さらに健康保険情報をあらかじめ取得せず,カルテをオープンする時に初めて取りに行くようにしたところ,15秒かかっていた検索時間を 1秒以下に減らすことができた。
 試しにリニューアルしたコードで「紅斑」を検索してみたところ,7,377件のヒットがあり,さすがにここまで多いと表示までに 30秒かかってしまった。件数があまり多い場合には,100件ずつ分けて表示する等の対処方法が考えられるが,今のところ,こんな検索をする方が悪いということにしておく。

RemotePatientServiceImpl.java

// pvt をまとめて取得する(高速化のため)
List<PatientVisitModel> pvts = em.createQuery("from PatientVisitModel p "
    + "where p.facilityId = :fid and p.status != :status and (p.patient in (:pts)) order by p.pvtDate desc")
    .setParameter("fid", fid)
    .setParameter("pts", ret)
    .setParameter("status", KarteState.CANCEL_PVT)
    .getResultList();

// まとめて取った pvt から最新の日付を PatientModel にセット
for (PatientModel pm : ret) {
  for (PatientVisitModel pvt : pvts) {
    // 最初にマッチした pvt が最新 (last visit) 
    if (pm.getId() == pvt.getPatient().getId()) {
      pm.setLastVisit(pvt.getPvtDate());
      break;
    }
  }
}

AbstractMainComponent

public void openKarte(final PatientModel patient) {
  if (canOpen(patient)) {
    Thread t = new Thread() {
      @Override
      public void run() {

        // 健康保険情報をフェッチする
        PatientDelegater pdl = new PatientDelegater();
        pdl.fetchHealthInsurance(patient);
    :

 

絞り込み検索

Hibernate search で,AND/OR 検索が普通にできるのは Apache Lucene Query Parser というのが検索語を解釈してクエリに変換してくれているからであった。つまり,PatientModel をインデックスしてあれば,検索語のレベルで PatientModel の id で絞り込み検索ができるようになる。
 そこで,下のようにインデックスを追加した。なお,DocumentModel は KarteEntryBean を extend してあり,DocumentModel が @Indexed されているので,KarteEntryBean も @Indexed 状態になっている。

KarteEntryBean.java
   @IndexedEmbedded
   KarteBean karte
 
KarteBean.java
    @IndexedEmbedded
    PatientModel patient

インデックスをつけると,@Field がついていなくても @Id は自動的に検索対象になる。つまり,上記のインデックス付けをすると,KarteBean と PatientModel の id が自動的に検索対象になり,それぞれ "karte.id","karte.patient.id" というキーで検索できる。例えば PatientModel の primary key = 945 で絞り込みをしたい場合,"SearchText AND karte.patient.id:945" という検索語で検索できるようになる。今回作成した全文検索の絞り込み検索モードにこれを利用している。

 

入力フィールドの補完

Java Swing Hacks を参考に,入力履歴を覚えておいて補完する CompletableJTextField.java を作った。いろいろ Listener をつけて,入力中に矢印キーで履歴を選択できるようにしたり,リターンキーで自動的に補完履歴に登録されるようにしたり,補完ウインドウが出ているときにフォーカスを失った場合や,フレームを動かした場合の処理なども加えている。なお,補完ウインドウを半透明にするコード(com.sun.awt.AWTUtilities.setWindowOpacity)は java 7 では動かないらしい。

検索パネルのリニューアル

検索パネルの動作を見直した。

「患者検索」「カルテ検索」「メモ検索」の選択ボタンをなくして(左),できるだけ自動判定するようにした。
 検索フィールドに数字を入力してリターンを押すと患者番号検索になる(右)。何も入力せずにリターンを押すか,検索フィールド右の「×」を押すと,検索結果はクリアされて,最初の状態(左)に戻る。
 なお,検索後にテキストの背景が黄色くなっているのは(右),後で説明する「絞り込み検索モード」になっていることを表している。

Initial_2 Numbersearch

西暦形式の日付を入力すると受診日検索となる(左)。年号形式の日付を入力すると誕生日検索となる(右)。

Datesearch Birthdaysearch

「カタカナ」「ひらがな」だけを入力すると名前のカナ検索になる(左)。それ以外の文字を入れると,カルテ内の全文検索になる(右)。ただし,薬の名前を検索するときに患者名検索になってしまって不便なので,名前検索で結果が 0 の時は,引き続き全文検索に切り替えて検索しなおすようにプログラムしてある。

Kanasearch Fulltextsearch

上記の自動判定検索で,ほとんどの場合は大丈夫と思われるが,より正確に検索モードを指定したい場合は,検索語の前に半角アルファベット+スペースを入力する。"N " をつければ,漢字の名前で検索することができる(左)。虫眼鏡をクリックすると,入力に必要なアルファベットをメニューから選べるようにした。

Namesearch Loupemenu

同じメニューで絞り込み検索モードの on/off,インデックス作成もできるようにした(左)。また,検索フィールドの入力履歴を覚えておいて,補完できるようにした(右)。

Loupemenu2 Completion

絞り込みモードが on の場合,検索してテーブルに検索結果リストができると,検索フィールドの背景が黄色くなり,自動的に絞り込み検索モードに入る。このモードでは,検索結果リストの中からだけ検索するようになる。つまり,例えば受診日検索で 2013-02-12 の受診患者リストを検索し(左),絞り込みモードでその日の「北大」から紹介された患者さんを抽出するという風に使うことができる(右)。

Narrowing1 Narrowing2

2013年2月14日 (木)

プリンタ購入

開業当時からずっとつかっていたプリンタ Brother HL-5250DN が,5年たって紙送りの時にギシギシ怪しげな音を立てるようになってきた。印刷には全く問題ないのであるが,診療中に突然止まったりしたら困るので,バックアップにまわして,新しいのを買うことにした。ちなみに,ページカウントは 143,441 であった。
 後継機種の Brother HL-5450DN というのを新しく買って取り替えたが,ドライバ等は何も変更しなくても普通に使えている。

2013年2月13日 (水)

5年以上前のカルテ表示

当院はこの2月で,ついに開業6年目に突入した。オリジナルの OpenDolphin-1.3.0 は,5年以上前に作成したカルテは見られない仕様で,開業当初に作成したカルテが見られなくなった。そこで,ちょっとソースを書き換えて,見られるようにした。数行の書き換えでできる簡単な改造なのだが,開業当初はこの改造が必要になる日なんて想像できなかったので,なかなか感慨深いものがある。

2013年2月12日 (火)

Hibernate Search 関連(2)

うまくいったかどうかのテスト。

  • こんなカルテを作って一旦保存。

    Jugem Gurindai

  • "寿限無","グーリンダイ" で検索すると確かに出てくる。なお,検索語をいちいちクオーテートしているのは,そうしないとうまく検索できなかった(グーリンダイで検索すると,「グーリン」も「ダイ」も引っかかってくる)からなのだが,これについては増田先生にわけを教えて頂いて修正した。

    Searchjugem Searchgurindai

  • カルテを編集して,"寿限無" ,"グーリンダイ" を削除,処方を加える。

    Editjugem Editgurindal

  • 編集後なくなった "寿限無","グーリンダイ" では検索されなくなっている。

    Searcheditjugem_2 Searcheditgurindai

  • 本文と処方の AND 検索もできる。これは,ModuleModel で index していた時にはできなかった検索である。

    Searchandjugem Searchandgrindai

さて,動作は問題ないようなので,開発用マシンで全インデックスを作成してみたところ,117,673 件の DocumentModel にインデックスするのに4時間近くかかってしまった。(インデックスが 4分以下で終了していた時代が懐かしい)

INFO  boot.logger - IndexTask started at Mon Feb 11 04:58:28 JST 2013
INFO  boot.logger - IndexTask ended at Mon Feb 11 08:44:58 JST 2013

そこで,MassIndexer というのを試してみた。どうせ index は1回つくればいいことなので,client への進捗フィードバックとか全部すっとばしてコーディングしてみたところ,1時間22分で終了した。ただし注意点として,デフォルトの standalone.xml の設定だとトランザクションが 300秒 (5分) で timeout して止まってしまうので,インデックス作成の時だけ,かかる時間以上に設定しておく必要がある。下の例では 14,400秒 (4時間) に設定した。
standalone.xml の該当部分

<subsystem xmlns="urn:jboss:domain:transactions:1.2">
  <core-environment>
    <process-id>
      <uuid/>
    </process-id>
  </core-environment>
  <recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
  <coordinator-environment default-timeout="14400" />
</subsystem>

2013年2月11日 (月)

Hibernate Search 関連(1)

OpenDolphin における hibernate search 導入のフロンティアといえば何といっても増田先生である。現在当院で使っているコードは増田先生の初期バージョンのものであるが,今回 JBoss AS 7.1 導入に合わせて,最新版の 2.3m から hibernate search 関連部分コードを使わせていただいて導入した。初期バージョンでは ModuleModel で index されていたが,現在の 2.3m では DocumentModel に index するように変更されている。

  • ModuleModel.java: @Indexed をコメントアウト
    @Entity
    //@Indexed(index="module")   // masuda
    @Table(name = "d_module")
    public class ModuleModel extends KarteEntryBean {    
     :
        @Lob
        // masuda
        @Field(index = Index.YES)
        @FieldBridge(impl = ModuleModelBridge.class)
        @Analyzer(impl = CJKAnalyzer.class)
        // masuda
    
  • DocumentModel.java: @Indexed して,modules を @IndexEmbedded する
    @Indexed(index="document")      // hibernate search
    @Entity
    @Table(name = "d_document")
    public class DocumentModel extends KarteEntryBean {
     :
        @IndexedEmbedded        // hibernate search
        @OneToMany(mappedBy="document", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
        private Collection

    modules;

  • RemoteDocumentPeekerServiceImpl.java: ModuleModel に対して行っていた操作を DocumentModel に変更する。
    public void clearIndex() {
      FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
      fullTextEntityManager.purgeAll(DocumentModel.class);
      :
    public void makeInitialIndex(Long fromId, Long toId) {
      final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
      final String sql = "from DocumentModel m where m.status = 'F' and m.id >= :min and m.id < :max";
            
      List<DocumentModel> modules = em.createQuery(sql)
     :
      for (DocumentModel dm : modules) {
                fullTextEntityManager.index(dm);
     :
    public List

    getPatientOfKarte(String text) {  :   final Analyzer analyzer = fullTextEntityManager.getSearchFactory().getAnalyzer(DocumentModel.class);  :     org.apache.lucene.queryParser.QueryParser parser = new QueryParser(ver, "modules.beanBytes", analyzer);  :

  • RemoteKarteServiceImpl.java: addDocument と deleteDocument に index 消去の処理を加える。(増田先生バージョンでは仮保存文書の更新処理と,再帰的文書削除処理も作られていたので,それもほぼそのまま使わせていただいた)
    @Override
    public long addDocument(DocumentModel document) {
     :        
       // オリジナルを取得し 終了日と status = M を設定する
       DocumentModel old = em.find(DocumentModel.class, parentPk);
       old.setEnded(ended);
       old.setStatus(STATUS_MODIFIED);
                
       // HibernateSearchのFulTextEntityManagerを用意。修正済みのものはインデックスから削除する by masuda-sensei
       final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
       fullTextEntityManager.purge(DocumentModel.class, parentPk);
     :
    

2013年2月10日 (日)

JBoss AS 7.1 に移行(3)

OpenDolphin クライアントの maven 化

  1. プロジェクトを追加 → Maven → Javaアプリケーション
    • プロジェクト名:opendolphin
    • グループ:jp.motomachi-hifuka
    • バージョン:1.3.0.8
    • パッケージ:なし
  2. maven レポジトリに quaqua-pns.jar と glulogic.jar を加える。
    $ mvn install:install-file -Dfile=glulogic.jar -DgroupId=jp.motomachi-hifuka -DartifactId=glulogic -Dversion=1.0 -Dpackaging=jar
    $ mvn install:install-file -Dfile=quaqua-pns.jar -DgroupId=jp.motomachi-hifuka -DartifactId=quaqua-pns -Dversion=7.3.4-pns -Dpackaging=jar
    
  3. 依存性の追加
    • 開いているプロジェクト → opendolphin-ea-ejb
    • org.jboss : jboss-ejb-client 1.0.16.Final [jar]
    • org.jboss.remoting3 : jboss-remoting 3.2.14.GA [jar]
    • org.jboss.spec.javax.ejb : jboss-ejb-api_3.1_spec 1.0.2.Final [jar]
    • org.jboss.xnio : xnio-api 3.0.7.GA [jar]
    • org.jboss.xnio : xnio-nio 3.0.7.GA [jar]
    • org.jboss.marshalling : jboss-marshalling 1.3.16.GA
    • org.jboss.marshalling : jboss-marshalling-river 1.3.16.GA
    • javax.activation : activation 1.1.1 [jar]
    • postgresql : postgresql 9.1-901.jdbc4 [jar]
    • org.apache.velocity : velocity 1.7 [jar]
    • javax.mail : mail 1.4.5 [jar]
    • jp.motomachi-hifuka : quaqua-pns 7.3.4-pns [jar]
    • jp.motomachi-hifuka : glulogic 1.0 [jar]
    • javaee-api-6.0 を依存性から削除(これがあると javax.mail.internet.MimeUtility 関連のコードが動かなくなる)
  4. opendolphin フォルダに libGlulogicMT.jnlib と libquaqua64.jnlib をコピー。
  5. OpenDolphin-1.3.0.7 のソースを opendolphin/src/main/java/ にコピーする
  6. ソースの書き換えのポイント
    • ProjectStub.java, HostSettingPanel.java
      ホストのポート番号が 1099 から 4447 に変わった。
    • LoginDialog.java
      LoginContext,SecurityAssociation がなくなったので,EJBClientContext を使うように書き換える。
    • BusinessDelegater.java
      InitalContext にセットするプロパティーは Context.URL_PKG_PREFIXES だけでよくなった。
      jndi lookup の名前が変わった。 [参考]

2013年2月 9日 (土)

JBoss AS 7.1 に移行(2)

ソースは bitbucket さんに上げた。

NetBeans で OpenDolphin サーバーを maven 化

  1. プロジェクトを追加 → maven → エンタープライズアプリケーション
    • プロジェクト名:opendolphin-ea
    • グループ:jp.motomachi-hifuka
    • バージョン:1.3.0.8
    • パッケージ:なし
    • サーバ:選択しない(JBoss AS 7 はサポートされていない)
    • EJBモジュールの作成チェック,Webアプリケーションモジュールなし
  2. この時点で,opendolphin-ea-ejb の依存性に javaee-api-6.0.jar がセットされている。依存性を右クリックして,さらに依存性を追加する。以下のものを追加すると,関連する依存は自動的に追加される。
    • org.hibernate : hibernate-core 4.1.6.Final [jar]
    • org.jboss.logging : jboss-logging 3.1.2.GA [jar]
    • org.jboss.spec.javax.transaction : jboss-transaction-api_1.1_spec 1.0.1.Final [jar]
    • org.jboss.spec.javax.annotation : jboss-annotations-api_1.1_spec 1.0.1.Final [jar]
    • org.jboss.ejb3 : jboss-ejb3-ext-api 2.0.0 [jar]
    • org.hibernate : hibernate-search 4.1.1.Final [jar]
    • commons-lang : commons-lang 2.6 [jar]
    • commons-collections : commons-collections 3.2.1 [jar]
    • commons-io : commons-io 2.1 [jar]
    • jdom : jdom 1.0 [jar]
    • log4j : log4j 1.2.16 [jar]
  3. OpenDolphin-1.3.0.7-EJB のソースを src/main/java にコピーする
  4. JBoss AS 7 では @RemoteBinding は使えないので削除。PvtServer のServiceMBean も使えないので,本家からいただいた PVT-MBEAN のソースを参考に @Singleton に書き換える。
  5. persistence.xmlsrc/main/resources/META-INF/ にコピーする。hibernate-search 関係のプロパティーの書き換えが必要。
    <property name="hibernate.search.default.directory_provider" value="filesystem"/>
    
  6. hibernate-search 関連のとりあえず最低限動くだけの書き換え。ここらへんは後でもっと何とかしたい。
    • ModuleModel.java
      @Field(index = Index.YES)
      
    • RemoteDocumentPeekerServiceImpl.java
      public List getPatientTextSearch2(String text) {
      
          final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);
          final Analyzer analyzer = fullTextEntityManager.getSearchFactory().getAnalyzer(ModuleModel.class);
          final org.apache.lucene.util.Version ver = org.apache.lucene.util.Version.LUCENE_35;
      
          try {
              // create native Lucene query
              org.apache.lucene.queryParser.QueryParser parser = new QueryParser(ver, "beanBytes", analyzer);
       
  7. opendolphin-ea をビルドすると,opendolphin-ea/opendolphin-ear/target/opendolphin-ea-ear-1.3.0.8.ear ができる。これを jboss の standalone/deploy フォルダにコピーしてデプロイさせる。
    =========================================================================
      JBoss Bootstrap Environment
      JBOSS_HOME: /Applications/jboss-as-7.1.4.Final
     :
    04:12:30,356 INFO  [CONSOLE] (Thread-84) PvtServer: waiting for connection
    04:12:30,368 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 28) JBAS018559: "opendolphin-ea-ear-1.3.0.8.ear" をデプロイしました。
     :
    
  8. dolphin サーバで自動起動させる時の /etc/init.d/ スクリプト。
    #!/bin/sh
    
    JBOSS_HOME=/usr/local/jboss
    
    case "$1" in
        start)
            echo "Starting JBoss AS"
            start-stop-daemon --start --quiet --background --exec ${JBOSS_HOME}/bin/standalone.sh -- -b 0.0.0.0
        ;;
        stop)
            echo "Stopping JBoss AS"
            start-stop-daemon --start --quiet --background --exec ${JBOSS_HOME}/bin/jboss-cli.sh -- --connect command=:shutdown
        ;;
        *)
            echo "Usage: /etc/init.d/jboss {start|stop}"
            exit 1
        ;;
    esac
    
    exit 0
    

JBoss AS 7.1 に移行(1)

本家 OpenDolphin および増田先生バージョンは,ずいぶん前からとっくに JBoss AS 7 に移行されていたが,当院バージョンはずっと JBoss AS 5 のままであった。今回,当院でも遅ればせながらやっと JBoss AS 7 に移行できた。

JBoss AS 7.1 をビルド

  1. 最新版の jboss as 7.1 の zip を github からもってくる。ブランチから 7.1 を選択して zip でダウンロードする。ちなみに,master は 7.2.Alpha である。
  2. build で25分かかった。これで 7.1.4.Final ができる。
    $ cd jboss-as-7.1
    $ export JAVA_HOME=`/usr/libexec/java_home -v 1.6` 
    $ ./build.sh
      :
    $ cp -r build/target/jboss-as-7.1.4.Final-SNAPSHOT /Applications/
    
  3. bin/standalone.conf にオプションを加えて,メッセージの文字化けを防ぐ。
     JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF8"
    
  4. bin フォルダのstandalone.sh -b 0.0.0.0 で起動を確認。jboss 5 と比較すると起動が異常に早い。
    $ ./standalone.sh -b 0.0.0.0
    =========================================================================
      JBoss Bootstrap Environment
      JBOSS_HOME: /Applications/jboss-as-7.1.4.Final
      JAVA: /Library/Java/JavaVirtualMachines/1.6.0_29-b11-402.jdk/Contents/Home/bin/java
      JAVA_OPTS: -d32 -client -Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Dorg.jboss.resolver.warning=true -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Djboss.server.default.config=standalone.xml -Dfile.encoding=UTF8
    =========================================================================
    20:51:46,707 INFO  [org.jboss.modules] JBoss Modules version 1.1.3.GA
    20:51:46,854 INFO  [org.jboss.msc] JBoss MSC version 1.0.2.GA
    20:51:46,906 INFO  [org.jboss.as] JBAS015899: JBoss AS 7.1.4.Final-SNAPSHOT "Arges" が起動しています。
    20:51:47,491 INFO  [org.xnio] XNIO Version 3.0.7.GA
    20:51:47,493 INFO  [org.jboss.as.server] JBAS015888: socket-binding (management-http) を使い http 管理サービスを作成しています。
    20:51:47,497 INFO  [org.xnio.nio] XNIO NIO Implementation Version 3.0.7.GA
    20:51:47,503 INFO  [org.jboss.remoting] JBoss Remoting version 3.2.13.GA
     :
    20:51:47,937 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss AS 7.1.4.Final-SNAPSHOT "Arges" は 1482ms でスタートしました -  サービス 222 個のうち 144 個を開始しました (76 のサービスはパッシブあるいはオンデマンドとなっています)。
    

データソースの作成

  1. modules/org/postgresql/main フォルダを作成し,ドライバ postgresql-9.1-901.jdbc4.jar をコピーする。
  2. 同じフォルダに以下の module.xml を作成する
    <?xml version="1.0" encoding="UTF-8"?>
    <module xmlns="urn:jboss:module:1.0" name="org.postgresql">
      <resources>
        <resource-root path="postgresql-9.1-901.jdbc4.jar"/>
      </resources>
      <dependencies>
        <module name="javax.api"/>
      </dependencies>
    </module>
    
  3. standalone/configuration/standalone.xml にドライバとデータソースを書き込む。jta=true にしないと,"Large Objects may not be used in auto-commit mode" という postgres エラーが出る。
    <subsystem xmlns="urn:jboss:domain:datasources:1.0">
      <datasources>
        <datasource jta="true" jndi-name="java:/PostgresDS" pool-name="PostgresDS" enabled="true" use-ccm="false">
          <connection-url>jdbc:postgresql://localhost:5432/dolphin</connection-url>
          <driver-class>org.postgresql.Driver</driver-class>
          <driver>postgresql</driver>
          <security>
              <user-name>dolphin</user-name>
          </security>
          <validation>
              <validate-on-match>false</validate-on-match>
              <background-validation>false</background-validation>
          </validation>
          <statement>
              <share-prepared-statements>false</share-prepared-statements>
          </statement>
        </datasource>
        <drivers>
          <driver name="postgresql" module="org.postgresql"/>
        </drivers>
      </datasources>
    </subsystem>
    

Security 設定

standalone.xml に以下の設定をする
  1. security-realm 作成
     <security-realm name="DolphinRealm">
       <authentication>
         <jaas name="openDolphin"/>
       </authentication>
    </security-realm>
    
  2. リモートアクセスで上記の security-realm を使うように設定(デフォルトでは ApplicationRealm が設定されている)
    <subsystem xmlns="urn:jboss:domain:remoting:1.1">
      <connector name="remoting-connector" socket-binding="remoting" security-realm="DolphinRealm"/>
    </subsystem>
    
  3. security-domain 設定。これは jboss 5 の login-config.xml と同じもの。
    <security-domain name="openDolphin" cache-type="default">
      <authentication>
        <login-module code="Database" flag="required">
          <module-option name="dsJndiName" value="java:/PostgresDS"/>
          <module-option name="principalsQuery" value="SELECT PASSWORD FROM D_USERS WHERE USERID=?"/>
          <module-option name="rolesQuery" value="SELECT C_ROLE, 'Roles' FROM D_ROLES WHERE USER_ID=?"/>
          <module-option name="hashAlgorithm" value="MD5"/>
          <module-option name="hashEncoding" value="hex"/>
        </login-module>
      </authentication>
    </security-domain>
    

2013年2月 6日 (水)

5年目の運用まとめ

  • サーバのハードディスクを交換した
    SiliconPower High Performance Velox series V30 120GB → Plextor M5 Pro 256
  • サーバをバージョンアップした
    • dom-0 を ubuntu 12.04 (64bit) xen 4.1 にした
    • dolphin サーバと samba サーバを ubuntu 12.04 (64bit)にした
    • orca サーバを ubuntu 10.04 (32bit) にした
  • データベースの PatientModel の件数
    dolphin=# select count(*) from d_patient;
     count 
    -------
    15997
    (1 row)
    
  • データベースの ModuleModel の件数
    dolphin=# select count(*) from d_module;
     count  
    --------
     585721
    (1 row)
    
  • Dolphinサーバの df 。サーバを更新したら,昨年より使用容量が減った。
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/xvda1       46G  8.3G   36G  20% /
    udev            600M  4.0K  600M   1% /dev
    tmpfs           249M  168K  248M   1% /run
    none            5.0M     0  5.0M   0% /run/lock
    none            621M     0  621M   0% /run/shm
    
  • ORCA サーバの df 。こちらも,サーバを更新したら使用容量が減っている。
    /dev/xvda1             28G  4.3G   22G  17% /
    none                  745M  108K  745M   1% /dev
    none                  777M     0  777M   0% /dev/shm
    none                  777M   72K  776M   1% /var/run
    none                  777M     0  777M   0% /var/lock
    none                  777M     0  777M   0% /lib/init/rw
    
  • データベースの dump ファイルのサイズ。これは順調に増えている。
    Backup/dolphin_db.dump.gpg  1,178,307,353  
    Backup/orca_db.dump.gpg  53,630,204  
    
  • 作成したスタンプ数。大分新規に作るスタンプが減った。
    $ grep -c stampInfo stamp.xml 
    1944
    

トラブル記録

  • この1年も特にトラブルなし。

2013年2月 1日 (金)

dolphin サーバを ubuntu 12.04 に

orca サーバをやっと ubuntu 8.04 hardy から 10.04 lucid にアップデートしたが,dolphin サーバの方は hardy のままであった。そこで,将来的には orca も ubuntu 12.04 precise の 64bit に移行することになるので,先に dolphin サーバを precise 64bit に移行してみることにした。
 既に xen の dom-0 は precise 64bit にしてあるので,xen dom-0 と同じカーネルを dom-U でもつかえる。なので,わざわざ debootstrap を使ってインストールしなくても,普通に cd から新しいパーティションにインストールして,xen で dom-0 のカーネルを使って,新規インストールしたパーティションから dom-U を立ち上げればよい。
 desktop バージョンのインストーラーは勝手に MBR を書き直したりしてうざいので,サーバー版のインストーラを使ってインストールした。ちなみに,precise から,server 版と desktop 版のカーネルに違いはないらしい。まずはテスト環境の mac mini の sdb4 に 30GB のパーティションを作ってインストールしてみた。
 インストール自体は特に問題なかったので,以下はインストール後の dom-U 設定の記録。

  1. ethtool をインストールして,/etc/network/interfaces に offload-tso off を入れておかないと,dom-0,dom-U 間で大きなファイルを転送すると stall する。
    $ sudo aptitude install ethtool
    $ cat /etc/network/interfaces
    # The loopback network interface
    auto lo
    iface lo inet loopback
    
    # The primary network interface
    auto eth0
    iface eth0 inet static
            address 192.168.1.102
            netmask 255.255.255.0
            network 192.168.1.0
            broadcast 192.168.1.255
            gateway 192.168.1.1
            dns-nameservers 192.168.1.1
            offload-tso off
    
  2. java のインストール
    $ sudo aptitude install openjdk6-jdk
    $ java -version
    Java version "1.6.0_24"
    OpenJDK Runtime Environment (IcedTea6 1.11.5) (6b24-1.11.5-0ubuntu1~12.04.1)
    OpenJDK 64-Bit Server VM (build 20.0-b12, mixed mode)
    
    export JAVA_HOME='/usr/lib/jvm/java-6-openjdk-amd64'
    
  3. postgres のインストールと dolphin データベースの移行。hardy のデータベースをダンプして持ってくる。
    $ sudo aptitude install postgresql
    $ sudo -u postgres createuser dolphin
    Shall the new role be a superuser? (y/n) y
    $ sudo -u postgres createdb dolphin
    $ pg_restore -Fc -d dolphin dolphin_db.dump
    
  4. dolphin ユーザのパスワードと pg_hba.conf の設定
    $ psql
    dolphin=# alter user dolphin with password '' ;
    
    pg_hba.conf
    local   all             all                trust
    
  5. jboss のインストール。これは,hardy から /usr/local/jboss をそのままコピーするだけ。
  6. 起動時に自動的に jboss が立ち上がるように設定
    # cd /etc/init.d/
    # ln -s /usr/local/jboss/bin/jboss_init_ubuntu.sh jboss
    # update-rc.d jboss defaults
    

テスト環境で動作を確認した後,実運用開始したが,今のところ問題なく動いている。ついでに,ファイルサーバ(samba)も 32bit から 64bit にインストールしなおした。あとは orca だけである。

« 2013年1月 | トップページ | 2013年3月 »