« 2014年12月 | トップページ | 2015年3月 »

2015年2月

2015年2月28日 (土)

受付情報の扱い

ORCA から CLAIM で送られてくる受付情報で,同じ patient id があった場合,単純に Dolphin 側の受付リストの該当項目を置き換える処理を行っていたが,受付情報を pvtDate で identify することにして,以下のように整理した。
  • ORCA の受付リストにある項目を選択して再受付した場合
    例えば,新患受付で,受付後に生年月日入力ミスに気付いて,患者登録で変更してから受付画面に戻って再受付した場合など。この場合は,Dolphin 側のリストの該当項目を,送られた受付情報に置き換える。再受付の場合は受付時間が同じになるのを利用して,置き換えるべき受付情報かどうかを pvtDate を秒単位まで比較して,一致したかどうかで判定する。
  • ORCA の受付リストにある項目を一旦削除して,後でもう一回受付した場合
    稀に,同ビルの別の科と間違って新患受付してしまい,「あ,ここ皮膚科ですか! 間違えました!」となることがある。この場合,その次に来院した新患が同じ患者番号で登録されることになる。また,午前中に受付した再診患者が一旦受付キャンセルして,午後から再受診した場合もこれに該当する。この場合は,同じ患者番号でも,違う受付と判断して,新たにリストに加える。
