Additional Features

CORBA brokers make use of typing information to provide additional features, which facilitate some aspects of distributed programming using ORBs. Basically, these features help to reduce the interference of the broker in the application. Additionally, CORBA brokers also provide an additional API for manipulation of typing information, as described further in this section.

Implicit Servants

CORBA brokers allow that plain objects be used whenever an object reference is required. For example, suppose the server below that create file objects, which are used to access files locally stored in the host the server is running.

local oo = require "loop.base"
local oil = require "oil"

oil.main(function()
  local broker = oil.init()
  broker:loadidl[[
    interface File {
      typedef sequence Contents;
      enum AccessMode { read, write };
      exception AccessError { string error; };
    
      Contents read(in long size) raises (AccessError);
      void write(in Contents data) raises (AccessError);
      void close();
    };
    interface FileSystem {
      File open(in string path, in File::AccessMode mode)
        raises (File::AccessError);
    };
  ]]
  
  File = oo.class()
  function File:read(size)
    local data, err = self.handler:read(size)
    if data==nil then error{"IDL:File/AccessError:1.0",error=err} end
    return data
  end
  function File:write(data)
    local ok, err = self.handler:write(data)
    if ok==nil then error{"IDL:File/AccessError:1.0",error=err} end
  end
  function File:close()
    self.handler:close()
  end
  
  system = {} -- singleton object, no need for a class
  function system:open(path, mode)
    local file, err = io.open(self.path, mode:sub(1, 1))
    if file then error{"IDL:File/AccessError:1.0",error=err} end
    file = File{ handler = file }
    return file -- implicitly created servant
  end
  
  oil.writeto("fs.ior",
    broker:tostring(
      broker:newservant(system, "FileSystem")))
  broker:run()
end)

Note that IDL operation FileSystem::open returns an object reference that implements interface File. However, its implementation returns a simple object instead of a servant (i.e., an object reference). In this case, the CORBA broker implicitly registers the returned object as a servant with interface File, which is the interface of the returned value.

Implicitly created servants reduce the need for additional code related to CORBA issues. However, there are some drawback that must be observed. First, implicitly created servants are registered with automatic keys and the interface they implement is the type of the formal parameter they are being coerced to. In the example above, this means that the object returned by method open will be registered with interface File regardless whether the object implements a more specific interface.

To solve this problem, each object that may be used in the implicit creation of a servant may provide either field __objkey or __type to informs the object key and the interface name or ID of the servant, respectively. Alternatively, the field may also be provided by the object's metatable. The example below, illustrates this.

local oo = require "loop.base"
local oil = require "oil"

oil.main(function()
  local broker = oil.init()
  broker:loadidl[[
    interface Element { readonly attribute path; };
    
    interface File : Element {
      typedef sequence Contents;
      enum AccessMode { read, write };
      exception AccessError { string error; };
      interface Handler {
        Contents read(in long size) raises (AccessError);
        void write(in Contents data) raises (AccessError);
        void close();
      };
      Handler open(in AccessMode size) raises (AccessError);
    };
    sequence FileSeq;
    
    interface Directory : Element {
      FileSeq contents();
    }
    
    interface FileSystem {
      Element search(in string path);
    };
  ]]
  
  File = oo.class{
    -- implementation of interface 'File'
  }
  Directory = oo.class{
    -- implementation of interface 'Directory'
  }
  
  system = {} -- singleton object, no need for a class
  function system:search(path)
    local kind, errmsg = lfs.attributes(path, "mode")
    if kind == "file" then
      return File{ -- implicitly created servant
        path = path,
        __type = "File",
      }
    elseif kind == "directory" then
      return Directory{ -- implicitly created servant
        path = path,
        __type = "Directory",
      }
    end
  end
  
  oil.writeto("fs.ior",
    broker:tostring(
      broker:newservant(system, "FileSystem")))
  broker:run()
end)

In the example above, method FileSystem::search return an object reference of "abstract" interface Element. However, the actual objects referenced supports either interface File or Directory. Therefore, they define field __type to tell OiL which interface they should be registered with.

