« 文書履歴の自動調節 | トップページ | google-cord-prettify 導入 »

2013年2月25日 (月)

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

« 文書履歴の自動調節 | トップページ | google-cord-prettify 導入 »

OpenDolphin」カテゴリの記事