PvtServiceImpl
public int addPvt(PatientVisitModel pvt, String facilityId) {
  :
  // 同じ pvtDate の pvt がすでに登録されていないかどうかチェック
  // pvtDate が違えば,同じ patient id でも新たな pvt と判断する
  List result = em.createQuery(
    "select p from PatientVisitModel p where p.facilityId = :fid and p.pvtDate = :date", PatientVisitModel.class)
    .setParameter("fid", pvt.getFacilityId())
    .setParameter("date", pvt.getPvtDate())
    .getResultList();

    if (! result.isEmpty()) {
      // 重複がある場合は既存の id をコピーして新しい pvt にすげ替える
      PatientVisitModel exist = result.get(0);
      pvt.setId(exist.getId());
    }

    em.merge(pvt); // record がなければ persist 動作になる
WatingListImpl
if (localPvt != null) {
    // localPvt がみつかった場合,更新である
    hostPvt.setNumber(localPvt.getNumber());
    pvtTableModel.getObjectList().set(row, hostPvt);
    // changeRow を fire,ただしカルテが開いていたら fire しない
    if (! ChartImpl.isKarteOpened(localPvt)) {
        pvtTableModel.fireTableRowsUpdated(row, row);
    }
    // 待ち時間更新
    setPvtCount();

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

2015年2月27日 (金)

beanBytes の処理:REST(付録)

以上の改造で RESTEasy で動くようになったが,1つ気になる部分があった。HealthInsuranceModel,StampModel,ModuleModel には beanBytes というフィールドがあり,bean object を xml 変換して,さらにbyte 配列に変換したものが入って永続化されている。今回,REST化で json を使ったため,object が xml に変換されてさらに byte 配列に変換された beanBytes が json 化でさらに Base64 の文字列に変換されて流されるという,何だかたいそう複雑なことになってしまっていた。そこで,beanBytes の encode/decode はサーバ側でするように書き直した。こうすると,流れるのは bean の json だけになる。

サーバの変更箇所

  • HealthInsuranceModel
    PatientServiceImpl.getHealthInsuranceList と PatientDelegater.fetchHealthInsurance で,サーバからクライアントに HealthInsuranceModel を返して,クライアントの delegater で HealthInsuranceModel の beanBytes を PVTHealthInsuranceModel にデコードしていたところを,サーバでデコードして,クライアントには PVTHelthInsuranceModel を返してそのまま使えるようにした。
  • StampModel
    StampModel に @JsonTypeInfo,@Transient を付けた IInfoModel stamp を追加する
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
    @Transient
    private IInfoModel stamp;
    
  • StampServiceImpl
    putStamp / getStamp で, stampBytes を Stamp 実体に encode / decode する処理を入れる。
  • KarteServiceImpl
    • getDocumentList で,ModuleModel を detach して beanBytes を decode してからクライアントに返す。
    • getModuleListで,ModuleModel 一つ一つ detach してから decode して返す。
    • addDocument で,model を beanBytes に encode してから永続化する。

client の変更箇所

  • StampDelegater
    StampDelegater を使っているクラス全て getStampBytes() しているところを getStamp() に変えていく
    DiangosisDocument, KartePane, StampBoxPluginExtraMenu, StampHolderTransferHandler, StampTree, StampTreeModules, StampMakerPanel
    
  • DocmentDelegater
    getDocuments,getModuleList の beanBytes のデコード部分を消す。
  • PnsServiceImpl
    peekKarte で beanBytes をデコードしてから返すようにする。

SchemaModel

SchemaModel にも byte[] のフィールドがあるが,これは jpegBytes で,bean ではないので,そのままにする

StampTreeModel

StampTreeModel にも treeBytes という byte[] のフィールドがある。これは単純な bean ではなく,StampTree のツリー構造を xml で表現したものである。json では byte[] は Base64 変換されて送信されるが,文字列の treeXml で送受信するように書き換えてみたところ,treeBytes で 607kb あった送信データが,treeXml では 469kb に減った。

StampTreeModel (InfoModel) の改名

StampTreeModel がクライアントの open.dolphin.client とサーバの open.dolphin.infomodel の両方にあって,以前から混乱の元になることがあった。この機会にこれを何とかすることにした。
  • サーバ側の open.dolphin.infomodel.StampTreeModel を PersonalTreeModel という名前に変更した。
  • PersonalTreeModel と PublishedTreeModel がほとんど同じだったため,これらの共通部分を @MappedSuperclass の StampTreeBean として書き出し,両者をこれを extend するように書き直した。これに伴い,IStampTreeModel を廃止した。
  • PersonalTreeModel の name フィールドに tree_name という名前が付いていたが,PublishedTreeModel にはついていなかったため,フィールド名は name に統一することにして,d_stamp_tree の tree_name フィールドを psql を使って name にリネームした。
    dolphin=> alter table d_stamp_tree rename tree_name to name;
    
    以前のバージョンのデータベースを restore した場合は,フィールド名の変更が必要になる。
  • これに合わせて,クライアント側で open.dolphin.infomodel.StampTreeModel を PersonalTreeModel に,IStampTreeModel を StampTreeBean に置き換えた。

REST 化の効果

結構大きなプログラム改変であったが,使用感はほとんど変わってないので,変更があったことに誰も気付いていないようだ。

2015年2月26日 (木)

Client の引っ越し:REST(8)

  • クライアントのサーバとのインターフェースは Delegater パッケージに集まっているので,ここを書き換える。jndi 呼び出して RemoteService を取り出していた部分を,ResteasyWebTarget から proxy 経由で取り出すように書き換える。
  • NetBeans から起動したときは問題なかったのに,JAR から立ち上げたときログインで
    javax.ws.rs.client.ResponseProcessingException: javax.ws.rs.ProcessingException: Unable to find a MessageBodyReader of content-type application/json and type class open.dolphin.infomodel.UserModel
    at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.extractResult(ClientInvocation.java:140)
    
    というエラーが出て立ち上がらなかった。以下のようなテストコードで確認してみたところ,
    ResteasyClient client = new ResteasyClientBuilder().connectionPoolSize(20).build();
    ClientConfiguration conf = (ClientConfiguration) client.getConfiguration();
    MessageBodyReader mes = conf.getMessageBodyReader(UserModel.class, null, new Annotation[]{}, MediaType.APPLICATION_JSON_TYPE);
    System.out.println("MessageBodyReader = " + mes);
    
    これを,NetBeans から立ち上げた場合
    MessageBodyReader = org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider@2ac7b3a3
    
    JAR から立ち上げた場合
    MessageBodyReader = null
    
    なんでこういうことになるのか分からなかったが,とりあえず動けばいいと思って,Provider に ResteasyJackson2Provider を無理矢理入れたら立ち上がるようになった。
    ResteasyJackson2Provider jackson2Provider = new ResteasyJackson2Provider();
    client.register(jackson2Provider);
    

2015年2月25日 (水)

PvtServer の引っ越し:REST(7)

ORCA から 受付 CLAIM 情報を受けたときに WebSocket でクライアントに伝える仕組みを作った。

処理の流れ

クライアントは起動時にサーバの WebSocket につないで session を確立しておく → PvtServer は ORCA から CLAIM を受ける → CLAIM を受けた PvtServer は PatientVisitModel に変換して addPvt する → addPvt により PatientVisitModel テーブルが変化すると PatientVisitModelListener が呼ばれる → PatientVisitModelListener は WebSocket のクライアント全員に PatientVisitModel を送る

クライアント側

WebSocket につなぐ時に Endpoint を extend したクラスを指定して,そのクラスで pvt を受け取る。
  • DolphinClientContext
    public Session setEndpoint(Endpoint endpoint) {
      webSocketContainer.connectToServer(endpoint, webSocketUri);
    }
    
  • WatingListImpl
    DolphinClientContext.getContext().setEndpoint(new Endpoint(){
      @Override
      public void onOpen(Session session, EndpointConfig config) {
        session.addMessageHandler(new MessageHandler.Whole() {
          @Override
          public void onMessage(String message) {
             // pvt 処理
          });
        }
    });
    

サーバ側

  • PatientVisitModel にリスナをつける。
    @Entity
    @EntityListeners(PatientVisitModelListener.class)
    @Table(name = "d_patient_visit")
    public class PatientVisitModel extends InfoModel  {
      :
    
  • pvt が変化したら,通知を受けた PatientVisitModelListener から WebSocket クライアントに PatientVisitModel を送る。

2015年2月24日 (火)

Authorization:REST(6)

REST での Authorization

  • クライアント側では ClientRequestFilter を implement した AuthorizationFilter を作成し,ResteasyClient に register する。filter でヘッダに facilityId:username;password というかたまりを Base64 エンコードしてのせるようにした。
  • サーバ側では ContainerRequestFilter を implement した SecurityFilter を作成し,@Provider として登録する。filter でヘッダをデコードして情報を取り出し,データベースと照合して authorization する。

WebSocket での Authentication

  • クライアント側では ws://hostAddress:8080/dolphin/ws/{userId}/{password} という URL で WebSocket に接続する。
  • サーバ側では @PathParam として userId,password を取り出す。REST用 の SecurityFilter を inject して,authentication をしている部分を利用する。

2015年2月23日 (月)

infomodel,ejb,dto の引っ越し:REST(5)

InfoModel

InitDatabase が成功したので,今度は全ての InfoModel を引っ越しする。
  • InitDatabase 作成時に UserModel と RoleModel の bi-directional reference を @JsonIdentityInfo で解消したが,同じように全ての InfoModel で bi-directional reference を調べる必要がある。そこで,Class Visualizer というのを使用した。
    使い方
    • server の -classes.jar ファイルを読み込んで,open.dolphin.infomodel でフィルタをかける。
    • class を一つずつダブルクリックして,矢印なしの線で結ばれたクラスを調べていく。
    これを使って,DocumentModel,HealthInsuranceModel,LaboItemValue,LaboModuleValue,LaboSpecimenValue,ModuleModel,PatientModelmRoleModel,SchemaModel, UserModel に bi-directional reference があることが分かった。これらのクラスに @JsonIdentiyInfo を付けて対応した。このうち,DocumentModel,LaboModuleValue,ModuleModel,SchemaModel は KarteEntryBean の子なので,根元の KarteEntryBean にだけ付ける。
  • KarteBean "entries" フィールドについて
    このフィールドは便利で,型指定をしていないので,getKarte の際にアレルギーデータ,身長データ,体重データ,来院日,文書履歴,患者メモの6エントリーのリストなど,モデルのリスト,文字列のリストなど雑多な内容を入れることができるようになっていた。
     ところが,取ってきた entry をキャストするところで json からの deserialize がうまくいかなかった。entry に型指定がされていないので,持ってきたオブジェクトをうまく変換できないようだった。仕方がないので,entries を型指定をした複数の entry に分けて書き直した。
  • ModuleModel "model" フィールドについて
    このフィールドは BndleMed, BundleDolphin, ProgressCourse が入れられるように IInfoModel で型指定されている。IInfoModel は interface なので,このままだと json がインスタンスを作ることができない。こういう場合は,@JsonTypeInfo を使うと,オブジェクトの class に応じてプロパティーを入れてくれる。
    @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
    private IInfoModel model;
    
    以下のような json になる。
     {
      "@id" : "f371a3c1-33e2-47d2-8537-2948aeec082d",
      "id" : 0,
      "linkId" : 0,
      "moduleInfo" : {
        "stampNumber" : 0,
        "editable" : true,
        "asp" : false,
        "turnIn" : false
      },
      "model" : {
        "@class" : "open.dolphin.infomodel.BundleMed"
      }
    }
    

ejb

  • RemoteSystemService → SystemService,RemoteSystemServiceImpl → SystemServiceImpl の改変と同じように,他の RemoteOOOService も変更する。ここで,引数に primitive 型を使っているものは以下の Exception が出ることが分かった。
    java.lang.IllegalArgumentException: The type is incompatible with the class of the entity
    at javax.ws.rs.core.GenericEntity.checkTypeCompatibility(GenericEntity.java:173).
      at javax.ws.rs.core.GenericEntity.(GenericEntity.java:152)
      at org.jboss.resteasy.client.jaxrs.internal.proxy.processors.invocation.MessageBodyParameterProcessor.process(MessageBodyParameterProcessor.java:35)
      at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.createRequest(ClientInvoker.java:135)
      at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:101)
      at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:62)
      at com.sun.proxy.$Proxy38.getKarte(Unknown Source)
    
    GenericEntity を使っているようで,GenericEntity の T のところに primitive を入れてしまっているようだ。引数の primitive 型を class 型に変更して対応した。(戻り値は primitive 型でも大丈夫)

dto

  • dto については特に問題なく引っ越しできた。

2015年2月22日 (日)

InitDatabase の実行まで:REST(4)

  • JsonConverter の作成
    Json encode/decode のためのクラス。ContextResolver を implement して作る。これを登録すると,このクラスの Jackson ObjectMapper が使用されるようになる。 Lazy fetch を追いかけないために Hibernate4Module を登録している。
     hbm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
     hbm.configure(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION, false);
     mapper.registerModule(hbm);
    
    これをサーバ側では web.xml に加える。
     <context-param>
         <param-name>resteasy.providers</param-name>
         <param-value>open.dolphin.JsonConverter</param-value>
     </context-param>
    
    クライアント側では ResteasyClient に登録する。
    ResteasyClient client = new ResteasyClientBuilder().build();
    client.register(new JsonConverter());
    
  • open.dolphin.infomodel に以下のファイルをコピー
    • InfoModel
    • IInfoModel
    • UserModel
    • UserLiteModel
    • LicenseModel
    • DepartmentModel
    • FacilityModel
    • RoleModel
    • RadiologyMethodValue
  • bi-directional reference の解消
    UserModel と RoleModel はお互いにお互いのフィールドを参照しているため,そのままだと json 化の時に無限ループに入ってしまって Stack Overflow となる。対策には色々あるようだが,一番簡単そうな @JsonIdentityInfo を使う方法を使った。
    @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
    public class UserModel extends InfoModel  {
      :
    
    @JsonIdentityInfo(generator = ObjectIdGenerators.UUIDGenerator.class)
    public class RoleModel extends InfoModel  {
      :
    
    これで UserModel を json 化すると,以下のように UserModel の roles 内に出てくる UserModel が,親の UUID "f0c88e43-23bd-4aa4-8a71-e42ab6b2e257" への参照としてシリアライズされる。
    {
      "@id" : "f0c88e43-23bd-4aa4-8a71-e42ab6b2e257",
      "id" : 0,
      "roles" : [ {
        "@id" : "e0c1c1ce-06a1-45da-8795-afb59bb71ccf",
        "id" : 0,
        "user" : "f0c88e43-23bd-4aa4-8a71-e42ab6b2e257"
      } ]
    }
    
  • RemoteSystemService, RemoteSystemServiceImpl の改変
    RMI では @Remote を使っていたが,REST では @Path と @GET/@PUT/@POST/@DELETE を使う。また,json を使うために @Pruduces と @Consumes を付ける。これらを置き換えた SystemServiceSystemServiceImpl を作る。GET/PUT/POST の使い分けが難しいが,引数が1個の場合はとりあえず POST にしておけば,message body にパラメータを埋め込んでくれる。(引数が複数の場合は,@PathParam など,URL にパラメータを埋め込む必要がある)
  • クライアントからのアクセス方法
    以下のような手順で remote の Service クラスが取れる。ここらへんの手続きを DolphinClientContext というクラスにまとめた。
    // Resteasy
    ResteasyClient client = new ResteasyClientBuilder().connectionPoolSize(20).build();
    // register providers
    try {
        AuthorizationFilter authorizationFilter = new AuthorizationFilter(userId, hashPass);
        JsonConverter jsonConverter = new JsonConverter();
        client.register(authorizationFilter);
        client.register(jsonConverter);
    } catch (Exception e) {
        e.printStackTrace(System.err);
    }
    String restUrl = String.format(("http://%s/%s"), hostAddress, DOLPHIN_PATH);
    ResteasyWebTarget target = client.target(restUrl);
    SystemService service = target.proxy(SystemService.class);
    
    connectionPoolSize を設定しないと pool を使い果たした時点で以下のようなエラーとなる
    javax.ws.rs.ProcessingException: Unable to invoke request
    Caused by: java.lang.IllegalStateException: Invalid use of BasicClientConnManager: connection still allocated.
    Make sure to release the connection before allocating another one.
    
  • NetBeans でちょっと混乱したところ
    server を「ビルド」すると war と -classes jar ファイルができて client に反映される。しかし「ビルド」は deploy には影響しない。server を「実行」すると現在の class がデプロイされる。server のファイルを編集して保存するとすぐ deploy されるが,この時 -classes.jar ファイルはできていないので client はその編集を知らない。client に反映させるためには「ビルド」が必要になる。

2015年2月21日 (土)

依存性:REST(3)

クライアント pom.xml の要点

  • Web サーバは war ファイルで jar ではない。依存性に server を加えても server 側の infomodel などが読めないためビルドできない。
    Failed to execute goal on project client: Could not resolve dependencies for project open.dolphin:client:jar:1.3.0.10: Failure to find open.dolphin:server:jar:1.3.0.10 in http://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced -> [Help 1]
    
    そこで,後述の server 側の pom.xml で,server 側のクラスを入れた xxxx-classes.jar を作る設定にして,<classifier> を設定して,クライアントでそれを読むようにする。
    <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>open.dolphin.server</artifactId>
        <version>${project.version}</version>
        <classifier>classes</classifier>
    </dependency>
    
  • JBoss の REST は RESTEasy というもので,WildFly 8.2 には 3.0.10.Final が入っている。クライアントには同じバージョンのクライアントを入れる。
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-client</artifactId>
        <version>3.0.10.Final</version>
        <type>jar</type>
    </dependency>
    
  • これまで,受付リストのリアルタイム同期に JMS というシステムを使っていたが,最近ホットなのは WebSocket というものらしく,WildFly 8.2 にも WebSocket 1.1 というのが入っている。クライアント側で standalone で WebSocket を使用するためには以下の依存性が必要になる。
    <dependency>
        <groupId>org.glassfish.tyrus.bundles</groupId>
        <artifactId>tyrus-standalone-client-jdk</artifactId>
        <version>1.10</version>
    </dependency>
    

サーバ pom.xml の要点

  • クライアント側の pom.xml でも触れたが,server の war をクライアントの依存に加えるためには jar ファイルを作る必要がある。maven-war-plugin に以下のように2行加えることで,xxxx-classes-jar ができるようになる。
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
            <attachClasses>true</attachClasses> <!-- this line is manually added -->
            <classesClassifier>classes</classesClassifier> <!-- this line is manually added -->
        </configuration>
    </plugin>
    
  • RESTEasy を使うための依存。ただし,WildFly 8.2 にはもう入っているので,scope を provided にする。
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jaxrs</artifactId>
        <version>3.0.10.Final</version>
        <scope>provided</scope>
    </dependency>
    
  • Jackson 関連では,以下のものだけ provided にする。
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jackson2-provider</artifactId>
        <version>3.0.10.Final</version>
        <scope>provided</scope>
    </dependency>
    
  • Jackson で json encode/decode する際に,そのままだと entity の lazy fetch を追いかけてしまい,以下の Exception が出る。
    org.jboss.resteasy.spi.UnhandledException: Response is committed, can't handle exception
    Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: open.dolphin.infomodel.PatientModel.healthInsurances, could not initialize proxy - no Session (through reference chain: open.dolphin.infomodel.PatientVisitModel["patient"]->open.dolphin.infomodel.PatientModel["healthInsurances"])
    Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: open.dolphin.infomodel.PatientModel.healthInsurances, could not initialize proxy - no Session
    
    jackson-datatype-hibernate4 を使うと lazy fetch を追いかけないようにできる。
    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-hibernate4</artifactId>
        <version>2.4.5</version>
    </dependency>
    
  • Hibernate 関連で,最初,バージョンがぐちゃぐちゃになっているのに気付かないで,UnknownServiceException に苦しんだ。
    JBAS014134: EJB Invocation failed on component KarteServiceImpl for method public abstract long open.dolphin.service.KarteService.addDocument(open.dolphin.infomodel.DocumentModel): javax.ejb.EJBException: org.hibernate.service.UnknownServiceException: Unknown service requested [org.hibernate.service.jta.platform.spi.JtaPlatform]
    
    hibernate search を入れると,hibernate core 等の依存が自動的に入るが,それが古いバージョンになっていて,WildFly の hibernate とぶつかってしまうようだった。Hibernate の依存は難しいので,WildFly のソースに入っている pom.xml から該当部分をコピーして解決した。

追記

WildFly 8 には hibernate-search が入っているので,provided で依存を入れて,WEB-INF/jboss-deployment-structure.xml で hibernate search を有効化するとよいと増田先生の twitter で教えてもらった。
おかげさまで,pom.xml がかなりすっきりした(クライアント pom.xml,サーバ pom.xml)。さらに,war ファイルの容量がものすごく減った(21M → 3.5M)。 war 容量のほとんどが hibernate 関連のライブラリで占められてしまっていたようだ。

2015年2月20日 (金)

NetBeans でパッケージ作成:REST(2)

クライアント

  • 新規プロジェクト > Maven > Java アプリケーション
  • プロジェクト名: client
  • プロジェクトの場所: /path/to/opendolphin-1.3.0.10
  • グループ:jp.motomachi-hifuka
  • バージョン:1.3.0.10
  • パッケージ:open.dolphin.client
    パッケージができてから,右クリックでプロパティーを設定
  • アーティファクトID:open.dolphin.client
  • 名前:client
  • ソース/バイナリ形式:1.7
  • コンパイル:Javaプラットフォーム: JDK 1.7

サーバ

  • 新規プロジェクト > Maven > Web アプリケーション
  • プロジェクト名: server
  • プロジェクトの場所: /path/to/opendolphin-1.3.0.10
  • グループ:open.dolphin
  • バージョン:1.3.0.10
  • パッケージ:jp.motomachi-hifuka
  • サーバー:WildFly アプリケーションサーバ
  • Java EE バージョン:Java EE 7 Web
    できてから,プロパティーを設定。
  • アーティファクトID:open.dolphin.server
  • 名前:server
  • ソース/バイナリ形式:1.7
  • コンパイル Javaプラットフォーム: JDK 1.7
    (NetBeans を再起動しないと Java依存性が 1.7 にならないことがあった)
  • コンテキスト・パス: /dolphin
    コンテキスト・パスの設定により WEB-INF/jboss-web.xml の context-root が作成される
  • 「実行時にブラウザを表示」のチェックボックスを外す
  • web.xml 作成
    server 右クリック > 新規 > その他をクリック > Web > 標準のデプロイメントディスクリプタ
  • Application クラス作成
    server 右クリック > 新規 > Java ファイル > open.dolphin パッケージ
    Application を extend した DolphinApp.java を作る
    package open.dolphin;
    
    import javax.ws.rs.ApplicationPath;
    import javax.ws.rs.core.Application;
    
    @ApplicationPath("/")
    public class Dolphin extends Application {}
    
    REST ではサーバに http でアクセスするが,http://hostname:8080/context-root/ApplicationPath/xxx という URL になる。つまり,以上の設定で http://localhost:8080/dolphin/xxxx というふうにアクセスできるようになる

持続性ユニット作成

  • server 右クリック > 新規 > 持続性ユニット
  • 持続性ユニット名: DolphinPU
  • 永続性プロバイダ:Hibernate (JPA 2.1)
  • データソース:DolphinDS
  • Java Transaction API:使用する
  • 表作成戦略:なし
    デフォルトの create だと,2回目の実行時に org.postgresql.util.PSQLException: ERROR: relation "d_facility" already exists が出て,deploy に失敗する。
  • 以下のプロパティーを追加
    <property name="hibernate.id.new_generator_mappings" value="false"/>
    <property name="hibernate.hbm2ddl.auto" value="update"/>
    <!-- hibernate search -->
    <property name="hibernate.search.default.directory_provider" value="filesystem"/>
    <property name="hibernate.search.default.indexBase" value="/var/lucene/indexes"/>
    

2015年2月19日 (木)

サーバの REST 化に挑戦 〜 まずは OS X で WildFly 8.2.1 を起動する

当院の OpenDolphin はバージョン 1.3.0 ベースで,JBoss Application Server との通信に Java RMI (Remote Method Invocation) を使っている。最新の本家バージョンと増田先生バージョンは REST (Representational State Transfer) という仕組みを使うように変更されている。どうやら REST は http プロトコルを使うので,iPad からアクセスしたりできて,クライアントの自由度が上がるようだ。当院では RMI で特に困ってはいなかったのであるが,興味があったので REST 化に挑戦してみた。REST 化に際して,増田先生の 2.3m を参考にさせて頂いた。できたソースは Bitbucket さんで公開している。
まずは開発環境整備のため,mac で WildFly 8.2.1 を NetBeans から起動できるように設定するところから始めた。

Postgres の準備

postgres は Server.app を購入して,プログラミングの時だけ手動で起動するように設定した

WildFly 8.2.1 の準備

JBoss AS は現在は WildFly という名前になっている。
  • WIldFly の GitHub から branch 8.x を選択して "Download Zip" して解凍する。作業時点ではこれで 8.2.1.Final がダウンロードされた。
  • nekop 様のブログを参考にビルド開始。
    $ mvn -T4 clean install -DskipTests
     :
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 04:42 min (Wall Clock)
    [INFO] Finished at: 2015-01-30T20:08:10+09:00
    [INFO] Final Memory: 474M/2888M
    [INFO] ------------------------------------------------------------------------
    
    build/target/wildfly-8.2.1.Final-SNAPSHOT ができる。これを /Applications に持っていく。
  • /Applications/wildfly-8.2.1.FinalーSNAPSHOT/bin に移動して 起動してみる。
    $ ./standalone.sh -b 0.0.0.0
    =========================================================================
      JBoss Bootstrap Environment
      JBOSS_HOME: /Applications/wildfly-8.2.1.Final-SNAPSHOT
      JAVA: /Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/bin/java
      JAVA_OPTS:  -server -XX:+UseCompressedOops  -server -XX:+UseCompressedOops -Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true
    =========================================================================
      :
    15:30:03,780 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.2.1.Final-SNAPSHOT "Tweek" started in 9972ms - Started 450 of 503 services (92 services are lazy, passive or on-demand)
    
  • 管理ユーザ登録
    ./add-user.sh する。
    $ ./add-user.sh
    What type of user do you wish to add?
     a) Management User (mgmt-users.properties)
     b) Application User (application-users.properties)
    (a):
    Enter the details of the new user to add.
    Using realm 'ManagementRealm' as discovered from the existing property files.
    Username : user
    Password recommendations are listed below. To modify these restrictions edit the add-user.properties configuration file.
     - The password should not be one of the following restricted values {root, admin, administrator}
     - The password should contain at least 8 characters, 1 alphabetic character(s), 1 digit(s), 1 non-alphanumeric symbol(s)
     - The password should be different from the username
    Password : xxxx
    Re-enter Password : xxxx
    What groups do you want this user to belong to? (Please enter a comma separated list, or leave blank for none)[  ]:
    About to add user 'user' for realm 'ManagementRealm'
    Is this correct yes/no? yes
    Added user 'user' to file '/Applications/wildfly-8.2.1.Final-SNAPSHOT/standalone/configuration/mgmt-users.properties'
    Added user 'user' to file '/Applications/wildfly-8.2.1.Final-SNAPSHOT/domain/configuration/mgmt-users.properties'
    Added user 'user' with groups  to file '/Applications/wildfly-8.2.1.Final-SNAPSHOT/standalone/configuration/mgmt-groups.properties'
    Added user 'user' with groups  to file '/Applications/wildfly-8.2.1.Final-SNAPSHOT/domain/configuration/mgmt-groups.properties'
    Is this new user going to be used for one AS process to connect to another AS process?
    e.g. for a slave host controller connecting to the master or for a Remoting connection for server to server EJB calls.
    yes/no? yes
    To represent the user add the following to the server-identities definition 
    
  • JDBC の登録。JDBC 41 ドライバ(postgresql-9.4-1200.jdbc41.jar)をダウンロードする。ダウンロードした JDBC を登録し,それを使った Datasource (DolphinDS) を作成する。
    $ ./jboss-cli.sh --connect
    [standalone@localhost:9990 /] module add --name=org.postgres --resources=~/Downloads/postgresql-9.4-1200.jdbc41.jar --dependencies=javax.api,javax.transaction.api
    [standalone@localhost:9990 /] /subsystem=datasources/jdbc-driver=postgres:add(driver-name="postgres",driver-module-name="org.postgres",driver-class-name=org.postgresql.Driver)
    {"outcome" => "success"}
    [standalone@localhost:9990 /] data-source add --jndi-name=java:jboss/datasouces/DolphinDS --name=DolphinDS --connection-url=jdbc:postgresql://localhost/dolphin --driver-name=postgres --user-name=dolphin
    [standalone@localhost:9990 /] quit
    
  • Datasource がセットされているのをブラウザで確認。
    http://localhost:9990/console にアクセスし,登録したユーザでログイン,Configuration > Datasources とクリック
  • ちなみに,ログの設定方法。
    [standalone@localhost:9990 /] /subsystem=logging/root-logger=ROOT:write-attribute(name=level,value=DEBUG)
    
    SQL を表示させるには
    [standalone@localhost:9990 /] /subsystem=logging/logger=org.hibernate.SQL:add(level=INFO)
    
    元に戻すには
    [standalone@localhost:9990 /] /subsystem=logging/logger=org.hibernate.SQL:change-log-level(level=INFO)
    
    WildFly の設定はこれで終了。^C で WildFly を終了する。zip で固めて他の環境へ持って行けるようにしておく。

NetBeans 8.0.2 で WildFly 起動

  • サービス > サーバ と選択し,右クリックから「サーバの追加」を選択,WildFly アプリケーションサーバを選択。
  • サーバの場所を指定,構成は standalone.xml にする。
  • 右クリック > 起動で,NetBeans から WildFly が起動される

2015年2月16日 (月)

プリンタ故障

処方箋を打ち出している HL-5450 DN が,印刷開始時にバチンと音がでて,その後,印刷がにじむようになってしまった。ドラムを換えても症状変わらず。そのうち,ビニールの切れ端みたいなものが排出される(!?)に至って寿命と判断。同機種の新品に交換した。ページカウンタ 90,227。前の HL-5250 の 14万ページにはかなわないが,まあ,よく働いてくれた方だと思う。

2015年2月12日 (木)

7年目の運用まとめ

7年目はハードの更新もなく,windows マシン1台でグラフィックボードが故障して交換した以外は特にシステムに変わったことはなかった。安定した1年だったと思う。

  • データベースの PatientModel の件数
    dolphin=# select count(*) from d_patient;
     count
    -------
     21958
    (1 row)
    
  • データベースの ModuleModel の件数
    dolphin=# select count(*) from d_module;
     count
    --------
     838591
    (1 row)
    
  • Dolphin サーバの df。1年で 12G → 14G に増加。
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/xvda1       46G   14G   31G  30% /
    udev            976M  4.0K  976M   1% /dev
    tmpfs           200M  172K  200M   1% /run
    none            5.0M     0  5.0M   0% /run/lock
    none            997M     0  997M   0% /run/shm
    
  • Orca サーバの df。5.9G → 7.4G に増加。
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/xvda1       28G  7.4G   19G  29% /
    udev            976M  4.0K  976M   1% /dev
    tmpfs           200M  208K  200M   1% /run
    none            5.0M     0  5.0M   0% /run/lock
    none            997M     0  997M   0% /run/shm
    
  • データベースの dump ファイルのサイズ。
    dolphin_db.dump.gpg 1,707,078,356 
    orca_db.dump.gpg 76,656,853  
    
  • 作成したスタンプ数。
    $ grep -c stampInfo stamp.xml 
    2045
    

2015年2月 5日 (木)

jma-receview が動かなくなった

レセプトチェックをしようと思って jma-receview を起動しようとしたら起動しなかった orz。そういえば,数日前にものすごく久しぶりに aptitude upgrade していたことを思い出した。jma-receview が update されて,当院の環境で動かなくなってしまったものと考えられた
jma-receview は ruby プログラムなので,以下のようにすると debug 情報が得られる。
$ ruby -d -w /usr/bin/jma-receview
Exception `NoMethodError' at /usr/lib/ruby/1.8/rational.rb:78 - undefined method `gcd' for Rational(1, 2):Rational
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/glib2.rb:97 - no such file to load -- 1.8/glib2.so
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/glib2.rb:220 - no such file to load -- gettext
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/atk.rb:9 - no such file to load -- 1.8/atk.so
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/cairo.rb:44 - no such file to load -- 1.8/cairo.so
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/pango.rb:26 - no such file to load -- 1.8/pango.so
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/gdk_pixbuf2.rb:9 - no such file to load -- 1.8/gdk_pixbuf2.so
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/gtk2/base.rb:23 - no such file to load -- 1.8/gtk2.so
Exception `LoadError' at /usr/lib/ruby/1.8/jma/receview/gtk2_fix.rb:28 - no such file to load -- gtksourceview2
Exception `LoadError' at /usr/lib/ruby/1.8/jma/receview/gtk2_fix.rb:31 - no such file to load -- gtksourceview
Exception `RuntimeError' at /usr/lib/ruby/1.8/jma/receview/print.rb:10 -
Exception `LoadError' at /usr/lib/ruby/1.8/tmpdir.rb:14 - no such file to load -- Win32API
Exception `LoadError' at /usr/lib/ruby/vendor_ruby/1.8/poppler.rb:40 - no such file to load -- 1.8/poppler.so
Exception `LoadError' at /usr/lib/ruby/1.8/jma/receview/isoimage.rb:66 - no such file to load -- cdio
Exception `Errno::ENOENT' at /usr/lib/ruby/1.8/jma/receview/config.rb:335 - そのようなファイルやディレクトリはありません - receview.conf
Exception `NoMethodError' at /usr/lib/ruby/1.8/jma/receview/gui.rb:2941 - undefined method `+' for nil:NilClass
/usr/lib/ruby/1.8/jma/receview/gui.rb:2941:in `event': undefined method `+' for nil:NilClass (NoMethodError)
	from /usr/lib/ruby/1.8/jma/receview/gui.rb:2918:in `initialize'
	from /usr/lib/ruby/1.8/jma/receview/gui.rb:97:in `new'
	from /usr/lib/ruby/1.8/jma/receview/gui.rb:97:in `initialize'
	from /usr/bin/jma-receview:12291:in `new'
	from /usr/bin/jma-receview:12291
gui.rb の event で何かが nil になって止まっているらしい。puts を入れて調べてみたところ,@base.desktop_native と @base.doc_native が nil になっていた。
def event
 :
  else
    if Gtk::platform_support_os_linux(Gtk::GTK_SUPPORT_VERSION_AMD64)
      desktop = @base.desktop_native + @base.path_char
      @gtk_desk_button.signal_connect("clicked") do
        self.set_filename(desktop)
        self.dir_list.signal_emit("cursor-changed")
      end

      doc = @base.doc_native + @base.path_char
      @gtk_doc_button.signal_connect("clicked") do
        self.set_filename(doc)
        self.dir_list.signal_emit("cursor-changed")
      end
    end
  end
end
base.rb を読んでみると $HOME/.config/user-dirs.dirs ファイル内の XDG_DESKTOP_DIR, XDG_DOCUMENTS_DIR というプロパティーを読みに行っている。調べてみたら,これは xdg-user-dirs というシステムの仕組みらしい。いつの間にか,xdg-user-dirs の設定が必須になってしまっていたようだ。
当院の orca にデスクトップはインストールしていない(jma-receview は mac の x-window サーバで表示させている)ので,当然 xdg-user-dirs などというものも入っていない。
以下のように自分で ~/.config/user-dirs.dirs を作って無事動くようになった。
XDG_DESKTOP_DIR="$HOME"
XDG_DOCUMENTS_DIR="$HOME"

« 2014年12月 | トップページ | 2015年3月 »