この付録では、Oracle Diameterアプリケーションのプログラミングについて説明します。内容は次のとおりです。
Java Diameterアプリケーションで遠隔ピアとのメッセージ交換を処理するには、次のように、アプリケーションでIP構成とDiameterプロトコル固有の構成を行う必要があります。
DiameterStackインスタンスを作成します。
DiameterスタックにDiameterアプリケーションを登録します。
ローカル・トランスポート・アドレスにバインドするリスニング・ポイントを作成します。
ルートを構成し、Diameterピアに接続します。
Diameterスタックのインスタンスは、次のように作成できます。
import oracle.sdp.diameter.*;
DiameterFactory myFactory;
DiameterStack myStack;
myFactory = DiameterFactory.getInstance();
myStack = myFactory.createDiameterStack("realm.domain.com",
"server.realm.domain.com",
null);
このコードにより、ローカルDiameterノードで使用されるDiameterスタックが作成されます。完全修飾ドメイン名(FQDN)はserver.realm.domain.com、オリジン・レルムはrealm.domain.netです。
Diameterアプリケーションが1つ以上のトランスポート・アドレスで着信接続をリスニングする必要がある場合は、DiameterListeningPointインタフェースのインスタンスを1つ以上作成する必要があります。
String localURI = "aaa://server.realm.domain.com:41001"; myStack.createDiameterListeningPoint(localURI);
リスニング・ポイントが作成されると、Diameterスタックはリモート・ピアからの着信接続を受け入れる準備ができます。Diameterスタックが、ルーティング表で宣言されていないピアから接続リクエストを受信した場合は、DiameterListenerインタフェースのisUnknownPeerAuthorized()がコールされます。接続は、このメソッドがtrueを返した場合のみ受け入れられます。
|
注意: リスニング・ポイントは、後でDiameterStack.getDiameterListeningPoints()をコールして取得できるので、ユーザー・アプリケーションでこれらの参照を維持する必要はありません。 |
Diameterクライアント・アプリケーションは、createDiameterRoute()メソッドを使用してリモート・ピアを宣言できます。
例B-1のコード部分では、2つのDiameterレルム、realm1.domain.comとrealm2.domain.comが構成されます。最初のレルムにはpeer1.realm1.domain.comとpeer2.realm1.domain.comという2つのピアが、2番目のレルムにはpeer.realm2.domain.comという1つのピアのみがサービスを提供します。メトリック値(1と2)により、peer1とpeer2はマスターおよびバックアップ・モードで設定されます。例B-1は、このピア構成を設定するソース・コードです。
例B-1 ピアの構成
myStack.createDiameterRoute("ExampleApp", "realm1.domain.com",
"aaa://peer1.realm1.domain.com", 1);
myStack.createDiameterRoute("ExampleApp", "realm1.domain.com",
"aaa://peer2.realm1.domain.com", 2);
myStack.createDiameterRoute("ExampleApp", "realm2.domain.com",
"aaa://peer.realm2.domain.com:41002", 1);
|
注意: ピア名(FQDN)がcreateDiameterRoute()で使用され、そのピアがローカル・スタックにまだ認識されていない場合、そのピアとのトランスポート接続は、指定されたピアURI(ポート番号やトランスポート・プロトコルなどのURIオプション情報を考慮)を使用して開始されます。反対に、URIのFQDN部分で指定されたピアがすでに認識されている場合、URIは無視され、既存のピア・エントリが、指定されたレルムとアプリケーションIDのルーティング表に追加されます。 |
DiameterRealmStateChangeEventクラスは、ピアの起動または停止により、リモート・レルムにアクセス可能かどうかをアプリケーションに通知します。リモート・レルムが使用不能の場合、Diameterスタックは発信メッセージを受け付けないため、この情報は重要です。したがって、アプリケーションは、レルムが使用可能になってからリクエストを送信する必要があります。
ピアの使用可能性(Remote-Realm、Application-ID)が変化するたびに、RealmStateChangeイベントがDiameterListener.processEvent()に渡されます。特定のアプリケーションIDのリモート・レルムの使用可能性は、そのレルムおよびアプリケーションIDにサービスを提供できる少なくとも1つのリモート・ピアとのアクティブな接続を使用できるかどうかによって決まります。ルートを使用できないため、アプリケーションはリモート・レルムのピアとメッセージを交換できません。
例B-2は、DiameterListener.processEvent()メソッドの一般的な実装方法を示しています。
例B-2 DiameterListener.processEvent()メソッドの実装
public void processEvent(DiameterEvent event)
{
if (event instanceof DiameterRealmStateChangeEvent) {
// A remote realm has become available or unavailable
DiameterRealmStateChangeEvent event =
(DiameterRealmStateChangeEvent)event;
if (event.isRealmAvailable()) {
System.out.println("Realm " + event.getRealm() + " is available");
} else {
System.out.println("Realm " + event.getRealm() + " is unavailable");
}
// ...
}
Diameterスタックの初期化では、定義されたカウンタ・セットが初期化され、アプリケーションによって作成されたDiameterStackとDiameterProviderの各インスタンスに関連付けられます。これらのカウンタは、DiameterStackImplMBeanおよびDiameterProviderImplMBean管理インタフェースで定義されます。
これらのカウンタにアクセスする方法は2つあります。
直接アクセスするには、両方の管理インタフェースで定義された様々なメソッドの1つをコールします。
リモートからアクセスする場合は、javax.managementパッケージを使用して、Diameter MBeansをJMXエージェントに登録します。このパッケージを提供しているのは、JDK 1.5だけです。
oracle.sdp.diameterimplパッケージでは、次の2つの管理インタフェースが定義されています。
DiameterStackImplMBean: このインタフェースは、DiameterStackインタフェースのインスタンス用の管理APIを表します。
DiameterProviderImplMBean: このインタフェースは、DiameterProviderインタフェースのインスタンス用の管理APIを表します。
例B-3は、これら定義済カウンタの1つの値を直接取得する方法を示しています。
Diameterアプリケーションは、Diameter MBeanをJMXエージェントに登録し、Java JConsoleプログラムから監視できるようにして、リモート管理できます。
Diameter APIを使用するJavaアプリケーションは、DiameterStackImplMBeanおよびDiameterProviderImplMBeanインタフェースのインスタンスをJMXエージェントに登録して、管理情報を公開できます。その方法は次のとおりです。
import javax.management.MBeanServerFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import oracle.sdp.diameter.*;
// Create DiameterStack and DiameterProvider instances.
// DiameterStack myStack = ...
// DiameterProvider myProvider = ...
List srvList = MBeanServerFactory.findMBeanServer(null);
if (srvList.isEmpty() == false) {
MBeanServer server = (MBeanServer)srvList.iterator().next();
try {
ObjectName name;
name = new ObjectName( "oracle.sdp.diameterimpl:name=DiameterProvider");
server.registerMBean(myProvider, name);
name = new ObjectName("oracle.sdp.diameterimpl:name=DiameterStack");
server.registerMBean(myStack, name);
}
catch (Exception e) {
// Handle register exception
// ...
}
}
|
注意: このコードにはJDK 1.5以降が必要です。旧バージョンには、必要なjavax.managementパッケージがありません。
MBeanへのリモート・アクセスを許可する場合は、アプリケーションの実行時に、次のようにJavaプロパティ java -Dcom.sun.management.jmxremote -classpath mdiameter.jar MyApplication |
ここでは、次のトピックについて説明します。
ユーザーのアプリケーションで、デフォルトでロードされるアプリケーション・ディクショナリで定義されていないコマンドやAVPを使用する必要がある場合、ユーザーはディクショナリを拡張して、Diameterスタックで使用される新しいコマンドやAVP構文を定義できます。
Diameterディクショナリ拡張のルート、つまり最上位の要素は<dictionary>要素です。
<dictionary> .... (other elements) </dictionary>
<dictionary>要素には、任意の数の<vendor>要素と<application>要素が含まれています。まったく含まれていない場合もあります。
<vendor>要素では、名前と関連IANAでベンダーを定義します。
<vendor>には次の属性があります。
vendor id属性は、ディクショナリのすべての<vendor>要素定義で一意である必要があります。値0は、[RFC-3588]および[RFC-4006]で定義された構文に対応する基本プロトコル専用です。
vendor name属性は、ベンダーを説明するテキストです。
例B-4の<vendor>要素では、エンタープライズ・コードが10415の3GPPという名前のベンダーが定義されています。
例B-4 ベンダーの定義
<dictionary>
<vendor id="10415" name="3GPP">
....(other elements)
</vendor>
</dictionary>
<vendor>要素には、任意の数の<returnCode>要素と<avp>要素が含まれています。まったく含まれていない場合もあります。
Diameterプロトコルを拡張する方法の1つに、新しいアプリケーションを追加する方法があります。
<application>要素では、新しいベンダーのDiameterアプリケーションをサポートするために必要な新しいコマンドを定義します。
<application>には次の属性があります。
application id属性は、このアプリケーションにIANAによって割り当てられたアプリケーション識別子です。値0は、RFC-3588およびRFC-4006で定義されたコマンドに対応する基本プロトコル専用です。
application name属性は、このアプリケーションのわかりやすい名前です。
application vendor属性は、<vendor>要素で前に定義したアプリケーション・ベンダーの名前です。
application service-type属性では、アプリケーションが提供するサービスの種類を定義します。可能な値は、アカウンティングの場合はAcct、承認の場合はAuthです。
例B-5では、<application>要素には、値3で識別される3GPPアカウンティングRfアプリケーションの情報が含まれています。
例B-5 <application>要素の定義
<dictionary>
<application id="3" name="Rf" vendor="3GPP" service-type="Acct">
....(other elements)
</application>
</dictionary>
<application>要素には任意の数の<command>要素が含まれています。まったく含まれていない場合もあります。
<command>要素では、コマンドの属性を定義します。
<command>には次の属性があります。
command name属性では、コマンドの名前を定義します。「リクエスト」と「アンサー」の両方に1つのコマンドしか定義されないので、Accountingコマンドにより、Accounting-RequestとAccounting-Answerの両方のメッセージが定義されます。
command code属性では、このコマンドを送信するために使用するコマンド・コードを定義します。
例B-6では、Rfアプリケーションにはコードが271のコマンドAccountingが含まれています。
<returnCode>要素では、Result-Code AVPの可能な値を定義します。例B-7では、3GPPベンダーはDIAMETER_USER_UNKNOWNというreturnCode 5030を定義しています。
<avp>要素では、RFC-3588で説明されているAVPを定義します。
<avp>には次の属性があります。
avp name属性は、このAVPのわかりやすい名前です。
avp code属性では、ネットワークでの転送用にAVPをエンコードするために使用する整数値を定義します。
avp mandatory属性では、このAVPの必須ビットを設定する必要があるかどうかを定義します。設定可能な値は、mustまたはmustnotです。
avp protected 属性では、このAVPの保護ビットを設定する必要があるかどうかを定義します。設定可能な値は、mayまたはmaynotです。
avp may-encrypt属性では、CMSセキュリティの使用の場合にAVPを暗号化するかどうかを定義します。設定可能な値は、yesまたはnoです。
avp vendor-specific属性では、これがベンダー固有のAVPかどうかを指定します。設定可能な値は、yesまたはnoです。
例B-8では、3GPPベンダーはディクショナリをAVP Application-provided-called-party-addressで拡張します。
例B-8 <avp>要素の定義
<dictionary>
<vendor id="10415" name="3GPP">
<avp name="Application-provided-called-party-address"
code="837"
mandatory="mustnot"
protected="may"
may-encrypt="no"
vendor-specific="yes">
....
</avp>
</vendor>
</dictionary>
<avp>要素は、<type>要素または<grouped>要素を再グループ化します。
<type>要素では、AVPを表示するデータ型を定義します。この要素は、グループ化されていないすべてのAVP定義に必要です。
<type>要素のtype-name属性には、RFC-3588で定義されているデータ型名が含まれます。設定可能な値は次のとおりです。
"OCTETSTRING"
"INTEGER32"
"INTEGER64"
"UNSIGNED32"
"UNSIGNED64"
"FLOAT32"
"FLOAT64"
"ADDRESS"
"IPADDRESS"
"TIME"
"UTF8STRING"
"DIAMETERIDENTITY"
"DIAMETERURI"
"IPFILTERRULE"
"QOSFILTERRULE"
"ENUMERATED"
"GROUPED"
|
注意: これらの値では、大文字と小文字が区別されます。 |
例B-9では、AVP Application-provided-called-party-addressはUTF8Stringです。
<enum>要素では、データ型がUnsigned32のAVPのエンコードおよびデコード時に使用されるUnsigned32値にマップされる名前を定義します。列挙要素は、データ型がUnsigned32のAVPでのみ使用してください。
<enum>要素の属性は次のとおりです。
enum name属性は、属性の特定の値に対応するテキストです。
enum code属性は、この列挙された値に対応するUnsigned32値です。
例B-10では、Accounting-Record-Type AVPには、EVENT_RECORD、START_RECORD、START_RECORD, INTERIM_RECORDおよびSTOP_RECORDという4つの値があります。
例B-10 <enum>要素の定義
<dictionary>
<vendor id="10415" name="3GPP">
<avp name="Accounting-Record-Type"
code="480"
mandatory="must"
protected="may"
may-encrypt="yes">
<type type-name="Unsigned32"/>
<enum name="EVENT_RECORD" code="1"/>
<enum name="START_RECORD" code="2"/>
<enum name="INTERIM_RECORD" code="3"/>
<enum name="STOP_RECORD" code="4"/>
</avp>
</vendor>
</dictionary>
<grouped>要素では、一連のAVPを1つのペイロードとしてカプセル化するAVPを定義します。このAVPでは1つ以上の<gavp>要素がグループ化されています。このようにして、1つのgrouped要素に、複数のAVPの参照を含めることができます。各<gavp>要素には、AVP nameおよびvendor-id属性があります。
<gavp>には次の属性があります。
gavp name属性は、既存のAVPのname属性と一致している必要があります。
gavp vendor-id属性は、既存のベンダーのid属性を示します。
例B-11では、3GPPベンダーは、前に定義したUnit-ValueおよびCurrency-Codeという名前のAVPを含む、CC-Moneyという名前のAVPを定義します。
Diameterディクショナリ拡張を定義したら、extendGrammar()メソッドを使用して、次のようにデフォルト・ディクショナリに拡張を適用します。
//--> Define dictionary extension string
String myDictionary =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE dictionary SYSTEM \"dictionary.dtd\">\n" +
"<dictionary>\n" +
" <vendor id=\"10415\" name=\"3GPP\">\n" +
" </vendor>\n" +
" <application id=\"3\" name=\"Rf\" vendor=\"3GPP\" \n" +
" service-type=\"Acct\">\n" +
" <command name=\"Accounting\" code=\"271\" />\n" +
" </application>\n" +
"</dictionary>\n";
//--> Apply new extension to current dictionary
try {
myStack . extendGrammar(myDictionary);
} catch (DiameterException e) {
// Handle dictionary syntax errors ...
}
実際のアプリケーションの柔軟性を高めるには、XML構文の説明をJavaソース・コードに埋め込まずにファイルから読み込みます。こうすると、アプリケーションを再コンパイルせずに、名前とコードのマッピングを変更できます。
3GPP RfインタフェースのディクショナリはgetRfDictionary()によって、3GPP RoインタフェースのディクショナリはgetRoDictionary()によって返され、Diameterスタックによって拡張できます。
DiameterTraceLoggerListenerクラスは、Diameterのトレースおよびロギング・メカニズムのインタフェースです。このインタフェースは、Diameterスタック実装からのデバッグ・トレースとログを受信するための通信チャネルで、アプリケーションによって実装されます。ログは、DiameterStackのユーザーをターゲットとするメッセージです。トレースは内部で使用され、DiameterStack実装をよく理解しているユーザーのみが使用します。
DiameterTraceLoggerListener.log()ロギング・インタフェースから送信されるすべてのメッセージは、LogMessages.defで定義されます。トレース・メッセージには定義ファイルはありません。
デフォルトでは、ログはstdoutに送信されます。トレースは送信されません。この動作は、DiameterTraceLoggerListenerのユーザー定義サブクラスを登録するか、特定の環境変数を定義して、ユーザーが変更できます。DiameterTraceLoggerListener実装の例を下に示します。
Class MyTraceLoggerListener implements DiameterTraceLoggerListener
{
// true or false.
public boolean isTracingEnabled()
{
return true;
}
public void log (String file, int line, int severity, String message)
{
String severity;
switch (severity) {
case LOG_INFO_SEVERITY: severity="INFO"; break;
case LOG_WARNING_SEVERITY: severity="WARNING"; break;
case LOG_ERROR_SEVERITY: severity="ERROR"; break;
case LOG_DISASTER_SEVERITY: severity="DISASTER"; break;
default: severity="?"; break;
}
// ...
}
public void trace (String file, int line, int mask, String message)
{
System.err.println(...);
}
}