LoganBerryStubImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Doug Terry, January 28, 1987 2:33:42 pm PST
LoganBerryStub provides easy access to local or remote LoganBerry database servers. It attempts to hide any RPC details. That is, it (1) catches RPC failures and attempts to either recover or turn them into LoganBerry errors, (2) imports RPC interfaces from multiple servers on demand, (3) uses secure RPC conversations, and (4) tries to keep LoganBerry databases open. The interface is identical to LoganBerry.mesa.
DIRECTORY
FS USING [ComponentPositions, ExpandName],
LoganBerry,
LoganBerryExtras,
LoganBerryRpcControl,
RefID USING [Seal, Unseal],
Rope USING [Equal, ROPE, Substr, Replace],
RPC USING [ShortROPE, VersionRange, matchAllVersions, SecurityLevel, Conversation, EncryptionKey, MakeKey, StartConversation, AuthenticateFailed, AuthenticateFailure, CallFailed, CallFailure, ImportFailed, ImportFailure],
UserCredentials USING [Get],
LoganBerryStubExtras,
LoganBerryStub;
LoganBerryStubImpl: CEDAR PROGRAM
IMPORTS FS, LoganBerry, LoganBerryExtras, LoganBerryRpcControl, RefID, Rope, RPC, UserCredentials
EXPORTS LoganBerryStub, LoganBerryStubExtras
~ BEGIN
OPEN LoganBerryStub;
ROPE: TYPE = Rope.ROPE;
loganBerryType: RPC.ShortROPE = "LoganBerry.Lark";
loganBerryVersion: RPC.VersionRange = RPC.matchAllVersions;
securityLevel: RPC.SecurityLevel = authOnly;
InstanceInterface: TYPE = REF InstanceInterfaceRecord;
InstanceInterfaceRecord: TYPE = RECORD [
instance: RPC.ShortROPE,
interface: LoganBerryRpcControl.InterfaceRecord,
conv: RPC.Conversation
];
interfaceCache: LIST OF InstanceInterface;
DBInterface: TYPE = REF DBInterfaceRecord;
DBInterfaceRecord: TYPE = RECORD [
interface: LoganBerryRpcControl.InterfaceRecord,
conv: RPC.Conversation,
db: LoganBerry.OpenDB,
instance: RPC.ShortROPE,
dbName: ROPE
];
CursorInterface: TYPE = REF CursorInterfaceRecord;
CursorInterfaceRecord: TYPE = RECORD [
dbi: DBInterface,
cursor: LoganBerry.Cursor
];
LoganBerry operations
Error: PUBLIC ERROR [ec: ErrorCode, explanation: ROPENIL] = CODE;
Open: PUBLIC PROC [conv: Conv ← NIL, dbName: ROPE] RETURNS [db: OpenDB] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, NIL]; RETRY;};
LoganBerry.Error => ERROR Error[ec, explanation];
};
instance: RPC.ShortROPE;
interface: LoganBerryRpcControl.InterfaceRecord;
conversation: RPC.Conversation;
cp: FS.ComponentPositions;
fullDBName: ROPE;
remoteDB: LoganBerry.OpenDB;
[fullDBName, cp] ← FS.ExpandName[dbName];
instance ← Rope.Substr[fullDBName, cp.server.start, cp.server.length];
fullDBName ← Rope.Replace[base: fullDBName, start: cp.server.start, len: cp.server.length, with: NIL];
[interface, conversation] ← ImportLoganBerry[instance];
IF conv # NIL THEN conversation ← conv; -- use the conversation presented if possible
remoteDB ← interface.Open[conversation, fullDBName];
db ← SaveDBInterface[interface, conversation, remoteDB, instance, fullDBName];
};
ReadEntry: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB, key: AttributeType, value: AttributeValue] RETURNS [entry: Entry, others: BOOLEAN] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
[entry, others] ← dbi.interface.ReadEntry[dbi.conv, dbi.db, key, value];
};
EnumerateEntries: PUBLIC PROC [db: OpenDB, key: AttributeType, start: AttributeValue ← NIL, end: AttributeValue ← NIL, proc: EntryProc] RETURNS [] ~ {
This operation can only be called on local databases.
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
isLocal: BOOLEANFALSE;
dbi: DBInterface ← GetDBInterface[db];
FOR l: LIST OF InstanceInterface ← interfaceCache, l.rest WHILE l # NIL DO
IF Rope.Equal[l.first.instance, ""] THEN {
IF l.first.interface = dbi.interface THEN -- beware: pointer comparison
isLocal ← TRUE;
EXIT;
};
ENDLOOP;
IF NOT isLocal THEN ERROR Error[$BadDBHandle, "Enumerates can only be done on local databases."];
dbi.interface.EnumerateEntries[dbi.db, key, start, end, proc];
};
GenerateEntries: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB, key: AttributeType, start: AttributeValue ← NIL, end: AttributeValue ← NIL] RETURNS [cursor: Cursor] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
remoteCursor: LoganBerry.Cursor;
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
remoteCursor ← dbi.interface.GenerateEntries[dbi.conv, dbi.db, key, start, end];
cursor ← SaveCursorInterface[dbi, remoteCursor];
};
NextEntry: PUBLIC PROC [conv: Conv ← NIL, cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: Entry] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetCursorInterface[cursor].dbi]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetCursorInterface[cursor].dbi]; RETRY;};
};
ci: CursorInterface ← GetCursorInterface[cursor];
IF conv # NIL THEN ci.dbi.conv ← conv;
entry ← ci.dbi.interface.NextEntry[ci.dbi.conv, ci.cursor, dir];
};
EndGenerate: PUBLIC PROC [conv: Conv ← NIL, cursor: Cursor] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetCursorInterface[cursor].dbi]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetCursorInterface[cursor].dbi]; RETRY;};
};
ci: CursorInterface ← GetCursorInterface[cursor];
IF conv # NIL THEN ci.dbi.conv ← conv;
ci.dbi.interface.EndGenerate[ci.dbi.conv, ci.cursor];
};
WriteEntry: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB, entry: Entry, log: LogID ← activityLog, replace: BOOLEANFALSE] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.WriteEntry[dbi.conv, dbi.db, entry, log, replace];
};
DeleteEntry: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB, key: AttributeType, value: AttributeValue] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.DeleteEntry[dbi.conv, dbi.db, key, value];
};
Close: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.Close[dbi.conv, dbi.db];
};
BuildIndices: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.BuildIndices[dbi.conv, dbi.db];
};
CompactLogs: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
dbi.interface.CompactLogs[dbi.conv, dbi.db];
};
Describe: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB] RETURNS [info: SchemaInfo] ~ {
ENABLE {
RPC.CallFailed => {PostRpcCallError[why, GetDBInterface[db]]; RETRY;};
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
info ← dbi.interface.Describe[dbi.conv, dbi.db];
};
Exported to LoganBerryStubExtras for now...
Describe2: PUBLIC PROC [conv: Conv ← NIL, db: OpenDB] RETURNS [info: LoganBerryStubExtras.SchemaInfo] ~ {
ENABLE {
LoganBerry.Error => {PostLoganBerryError[ec, explanation, GetDBInterface[db]]; RETRY;};
};
dbi: DBInterface ← GetDBInterface[db];
IF conv # NIL THEN dbi.conv ← conv;
info ← LoganBerryExtras.Describe2[dbi.conv, dbi.db];
};
Importing interfaces
Keep a cache of interface records and conversations for various instances.
ImportLoganBerry: PROC [instance: RPC.ShortROPE] RETURNS [interface: LoganBerryRpcControl.InterfaceRecord, conv: RPC.Conversation] ~ {
Searches the interface cache for an existing interface for the given instance. If one is not found then a new interface is imported. Also, a single conversation is used in conjunction with a given interface.
interface ← NIL;
FOR l: LIST OF InstanceInterface ← interfaceCache, l.rest WHILE l # NIL DO
IF Rope.Equal[l.first.instance, instance] THEN {
interface ← l.first.interface;
conv ← l.first.conv;
EXIT;
};
ENDLOOP;
IF interface = NIL THEN {
IF Rope.Equal[instance, ""] THEN { -- need local interface
interface ← NewLocalInterface[];
conv ← NIL;
}
ELSE { -- import remote interface
interface ← LoganBerryRpcControl.ImportNewInterface[[type: loganBerryType, instance: instance, version: loganBerryVersion] ! RPC.ImportFailed => PostRpcImportError[why]];
conv ← NewConversation[instance];
};
interfaceCache ← CONS[NEW[InstanceInterfaceRecord ← [instance, interface, conv]], interfaceCache];
};
};
InvalidateInterface: PROC [instance: RPC.ShortROPE] RETURNS [] ~ {
Removes the interface associated with the given instance from the interface cache. Subsequent attempts to import the instance will create a new cache entry.
prev: LIST OF InstanceInterface ← NIL;
FOR l: LIST OF InstanceInterface ← interfaceCache, l.rest WHILE l # NIL DO
IF Rope.Equal[l.first.instance, instance] THEN
IF prev = NIL THEN
interfaceCache ← l.rest
ELSE
prev.rest ← l.rest;
prev ← l;
ENDLOOP;
};
NewLocalInterface: PROC [] RETURNS [interface: LoganBerryRpcControl.InterfaceRecord] ~ {
Creates an interface record and fills it in with procedures that do local, rather than remote, calls.
interface ← NEW[LoganBerryRpcControl.InterfaceRecordObject];
interface^ ← [Error: LoganBerry.Error, clientStubOpen: localOpen, clientStubDescribe: localDescribe, clientStubReadEntry: localReadEntry, clientStubEnumerateEntries: localEnumerateEntries, clientStubGenerateEntries: localGenerateEntries, clientStubNextEntry: localNextEntry, clientStubEndGenerate: localEndGenerate, clientStubWriteEntry: localWriteEntry, clientStubDeleteEntry: localDeleteEntry, clientStubClose: localClose, clientStubBuildIndices: localBuildIndices, clientStubCompactLogs: localCompactLogs];
};
local calls to the LoganBerry interface
localOpen: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, dbName: ROPE] RETURNS [db: OpenDB] ~ {
RETURN[LoganBerry.Open[conv, dbName]];
};
localReadEntry: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB, key: AttributeType, value: AttributeValue] RETURNS [entry: Entry, others: BOOLEAN] ~ {
[entry, others] ← LoganBerry.ReadEntry[conv, db, key, value];
};
localEnumerateEntries: PROC [interface: LoganBerryRpcControl.InterfaceRecord, db: OpenDB, key: AttributeType, start: AttributeValue ← NIL, end: AttributeValue ← NIL, proc: EntryProc] RETURNS [] ~ {
LoganBerry.EnumerateEntries[db, key, start, end, proc];
};
localGenerateEntries: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB, key: AttributeType, start: AttributeValue ← NIL, end: AttributeValue ← NIL] RETURNS [cursor: Cursor] ~ {
RETURN[LoganBerry.GenerateEntries[conv, db, key, start, end]];
};
localNextEntry: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: Entry] ~ {
RETURN[LoganBerry.NextEntry[conv, cursor, dir]];
};
localEndGenerate: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, cursor: Cursor] RETURNS [] ~ {
LoganBerry.EndGenerate[conv, cursor];
};
localWriteEntry: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB, entry: Entry, log: LogID ← activityLog, replace: BOOLEANFALSE] RETURNS [] ~ {
LoganBerry.WriteEntry[conv, db, entry, log, replace];
};
localDeleteEntry: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB, key: AttributeType, value: AttributeValue] RETURNS [] ~ {
LoganBerry.DeleteEntry[conv, db, key, value];
};
localClose: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
LoganBerry.Close[conv, db];
};
localBuildIndices: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
LoganBerry.BuildIndices[conv, db];
};
localCompactLogs: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB] RETURNS [] ~ {
LoganBerry.CompactLogs[conv, db];
};
localDescribe: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: Conv ← NIL, db: OpenDB] RETURNS [info: SchemaInfo] ~ {
RETURN[LoganBerry.Describe[conv, db]];
};
Security
Secure RPC conversations are used so that callers can be authenticated and identified by a remote server. Currently, a security level of "authOnly" is used, i.e. calls are not encrypted, and a one-to-one correspondence exists between conversations and interfaces. An alternative would be to generate a new conversation for every Open call.
NewConversation: PROC [instance: RPC.ShortROPE] RETURNS [conv: RPC.Conversation] ~ {
caller: ROPE ← UserCredentials.Get[].name;
key: RPC.EncryptionKey ← RPC.MakeKey[UserCredentials.Get[].password];
conv ← RPC.StartConversation[caller: caller, key: key, callee: instance, level: securityLevel !
RPC.AuthenticateFailed => PostRpcAuthError[why]];
};
Handles and interface mappings
Keep a table mapping open database handles to the interfaces that should be used for them. The database handle presented to clients of LoganBerryStub is not the same as the one returned by a remote LoganBerry server since remote handles are not globally unique. Thus, a new (unique) handle is generated. This handle is mapped to the actual remote handle, the proper interface, and the conversation on every call. Cursors are handled in a similar manner.
Note: there are probably more efficient ways to perform these mappings than using the RefID table.
SaveDBInterface: PROC [interface: LoganBerryRpcControl.InterfaceRecord, conv: RPC.Conversation, db: LoganBerry.OpenDB, instance: RPC.ShortROPE, dbName: ROPE] RETURNS [localDB: LoganBerry.OpenDB] ~ {
dbi: DBInterface ← NEW[DBInterfaceRecord ← [interface, conv, db, instance, dbName]];
RETURN[RefID.Seal[dbi]];
};
GetDBInterface: PROC [localDB: LoganBerry.OpenDB] RETURNS [dbi: DBInterface] ~ {
ref: REF = RefID.Unseal[localDB];
IF ref = NIL THEN
ERROR Error[$BadDBHandle, "NIL OpenDB handle."];
WITH ref SELECT FROM
dbi: DBInterface => RETURN[dbi];
ENDCASE => ERROR Error[$BadDBHandle, "Invalid OpenDB handle."];
};
SaveCursorInterface: PROC [dbi: DBInterface, cursor: LoganBerry.Cursor] RETURNS [localCursor: LoganBerry.Cursor] ~ {
ci: CursorInterface ← NEW[CursorInterfaceRecord ← [dbi, cursor]];
RETURN[RefID.Seal[ci]];
};
GetCursorInterface: PROC [localCursor: LoganBerry.Cursor] RETURNS [ci: CursorInterface] ~ {
ref: REF = RefID.Unseal[localCursor];
IF ref = NIL THEN
ERROR Error[$BadCursor, "NIL cursor handle."];
WITH ref SELECT FROM
ci: CursorInterface => RETURN[ci];
ENDCASE => ERROR Error[$BadCursor, "Invalid cursor passed to NextEntry."];
};
Error handling
The following error handling procedures attempt to reconcile LoganBerry and RPC errors if possible. If an error can not be recovered from, then it is re-raised as a LoganBerryStub.Error, i.e. the handler never returns to its caller. A successful return from one of these error handlers indicates that the operation producing the error should be retried.
PostLoganBerryError: PROC[ec: LoganBerry.ErrorCode, explanation: Rope.ROPE, dbi: DBInterface] RETURNS [] = {
If the database has been closed, then attempt to reopen it. The error $BadDBHandle should never occur; but if it does then we also attempt to reopen the database in order to get a new handle. The attempt to reopen the database may in turn raise LoganBerry errors that cause this handler to be called recursively; we don't have to worry about infinite recursion since the Open operation can not raise $DBClosed or $BadDBHandle. RPC errors are caught elsewhere.
SELECT ec FROM
$DBClosed, $BadDBHandle => { -- try to reopen database
dbi.db ← dbi.interface.Open[dbi.conv, dbi.dbName ! LoganBerry.Error => PostLoganBerryError[ec, explanation, dbi]];
};
ENDCASE => { -- propagate the error as a LoganBerryStub.Error
ERROR Error[ec, explanation];
};
};
PostRpcCallError: PROC[why: RPC.CallFailure, dbi: DBInterface] RETURNS [] = {
Trys to reimport an interface if an $unbound error is encountered. All other errors are propagated as LoganBerryStub.Errors.
SELECT why FROM
$timeout => ERROR Error[$CallFailed, "no acknowledgement within reasonable time"];
$busy => ERROR Error[$CallFailed, "server says it is too busy"];
$runtimeProtocol => ERROR Error[$CallFailed, "user/server runtimes do not understand each other"];
$stubProtocol => ERROR Error[$CallFailed, "user/server stubs do not understand each other"];
$unbound => { -- try to reimport the interface
IF dbi # NIL THEN {
InvalidateInterface[dbi.instance];
[interface: dbi.interface] ← ImportLoganBerry[dbi.instance];
};
};
ENDCASE => ERROR Error[$CallFailed, "unknown reason"];
};
PostRpcAuthError: PROC [why: RPC.AuthenticateFailure] RETURNS [] ~ {
explanation: ROPESELECT why FROM
$communications => "could not contact authentication server",
$badCaller => "invalid caller name",
$badKey => "incorrect caller password",
$badCallee => "invalid callee name",
ENDCASE => "unknown reason";
ERROR Error[$AuthenticateFailed, explanation];
};
PostRpcImportError: PROC [why: RPC.ImportFailure] RETURNS [] ~ {
explanation: ROPESELECT why FROM
$communications => "could not access binding database",
$badType => "unacceptable interface type name",
$badInstance => "unacceptable interface instance name",
$badVersion => "statically silly version range",
$wrongVersion => "exported version not in required range",
$unbound => "this instance not exported",
$stubProtocol => "exporter protocol incompatible with importer",
ENDCASE => "unknown reason";
ERROR Error[$ImportFailed, explanation];
};
END.
Doug Terry, April 17, 1986 6:02:39 pm PST
Created.
changes to: DIRECTORY, LoganBerryStubImpl, EXPORTS, ~, Describe, interfaceCache, interfaceCacheSize, interfaceCache, IF, END
Doug Terry, April 18, 1986 3:40:47 pm PST
Support for multiple import and efficient local import.
changes to: ~, ImportLoganBerry, NewLocalInterface, localOpen, localReadEntry, localEnumerateEntries, localGenerateEntries, localNextEntry, localEndGenerate, localWriteEntry, localDeleteEntry, localClose, localBuildIndices, localCompactLogs, localDescribe, Open, ReadEntry, NewConversation, EnumerateEntries, GenerateEntries, NextEntry, EndGenerate, WriteEntry, DeleteEntry, Close, BuildIndices, CompactLogs, Describe, SaveDBInterface, GetDBInterface, SaveCursorInterface, GetCursorInterface, DIRECTORY, IMPORTS
Doug Terry, April 23, 1986 3:58:07 pm PST
Added error handling routines.
changes to: ~, PostLBError, Open, SaveDBInterface, ReadEntry, DIRECTORY, NewConversation, PostRPCError, PostRpcAuthError, ImportLoganBerry, PostRpcImportError, InvalidateInterface, GenerateEntries, NextEntry, EndGenerate, SaveCursorInterface, EnumerateEntries, WriteEntry, DeleteEntry, Close, BuildIndices, CompactLogs, Describe