クリアフィルター
記事
Megumi Kakechi · 2023年4月3日
これは InterSystems FAQ サイトの記事です。
データ取込み処理の性能・エラー(Lock Table Full)対策として、一般メモリヒープ(gmheap)や ロックテーブルサイズ(locksiz)のパラメータチューニングを行う場合があると思います。
実際に、現在どのくらいの一般メモリヒープが確保できているのかは、ターミナルと管理ポータルで確認することができます。
★ターミナルの場合
// 一般メモリヒープサマリ
USER>w $system.Config.SharedMemoryHeap.GetUsageSummary()
4992226,6029312,59441152
一般メモリヒープサマリは、使用量,アロケート量,構成量(bytes) で戻り値が表示されます。
使用量は、アロケートされたロックテーブルやプロセステーブルなどで実際に使用されている量になります。アロケート量は、gmheapの領域でロックテーブルやプロセステーブルなどでアロケートされている量になります。構成量は、gmheap(KB) +IRISシステム追加領域 で、これが現在の最大利用可能な量(実際の一般メモリヒープの領域の値)になります。
上で述べたように、構成量は構成パラメータの gmheap の単体の値と一致していません。これは、IRISが自動で 構成パラメータ gmheap に内部で使用するメモリ領域分を付加して、一般メモリヒープの領域を構成しているためになります。詳細は以下のドキュメントをご覧ください。
gmheapについて
以下のコマンドでは、ロックテーブル使用量を取得できます。使用可能量, ユーザ使用可能量, 使用量(bytes) で戻り値が表示されます。詳細はこちらの記事をご覧ください。
%SYS>w ##class(SYS.Lock).GetLockSpaceInfo()
16772624,16764624,4592
★管理ポータルの場合
システムオペレーション > システム使用 > 共有メモリヒープ使用状況 より確認できます。
一般メモリヒープ全体については、 "Total SMH Pages Used"の項目の"割り当てられたSMH/ST"が、アロケート量(bytes) を示します。
ロックテーブルについては "Lock Table"の項目の"SMH/ST使用中"が、ロックテーブルの使用量(byte) を示します。 ユーザ使用可能量は、locksiz 値からこの値の差分により求める必要があります。
gmheap を変更する場合、IRISインスタンスの再起動を伴います。現在の gmheap 内で設定可能な、locksiz の最大値を求めるには以下のように行います。locksiz のみであれば、再起動なしに変更が可能です。
%SYS>write ##class(SYS.Lock).GetMaxLockTableSize()
16777216
GetMaxLockTableSize() で取得できる値よりも大きな locksiz を指定したい場合は、差分を gmheap に追加して設定する必要があります。
その場合は、IRISインスタンスの再起動後に新しい設定値が反映されます。
記事
Mihoko Iijima · 2024年12月24日
これは InterSystems FAQ サイトの記事です。
イベントログの削除には、Ens.Util.LogクラスのPurge()メソッドを使用します。実行時以下の引数を指定します。
第1引数:削除数(参照渡し)
第2引数:保持日数(デフォルト7)
メッセージの削除には、2種類の方法があります。
1) 2022.1.2以降の導入されたマルチプロセスで削除する方法
Ens.Ens.Util.MessagePurgeクラスのPurge()メソッドを使用します。実行時以下の引数を指定します。
第1引数:削除数(参照渡し)
第2引数:保持日数(デフォルト7)
第3引数:1を指定(Completeではないメッセージの削除を防止するための指定)
第4引数:メッセージボディも一緒に削除する場合は1を指定
第5引数:デフォルトは500(秒)が設定されていますが、大量のメッセージをパージするとクリアされたビットマップの最適化に時間を要して最適化が完了しない場合があるため、大量削除の場合は 10000000000など大きな値を指定します。
2) Ens.MessageHeaderクラスのPurge()メソッドを使用する方法。
実行時以下の引数を指定します。
第1引数:削除数(参照渡し)
第2引数:保持日数(デフォルト7)
第3引数:1を指定(Completeではないメッセージの削除を防止するための指定)
第4引数:メッセージボディも一緒に削除する場合は1を指定
上記パージ用メソッド実行時、ログ削除の内容もジャーナルに記録されますので、Purge()メソッド実行中プロセスのジャーナルファイルへの書き込みを無効にする設定を使用します。
※ジャーナルファイルへの書き込みが無効化されるのは、以下ユーティリティを実行しているプロセスのみのため、システム全体に影響はありません。
※注意※ ミラーリングを使用している環境でミラーデータベースへの更新ではこのジャーナル無効化の影響を受けず、ジャーナルが記録されますのでご注意ください。
以下実行例です。
//使用中プロセスのジャーナルファイルへの書き込み無効化
do DISABLE^%NOJRN
//イベントログを直近7日分を保持して削除
set st=##class(Ens.Util.Log).Purge(.cnt,7,1)
//削除数確認
write cnt,!
//マルチスレッド対応のメッセージヘッダとボディを直近7日分を保持して削除する
set st=##class(Ens.Util.MessagePurge).Purge(.cnt2,7,1,1,10000000000)
//削除数確認
write cnt2,!
//メッセージヘッダとボディを直近7日間分を保持して削除
set st=##class(Ens.MessageHeader).Purge(.cnt3,7,1,1)
//削除数確認
write cnt3,!
//使用中プロセスのジャーナルファイルへの書き込み有効化
do ENABLE^%NOJRN
記事
Toshihiko Minamoto · 2021年10月28日
InterSystems開発者コミュニティにおいて、CachéアプリケーションへのTWAINインターフェースの作成の可能性に関する質問が上がりました。 Webクライアントの撮像装置からサーバーにデータを取得し、そのデータをデータベースに保管する方法について、素晴らしい提案がいくつかなされました。
しかし、こういった提案を実装するには、Webクライアントからデータベースサーバーにデータを転送し、受信データをクラスプロパティ(または質問のケースで言えばテーブルのセル)に格納できなければなりません。 この方法は、TWAINデバイスから受信した撮像データを転送するためだけでなく、ファイルアーカイブや画像共有などの整理といったほかのタスクにも役立つ可能性があります。
そこで、この記事では主に、HTTP POSTコマンドの本体から、raw状態またはJSON構造にラップしてデータを取得するRESTfulサービスを記述する方法を説明することにします。
RESTの基本
具体的な話に入る前に、まずREST全般と、IRISでRESTfulサービスがどのように作成されるかについて簡単に説明しましょう。
Representational state transfer(REST)は、分散ハイパーメディアシステムのためのアーキテクチャスタイルです。 RESTにおける情報の重要な抽象化は、適切な識別子を持ち、JSON、XML、またはサーバーとクライアントの両方が認識するほかの形式で表されるリソースです。
通常、クライアントとサーバー間でのデータの転送にはHTTPが使用されます。 CRUD(作成、読み取り、更新、削除)操作ごとに独自のHTTPメソッド(POST、GET、PUT、DELETE)があり、リソースまたは一連のリソースには独自のURIがあります。
この記事では、POSTメソッドのみを使用して新しい値をデータベースに挿入するため、その制約を知る必要があります。
「[IETF RFC7231 4.3.3 POST](https://tools.ietf.org/html/rfc7231#section-4.3.3) 」の仕様によると、POSTには本文に格納されるデータサイズに関する制限はありません。 しかし、Webサーバーやブラウザでは独自の制限が適用され、通常は1 MBから2 GBとなっています。 たとえば、[Apache](https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestbody)の制限は最大2 GBとなっています。 いずれにせよ、2 GBのファイルを送信する必要がある場合は、アプローチを考え直すことをお勧めします。
IRISにおけるRESTfulサービスの要件
IRISでRESTfulサービスを実装するには、次を行う必要があります。
抽象クラス%CSP.RESTを拡張するクラスブローカーを作成します。 これは逆に、%CSP.Pageを拡張するため、さまざまな便利なメソッド、パラメーター、オブジェクト、特に%requestへのアクセスが可能になります。
ルートを定義するUrlMapを指定します。
オプションとしてUseSessionパラメーターを設定し、各REST呼び出しが独自のWebセッションで実行されるのか、ほかのREST呼び出しで1つのセッションを共有するのかを設定します。
ルートで定義された演算を実行するクラスメソッドを提供します。
CSP Webアプリケーションを定義し、そのセキュリティをWebアプリケーションページ(システム管理 > セキュリティ > アプリケーション > Webアプリケーション)に指定します。Dispatchクラスには、ユーザークラスの名前とNameが格納されています。これはREST呼び出しのURLの最初の部分です。
一般に、大きなデータ(ファイル)のチャンクとメタデータをクライアントからサーバーに送信するには、次のようにいくつかの方法があります。
ファイルとメタデータをBase64-encodeで暗号化し、サーバーとクライアントの両方に暗号化/復号化を行うための処理オーバーヘッドを追加します。
最初にファイルを送信し、クライアントにIDを返します。すると、そのIDを持つメタデータが送信されます。 サーバーはファイルとメタデータをもう一度関連付けます。
最初にメタデータを送信し、クライアントにIDを返します。すると、そのIDのファイルが送信されます。サーバーはファイルとメタデータをもう一度関連付けます。
この第1回目の記事では、基本的に2つ目のアプローチを使用しますが、クライアントへのIDの送信とメタデータ(別のプロパティとして格納するためのファイル名)の追加については、このタスクでは特に意味を持たないため、行いません。 第2回目の記事では、最初のアプローチを使用しますが、私のファイルとメタデータをサーバーに送信する前にJSON構造にパッケージ化します。
RESTfulサービスの実装
では、具体的な内容に入りましょう。 まず、設定しようとしているクラスとプロパティを定義しましょう。
Class RestTransfer.FileDesc Extends %Persistent
{
Property File As %Stream.GlobalBinary;
Property Name As %String;
}
もちろん、通常はファイルに使用するメタデータがほかにもありますが、私たちの目的にはこれで十分です。
次に、クラスブローカーを作成する必要があります。これは、ルートとメソッドを使用して後で拡張します。
Class RestTransfer.Broker Extends %CSP.REST
{
XData UrlMap
{
<Routes>
</Routes>
}
}
そして最後に、前段階のセットアップとして、このアプリケーションをWebアプリケーションのリストに指定する必要があります。
前段階のセットアップが完了したので、RESTクライアント([Advanced REST Client](https://install.advancedrestclient.com/install)を使用します)から受信するファイルをクラスRestTransfer.FileDescのインスタンスのFileプロパティとしてデータベースに格納するメソッドを記述できるようになりました。
では、POSTメソッドの本体にRawデータとして格納されている情報から始めましょう。 まず、新しいルートをUrlMapに追加しましょう。
<Route Url="/file" Method="POST" Call="InsertFileContents"/>
このルートは、サービスがURL/RestTransfer/fileでPOSTコマンドを受信すると、InsertFileContentsクラスメソッドを呼び出すことを指定します。 より簡単に行うために、このメソッドの中にクラスRestTransfer.FileDescの新しいインスタンスを作成して、そのFileプロパティを受信データに設定します。 これにより、ステータスと、成功またはエラーのいずれかを示すJSON形式のメッセージが返されます。 以下はそのクラスメソッドです。
ClassMethod InsertFileContents() As %Status
{
Set result={}
Set st=0
set f = ##class(RestTransfer.FileDesc).%New()
if (f = $$$NULLOREF) {
do result.%Set("Message","Couldn't create an instance of the class")
} else {
set st = f.File.CopyFrom(%request.Content)
If $$$ISOK(st) {
set st = f.%Save()
If $$$ISOK(st) {
do result.%Set("Status","OK")
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
} else {
do result.%Set("Message",$system.Status.GetOneErrorText(st))
}
}
write result.%ToJSON()
Quit st
}
まず、クラスRestTransfer.FileDescの新しいインスタンスを作成し、正常に作成されたこととOREFがあることをチェックします。 オブジェクトを作成できなかった場合は、JSON構造を作成します。
{"Message", "Couldn't create an instance of class"}
オブジェクトが作成された場合は、リクエストのコンテンツがプロパティFileにコピーされます。 コピーに成功しなかった場合は、エラーの説明を含むJSON構造を作成します。
{"Message",$system.Status.GetOneErrorText(st)}
コンテンツがコピーされた場合は、オブジェクトを保存し、保存に成功した場合は、JSON {"Status","OK"}を作成します。 そうでない場合、JSONはエラーの説明を返します。
最後に、このJSONをレスポンスに書き込んで、ステータスstを返します。
画像を転送する例を次に示します。
データベースへの保存方法:
このストリームをファイルに保存して、変更されずに転送されたことを確認できます。
set f = ##class(RestTransfer.FileDesc).%OpenId(4)
set s = ##class(%Stream.FileBinary).%New()
set s.Filename = "D:\Downloads\test1.jpg"
do s.CopyFromAndSave(f.File)
テキストファイルでも同じことができます。
これはグローバルでも表示可能です。
また、実行可能ファイルやその他の種類のデータを保存できます。
グローバルでサイズを確認できます。正しいサイズを確認できます。
この部分は正常に動作するようになったので、2番目のアプローチを確認することにしましょう。ファイルのコンテンツとJSON形式で格納されているメタデータ(ファイル名)を取得し、POSTメソッドの本体で転送するアプローチです。
RESTサービスの作成についての詳細は、InterSystemsの[ドキュメント](https://docs.intersystems.com/iris20191j/csp/docbook/Doc.View.cls?KEY=GREST)をご覧ください。
両方のアプローチのサンプルコードは[GitHub](https://github.com/Gra-ach/RESTFileTransfer)と[InterSystems Open Exchange](https://openexchange.intersystems.com/package/RESTFileTransfer)にあります。 いずれかに関連する質問や提案があれば、お気軽にコメント欄に書き込んでください。
記事
Hiroshi Sato · 2020年10月19日
これはInterSystems FAQ サイトの記事です。
2つのステップにて作業します。
クラス定義の移行
クラス定義を別システムへ移行するため、XML形式またはUDL形式(拡張子.cls)のファイルにエクスポートします。
スタジオでのエクスポート手順は以下の通りです。
[ツール] > [エクスポート]
> [追加]ボタンで移行したいクラスを複数選択
> [ローカルファイルにエクスポート]にチェック
> ファイルの種類がXMLであることを確認し、ファイル名を入力し、[OK]
この後、別システム上のスタジオで、エクスポートしたXML、UDLファイルをインポートします。
この手順で、クラス定義は移行できます。
スタジオでのインポート手順は以下の通りです。
[ツール] > [ローカルからインポート]
> 上記手順で出力したXML、UDLファイルを指定します。
データの移行
次に実際のデータを移行します。オブジェクトデータは既定では、以下の命名規則のグローバル変数内に格納されています。データ :^クラス名Dインデックス:^クラス名Iストリーム :^クラス名S例)User.testクラスのデータは以下の3つのグローバルに格納されます。^User.testD, ^User.testI, ^User.testS
これらのうち存在するグローバル変数をすべて、システム管理ポータル(Caché5.0以前では、エクスプローラ)から、エクスポートします。
エクスポート手順は以下の通りです。 【バージョン2011.1~】
管理ポータルを利用する場合、以下メニュからエクスポートを行います。
[システムエクスプローラ] > [グローバル] ([グローバル]をダブルクリックか、移動ボタンを押下)
> 左ペインからネームスペースを選択する> [エクスポート]をクリック> エクスポートしたいグローバルをチェックし、出力ファイル名を指定 【バージョン2010.2以前】
システム管理ポータルを利用する場合、以下メニュからエクスポートを行います。
データ管理→グローバル
→ 左ペインからネームスペースを選択する→ 「エクスポート」をクリック→ エクスポートしたいグローバルをチェックし、出力ファイル名を指定
次に上記ファイルを、別システムにインポートします。
インポート手順は以下の通りです。 【バージョン2011.1~】
管理ポータルを利用する場合、以下メニュからインポートします。
システムエクスプローラ→グローバル (「グローバル」をダブルクリックか、移動ボタンを押下)
→ 左ペインからネームスペースを選択する→ インポートをクリック→ エクスポートしたファイルを指定→ インポートを実行 【バージョン2010.2以前】
システム管理ポータルを利用する場合、以下メニュからインポートします。
[データ管理] > [グローバル]> 左ペインからネームスペースを選択する> インポートをクリック> エクスポートしたファイルを指定> インポートを実行
以上で、データの移行が完了します。
記事
Mihoko Iijima · 2023年3月13日
これは InterSystems FAQ サイトの記事です。
永続クラス定義では、データを格納するグローバル変数名を初回クラスコンパイル時に決定しています。グローバル変数名は、コンパイル後に表示されるストレージ定義(Storage)で確認できます。
例)
Class Training.Person Extends %Persistent{Property Name As %String; Property Email As %String; Storage Default{<Data name="PersonDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value><Value name="3"><Value>Email</Value></Value></Data><DataLocation>^Training.PersonD</DataLocation><DefaultData>PersonDefaultData</DefaultData><ExtentSize>0</ExtentSize><IdLocation>^Training.PersonD</IdLocation><IndexLocation>^Training.PersonI</IndexLocation><StreamLocation>^Training.PersonS</StreamLocation><Type>%Storage.Persistent</Type>}
}
DataLocation:^パッケージ名.クラス名D永続クラスのデータが登録されるグローバル変数です。
IndexLocation:^パッケージ名.クラス名Iインデックスが格納されるグローバル変数です。
StreamLocation:^パッケージ名.クラス名Sストリームプロパティのデータが格納される変数です。
例外として、31文字以上のクラス名を指定した場合、グローバル変数名の文字数制限を超えてしまうため、ネームスペースで一意となる適当なグローバル変数名を使用します。
Class TestPackage.VeryLongLongLongLongName Extends %Persistent{Property Name As %String; Storage Default{<Data name="VeryLongLongLongLongNameDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value></Data><DataLocation>^TestPackage.VeryLongLon92A3D</DataLocation><DefaultData>VeryLongLongLongLongNameDefaultData</DefaultData><IdLocation>^TestPackage.VeryLongLon92A3D</IdLocation><IndexLocation>^TestPackage.VeryLongLon92A3I</IndexLocation><StreamLocation>^TestPackage.VeryLongLon92A3S</StreamLocation><Type>%Storage.Persistent</Type>}}
ストレージ定義未作成の場合、DEFAULTGLOBALパラメータを利用してグローバル変数名を指定することができます。(指定したグローバル変数名の末尾にD、I、Sを付与した名称をストレージに定義します。)
Class TestPackage.VeryLongLongLongLongName Extends %Persistent{Property Name As %String;Parameter DEFAULTGLOBAL = "^Test.LongName";Storage Default{<Data name="VeryLongLongLongLongNameDefaultData"><Value name="1"><Value>%%CLASSNAME</Value></Value><Value name="2"><Value>Name</Value></Value></Data><DataLocation>^Test.LongNameD</DataLocation><DefaultData>VeryLongLongLongLongNameDefaultData</DefaultData><IdLocation>^Test.LongNameD</IdLocation><IndexLocation>^Test.LongNameI</IndexLocation><StreamLocation>^Test.LongNameS</StreamLocation><Type>%Storage.Persistent</Type>}}
詳細は以下ドキュメントもご参照ください。
標準のグローバル名
ユーザ定義のグローバル名
この他、2017.2以降からストレージに定義されるグローバル変数名をハッシュ化したグローバル変数名に変更できるパラメータ:USEREXTENTSETが、パフォーマンス向上のために追加されました。
ストレージ未作成時(クラス定義の初回コンパイル前)に設定することで、 ^EPgS.D8T6.1 のようなハッシュ化したグローバル変数名が設定されます。
Parameter USEEXTENTSET = 1;
USEEXTENTSETパラメータを使用する場合のストレージ定義について詳細は、関連トピックをご参照ください。
関連記事もご参照ください
テーブル定義のデータが格納されるグローバル変数名について
記事
Tomoko Furuzono · 2023年4月10日
これは、InterSystems FAQサイトの記事です。管理ポータル:システムエクスプローラの使用には、%DevelopmentリソースのUse特権が必要です。システムエクスプローラでの参照のみ利用可能とする権限をユーザに付与したい場合は、%DevelopmentリソースのUse特権(※1)と、該当のデータベースリソース(※2)への参照特権(R)を付与したロールを作成し、これをユーザに与えます。※1.「%Development:U」を付与している場合はターミナルやスタジオも参照のみで使用可能となります。※2.参照したいデータベースに割り当てられているリソースが%DB_DEFAULTリソースになっており、このデータベースのみに参照権限を設定したい場合は、事前に、このデータベース用の独自リソース(%DB_<データベース名>)を作成し、該当データベースに割り当てるようにします。
<図:独自リソースの作成>[管理ポータル]>[システム管理]>[構成]>[システム構成]>[ローカルデータベース]>(該当データベースを選択)
【例】:testAユーザに、TESTデータベースへの参照のみを許可する。1.新規ロール作成
2.ロール編集(リソースへの権限の追加)
3.上記で作成したロールをユーザに付与。以上の設定で、testAユーザは、システムエクスプローラでの該当データベースへの参照のみが可能となります。※実際に対象ユーザ(上記testA)が使用する場合は、管理ポータルアクセス後、参照するデータベースに接続できるネームスペースに切り替えた後、システムエクスプローラでの参照を行ってください。
ロールの追加が完了したら、管理ポータルのユーザ定義一覧から該当ユーザのプロファイルを確認し、希望の条件を満たしているか、確認してください。[管理ポータル]>[システム管理]>[セキュリティ]>[ロール]>ユーザ一覧>[プロファイル](一覧の該当ユーザの右端のリンク)
なお、インストール時の初期セキュリティが「最小」の場合、デフォルトでは管理ポータルへはユーザログイン無しでアクセスできます。ユーザログイン出来るようにするためには、管理ポータル用のWebアプリケーションパスの設定として[許可された認証方法]で「パスワード」のみをチェックして、パスワード認証を有効にする必要があります。詳細は、関連トピック「管理ポータル/スタジオ/ターミナルにパスワード認証を設定するにはどうしたらいいですか?」をご参照ください。さらに、SQLでの参照を行いたい場合には、参照したいテーブルへのSQL権限を付与する必要があります。これについての詳細は、関連トピック「SELECTのみを実行できるユーザ作成方法について」をご確認ください。
記事
Megumi Kakechi · 2023年5月30日
これは InterSystems FAQ サイトの記事です。
Apache環境でRESTを動かすための設定方法は以下のとおりです。
1. Webゲートウェイをインストールします
添付(Webゲートウェイインストール手順.pdf)の手順に従い、Webゲートウェイをインストールします。※Webゲートウェイをインストールする前に、Apacheを停止してください。
2. Apache 構成ファイルの設定を行います
/etc/httpd/conf/httpd.conf の末尾に以下を追加します。追加後、Apacheを再起動してください。
<Location /> CSP On SetHandler csp-handler-sa</Location>
※こちらの設定では、Apacheに対するすべてのリクエストをWebゲートウェイに渡す設定になります。 <Location />ではなく、<Location /rest> にすると、/rest のみWebゲートウェイに渡すようになります。 (既に他の目的でApacheを使用している場合、<Location /> の設定にするとそちらが動かなくなりますのでご注意ください)
Apacheの再起動:
# systemctl stop httpd.service // Apache の停止(開始している場合)
# systemctl start httpd.service // Apache の開始
# systemctl status httpd.service // ステータスの確認
3. Webゲートウェイ管理ページに接続できることを確認します
Web Gateway 管理ページ http://localhost/csp/bin/Systems/Module.cxw
4. Webゲートウェイ管理ページで以下の設定を行います
4-1. 接続先IRISサーバの設定
WebGateway の Server Access より、サーバの設定を確認します。※以下はローカルにIRISをインストールしている場合
4-2. アプリケーションアクセスの設定
Web Gateway の Application Access から /rest を追加します。→既存アプリケーションをクリックしてコピーし、アプリケーションパスを /rest に設定 & 4-1.で設定したIRISサーバを指定します。
この構成により、Webゲートウェイは /rest アプリケーションをIRISサーバに転送します。
5. IRISサーバの構成でウェブアプリケーションの設定を行います
IRISサーバの管理ポータル(http://localhost/csp/sys/UtilHome.csp)を開き、
システム構成 > セキュリティ > アプリケーション > ウェブ・アプリケーション
より /rest アプリケーションを追加します。RESTのクラスをディスパッチクラスに指定します(この記事の後方にサンプルコードを載せています)。
この構成により、IRIS は /rest アプリケーションを対象ネームスペースに転送し、対象ディスパッチクラスを呼び出します。
6. Postman や Webブラウザより、GETリクエストを試してみます
以下はローカルにIRISをインストールしている場合です。必要に応じてサーバのIPアドレスに変更してください。 http://localhost/rest/req2 パスワード認証を有効にしている場合、ブラウザからは以下のように実行します。 http://localhost/rest/req2?IRISUserName=<UserName>&IRISPassword=<Password>以下のように、日時が表示されます。
※サンプルコード:
Class User.REST Extends %CSP.REST
{
XData UrlMap
{
<Routes>
<Route Url="/req2" Method="GET" Call="req2"/>
</Routes>
}
ClassMethod req2() As %Status
{
write $ZDT($H)
quit $$$OK
}
}
【ご参考】IISでRESTを動かす場合の設定方法
記事
Toshihiko Minamoto · 2021年2月23日
# Java Business Host から PEX への移行
InterSystems IRIS 2020.1 および InterSystems IRIS for Health 2020.1 で [PEX](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EPEX) がリリースされ、Java Business Host を使うよりも優れたかたちで Java プログラムをプロダクション環境に取り込めるようになりました。 PEX は、相互運用性のコンポーネントを構築するための API をすべて提供するほか、Java と .NET の両方で使用できます。 Java Business Host は非推奨となり、今後のリリースで廃止される予定です。
PEX を使うメリット
* デベロッパーはプロダクションのすべてのコンポーネントを、Java と .Net のどちらでも構築できる
* コンポーネント間で一層複雑なメッセージ構造の受け渡しができる
* シンプルな設定
* 開発のワークフローがシンプルな上に、ObjectScript を使う必要がない
ここからは、既存の Java Business Host のコードを PEX に移行する方法に注目します。
## 概要
PEX で使用されるクラスとインターフェースは、Java Business Host (JBH) のものとは異なります。 本記事では、その相違点を要約して解説しますが、詳細は[完全なドキュメント](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EPEX_apiref)をご覧ください。
* [ビジネスサービスの変換](#Converting-a-Business-Service-from-Java-Business-Host-to-PEX)
* [ビジネスオペレーションの変換](#Converting-a-Business-Operation-from-Java-Business-Host-to-PEX)
* [設定](#Settings)
* [メッセージ](#Messages)
* [ログ](#Logging)
## ビジネスサービスを Java Business Host から PEX に変換する
PEX のビジネスサービスを構築するには、`com.intersystems.gateway.bh.BusinessService` の代わりに、`com.intersystems.enslib.pex.BusinessService` を実装する必要があります。
PEX で使用されるビジネスサービスの設計パターンは、サービスがスレッドを開始してメッセージを作成するというものから、サービスが定期的に呼び出される関数を実装してメッセージを作成するというものに一変しました。
JBH では、以下のようなコードが使われています。
```java
@Override
public boolean OnInit(Production p) throws Exception {
production = p;
if (messageThread == null) {
Messager messager = new Messager();
messageThread = new Thread(messager);
messageThread.start();
}
return true;
}
```
一方の PEX では、関数を 3 つ実装するだけで OK です。
```java
public void OnInit() throws Exception {
// 初期化
return;
}
public Object OnProcessInput(Object messageInput) throws Exception {
// ここで SendMessage() か SendMessageAsync() を呼び出します
return null;
}
public void OnTearDown() throws Exception {
// シャットダウン
return;
}
```
また、設定はどのように使用され、メッセージはどのように配信され、ログはどのように記録されるのかも変更する必要があります。 詳しくは後ほど解説します。
## ビジネスオペレーションを Java Business Host から PEX に変換する
PEX のビジネスオペレーションを構築するには、`com.intersystems.gateway.bh.BusinessOperation` の代わりに `com.intersystems.enslib.pex.BusinessOperation` を実装する必要があります。
ビジネスオペレーションの設計パターンは、JBH も PEX も構造的には同じですが、2 つのメインエントリーポイントへのパラメーターが変更されています。
### OnInit() の変更点
PEX の `OnInit()` はパラメーターを受け取りません。
### OnMessage() の変更点
PEX の場合、`OnMessage()` には、JBH で使用される `String` の代わりに、ジェネリック型の `Object` が与えられます。 これにより、プロダクションの作成者は好きなメッセージを渡すことができます。
JBH では、アプリケーションに以下のようなコードが使われていたのではないでしょうか
```java
public boolean OnMessage(String message) throws Exception {
// ビジネスロジックを実行
return true;
}
```
PEX では、パラメーターにジェネリック型の Java Object が使用されます。適切にキャストする必要がありますが、String を使った場合よりも一層複雑なメッセージを送信できます。 以下は、ファイルストリームであるリクエストを抽出する方法を示した例です。
```java
public Object OnMessage(Object request) throws Exception {
com.intersystems.jdbc.IRISObject streamContainer = (com.intersystems.jdbc.IRISObject)request;
com.intersystems.jdbc.IRISObject str = (com.intersystems.jdbc.IRISObject)streamContainer.get("Stream");
String originalFilename = (String)streamContainer.get("OriginalFilename");
Long contentSize = (Long)str.get("Size");
String content = (String)str.invoke("Read", contentSize);
// ビジネスロジックを実行
return null;
}
```
また、設定が使用される方法、メッセージが配信される方法、ログが記録される方法も変更する必要があります。 詳しくは後ほど解説します。
## 設定
設定の宣言が簡単になりました。
JBH では、設定は `SETTINGS` 文字列を使って宣言され、以下のようなコードで取り込まれていました。
```java
String setting = production.GetSetting("Min");
if (!setting.isEmpty()) {
min = Integer.parseInt(setting);
}
```
PEX の場合、設定は単純に public メンバフィールドとなります。 これらは、クラスがインスタンス化されるときに自動的に設定されます。
```java
public int Min = 0;
```
public メンバーフィールドは、Java の基本データ型 (String や int など) であれば、何でもプロダクション環境で設定できます。
## メッセージ
メッセージはよりパワフルに送信できます。 JBH では、メッセージは文字列として送信されます。 一方の PEX を使うと、メッセージは、 ObjectScript で定義されるオブジェクトの場合であれは、オブジェクト (IRISObject) として送信され、Java で定義されているクラスの場合であれば、`com.intersystems.enslib.pex.Message` のサブクラスとして送信されます。
JBH の場合は、以下のようなコードが使われます
```java
production.SendRequest(value.toString());
```
PEX の場合だと、以下のようなコードが使われます
```java
MyExampleMessageClass req = new MyExampleMessageClass("message to send");
SendRequestAsync(Target, req);
```
## ログ
ログ機能はすべて似たようなものですが、名前だけが違います。
PEX で情報メッセージをログするときは、`LOGINFO()` を使います。
```java
LOGINFO("Received message");
```
## オブジェクトのゲートウェイ
Java Business Host では、専用のゲートウェイが必要でしたが、 PEX では、Java ゲートウェイ 1 つで、Java のすべてのニーズに対応できます。 また、ゲートウェイはたくさん使うこともできます。 これはあなた次第です。 こちらの [Java ゲートウェイの手引き](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EJVG_intro) がおすすめです。
## 結論とフィードバック
まだ PEX を試していないという方は、ぜひぜひお試しください。 PEX を使えば、少ないコードで解決できるビジネスの問題の幅をぐっと広げることができます。また、今はすべての作業を .NET で実行できるようにもなりました。
JBH のアプリケーションを PEX へ移行させることに関するご質問や問題は、私か WRC までご連絡ください。
記事
Mihoko Iijima · 2021年10月12日
開発者の皆さん、こんにちは。
いつも使用しているユーザでアプリケーションや InterSystems 製品(IRIS/Caché/Ensemble/HealthShare) にアクセスしたとき、セキュリティ設定変更などの影響で急にアクセスできなくなった!という場合に、調査に便利な監査ログの参照方法をご紹介します。
ここでは、%Allロールを持つシステム管理ユーザ( _system や SuperUser )で管理ポータルにアクセスできる状態での確認方法をご紹介します。
監査ログですが、まずはシステムで監査が取られる設定になっているかご確認ください(通常無効化されている場合は、調査の時だけ有効に変更してください)。
管理ポータル > システム管理 > セキュリティ > 監査 > 監査を有効に
次に、アクセスできなくなった原因を探るため、以下のシステムイベントの監査を取得できるように変更します。
管理ポータル > システム管理 > セキュリティ > 監査 > システムイベントを構成以下のイベントの「状態変更」をクリックし、 Enabled に はい と表示されるようにします。
%System/%Login/LoginFailure
%System/%Security/Protect
この状態で、アクセスできない操作をもう1度試し、試した後で「監査データベースの閲覧」ページでエラーの内容を確認します。
管理ポータル > システム管理 > セキュリティ > 監査データベースの閲覧
以下例では、ターミナルにログインした時の監査ログの記録をご紹介します。
ログインに使用するユーザ名は test、 パスワード test、ロールに %Operator が設定されていて、管理ポータルの [システムオペレーション] メニューが利用できるユーザとします。
1) パスワードが異なるとき
ターミナルにアクセスしたときの表示は以下の通りです。
ユーザ名:test
パスワード:****
アクセスが拒否されました。
監査データベースの閲覧画面を再表示した時の一覧には、「プログラマモード ログイン失敗」と表示されています。
詳細を確認するため、「詳細」のリンクをクリックします。
「イベントデータ」の行にエラーメッセージが記録されています。
エラーメッセージ: エラー #798: パスワード 認証が失敗しました
エラー #952: パスワードが不正です
2) ユーザが存在しない時
存在しないユーザ(abc)でログインした場合は、以下のエラーが記録されます(この時も「プログラマモード ログイン失敗」と一覧に表示されます)。
詳細のリンクから「イベントデータ」を確認すると、以下のエラーメッセージが記録されています。
エラーメッセージ: エラー #798: パスワード 認証が失敗しました
エラー #838: ユーザ abc が存在しません
3) ユーザ名とパスワードはあっているのにターミナルにアクセスできない時(アクセス拒否 と出るとき)
ユーザ名、パスワードの指定はあっていそうなのに、ターミナルにアクセスできないエラーが出ているときの状態です。
以下のエラーが記録されます(この時も「プログラマモード ログイン失敗」と一覧に表示されます)。
詳細を確認します。
今回は、「エラーメッセージ: エラー #836: プログラマーアクセスの権限が不十分です」を出ています。
テストに使用しているユーザは %Operatorロールを持ちますが、ターミナルのアクセスに必要な %Developerロールを持っていません。そのため、アクセス権限不十分とエラーが出ています。
この他、使用しているユーザが「無効」になっている場合もアクセス拒否となり「プログラマーモード ログイン失敗」と表示され、詳細には以下のエラーメッセージが表示されます。
4) ターミナルにアクセスできるけど、特定のネームスペースにアクセスできない状態
ユーザ test の役割が変わり、開発者としてターミナルにアクセスできるユーザに変更する必要があるとします。
ここで、管理者がユーザ test から %Operator ロールを削除し、%Developer ロールを付与したとします。
管理ポータル > システム管理 > セキュリティ > ユーザ > testを選択 > ロール > %Developer付与
ユーザ名:test
パスワード:****
USER>
やっとターミナルにアクセスできました!
管理ポータルの [システムオペレーション] メニューの操作をルーチンで試そうと %SYS ネームスペースに移動します。
USER>set $namespace="%SYS"
SET $NAMESPACE="%SYS"
^
<PROTECT> *c:\intersystems\irishealth3\mgr\
USER>
残念・・。エラーです。
エラーの原因を監査ログを参照して確認します。
Protect のイベントが記録され「Attempt to access a protected resource」と表示されています。
詳細を確認します。
この記録は、mgr以下にある IRIS.dat(= IRISSYSデータベース)に対する <PROTECT> エラーが発生したことを意味します。
これは、ユーザ test から %Operator ロールを削除することで、IRISSYS データベースに対する READ と WRITE の許可がなくなったことが原因です。
%Developer ロールだけでは、アクセスしたいデータベースに対する許可が不足するため、追加でユーザ test に適切なデータベースの許可を付与する必要があります。
例のように、%SYS ネームスペースにアクセスしたい場合は、IRISSYSのデータベースロール(%DB_IRISSYS)を付与することでデータベースに対してREAD/WRITE の許可が追加できます。
再度、ターミナルにユーザ test でログインし直してから %SYS ネームスペースに移動し、試しにユーティリティを実行してみます。
ユーザ名:test
パスワード:****
USER>set $namespace="%SYS"
%SYS>do ^TASKMGR
1) タスク作成
2) タスク編集
3) タスク一覧
4) タスク削除
5) タスク一時停止
6) タスク再開
7) タスク実行
8) タスクリポート
9) タスクマネージャオプション
10) 終了
オプション?
うまく行きました。
いかがでしたでしょうか。
今まで使用していたユーザで急にアクセスできない!という状況になった時、セキュリティ設定に変更がなかったかどうかご確認ください。
もし変更した後アクセスできなくなった場合は、この記事で試したように、監査を使用してどんなエラーが発生しているか確認することができます。
監査についてのドキュメントもあります。ぜひご参照ください。
最後に、普段監査を使用されていない環境は、調査が終わったら「無効化」することをお忘れなく!
記事
Toshihiko Minamoto · 2021年2月2日
この記事は Caché データベースの内部構造を説明したこちらの[記事](https://jp.community.intersystems.com/node/485976)の続編です。 そちらの記事では、様々なブロックタイプ、それぞれのつながりやグローバルとの関係について説明しました。 純粋に理論を述べた記事でした。 ブロックツリーを視覚化するのに役立つ[プロジェクト](https://github.com/daimor/CacheBlocksExplorer)を作成しましたので、この記事ではその仕組みを詳しく説明します。
[](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png)
デモを行うために、新しいデータベースを作成しましたが、Caché のデフォルト機能としてすべての新しいデータベースで初期化されるグローバルは消去しています。 それでは、シンプルなグローバルを作成しましょう。
set ^colors(1)="red"
set ^colors(2)="blue"
set ^colors(3)="green"
set ^colors(4)="yellow"

作成されたグローバルのブロックを表す画像をご覧ください。 これはシンプルなものなので、その説明は Type 9 のブロック (グローバルカタログのブロック) に記載されています。 次にくるのが、「上位ポインタと下位ポインタ」のブロック (Type 70) です (グローバルツリーはまだ浅いため)。ここでは、まだ 8KB の単一のブロックに収まるデータブロックへのポインタを使用できます。
それでは、単一のブロックには収まりきらないほどの数の値を別のグローバルに書き込んでみます。そして、最初のブロックに収まらなかった新しいデータブロックにポイントするポインタブロックの中に新しいノードが表示されます。
それでは、それぞれ 1000 文字を持つ値を 50 個書き込んでみましょう。 このデータベースのブロックサイズは 8192 バイトであることを覚えておいてください。
set str=""
for i=1:1:1000 {
set str=str_"1"
}
for i=1:1:50 {
set ^test(i)=str
}
quit
下の画像をご覧ください。

ポインタブロックレベルでデータブロックにポイントするノードがいくつかあります。 各データブロックには、次のブロックをポイントするポインタがあります (「適切なリンク」)。 Offset は、このデータブロック内で占有されているバイト数をポイントしています。
それでは、ブロックの分割をシミュレートしてみましょう。 ブロックの合計サイズが 8KB をオーバーしてしまうほどの数の値をブロックに追加しましょう。それにより、ブロックは半分に分割されます。
サンプルコード
set str=""
for i=1:1:1000 {
set str=str_"1"
}
set ^test(3,1)=str
set ^test(3,2)=str
set ^test(3,3)=str
結果は以下の通りです。

ブロック #50 が分割され、新しいデータが入っているのが分かります。 ブロック #50 から取り出された値はブロック #58 に置かれ、このブロックにポイントするポインタがポインタブロックに表示されているのが分かります。 他のブロックに変化はありません。
**長い文字列を使った例**
8KB (データブロックのサイズ) よりも長い文字列を使うと、「長いデータ」で構成されるブロックができます。 そのような状況は、例えば、文字列を 10000 バイトとして書き込んでシミュレートします。
サンプルコード
set str=""
for i=1:1:10000 {
set str=str_"1"
}
for i=1:1:50 {
set ^test(i)=str
}
結果を見てみましょう。

結果としては、新しいグローバルノードは加えずに、値を変更しただけなので、画像に表示されているブロック構造に変更はありません。 しかし、すべてのブロックで、Offset の値 (占有されているバイト数) に変化がありました。 例えば、ブロック #51 の Offset の値は、7088 から 172 に変わっています。 新しい値がブロックに収まらない場合は、データの最後のバイトへのポインタが変更されるということが分かりました。しかし、データはどこに行ったのでしょう? 現時点では、「大きなブロック」に関する情報を示す技術的な可能性はありません。 それでは、^REPAIR ツールを使って、ブロック #51 の新しいデータに関する情報を取得してみましょう。

このツールの仕組みを詳しく説明いたします。 右側のブロック #52 へのポインタがあり、同じ番号が次のノードの親ポインタブロックで指定されているのが分かります。 グローバルの照合順序は Type 5 に設定されています。 長い文字列を持つノードの数は 7 個です。 場合によっては、1 つのブロックの中に、いくつかのノードのデータ値と別のノードの長い文字列の両方が含まれる場合があります。 また、次のブロックの先頭で予測できる次のポインタ参照も表示されています。
長い文字列のブロックについて: キーワード「BIG」がグローバルの値をとして指定されているのが分かります。 それは、データが実際には「大きなブロック」に保管されていることを意味します。 同じ行には、含まれている文字列の長さの合計とこの値を保管するブロックの一覧が表示されています。 それでは、ブロック #73 (長い文字列のブロック) を見てみましょう。

残念ながら、このブロックはエンコードされた状態で表示されています。 しかし、ブロックヘッダーのサービス情報 (長さは常に 28 バイト) に続いて、私たちのデータが表示されているのが分かります。 データ型が分かっていると、ヘッダーの内容をとても簡単にデコードできます。
位置
値
説明
コメント
0-3
E4 1F 00 00
データの最後をポイントする Offset
8164 バイトとヘッダーの 28 バイトを合わせて合計 8192 バイトあり、ブロックは満タンです。
4
18
ブロックタイプ
記憶にあるかと思いますが、24 は長い文字列の型指定子です。
5
05
照合順序
照合順序 5 は「標準の Caché」を意味します
8-11
4A 00 00 00
適切なリンク
ここは 74 になっています。記憶にあるかと思いますが、値はブロック #73 と #74 に保管されます。
ブロック #51 のデータはわずか 172 バイトしか占有していないことを覚えていますか? 大きな値を保存したにも関わらずです。 つまり、有効なデータがわずか 172 バイトとなり、ブロックはほぼ空になったように思えますが、それでも 8KB を占有しているのです! そのような場合、空きスペースには新しい値が入力されることが分かりましたが、Caché ではそのようなグローバルを圧縮することもできます。 [%Library.GlobalEdit](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Library.GlobalEdit) クラスに CompactGlobal メソッドがあるのはそのためです。 このメソッドの効率を確認するために、サンプルコードを使って大規模のデータを作成してみましょう。例えば、ノードを 500 個作成します。
こちらのコードを実行します。
kill ^test
for l=1000,10000 {
set str=""
for i=1:1:l {
set str=str_"1"
}
for i=1:1:500 {
set ^test(i)=str
}
}
quit
すべてのブロックを表示するのは控えますが、要点は理解していただけると思います。 データブロックはたくさんありますが、ノードの数は少なくなっています。

以下のように CompactGlobal メソッドを実行します
write ##class(%GlobalEdit).CompactGlobal("test","c:\intersystems\ensemble\mgr\test")
結果を見てみましょう。 ポインタブロックにはノードが 2 個しかありません。つまり、最初はポインタブロックにノードが 72 個もあったのに対し、実際はすべての値が 2 個のノードに移動されているのです。 従い、70 個ものノードを取り除いたことになり、ブロックの読み取り操作を行う回数が減ったため、グローバルをイテレーションしてデータにアクセスする時間が短縮されました。

CompactGlobal には、グローバルの名前やデータベース、ターゲットとするフィル値 (デフォルトは 90%) など、様々なパラメーターを渡すことができます。 そして、Offset (占有されているバイト数) の値は 7360 となり、デフォルトのフィル値 90% に近くなったことが分かります。 関数には、処理されたメガバイト数や圧縮後のメガバイト数など、複数の出力パラメーターがあります。 以前、グローバルは、今や廃止ツールとされる ^GCOMPACT を使って圧縮されていました。
ちなみに、ブロックが部分的に満たされた状態で変化しないというのはいたって普通のことです。 また、グローバルを圧縮するのは好ましくないと考えられる場合もあります。 ですが、例えば、ほぼ読み取るだけで、変更することが滅多にないというグローバルは、圧縮すると良いかもしれません。 但し、グローバルがしょっちゅう変更される場合なら、データブロックの密度が低いと頻繁にブロックを分割する手間が省けるほか、新しいデータもより素早く保存できます。
本記事のまたさらに次の続編では、InterSystems School 2015 で初の開催となった InterSystems [ハッカソン (hackathon)](http://writeimagejournal.com/?p=1912) の最中に導入された、私自作のプロジェクトのまた別の機能「データベースブロックの分布状況を表すマップ」とその実用的な活用方法について解説いたします。
記事
Mihoko Iijima · 2021年6月17日
開発者の皆さん、こんにちは!
2023/2/21追記
チュートリアルページが新しくなり「Developer Hub」に変わりました!(ユーザ登録不要です)
チュートリアルの種類や使い方については、「InterSystems Developer Hub:クリック1回で開始できるチュートリアル(4種)のご紹介」をご参照ください。
この記事では、GettingStarted ページのチュートリアルを試す環境として利用できる、無料体験環境 Sandbox の開始手順についてご紹介します。
GettingStarted ページで何ができるのか?については、前の記事でご紹介しています。ぜひご参照ください。
どのチュートリアルからでも Sandbox の作成やアクセス情報を確認することができます。
この記事では、Full Stack Tutorial を試す流れで Sandbox を作成する手順をご紹介します。
1) REGISTERボタンがある画面へ移動
画面左目メニューで、Full Stack Tutorial > Part1:Creating databases with SQL をクリックし、画面右側の「REGISTER」ボタンをクリックすると、ユーザ情報登録画面に移動します。
2) ユーザ情報登録
Sandbox 作成時、以下図の情報をご記入いただきます(* の付いた項目は必須です)。
実際の画面は縦長の画面ですが、図解用に左右に画面を貼り付けています。赤い四角で囲われた項目の入力とチェック、ご記入が終わったら「Continue」ボタンをクリックしてください。
この後、ご登録時にご記入いただいたメールアドレスへトークンを送付します。メールをご確認ください。
3) トークンの入力とパスワードの設定
登録したメールアドレスにトークンが届きます。テキストボックスにトークンを記入し、パスワード設定画面に移動します。
パスワードの設定完了でユーザ登録も完了します。
最後に、「Return to Login」のリンクをクリックします。
4) Sandbox へログインと環境作成
LOGINボタンを押すとユーザ登録情報を使用して自動的にログインが行われます。
(ユーザ名、パスワードの入力画面が出た場合は、ユーザ登録時のメールアドレスと設定されたパスワードでログインを行ってください)
LOGIN後、環境作成用の「PROVISION SANDBOX」ボタンが表示されるので、クリックします。
環境構築には数分かかるので、しばらくそのままでお待ちください。
Sandbox の作成が完了すると、Sandbox 専用 IDE へのリンクと、IRIS の管理ポータルへのリンクが表示されます。
Sandbox へのアクセス情報は、GettingStarted ページのどのチュートリアルからでも参照できます。
作成した Sandbox は72時間後に消去されますので、消去された場合は「PROVISION SANDBOX」ボタンを押すことで新環境を作成できます。
72時間経過で自動削除される場合、それまで操作されていた内容も消去されます。予めご了承ください。
以上で、Sandbox の作成方法は終了です。
次は、いよいよチュートリアルの開始方法をご紹介します!
記事
Megumi Kakechi · 2023年2月12日
これは InterSystems FAQ サイトの記事です。
日時検索で、TimeStamp型のクエリのパフォーマンスが出ない場合の対処法をご紹介します。
%TimeStamp データ型形式 (yyyy-mm-dd hh:mm:ss.ffff)は、人が読めることを目的とした ODBC 日付形式の文字列として格納されます。そのため、どうしてもデータサイズが大きくなりクエリの実行に時間がかかってしまいます。%TimeStamp型のプロパティにインデックスを作成している場合にも、クエリオプティマイザはそのインデックスを優先して最適化するようにはなっておりません。
IRISでは、POSIX 時刻(※)をサポートしているため、TimeStamp値を表すのに %Library.PosixTime データ型形式を使用できます。こちらは、Integer型で保存され、%Timestampの高性能な代替法となります。
※POSIX 時間は、協定世界時 (UTC) 1970年1月1日 00:00:00(UNIXエポック)からの経過秒数として表されます。 1970-01-01 00:00:00より前の日付は、負の論理値で表されます。
%PosixTime データ型形式(エンコードされた 64 ビットの符号付き整数)は、%TimeStamp データ型よりも少ないディスク容量とメモリ使用で済むため、%TimeStamp よりも優れたパフォーマンスが期待されます。%PosixTime でサポートされる最も古い日時は、0001-01-01 00:00:00 で、論理値は -6979664624441081856 です。そして、サポートされる最後の日時は 9999-12-31 23:59:59.999999 で、論理値は 1406323805406846975 です。
現在日時を%PosixTime型で出力したり、%TimeStamp データ型形式にしたい場合は以下のように行えます。
USER>set ptime = ##Class(%Library.PosixTime).CurrentTimeStamp()
USER>write ptime
1154596073773251031
USER>write ##Class(%Library.PosixTime).LogicalToTimeStamp(ptime)
2023-01-24 14:06:06.404055
USER>write ##Class(%Library.PosixTime).TimeStampToLogical("2023-01-24 14:06:06.404055")
1154596073773251031
詳細はクラスリファレンスをご覧ください。%Library.PosixTime
***
Cachéをお使いのお客様は、残念ながら%PosixTime データ型形式は使用できません。「それでも何とか TimeStamp型のクエリのパフォーマンスを向上させたい!」場合は、代替案として新しい計算フィールド(Calculated/SqlComputed)をテーブルに追加し、それらにインデックスを作成する方法があります。
こちらを使用する場合、タイムスタンプを $H 形式の文字列で保存するため、インデックスのデータサイズが小さくなった分だけのパフォーマンスが向上します。
例:以下のような計算プロパティを追加します。
Property DataTS As %TimeStamp;
Property DataTS2 As %String [ Calculated, SqlComputeCode = { Set {*}=$ZDTH({DataTS},3)}, SqlComputed ]; // これを追加
インデックスは、"2022-09-21 11:31:00" の代わりに "66373,41452" で作成するようになります。
この場合、アプリで使用しているクエリ自体も変更する必要があります。日付の比較は以下のように行います。
SELECT *
FROM
TEST.TABLE1
WHERE
tochar(DataTS2, 'YYYY-MM-DD')||' '||tochar(substring(DataTS2,7,5), 'HH24:MI:SS')
BETWEEN
'2000-01-01 00:00:00' and '2022-12-31 23:59:59'
/// $H 日付データを 'YYYY-MM-DD HH24:MI:SS' 形式に変更する
/// tochar(DataTS2, 'YYYY-MM-DD')||' '||tochar(substring(DataTS2,7,5), 'HH24:MI:SS')
【関連】TIMESTAMP型のフォーマットについて日付範囲クエリのSQLパフォーマンスを改善する TIMESTAMP型の項目に対して、TO_CHAR() や TO_DATE() を用いた SELECT を実行するとエラーになります
記事
Toshihiko Minamoto · 2021年11月9日
IRISインターオペラビリティのメッセージビューワで何かを変更できるとしたら、何を変更しますか?
「Dashboard IRIS History Monitor」の記事を公開したところ、素晴らしいフィードバックやリクエストをいただきました。 中には、メッセージビューワの拡張に関するリクエストがありました。
まだプロジェクトを確認していない方は、ぜひご覧ください。絶対に見る価値がありますし、[2019年の最高のInterSystems Open Exchange開発者およびアプリケーション](https://community.intersystems.com/post/best-intersystems-open-exchange-developers-and-applications-2019)の1つとしてブロンズ賞を受賞しました。
「新しい」メッセージビューワに含めようと思う機能についてのアイデアを書き留め始めましたが、これらのリソースをどのようにすれば素早く簡単に見せることができるのでしょうか。
まずは、 一般的に、相互運用性の本番環境をセットアップし、[ドキュメント](https://docs.intersystems.com/iris20191j/csp/docbook/Doc.View.cls?KEY=EGDV_deploying)の指示のとおりに、ターゲットシステムにエクスポートしてデプロイすることから始めます。 これは私があまり好まないプロセスです。 特に何か悪いというわけではありませんが、 コードを使ってすべてを行う考えがあるためです。
誰かがこういったプロジェクトを実行するたびに、次のように開始することを期待しています。
`$ docker-compose build`
`$ docker-compose up -d`
いかがでしょうか!!!
たったこれだけのステップを思い浮かべながら、InterSystemsコミュニティを調べ始めると、いくつかのヒントが見つかりました。 ある投稿では、私が自問していた質問が挙げられていました。「[ルーチンを使って本番環境を作成するにはどうすればよいのか。](https://community.intersystems.com/post/how-create-productions-routine)」
その投稿の中で、@Eduard.Lebedyuk が、コードを使って本番環境を作成する方法を次のように回答しています。
「本番クラスを自動的に作成するには、次を行う必要があります。
1. テストプロダクション用の%Dictionary.ClassDefinitionオブジェクトを作成します。
2. Ens.Config.Productionオブジェクトを作成します。
3. %Dictionary.XDataDefinitionを作成します。
4. (2) を (3) にシリアル化します。
5. XData (3) を (1) に挿入します。
6. (1) を保存してコンパイルします。」
@Jenny Amesのコメントにも、次のように書かれていました。
「私たちがよくお勧めしているベストプラクティスは、逆方向に構築することです。 ビジネスオペレーションを先に構築してから、ビジネスプロセス、そしてビジネスサービスを構築していく方法です...」
というわけで、早速やってみましょう!
##
リクエスト、ビジネスオペレーション、およびビジネスサービス
クラス**diashenrique.messageviewer.util.InstallerProduction.cls**は、名前から想像できるように、プロダクションのインストールを担当するクラスです。 インストーラのマニフェストは、そのクラスからClassMethod **Install**を呼び出します。
/// 拡張ビューワの表示機能にプロダクションをインストールするヘルパー
ClassMethod Install() As %Status
{
Set sc = $$$OK
Try {
Set sc = $$$ADDSC(sc,..InstallProduction()) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..GenerateMessages()) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..GenerateUsingEnsDirector()) quit:$$$ISERR(sc)
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
クラスメソッド**InstallProduction**は、次を作成することで、プロダクションを作成するためのメインの構造をまとめます。
* リクエスト
* ビジネスオペレーション
* ビジネスサービス
* 相互運用性プロダクション
コードを使用して相互運用性プロダクションを作成しようと考えているため、完全なコーディングモードに移行して、リクエスト、ビジネスオペレーション、およびビジネスサービスの全クラスを作成しましょう。 これを行うには、いくつかのInterSystemsライブラリパッケージを広範に使用します。
* %Dictionary.ClassDefinition
* %Dictionary.PropertyDefinition
* %Dictionary.XDataDefinition
* %Dictionary.MethodDefinition
* %Dictionary.ParameterDefinition
クラスメソッド**InstallProduction**は、次のコードを使用して、**Ens.Request**を継承した2つのクラスを作成します。
Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.SimpleRequest","Message")) quit:$$$ISERR(sc)
Set sc = $$$ADDSC(sc,..CreateRequest("diashenrique.messageviewer.Message.AnotherRequest","Something")) quit:$$$ISERR(sc)
ClassMethod CreateRequest(classname As %String, prop As %String) As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "Ens.Request"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set sc = $$$ADDSC(sc,class.%Save())
#; create adapter
Set property = ##class(%Dictionary.PropertyDefinition).%New(classname)
Set property.Name = prop
Set property.Type = "%String"
Set sc = $$$ADDSC(sc,property.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
では、**Ens.BusinessOperation**を継承したビジネスオペレーションのクラスを作成しましょう。
Set sc = $$$ADDSC(sc,..CreateOperation()) quit:$$$ISERR(sc)
このクラスを作成するほかに、MessageMapとメソッドConsumeを作成します。
ClassMethod CreateOperation() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set classname = "diashenrique.messageviewer.Operation.Consumer"
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "Ens.BusinessOperation"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "MessageMap"
Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("Consume")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("Consume")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "Consume"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:diashenrique.messageviewer.Message.SimpleRequest,&output:Ens.Response"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" $$$TRACE(input.Message)")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
相互運用性プロダクションを作成する直前のステップでは、ビジネスサービスクラスを作成しましょう。
Set sc = $$$ADDSC(sc,..CreateRESTService()) quit:$$$ISERR(sc)
このクラスにはHttpリクエストを受信するためのUrlMapとRoutesがあります。
ClassMethod CreateRESTService() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set classname = "diashenrique.messageviewer.Service.REST"
Set class = ##class(%Dictionary.ClassDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set class.Super = "EnsLib.REST.Service, Ens.BusinessService"
Set class.ProcedureBlock = 1
Set class.Inheritance = "left"
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "UrlMap"
Set xdata.XMLNamespace = "http://www.intersystems.com/urlmap"
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do xdata.Data.WriteLine("")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
#; create adapter
Set adapter = ##class(%Dictionary.ParameterDefinition).%New(classname)
Set class.GeneratedBy = $ClassName()
Set adapter.Name = "ADAPTER"
Set adapter.SequenceNumber = 1
Set adapter.Default = "EnsLib.HTTP.InboundAdapter"
Set sc = $$$ADDSC(sc,adapter.%Save())
#; add prefix
Set prefix = ##class(%Dictionary.ParameterDefinition).%New(classname)
Set prefix.Name = "EnsServicePrefix"
Set prefix.SequenceNumber = 2
Set prefix.Default = "|demoiris"
Set sc = $$$ADDSC(sc,prefix.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "SendMessage"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" set request = ##class(diashenrique.messageviewer.Message.SimpleRequest).%New()")
Do stream.WriteLine(" set data = {}.%FromJSON(input)")
Do stream.WriteLine(" set request.Message = data.Message")
Do stream.WriteLine(" set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set method = ##class(%Dictionary.MethodDefinition).%New(classname)
Set method.Name = "SendSomething"
Set method.ClassMethod = 0
Set method.ReturnType = "%Status"
Set method.FormalSpec = "input:%Library.AbstractStream,&output:%Stream.Object"
Set stream = ##class(%Stream.TmpCharacter).%New()
Do stream.WriteLine(" set sc = $$$OK")
Do stream.WriteLine(" set request = ##class(diashenrique.messageviewer.Message.AnotherRequest).%New()")
Do stream.WriteLine(" set data = {}.%FromJSON(input)")
Do stream.WriteLine(" set request.Something = data.Something")
Do stream.WriteLine(" set sc = $$$ADDSC(sc,..SendRequestSync(""diashenrique.messageviewer.Operation.Consumer"",request,.response))")
Do stream.WriteLine(" return sc")
Set method.Implementation = stream
Set sc = $$$ADDSC(sc,method.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(classname,"fck-dv"))
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
## Visual Studioコードの使用
%Dictionaryパッケージを使用してクラスを作成するのは困難な場合があり、読みにくくもありますが、非常に便利です。 コードの可読性を良くしてアプローチをもう少しわかりやすくするために、Visual Studioコードを使用して新しいリクエスト、ビジネスサービス、およびビジネスオペレーションクラスを作成することにします。
* diashenrique.messageviewer.Message.SimpleMessage.cls
* diashenrique.messageviewer.Operation.ConsumeMessageClass.cls
* diashenrique.messageviewer.Service.SendMessage.cls
Class diashenrique.messageviewer.Message.SimpleMessage Extends Ens.Request [ Inheritance = left, ProcedureBlock ]
{
Property ClassMessage As %String;
}
Class diashenrique.messageviewer.Operation.ConsumeMessageClass Extends Ens.BusinessOperation [ Inheritance = left, ProcedureBlock ]
{
Method Consume(input As diashenrique.messageviewer.Message.SimpleMessage, ByRef output As Ens.Response) As %Status
{
Set sc = $$$OK
$$$TRACE(pRequest.ClassMessage)
Return sc
}
XData MessageMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
Consume
}
}
Class diashenrique.messageviewer.Service.SendMessage Extends Ens.BusinessService [ ProcedureBlock ]
{
Method OnProcessInput(input As %Library.AbstractStream, ByRef output As %Stream.Object) As %Status
{
Set tSC = $$$OK
// リクエストメッセージを作成
Set request = ##class(diashenrique.messageviewer.Message.SimpleMessage).%New()
// リクエストメッセージプロパティに値をセット
Set request.ClassMessage = input
// ビジネスプロセスに同期呼び出しを行い、レスポンスメッセージをレスポンスとして使用
Set tSC = ..SendRequestSync("diashenrique.messageviewer.Operation.ConsumeMessageClass",request,.output)
Quit tSC
}
}
コードの可読性の観点では、大きな差があります!
## 相互運用性プロダクションの作成
相互運用性プロダクションを仕上げましょう。 これを行うには、プロダクションクラスを作成してから、それをビジネスオペレーションとサービスクラスに関連付けます。
Set sc = $$$ADDSC(sc,..CreateProduction()) quit:$$$ISERR(sc)
ClassMethod CreateProduction(purge As %Boolean = 0) As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
#; create new production
Set class = ##class(%Dictionary.ClassDefinition).%New(..#PRODUCTION)
Set class.ProcedureBlock = 1
Set class.Super = "Ens.Production"
Set class.GeneratedBy = $ClassName()
Set xdata = ##class(%Dictionary.XDataDefinition).%New()
Set xdata.Name = "ProductionDefinition"
Do xdata.Data.Write("")
Do class.XDatas.Insert(xdata)
Set sc = $$$ADDSC(sc,class.%Save())
Set sc = $$$ADDSC(sc,$System.OBJ.Compile(..#PRODUCTION,"fck-dv"))
Set production = ##class(Ens.Config.Production).%OpenId(..#PRODUCTION)
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Service.REST"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Operation.Consumer"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Service.SendMessage"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
Set item = ##class(Ens.Config.Item).%New()
Set item.ClassName = "diashenrique.messageviewer.Operation.ConsumeMessageClass"
Do production.Items.Insert(item)
Set sc = $$$ADDSC(sc,production.%Save())
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
プロダクションクラスをビジネスオペレーションとサービスクラスに関連付けるために、クラス**Ens.Config.Item**を使用します。 これは、クラスの作成に%Dictionaryパッケージを使用したのか、VS Code、Studio、またはAtelierを使用したかに関係なく使用できます。
いずれにしても、達成できました! コードを使用して相互運用性プロダクションを作成できました。
ただし、このコードの元の目的を忘れてはいけません。拡張メッセージビューワの機能を示すプロダクションとメッセージを作成するという目的です。 以降のクラスメソッドを使用して、両方のビジネスサービスを実行し、メッセージを生成します。
### %Net.HttpRequestを使用たメッセージの生成:
ClassMethod GenerateMessages() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
Set action(0) = "/demoiris/send/message"
Set action(1) = "/demoiris/send/something"
For i=1:1:..#LIMIT {
Set content = { }
Set content.Message = "Hi, I'm just a random message named "_$Random(30000)
Set content.Something = "Hi, I'm just a random something named "_$Random(30000)
Set httprequest = ##class(%Net.HttpRequest).%New()
Set httprequest.SSLCheckServerIdentity = 0
Set httprequest.SSLConfiguration = ""
Set httprequest.Https = 0
Set httprequest.Server = "localhost"
Set httprequest.Port = 9980
Set serverUrl = action($Random(2))
Do httprequest.EntityBody.Write(content.%ToJSON())
Set sc = httprequest.Post(serverUrl)
Quit:$$$ISERR(sc)
}
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
### EnsDirectorを使用したメッセージの生成:
ClassMethod GenerateUsingEnsDirector() As %Status [ Private ]
{
New $Namespace
Set $Namespace = ..#NAMESPACE
Set sc = $$$OK
Try {
For i=1:1:..#LIMIT {
Set tSC = ##class(Ens.Director).CreateBusinessService("diashenrique.messageviewer.Service.SendMessage",.tService)
Set message = "Message Generated By CreateBusinessService "_$Random(1000)
Set tSC = tService.ProcessInput(message,.output)
Quit:$$$ISERR(sc)
}
}
Catch (err) {
Set sc = $$$ADDSC(sc,err.AsStatus())
}
Return sc
}
}
コードは以上です。 完全なプロジェクトは、[https://github.com/diashenrique/iris-message-viewer](https://github.com/diashenrique/iris-message-viewer.)をご覧ください。
## プロジェクトの実行
では、プロジェクトの実際の動作を確認しましょう。 まず、git cloneまたはgit pullで、任意のローカルディレクトリにリポジトリを作成します。
`git clone https://github.com/diashenrique/iris-message-viewer.git`
次に、このディレクトリでターミナルを開き、次を実行します。
`docker-compose build`
最後に、プロジェクトでIRISコンテナを実行します。
`docker-compose up -d`
さらに、を使用して管理ポータルにアクセスします。 次の画像のように、相互運用性のネームスペースMSGVIEWERが表示されます。

そしてこれが、私たちの愛らしいプロダクションです。2つのビジネスサービスと2つのビジネスオペレーションがあります。

非常にたくさんのメッセージがあります。

カスタムメッセージビューワですべてが稼働しているので、その機能を見てみましょう。
## 拡張メッセージビューワ
相互運用性プロダクションに有効になっているネームスペースのみが表示されることに注意してください。
[](https://raw.githubusercontent.com/diashenrique/iris-message-viewer/master/images/InteroperabilityNamespace.png)
拡張メッセージビューワには、さまざまなフィルタの作成、nレベルへの列のグループ化、Excelへのエクスポートなどを行える機能と柔軟性が備わっています。

さまざまなフィルタを使用して、必要な結果を得ることができます。 また、Shiftキーを押しながら列のヘッダーをクリックすると、複数の並べ替えを使用することも可能です。 データグリッドをExcelにエクスポートすることもできるのです!


さらに、フィルタビルダーオプションを使用して、複雑なフィルタを作成することができます。
使用できる任意の列に対してデータをグループ化し、必要なnレベルを使用して情報をまとめることができます。 デフォルトでは、このグループはDate Created(作成日)フィールドを使用して作成されます。

また、列を選択できる機能があります。 次のページには、Ens.MessageHeaderのすべての列があります。デフォルトの列のみが初期ビューに表示されていますが、 「Column Chooser」(列選択)ボタンを使って、ほかの列を選択することができます。

すべてのグループはワンクリックで折りたたみと展開が可能です。

SessionId(セッションID)フィールドの情報には、ビジュアルトレース機能へのリンクがあります。

必要に応じて、メッセージを再送することができます。 必要なメッセージを選択し、Resend をクリックするだけで再送信は完了です。 この機能には、次のクラスメソッドが使用されています。
`##class(Ens.MessageHeader).ResendDuplicatedMessage(id)`

最後に、前述のように、データグリッドをExcelにエクスポートすることができます。

Excelの結果には、キャッシュサーバーページ(CSP)に定義されているものと同じフォーマット、コンテンツ、およびグループが表示されます。
追伸: この問題への取り組みで大いに助けてくれた@Renan.Lourencoに、特に深くお礼申し上げます。
記事
Mihoko Iijima · 2021年11月4日
開発者の皆さん、こんにちは!
この記事では、【GettingStarted with IRIS】シリーズの MQTT アダプタを簡単に試せるサンプルの利用方法についてご紹介します!
(MQTTブローカーはインターネット上に公開されているテスト用ブローカーを利用しています)
サンプルは、こちら👉https://github.com/Intersystems-jp/Samples-MQTT-EKG-Devices (コンテナで動作します)
IRIS/IRIS for Health のバージョン2020.1から、IoT の世界でよく利用される MQTT プロトコルに対応できる MQTT アダプタが追加されました。
MQTTインバウンドアダプタでは、メッセージの Subscribe が行え、MQTTアウトバンドアダプタでは、メッセージの Publish が行えます。
サンプルでは、MQTT を使った遠隔モニタリングをテーマに、患者さんに装着した心電図から心拍数(BPM)をリアルタイムに近い状態で取得し、モニタ画面に患者さん毎の心拍数を表示します(IRIS の MQTT インバウンドアダプタを利用したメッセージの Subscribe をご体験いただけます)。
Publish されるトピックについて
サンプルでは、演習環境毎にユニークになるようにコンテナ開始時に以下の形式でトピックを作成しています(末尾の # はワイルドカードの指定です)。
/Student_4629/acmeHospital/EKG/#
実際に Publish されるトピックは患者さんに装着した心電図のデータになるので、# の部分は、Patient-1 や Patient-2 などのように患者さんを特定できる文字列が入ります。
サンプルのシナリオは、1つの医療機関の患者情報を取得する流れにしています(本来であれば複数の医療機関の患者情報をモニタできるようにしたほうが良いのですがシンプルに試すため、1つの医療機関の患者情報を取得する流れにしています)。
トピックの流れ
サンプルには1つHTMLファイルが用意されています。このファイルをブラウザで開くと MQTT ブローカーに接続し、データ(心拍数)を1秒ごとにブローカーへ Publish します。
IRIS は、MQTTブローカーから指定のトピックを Subscribe します。
MQTT ブローカーを Subscribe する設定を行った IRIS のサービスは、ブローカーからトピックを取得します。
実際の設定は以下の通りです。
受信したトピックは IRIS の中ではメッセージ(EnsLib.MQTT.Message)として扱われ、次のコンポーネントであるプロセス(図では Process_MQTT_Request)に渡します。
プロセスでは、受信した MQTT 用メッセージからモニタ表示に利用するデータ(Solution.HeartRate)に変換するため、データ変換を呼び出します。
プロセスエディタの開き方と、中で行われているデータ変換の呼び出しの設定を確認する方法は以下の通りです。
患者ごとの測定値を参照する流れ
MQTT ブローカーから Subscribe した心拍数をリアルタイムに近い状態で画面表示するため、データ変換で作成された Solution.HeartRate から1秒ごとに患者ごとの心拍数を収集し、画面に表示しています。
この表示を行うため、1秒間隔で Solution.HeartRate に対するSELECT文が実行されています。
このクエリを実行しているのが、メトリックと呼ぶクラスです。プロダクションでは以下の場所に設定されています。
メトリックでは、指定の呼び出し間隔で Solution.HeartRate から患者ごとの BPM を収集しています。詳細はソースコードをご参照ください。
ということで、実際にサンプルを動かして動作を確認してみましょう!手順は以下の通りです。
(1) git clone
(2) コンテナビルド&開始
(3) 演習環境毎のトピック作成
(4) プロダクション開始
(5) サンプルHTMLをブラウザで開いて Publish 開始!
(6) モニタ画面で状況確認
(1) git clone
git clone https://github.com/intersystems/Samples-MQTT-EKG-Devices
(2) コンテナの開始
git clone で作成されるディレクトリに移動します。
cd Samples-MQTT-EKG-Devices
Windows 以外でお試しいただいている場合は、setup.sh を実行します。
./setup.sh
コンテナを開始します。
docker-compose up -d
(3) 演習環境毎のトピック作成
docker-compose exec iris iris session iris -U %SYS "##class(App.Installer).InitializeDocker()"
この実行で演習環境で使用するトピックを指定しています。
注意:appp.htmlで使用するJavaScripとプロダクション設定に演習環境用のトピックを追記しています。この実行を行わないとサンプルは動作しません。
IRISの起動が完了していないと、上記メソッド実行後、以下のメッセージが出力されます。
Sign-on inhibited: Startup or Installation in progress
このメッセージが表示される場合は、しばらく待ってから再度実行してください。
正常に実行できると、以下のように演習環境用のトピックが表示されます。
You have successfully initiated the MQTT exercise
Please take note of your topic top-level string: /Student_4908/acmeHospital/EKG/#
press enter to continue
Enterで元の画面に戻ります。
(4) プロダクション開始
管理ポータルを開きます。👉 http://localhost:52773/csp/sys/UtilHome.csp
ユーザ名: SuperUser
パスワード(大文字で設定されています): SYS
Interoperability > INTEROPネームスペース選択 > 一覧 > プロダクション
(5) サンプルHTMLをブラウザで開いて Publish 開始!
app.html を開きます👉 http://localhost/app.html
サーバ名はご利用の環境に合わせてご変更ください。
IRIS が MQTT ブローカーから Subscribe したトピックは、メッセージを利用して確認できます。
開いたプロダクション画面で、サービス:From_EKG_MQTT をクリックし、画面右の「メッセージ」タブを選択します。ヘッダの列にある番号をクリックするとトレース画面が開き、受信したトピックとその値が確認できます。
(6) モニタ画面で状況確認
モニタ画面を開き、患者ごとの心拍数を確認します。
管理ポータル > Analytics > ユーザポータル > SolutionEKG
※管理ポータル 👉 http://localhost:52773/csp/sys/UtilHome.csp
初期状態では3名の患者データが表示されます。
app.html にある 「追加」ボタンで心電図を付けた患者を増やすことができます。
app.html で患者を増やすと、モニタ用画面にも表示が増えます。
また、各患者のバーをスライドさせ、心拍数を変化させると、グラフもそれに合わせて変化し、MQTT ブローカーから Subscribe できていることがわかります。
以上でサンプルの動作確認は終了です。
app.html を閉じると MQTT ブローカーへの Publish が終了します。
最後に、コンテナを停止します。
docker-compose stop
コンテナを破棄する場合は、以下のコマンドを実行してください。
docker-compose down
サンプルには、完成形の Solution パッケージの他に、Demo パッケージが用意されいてプロダクションの定義やプロセスの作成を試すこともできます。
作成方法については、次の記事でご紹介する予定です!
記事
Toshihiko Minamoto · 2021年8月11日
### 不在時に、セキュリティとプライバシーを維持しながら、コンピューターを相互に信頼させるにはどうすればよいでしょうか?

「ドライマルティーニを」と彼は言った。 「1 杯。 深いシャンパングラスで。」
「承知いたしました。」
「気が変わった。 ゴードンを 3、ヴォッカを 1、キナリレを半量。 キンキンに冷えるまでよくシェイクしてから、大きめの薄いレモンピールを 1 つ加えてくれ。 わかったかい?」
「お承りいたしました。」 バーテンダーはその考えが気に入ったようだった。
イアン・フレミング著『カジノ・ロワイヤル』(1953 年)より
OAuth は、ユーザーログイン情報を伴うサービスを「運用中」のデータベースから、物理的にも地理的にも分離する上で役立ちます。 このように分離すると、ID データの保護が強化され、必要であれば、諸国のデータ保護法の要件に準拠しやすくしてくれます。
OAuth を使用すると、ユーザーは、最小限の個人データをさまざまなサービスやアプリケーションに「公開」しながら、一度に複数のデバイスから安全に作業することができるようになります。 また、サービスのユーザーに関する「過剰な」データを操作しなくてよくなります(データはパーソナル化されていない形態で処理することができます)。
InterSystems IRIS を使用する場合、OAuth と OIDC サービスを自律的かつサードパーティのソフトウェア製品と連携してテストし、デプロイするための既成の完全なツールセットを利用できます。
### OAuth 2.0 と Open ID Connect
OAuth と Open ID Connect(OIDC または OpenID)は、アクセスと識別をデリゲートするためにオープンプロトコルを汎用的に組み合わせたもので、21 世紀現在、人気を得ているようです。 大規模な使用において、これより優れたオプションはまだ誰も思いついていません。 HTTP(S) プロトコル上にとどまり、[JWT(JSON Web Token)コンテナ](https://en.wikipedia.org/wiki/JSON_Web_Token)を使用するため、特にフロントエンドのエンジニアに人気があります。
OpenID は OAuth を使用して機能しています。実際、OpenID は OAuth のラッパーです。 OpenID を電子識別システムの認証と作成に使用するオープンスタンダードとして使用することは、開発者にとって目新しい事ではありません。 2019 年には、公開から 14 周年を迎えました(バージョン 3)。 Webとモバイル開発、そしてエンタープライズシステムで人気があります。
そのパートナーである OAuth オープンスタンダードはアクセスをデリゲートする役割を担っており、12 年目を迎えています。関連する RFC 5849 標準が登場してからは 9 年です。 この記事の目的により、プロトコルの最新バージョンである OAuth 2.0 と最新の [RFC 6749](https://tools.ietf.org/html/rfc6749) を使用することにしましょう。 (OAuth 2.0 は、その前身の OAuth 1.0 とは互換していません。)
厳密に言えば、OAuth はプロトコルではなく、ソフトウェアシステムにアクセス権制限アーキテクチャを実装する際に、ユーザー識別操作を分離して別のトラステッドサーバーに転送するための一連のルール(スキーム)です。
OAuth は特定のユーザーについて何も言及できないことに注意してください! ユーザーが誰であるか、ユーザーがどこにいるのか、またユーザーが現在コンピューターを使用しているかどうかさえも、知ることはできません。 ただし、OAuth を使用すれば、事前に発行されたアクセストークンを使用して、ユーザーが参加することなくシステムと対話することが可能であり、 これは重要なポイントです(詳細は、OAuth サイトにある「[User Authentication with OAuth 2.0](https://oauth.net/articles/authentication/)」をご覧ください)。
[User-Managed Access(UMA)](https://tools.ietf.org/html/draft-hardjono-oauth-umacore-14)プロトコルも OAuth に基づくプロトコルです。 OAuth、OIDC、および UMA を合わせて使用することで、次のような分野で保護された ID とアクセス管理(IdM、IAM)システムを実装することができます。
* 医療分野における患者の [HEART(Health Relationship Trust)](https://openid.net/wg/heart/)個人データプロファイルの使用。
* 製造会社および貿易会社向けの顧客 ID&アクセス管理(CIAM)プラットフォーム。
* [OAuth 2.0 Internet of Things (IoT) Client Credentials Grant](https://tools.ietf.org/html/draft-tschofenig-ace-oauth-iot-00) による、IoT(モノのインターネット)システムにおけるスマートデバイス向けデジタル証明書のパーソナル化。
API エコノミーの新しいアクセス制御ベン図
何よりも、個人データをシステムのほかの部分と同じ場所に保存してはいけません。 認証と認可は物理的に分離する必要があります。 そして、ID と認証を各個人に与えることが理想と言えます。 自分で保管せずに、 所有者のデバイスを信頼するのです。
### 信頼と認証
ユーザーの個人データを自分のアプリや作業データベースと組み合わさったストレージ場所に保存するのはベストプラクティスではありません。 言い換えれば、このサービスを提供できる信頼のある人を選ぶようにする必要があります。
このサービスは、次の項目で構成されます。
* ユーザー
* クライアントアプリ
* 識別サービス
* リソースサーバー
アクションは、ユーザーのコンピューターの Web ブラウザで実行されます。 ユーザーには識別サービスが備わったアカウントがあり、 クライアントアプリは、識別サービスと相互インターフェースとの契約に署名済みです。 リソースサーバーは、識別サービスを信頼して、識別できた人にアクセスキーを発行します。
ユーザーはクライアント Web アプリを実行して、リソースを要求します。 クライアントアプリは、アクセス権が必要なそのリソースへのキーを提示する必要があります。
ユーザーにキーがない場合、クライアントアプリはリソースサーバーへのキーを発行するために契約している識別サービスに接続します(ユーザーを識別サービスに転送します)。
識別サービスは、どのようなキーが必要かを問い合わせます。
ユーザーは、リソースにアクセスするためのパスワードを入力します。 この時点でユーザー認証が行われ、ユーザーの身元が確認されると、リソースへのキーが提供され(ユーザーをクライアントアプリに戻します)、ユーザーがリソースを利用できるようになります。
### 認可サービスの実装
InterSystems IRIS プラットフォームでは、必要に応じてさまざまなプラットフォームからのサービスをアセンブルできます。 次はその例です。
1. デモクライアントが登録された OAuth サーバーを構成して起動します。
2. デモ OAuth クライアントを OAuth サーバーと Web リソースに関連付けて構成します。
3. OAuth を使用できるクライアントアプリを開発します。 Java、Python、C#、または Node JS を使用できます。 以下の方に、ObjectScript でのアプリケーションコードの例を示しています。
OAuth にはさまざまな設定があるため、チェックリストが役立ちます。 例を見ていきましょう。 IRIS 管理ポータルに移動し、[システム管理]>[セキュリティ]>[OAuth 2.0]>[サーバー]の順に選択します。
各項目には設定行の名前とコロン、そして必要であればその後に例または説明が含まれます。 別の方法として、Daniel Kutac の 3 部構成になっている「[InterSystems IRIS Open Authorization Framework (OAuth 2.0)の実装 - パート1](https://jp.community.intersystems.com/node/478821)」、[パート2](https://jp.community.intersystems.com/node/480201)、そして[パート3](https://jp.community.intersystems.com/node/480196) に記載されているスクリーンショットのヒントを参考にしてください。
次のスクリーンショットはすべて、例として提示されています。 独自のアプリケーションを作成する際は、別のオプションを選択する必要があるでしょう。

[一般設定]タブで、次のように設定してください。
* 説明: 構成の説明を入力します。「認証サーバー」など。
* ジェネレーターのエンドポイント(以降「EPG」)のホスト名: サーバーの DNS 名。
* サポートされている許可の種類(少なくとも 1 つを選択):
* 認可コード
* 暗黙
* アカウントの詳細: リソース、所有者、パスワード
* クライアントアカウントの詳細
* SSL/TLS 構成: oauthserver
[スコープ]タブで、次を設定します。
* サポートされているスコープを追加: この例では「scope1」です。
[間隔]タブで、次を設定します。
* アクセスキー間隔: 3600
* 認可コードの間隔: 60
* キー更新の間隔: 86400
* セッション中断間隔: 86400
* クライアントキー(クライアントシークレット)の有効期間: 0
[JWT 設定]タブで、次を設定します。
* 入力アルゴリズム: RS512
* キー管理アルゴリズム: RSA-OAEP
* コンテンツ暗号化アルゴリズム: A256CBC-HS512
[カスタマイズ]タブで、次を設定します。
* 識別クラス: %OAuth2.Server.Authenticate
* ユーザークラスの確認: %OAuth2.Server.Validate
* セッションサービスクラス: OAuth2.Server.Session
* キーの生成クラス: %OAuth2.Server.JWT
* カスタムネームスペース: %SYS
* カスタマイズロール(少なくとも 1 つ選択): %DB_IRISSYS および %Manager
では、変更内容を保存します。
次のステップでは、OAuth サーバーにクライアントを登録します。 [顧客の説明]ボタンをクリックして、[顧客の説明を作成]をクリックします。

[一般設定]タブで、次の情報を入力します。
* 名前: OAuthClient
* 説明: 簡単な説明を入力します。
* クライアントタイプ: 機密
* リダイレクト URL: oauthclient から識別した後に、アプリに戻るポイントのアドレス。
* サポートされている付与の種類:
* 認可コード: はい
* 暗黙
* アカウントの詳細: リソース、所有者、パスワード
* クライアントアカウントの詳細
* JWT 認可
* サポートされているレスポンスタイプ: 次のすべてを選択してください。
* コード
* id_token
* id_token キー
* トークン
* 認可タイプ: シンプル
[クライアントアカウントの詳細]タブは自動的に入力されますが、クライアントの正しい情報であるかを確認してください。
[クライアント情報] タブには次の項目があります。
* 認可画面:
* クライアント名
* ロゴの URL
* クライアントのホームページ URL
* ポリシーの URL
* 利用規約の URL
では、[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]の順に移動して、OAuth サーバークライアントにバインディングを構成します。

サーバーの説明の作成:
* ジェネレーターのエンドポイント: 一般的なサーバーのパラメーターから取得されます(上記を参照)。
* SSL/TLS 構成: 事前構成済みのリストから選択します。
* 認可サーバー:
* 認可エンドポイント: EPG + /authorize
* キーエンドポイント: EPG + /token
* ユーザーエンドポイント: EPG + /userinfo
* キーのセルフテストエンドポイント: EPG + /revocation
* キーの終了エンドポイント: EPG + /introspection
* JSON Web Token(JWT)設定:
* 動的登録以外のほかのソース: URL から JWKS を選択します。
* URL: EPG + /jwks

このリストから、たとえばサーバーが OAuth-client にユーザーに関するさまざまな情報を提供できることがわかります(scopes\_supported および claims\_supported)。 また、アプリケーションを実装するときは、共有する準備ができているデータが何であるかをユーザーに尋ねても何の価値もありません。 以下の例では、scope1 の許可のみを要求します。
では、構成を保存しましょう。
SSL 構成に関するエラーがある場合は、[設定]>[システム管理]>[セキュリティ]>[SSL/TSL 構成]に移動して、構成を削除してください。

これで OAuth クライアントをセットアップする準備が整いました。
[システム管理]>[セキュリティ]>[OAuth 2.0]>[クライアント]>[クライアント構成]>[クライアント構成を作成]に移動します。 [一般]タブで、次を設定します。
* アプリケーション名: OAuthClient
* クライアント名: OAuthClient
* 説明: 説明を入力します。
* 有効: はい
* クライアントタイプ: 機密
* SSL/TCL 構成: oauthclient を選択します。
* クライアントリダイレクト URL: サーバーの DNS 名
* 必要な許可の種類:
* 認可コード: はい
* 暗黙
* アカウントの詳細: リソース、所有者、パスワード
* クライアントアカウントの詳細
* JWT 認可
* 認可タイプ: シンプル
[クライアント情報]タブで、次を設定します。
* 認可画面:
* ロゴの URL
* クライアントのホームページ URL
* ポリシーの URL
* 利用規約の URL
* デフォルトのボリューム: サーバーに以前に指定したものが取得されます(scope1 など)。
* 連絡先メールアドレス: カンマ区切りでアドレスを入力します。
* デフォルトの最大経過時間(分): 最大認可経過時間または省略できます。
[JWT 設定]タブで、次を設定します。
* JSON Web Token(JWT)設定
* X509 アカウントの詳細から JWT 設定を作成する
* IDToken アルゴリズム:
* 署名: RS256
* 暗号化: A256CBC
* キー: RSA-OAEP
* Userinfo アルゴリズム
* アクセストークンアルゴリズム
* クエリアルゴリズム
[クライアントログイン情報]タブで、次を設定します。
* クライアント ID: クライアントがサーバーに登録された際に発行された ID(上記を参照)。
* 発効されたクライアント ID: 入力されません
* クライアントシークレット: クライアントがサーバーに登録された際に発行されたシークレット(上記を参照)。
* クライアントシークレットの有効期限: 入力されません
* クライアント登録 URI: 入力されません
構成を保存しましょう。
### OAuth 認可を使用した Web アプリ
OAuth は、インタラクション参加体(サーバー、クライアント、Web アプリケーション、ユーザーのブラウザ、リソースサーバー)間の通信チャネルが何らかの形で保護されていることに依存しています。 この役割は SSL/TLS プロトコルが果たしているのがほとんどですが、 OAuth は、保護されていないチャネルでも機能します。 そのため、たとえばサーバー Keycloak はデフォルトで HTTP プロトコルを使用して、保護なしで実行します。 調整と調整時のデバッグが単純化されます。 サービスを実際に使用する際、OAuth のチャネル保護は、厳重な要件に含まれるべきであり、Keycloak ドキュメントに記述されている必要があります。 InterSystems IRIS の開発者は、OAuth に関するより厳密なアプローチに従っており、SSL/TSL の使用を要件としています。 単純化できる唯一の方法は、自己署名証明書を使用するか、組み込みの IRIS サービス PKI([システム管理]>>[セキュリティ]>>[公開鍵システム]を利用することです。
ユーザーの認可の検証は、UAuth サーバーに登録されているアプリケーションの名前と OAuth クライアントスコープの 2 つのパラメータを明示的に示すことで行えます。
Parameter OAUTH2APPNAME = "OAuthClient";
set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized(
..#OAUTH2APPNAME,
.sessionId,
"scope1",
.accessToken,
.idtoken,
.responseProperties,
.error)
認可がない場合に備え、ユーザー ID をリクエストして、アプリケーションを操作する許可を取得するためのリンクを準備しておきます。 ここでは、OAuth サーバーに登録されているアプリケーションの名前を指定して、OAuth クライアントと要求されるボリューム(スコープ)を入力するだけでなく、ユーザーを返す Web アプリケーションのポイントへのバックリンクも指定する必要があります。
Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/"
set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
..#OAUTH2APPNAME,
"scope1",
..#OAUTH2CLIENTREDIRECTURI,
.properties,
.isAuthorized,
.sc)
IRIS を使用して、ユーザーを IRIS OAuth サーバーに登録しましょう。 たとえば、ユーザーに名前とパスワードを設定するだけで十分です。
受信した参照の下でユーザーを転送すると、サーバーはユーザーを識別する手続きを実行し、Web アプリケーションのアカウントデータによって、操作許可が照会されます。また、%SYS フィールドのグローバル OAuth2.Server.Session で自身に結果を保持します。

3. 認可されたユーザーのデータを示します。 手続きが正常に完了したら、アクセストークンなどがあります。 それを取得しましょう。
set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT( .#OAUTH2APPNAME, accessToken, "scope1", .aud, .JWTJsonObject, .securityParameters, .sc )
以下に、完全に動作する OAuth の例のコードを示します。
Class OAuthClient.REST Extends %CSP.REST
{
Parameter OAUTH2APPNAME = "OAuthClient";
Parameter OAUTH2CLIENTREDIRECTURI = "https://52773b-76230063.labs.learning.intersystems.com/oauthclient/";
// to keep sessionId
Parameter UseSession As Integer = 1;
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Method="GET" Url = "/" Call = "Do" />
</Routes>
}
ClassMethod Do() As %Status
{
// Check for accessToken
set isAuthorized = ##class(%SYS.OAuth2.AccessToken).IsAuthorized(
..#OAUTH2APPNAME,
.sessionId,
"scope1",
.accessToken,
.idtoken,
.responseProperties,
.error)
// to show accessToken
if isAuthorized {
set valid = ##class(%SYS.OAuth2.Validation).ValidateJWT(
..#OAUTH2APPNAME,
accessToken,
"scope1",
.aud,
.JWTJsonObject,
.securityParameters,
.sc
)
&html< Hello!<br> >
w "You access token = ", JWTJsonObject.%ToJSON()
&html< </html> >
quit $$$OK
}
// perform the process of user and client identification and get accessToken
set url = ##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
..#OAUTH2APPNAME,
"scope1",
..#OAUTH2CLIENTREDIRECTURI,
.properties,
.isAuthorized,
.sc)
if $$$ISERR(sc) {
w "error handling here"
quit $$$OK
}
// url magic correction: change slashes in the query parameter to its code
set urlBase = $PIECE(url, "?")
set urlQuery = $PIECE(url, "?", 2)
set urlQuery = $REPLACE(urlQuery, "/", "%2F")
set url = urlBase _ "?" _ urlQuery
&html<
<html>
<h1>Authorization in IRIS via OAuth2</h1>
<a href = "#(url)#">Authorization in <b>IRIS</b></a>
</html>
>
quit $$$OK
}
}
コードの作業コピーは、InterSystems GitHub リポジトリ()にもあります。
必要に応じて、OAuth サーバーと OAuth クライアントに高度なデバッグメッセージモードを有効にしてください。これらは、%SYS エリアの ISCLOG グローバルに記述されます。
set ^%ISCLOG = 5
set ^%ISCLOG("Category", "OAuth2") = 5
set ^%ISCLOG("Category", "OAuth2Server") = 5
詳細については、「[IRIS、OAuth 2.0 と OpenID Connect の使用](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH)」ドキュメントをご覧ください。
### まとめ
これまで見てきたように、すべての OAuth 機能には簡単にアクセスでき、完全に使用できる状態になっています。 必要に応じて、ハンドラークラスとユーザーインターフェースを独自のものに置き換えることができます。 OAuth サーバーとクライアントの設定は、管理ポータルを使う代わりに、構成ファイルで構成することも可能です。