All Manuals > Developing Component Software with CORBA > 6 The Bank Server


6.4 Implementing the bank server

6.4.1 Implementing the servant classes

The bank fasl file defines three abstract servant classes
BankingDemo:checkingAccount-servant, and
BankingDemo:bank-servant corresponding to the IDL interfaces account, checkingAccount, and bank. The class
BankingDemo:checkingAccount-servant is defined to inherit from
BankingDemo:account-servant, matching the inheritance relationship in the IDL.

Note that each class inherits from the abstract class PortableServer:ServantBase, allowing instances of the class to be registered with a POA.

In our implementation of the bank server, these servant classes are implemented by the subclasses

The bank-implementation class implements BankingDemo:bank-servant by representing a bank as a connection to a database:

(defclass bank-implementation (BankingDemo:Bank-servant)
  ((connection :initarg :connection)
   (poa :initarg :poa)
   (account-impls :initform nil)))

We have included the poa slot to record the POA in which the bank servant is active, so that servants representing accounts at the bank can be registered in the same POA. A slot op:name corresponding to the attribute name defined in the IDL is inherited from the Bank-servant, as are definitions of accessor functions for this slot.

The account-implementation class implements

(defclass account-implementation (BankingDemo:Account-servant)
  ((bank :initarg :bank)))

An instance of this class represents an account. The bank slot provides a connection to the database that holds the account's record. Slots op:name and op:balance, corresponding to attributes defined in the IDL, are inherited from account-servant. The name slot identifies the record in the database.

Finally, the checkingAccount-implementation class implements
BankingDemo:checkingAccount-servant simply by inheriting from

(defclass checkingaccount-implementation   (Bankingdemo:Checkingaccount-servant account-implementation)

A slot op:limit, corresponding to the attribute limit defined in the IDL, is inherited from checkingaccount-servant.

6.4.2 Implementing the servant methods

The next step in implementing the server is to define methods, specialized on the implementation classes, for each of the protocol functions corresponding to an IDL attribute or operation.

Implementing a protocol function boils down to defining a concrete method for that function that specializes on the implementation class of its target object. Recall that the target object of a protocol function is the first parameter to that function.

We can now present the implementations of the protocol functions. The op:name method corresponding to the name attribute is automatically generated by the IDL compiler to reference a slot op:name in the class that takes the initarg :name to initialize it. The same rules apply to op:balance.

The op:credit method on an Account increments the record's balance field by executing a database update statement:

(corba:define-method op:credit ((self account-implementation) amount)
  (with-slots (op:name bank op:balance) self
    (with-slots (connection) bank
      (let ((old-balance (lookup-row-value op:name 
                         connection :balance)))
        (update-database-row op:name connection 
                             :balance (setf op:balance
                                    (+ old-balance amount)))))))

The op:debit method on an Account executes a database update statement that decrements the record's balance field, provided the balance exceeds the desired amount:

(corba:define-method op:debit ((self account-implementation) amount)
  (with-slots (op:name bank op:balance) self
    (with-slots (connection) bank
      (let ((old-balance op:balance))
        (if (< old-balance amount)
            (error 'BankingDemo:Account/Refusal 
                   :reason (format nil 
                               "Can't debit ~A because the 
                               balance is ~A." 
                              amount old-balance))
             op:name connection 
             :balance (setf op:balance (- 
                                  old-balance amount))))))))

The op:limit method is automatically generated, as it is also an attribute.

Because we defined checkingAccount-servant to inherit from
account-servant, there is no need to re-implement the op:credit method for this implementation class. However, we do want to define a specialized op:debit method on checkingAccount, to reflect that a checking account can be overdrawn up to its limit:

(corba:define-method op:debit (
                    (self checkingAccount-implementation) amount)
  (with-slots (op:name bank op:balance) self
    (with-slots (connection) bank
      (let ((old-balance (lookup-row-value op:name 
                          connection :balance))
            (limit (lookup-row-value op:name connection :limit)))
        (if (< (+ old-balance limit) amount)
            (error 'BankingDemo:Account/Refusal 
                   :reason (format nil "Can't debit ~A because the
                                    balance is ~A (limit is ~A)." 
                                   amount old-balance limit))
          (update-database-row op:name connection 
                               :balance (setf op:balance (- 
                                  old-balance amount))))))))

The BankingDemo bank op:name method returns the value of the bank's op:name slot and is automatically generated.

The op:openAccount method on Bank illustrates the raising of CORBA user exceptions:

(corba:define-method op:openAccount ((self bank-implementation)
  (with-slots (connection poa account-impls) self
    (when (find-database-row name connection)
      (error 'Bankingdemo:Bank/Duplicateaccount))
    (create-database-row name connection)
    (update-database-row name connection :balance 0)
    (let ((new-account (make-instance 'account-implementation
                                      :name name
                                      :bank self
                                      :balance 0)))
      (push new-account account-impls)
      (op:narrow 'BankingDemo:Account
                 (op:servant_to_reference poa new-account)))))

If the (find-database-row name connection) test succeeds, the call to (error 'Bankingdemo:Bank/Duplicateaccount) raises a Common Lisp condition. (We omit the definition of find-database-row, which can be found in the source.) Recall that the condition class
BankingDemo:bank/duplicateAccount corresponds to the IDL duplicateAccount exception. The POA that invoked this method in response to a client's request will catch the condition and send the duplicateAccount exception back to the client. If there is no existing account for the supplied name, the op:openAccount method creates a new record in the database.

Finally, the method makes a new servant of class account-implementation, registers it with the bank's POA with a call to op:servant_to_reference, and narrows the resulting object reference to the more specific class
BankingDemo:account, the class of object references to account objects.

The op:openCheckingAccount method is similar, except that it initializes the op:limit field of the new account record with the desired overdraft limit and registers a new servant of class checkingAccount-implementation, returning an object reference of class BankingDemo:checkingAccount:

(corba:define-method op:openCheckingAccount (
                           (self bank-implementation) name limit)
  (with-slots (connection poa account-impls) self
    (when (find-database-row name connection)
      (error 'Bankingdemo:Bank/Duplicateaccount))
    (create-database-row name connection)
    (update-database-row name connection :balance 0 :limit limit)
    (let ((new-account (make-instance 
                                      :name name
                                      :bank self
                                      :balance 0
                                      :limit limit)))
      (push new-account account-impls)
      (op:narrow 'Bankingdemo:Checkingaccount
                 (op:servant_to_reference poa new-account)))))

The op:retrieveAccount method uses the name parameter to find a database row of the given name. If the query returns nil, indicating that there is no record with that name, the method raises the CORBA user exception nonExistentAccount by signalling the corresponding Common Lisp error.

Otherwise, the method uses the value of the op:limit field to distinguish whether the account is an account or a current account, creating a new servant of the appropriate class:

(corba:define-method op:retrieveAccount ((self 
                                       bank-implementation) name)
  (with-slots (connection poa account-impls) self
    (unless (find-database-row name connection)
      (error 'Bankingdemo:Bank/NonExistentAccount))
    (let ((limit (lookup-row-value name connection :limit))
          (balance (lookup-row-value name connection :balance)))
      (if (not limit)
          (let ((account (make-instance 'account-implementation
                        :name name :bank self :balance balance)))
            (push account account-impls)
            (op:narrow 'BankingDemo:Account
        (let ((account (make-instance 
                                      :name name
                                      :bank self
                                      :balance balance
                                      :limit limit)))
          (push account account-impls)
          (op:narrow 'Bankingdemo:Checkingaccount

Finally, the closeAccount removes the record of an account from the database by executing delete-database-row call:

(corba:define-method op:closeaccount 
    ((self bank-implementation) account)   
  (with-slots (connection poa account-impls) self     
    (let ((servant (op:reference_to_servant poa account)))       
      (op:deactivate_object poa (op:reference_to_id poa account))       
      (removef account-impls servant)       
      (with-slots (op:name) servant         
         (delete-database-row op:name connection))))

Note that we need to de-reference the object reference account that is passed in as the parameter of the op:closeAccount operation, using a call to the op:reference_to_servant operation of the POA.

Here, we make implicit use of our knowledge that, in our application, the server only encounters object references registered with its local POA. This assumption is not true in general.

6.4.3 Obtaining the initial POA object and registering the first object reference

To complete the implementation of the server we need to write the code that enters it into the CORBA environment. In detail, we need to:

To do this, we need to make use of some additional operations specified in the CORBA module:

module CORBA {    
  interface ORB {     
    typedef string ObjectId;      
    exception InvalidName {};      
    Object resolve_initial_references (in ObjectId identifier)          
        raises (InvalidName);      
    void shutdown( in boolean wait_for_completion );    

The CORBA standard specifies the ORB operation resolve_initial_references. This operation provides a portable method for applications to obtain initial references to a small set of standard objects (objects other than the initial ORB). These objects are identified by a mnemonic name, using a string known as an ObjectId. For instance, the ObjectID for an initial POA object is RootPOA. (References to a select few other objects, such as the InterfaceRepository and NamingService, can also be obtained in this manner.)

The ORB operation resolve_initial_references returns the object associated with an ObjectId, raising the exception InvalidName for an unrecognized ObjectID.

Meanwhile, the shutdown operation instructs the ORB, and its object adapters, to shut down. If the wait_for_completion parameter is TRUE, the operation blocks until all pending ORB processing has completed, otherwise it simply shuts down the ORB immediately.

(defun bank-server ()
  (let* ((orb (op:orb_init nil "LispWorks ORB"))
         (rootPOA (op:resolve_initial_references orb "RootPOA")))
    (let ((bank-impl (make-instance 'bank-implementation 
                                   :connection (connect-to-database)
                                   :poa rootPOA)))
      (let ((bank-ref (op:servant_to_reference rootPOA 
        (object-to-file orb bank-ref)
        (capi:display (make-instance 'server-controller
                                     :bank-poa rootPOA
                                     :bank-ref bank-ref)))
      (op:activate (op:the_poamanager rootPOA)))))

The top-level function first initializes the LispWorks ORB by calling the Common Lisp generic function op:ORB_init, just as we initialized the ORB in the client.

The call returns an ORB pseudo-object. Invoking op:resolve_initial_references on this ORB, passing the
ObjectID RootPOA, returns a POA object of class PortableServer:POA. This is the CORBA standard method for obtaining the initial POA object. Note that root POA is initially in the holding state.

Next, we connect to the database and use the connection to make a bank servant. We register the servant with the POA, RootPOA, and publish the resulting object reference, encoded as a string, in the shared file.

We then obtain the POA Manager for the POA using the POA operation op:the_POAManager. The call to op:activate moves the POA out of the holding state, into the active state, ready to receive and process incoming requests.

This completes the description of our implementation of the server.


Developing Component Software with CORBA - 14 Feb 2015