Introduction

Until now, we only used built-in behaviors of objects: objects were totally defined in the quinoa.py file and the application only used the standard features of the objects (methods and properties) to run, without any customization.

Sometimes (not to say most of the time), application-specific behaviors are required. For that, custom methods are implemented to act on the objects, bags and contained resources.

The middleware provides an efficient way to enhance and derive its objects. A slight adaptation in the description of the object given in the quinoa.py file allows to define new capabilities for the object in a dedicated separated python file.

In the quinoa.py file, only the basic description of the object is done. Then, for this object, the list of associated bags and its behavior are defined in the new separated file.

In the current lesson, we enhance the Metro object with the purpose of easing the initialization process for large metro networks (more lines and/or more stations). For that, a new specific method is implemented in a separated file named object_Metro.py (please note the naming convention : object_XXX.py for an object named XXX in the quinoa.py file).

Here, we deal with the same problem as in the previous lesson: we want to initialize the Connection bag with all the resources required by the new design of the enhanced trom network.

Updates in the quinoa.py file

The quinoa.py file is updated in order to build a reference between the Metro object and its new associated complementary source file.

First, we update the Metro object declaration :

 # Object Metro
 MetroObject = data.ObjectDescription()
 MetroObject.name = "Metro"
 MetroObject.type = "Metro"
 MetroObject.host = H1
 MetroObject.port = 10000
 MetroObject.application = A1
 MetroObject.nameserver = NameServer
 MetroObject.params = {param.OBJECT_TRACE_LEVEL:TRACE_LEVEL}
 MetroObject.list_of_bags = []
 LIST_OF_OBJECTS.append(MetroObject)

The type value associated with the MetroObject object is now set to Metro (which is its name value). This creates a reference between the object and its dedicated complementary source code file in a file named object_Metro.py.

Then, we update the domain definition on which the object is attached (here, domain1), to indicate to the middleware in which directory additional libraries can be found. Here, the object_Metro.py source file is located in the same directory as the quinoa.py file. So, as the TOPDIR variable contains a reference to this directory, the new domain declaration becomes :

 # ----- Domain
 domain1 = data.DomainDescription()
 domain1.name = PREFERENCES[lincrc.DEFAULT_DOMAIN]
 domain1.bin_directory = BINDIR
 domain1.var_directory = "/tmp"
 domain1.type = "NO_NETWORK"
 domain1.library = [TOPDIR]        <-- directory where the complementary source file is stored

New object_Metro.py file

The new object_Metro.py file is created in the same directory as the quinoa.py file.

This file may be used as a template if you want to associate source code to customized objects.

As we want to enhance the behavior of the previous Metro object but also continue to benefit from the features provided by the standard objects, the class object_Metro inherits from object_Coordinator (note that the previous versions of the Metro object were of Coordinator type).

class Participant(object_Coordinator.Participant):

  def __init__(self):
    object_Coordinator.Participant.__init__(self)

Access to the list of bags is defined within the object_Metro.py file, with the following self-documented method :

def get_list_of_bags(self):
    """
    Define the different bags of the object
    """
    import LINC.Scripts.data
    # get the value returned by the inherited get_list_of_bags
    list_of_bags = object_Coordinator.Participant.get_list_of_bags(self)
    # Define the Connection bag and append it to the list
    bag2 = LINC.Scripts.data.BagDescription()
    bag2.name = "Connection"
    bag2.type = ("Multiset", "TupleSpace")
    bag2.fieldnames = ['line', 'departure', 'arrival']
    bag2.params = {}
    list_of_bags.append(bag2)
    # return the resulting list
    return list_of_bags

Internal Methods

In the previous lesson resources are inserted in the Connection bag via the action protocol and URLs. This previous way is quite convenient but requires sending 2 distinct HTTP requests to perform the resource insertion (insertion, and then confirmation).

A more application-friendly way is presented in this lesson: only one URL call is required to insert the resource in the bag.