A second observation with implicitly created servants is that if the same object is used twice implicitly create a servant, only one servant is created. This is because, whenever the same object is registered with the same key (automatic or nor) and interface, OiL reuses the previous servant.

Reference Resolution

A complementary feature for implicit servant creation is local reference resolution, which means that any object reference received by a broker that actually refers to a local servant currently registered is automatically resolved to the servant's implementation object. This means that the application receives the object originally registered as servant instead of the servant or proxy. To illustrate this feature consider a server like the one below.

require "oil"

oil.main(function()
  local broker = oil.init()
  broker:loadidl[[
    interface MyObject {};
    interface RefHolder {
      attribute MyObject reference;
    };
    interface RefHolderFactory {
      RefHolder create();
    };
  ]]
  
  Factory = {}
  function Factory:create()
    return {} -- implicitly created servant
  end
  
  oil.writeto("factory.ior",
    broker:tostring(
      broker:newservant(Factory, "RefHolderFactory")))
  broker:run()
end)

The server above is used to create objects that can hold an object reference. Now, the following application creates a RefHolder object to store a reference to a local object. When the reference is retrieved the original object is restored.

require "oil"

oil.main(function()
  local broker = oil.init()
  
  local factory = broker:newproxy(oil.readfrom("factory.ior"))
  local holder = factory:create()
  
  local original = {}
  
  holder:_set_reference(original)          -- implicitly created servant
  local restored = holder:_get_reference() -- automatically resolved reference
  
  assert(rawequal(original, restored))
end)

Interface Discovery and Narrowing

Each CORBA proxy is associated to an IDL interface. Only operations declared in this interface can be invoked using the proxy. CORBA brokers provide method narrow(proxy [, interface]) to create another proxy to the same object represented by proxy, but with interface defined as parameter interface. For an example, see the code below that access the file server described in a previous example.

require "oil"

oil.main(function()
  local broker = oil.init()
  local filesys = broker:newproxy(oil.readfrom("fs.ior"))
  
  local file = filesys:search("/usr/bin/lua") -- returns a 'Element'
  file = broker:narrow(file, "File")          -- narrow to a 'File'
  file:open("read")
  ...
end)

If narrow is used without a second parameter, then OiL uses operation _interface of CORBA object references to figure out its most specific interface and uses it. Additionally, if this interface is not known, then the broker automatically imports the interface from the remote servant. The same is true for when method newproxy is called without the second parameter, as illustrated in the examples above. If you look back the previous examples, you will notice that only servers load IDL specifications, while client applications just call newproxy without specifying an interface, which causes the broker to import the interface definition using basic operation _interface of CORBA object references.

Unfortunately, operation _interface is optional and therefore it is not supported by every servant. Additionally, the process of importing the entire IDL definition can be quite slow for some applications. In these cases, the alternative is to load the IDL in the client and use the second parameter of methods newproxy and narrow.

Exception Support

CORBA brokers provide two additional operations that helps the use of exceptions. First, you can create exceptions using exception's absolute name instead of repository ID using method newexcept, as illustrated in the example below.

File = oo.class()
function File:read(size)
  local data, err = self.handler:read(size)
  if data==nil then
    error(broker:newexcept{ "File::AccessError",
      error = err,
    })
  end
  return data
end
function File:write(data)
  local ok, err = self.handler:write(data)
  if ok==nil then
    error(broker:newexcept{ "File::AccessError",
      error = err
    })
  end
end
function File:close()
  self.handler:close()
end

Other feature is that exception handlers can be defined for proxies of a specific interface with method setexcatch(handler [, interface]). To do so, just inform the interface's absolute name or repository ID.

Internal Interface Repository

Finally, CORBA brokers provide field types containing an object that implements the CORBA Interface Repository and provides access to all the IDL descriptions currently loaded in the broker.

Copyright (C) 2004-2008 Tecgraf, PUC-Rio

This project is currently being maintained by Tecgraf at PUC-Rio.