XPagesの文書ロックライブラリ

XPagesで文書ロックをやってみよう。要は誰かが文書の編集を始めたら、別の人が編集を始めようとしてもエラーになるというもの。

原理は単純で、

この方法の特徴は、編集する文書とは別の文書にロック情報を保持させるため、文書そのものを編集と同時に保存しなくていいということ。 編集を途中でやめたくなってもすでに保存が走っているわけじゃないので、副作用が少なくていい。

デメリットはロック文書がたまっていくということ。ロック文書を削除するようにすればたまってはいかないけど、 DBの圧縮をこまめにやらないといけなくなるかも。

前準備

ロック文書用のフォームとロック文書検索用のビューが必要。検索用ビューは文書のNote-IDで検索するなら、1列目をそのIDにしておく。 文書ごとに何らかのユニークな管理番号をつけているなら、その管理番号を1列目にして、ソートしておけばいい。

後はロックしたり、ロック解除したり、ロック状況を確認するメソッドを用意しておけばいい。

文書ロック管理ライブラリ

文書ロックを管理するライブラリを作ってみた。

  01: $lock = {};
  02: (function() {
  03:     var VIEW_LOCK = "lockView";
  04:     var NAME_FIELD = "name";
  05:     var LOCKTO_FIELD = "lockTo";
  06:     var LOCK_FORM = "lock";
  07:     var DOCID_FIELD = "docID";
  08:     $lock.lock = function(doc_id) {
  09:         var view = database.getView(VIEW_LOCK);
  10:         var lock_doc:NotesDocument = view.getDocumentByKey(doc_id, true);
  11:         if (lock_doc) {
  12:             var lock_name = lock_doc.getItemValueString(NAME_FIELD);
  13:             var my_name = @UserName();
  14:             if ((lock_name == "") || (lock_name == my_name)) {
  15:                 // Lock Free or My Lock
  16:                 _update_lock(lock_doc, doc_id, my_name);
  17:                 return [true, ""];
  18:             } else {
  19:                 // Other Member's Lock
  20:                 if (_isOverLockTo(lock_doc)) {
  21:                     // Ober Time Lock
  22:                     _update_lock(lock_doc, doc_id, my_name);
  23:                     return [true, ""];
  24:                 } else {
  25:                     return [false, lock_name];
  26:                 }
  27:             }
  28:         } else {
  29:             // No Lock doc
  30:             var lock_doc = database.createDocument();
  31:             lock_doc.replaceItemValue("Form", LOCK_FORM);
  32:             var my_name = @UserName();
  33:             _update_lock(lock_doc, doc_id, my_name);
  34:             return [true, ""];
  35:         }
  36:     }
  37:
  38:     $lock.unlock = function(doc_id) {
  39:         var view = database.getView(VIEW_LOCK);
  40:         var lock_doc:NotesDocument = view.getDocumentByKey(doc_id);
  41:         if (lock_doc) {
  42:             var lock_name = lock_doc.getItemValueString(NAME_FIELD);
  43:             var my_name = @UserName();
  44:             if (lock_name == my_name) {
  45:               _clear_lockDoc(lock_doc);
  46:               return true;
  47:             } else {
  48:               // Other Member already lock this doc.
  49:               return false;
  50:             }
  51:         } else {
  52:             // Lock doc was deleted by someone.
  53:             return true;
  54:         }
  55:     }
  56:
  57:
  58:
  59:     // Private
  60:     var ADDITIONAL_TIME = 30; // 30min
  61:     function _update_lock(lock_doc, doc_id, my_name) {
  62:         var date:Date = @Now();
  63:         date = @Adjust(date, null, null, null, null, ADDITIONAL_TIME, null);
  64:         var timestamp = I18n.toString(date,"yyyy/MM/dd HH:mm:ss");
  65:         lock_doc.replaceItemValue(NAME_FIELD, my_name);
  66:         lock_doc.replaceItemValue(DOCID_FIELD, doc_id);
  67:         lock_doc.replaceItemValue(LOCKTO_FIELD, timestamp);
  68:         try {
  69:             lock_doc.save();
  70:             return true;
  71:         } catch(err) {
  72:             return false;
  73:         }
  74:     }
  75:
  76:     function _isOverLockTo(lock_doc) {
  77:         var date:Date = @Now();
  78:         var timestamp = I18n.toString(date, "yyyy/MM/dd HH:mm:ss");
  79:         var lockTo = lock_doc.getItemValueString(LOCKTO_FIELD);
  80:         if (timestamp > lockTo) {
  81:             return true;
  82:         } else {
  83:             return false;
  84:         }
  85:     }
  86:
  87:     function _clear_lockDoc(lock_doc) {
  88:         lock_doc.replaceItemValue(NAME_FIELD, "");
  89:         lock_doc.replaceItemValue(LOCKTO_FIELD, "");
  90:         try {
  91:             lock_doc.save();
  92:             return true;
  93:         } catch(err) {
  94:             return false;
  95:         }
  96:     }
  97: })();
  

文書ロック管理ライブラリの使い方

文書ロック管理ライブラリはこんな感じで使う。

以下のは編集開始のボタン。

  01:   <xp:button id="button1" value="編集">
  02:   <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
  03:       <xp:this.action><![CDATA[#{javascript:
  04:         var doc_id = document1.getItemValueString("管理番号");
  05:         var result = $lock.lock(doc_id);
  06:         if (result[0]) {
  07:           context.setDocumentMode("edit");
  08:         } else {
  09:           @ErrorMessage(result[1] + "さんが編集中です", "errMessage");
  10:         }
  11:        }]]></xp:this.action>
  12:   </xp:eventHandler>01:   <xp:button id="button1" value="編集">
  02:   <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
  03:       <xp:this.action><![CDATA[#{javascript:
  04:         var doc_id = document1.getItemValueString("管理番号");
  05:         var result = $lock.lock(doc_id);
  06:         if (result[0]) {
  07:           context.setDocumentMode("edit");
  08:         } else {
  09:           @ErrorMessage(result[1] + "さんが編集中です", "errMessage");
  10:         }
  11:        }]]></xp:this.action>
  12:   </xp:eventHandler>
  

今度は保存ボタンのコード。

  01:   <xp:button value="保存" id="button2">
  02:       <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
  03:           <xp:this.action><![CDATA[#{javascript:
  04:             var doc_id = document1.getItemValueString("管理番号");
  05:             if ($lock.unlock(doc_id)) {
  06:               document1.save();
  07:               var unid = document1.getDocument().getUniversalID();
  08:               var url = "/form.xsp?documentId=" + unid + "&action=openDocument";
  09:               context.redirectToPage(url);
  10:             } else {
  11:               @ErrorMessage("誰かが編集に割り込んできたため保存できません。", "errMessage");
  12:             }
  13:           }]]></xp:this.action>
  14:       </xp:eventHandler>
  15:   </xp:button>