Here, a new python class is defined to customize the implementation of the native Metro object. This object provides a new internal method which is used during the content initialization of the Connection bag attached to the object.

This internal method is accessible via URL, formated as described below.

object_Metro.py

Internal method

The custom internal method is named insert_connection.

This method is designed to receive a series of arguments. When the method is called via a URL, the arguments include the URL parameters.

The insert_connection method is reported below. And once again, understanding its code is quite straightforward :

  • The value of the parameters parameter is read from the received arguments and stored in a resource variable
  • As the get_parameter method returns a list of items, and as the method is supposed to insert one Connection resource at a time, only the first item of the resource list variable is preserved. This item is supposed to contain a Connection resource (passed as tuple).
  • Calling the inherited get_bag() method, a stub to the Connection bag is obtained.
  • This stub is then used to perform the 2 phases required by the protocol to validate an insertion
    • The incoming resource is put() into the Connection bag, using a method exposed by the stub which returns a storage_id (named leaseid in the previous lesson)
    • Then the confirmation phase applied, using the dedicated method of the opened stub, provided with the previously returned storage_id.
  • When the resource is inserted, a signal is raised to propagate the fact that a new resource is present within the Connection bag.
 def insert_connection(self,args):
     """
     Insert a resource in the Connection bag
     This method is used while populating the bag for designing the network,
       called from the build_map_METHOD.py file
     """
     # get the string associated to "parameters"
     resource = args.get_parameter("parameters")
     try:
       # eval the received string
       resource = eval(resource)
       # as it is a list we select the first element
       resource = resource[0]
       print resource
       # get a stub for interacting with the Connection bag
       stub = self.builtins.get_bag("Connection")
       # put() and then confirm() the insertion of the ressource
       storage_id = stub.bagaction_put(resource)
       stub.bagaction_confirm(storage_id)
       # Notify the insertion of a new resource in the Connection Bag
       self.builtins.signal_new_resources(["Connection"])
       return ""
     except SyntaxError: # in case we received garbage in the url
       pass

Inserting resources using custom internal methods

As we already said, this insert_connection() method is used during the initialization phase: Connection resources are inserted to "shape" the designed trom network.

The principle followed is similar to what was done in the previous lesson: the Connection resources are inserted by launching a dedicated python file.

buildMap_METHOD.py

The main structure of the buildMap_METHOD.py is nearly the same as the one of the previous buildMap_ACTION.py source file.

Here, as an internal method is used, the METHOD sub-path is, by convention, targeted under the Metro object base URL.

In the buildMap_METHOD.py file, the insertion operation became:

# Method access base URL
url = 'http://localhost:10000/Metro/METHOD?'

# insertion of all the resources
for resource in list_of_resources_to_insert:
    print 'put operation with resource=', resource
    # Initialize and encode parameters values
    url_parameters = {'methodname':'insert_connection',
                      'parameters':[resource]}
    s = urllib.urlencode(url_parameters)
    # Send the resource insertion request and get the reply
    fp = urllib.urlopen(url + s)
    txt = fp.read()
    #print txt
  • METHOD is the targeted entry point. Here, a method call is used (as in previous lesson, ACTION indicates that the "native" operations are targeted).
  • methodname is the name of method called. Here, the method name is insert_connection, defined in object_Metro.py.
  • parameters is the resource to insert, transmitted to the method within its args parameter.

The rules (init.ls et trom.rules) are not modified.

Inserting Connection resources

Start the application with the following command:

python quinoa.py --start_objects All

Run the script buildMap_METHOD.py as follow:

python buildMap_METHOD.py

Open the browser to URL http://localhost:9999/NameServer/MONITOR. Navigate to the Metro object, then select the visualization of the content of the Connection bag: the resources are inserted.

Note: the insertion mechanisms exposed in this and the previous lessons are not exhaustive. Depending of the application, it's up to the developer to select the most appropriate mechanism.

Run the scripts with the following command:

python quinoa.py --run_scripts All Check the results obtained in Supervision.

To stop the application, use the following command: python quinoa.py --stop_all