Introduction

In the previous lessons, bags were totally defined in the quinoa.py file using default types and standard features existing within the middleware (methods and properties), without any customization.

Sometimes, applications impose a specific behavior while calling put(), get() or rd() operation, therefore default bag-types using standard methods are no longer sufficient and new bag-types with custom methods need to be defined to respond to the requirements of the applications.

For that, the middleware provides an efficient way to create your own bag type and specify particular treatments that we can perform. A slight adaptation in the description of the given bag in the quinoa.py file allows to define new capabilities for it in a dedicated separated python file.

In the quinoa.py file, only the basic description of the bag is done. Then, its behavior is defined in the new separated python file.

In the current lesson, we will enhance the Travel bag in order to generate a log file for each customer, containing all the travel that he has done and calculates the amount of money spent knowing that a single travel costs 1.5 euros.

Updating the quinoa.py file

To build a reference between the Travel bag and its new associated complementary source file, we should updated the quinoa.py file by editing the type of the bag LINC_Trom_tutorial_-_First_Steps#Description of the object of the distributed application declared as Multiset one of the default types of the middleware.

 bag4 = data.BagDescription()

 bag4.name = "Travel"
 bag4.type = ("Travel", "TupleSpace") #  <------------------ Updated to our new type -------------------
 bag4.fieldnames = ['departure', 'arrival', 'name']
 bag4.params = {}

Before we start the discution about the contents of the python file corresponding to the bag, we should notice that its name shouldn't be picked randomly but should be adapted to a standard form (the name of the bag prefixed by "bag_") in order to be supported by the middleware. In our case it should be "bag_Travel.py".

New bag_Travel.py file

As we have seen earlyer in this lesson, this file will contain methods that concretise the particular behavior needed from the bag while executing put(), get() or rd() operation. In our case this particular behavior is needed only when calling the put operation, thus we don't need to implement responces to the get() or rd() operation, we could just inherit from one of the standard bags existing in the middleware (bag_Template for example) and no need to reinvent the wheel by implementing them again.

Initialisation

 class Behavior(bag_Template.Behavior):

    def __init__(self, args):      <-------- Default constructor ------

        bag_Template.Behavior.__init__(self, args)

The init method is a kind of default constructor. Python calls init() during the instantiation to define additional behavior that should occur when a class is instantiated, basically setting up some beginning values for that object or running a routine required on instantiation. In our case we will only initiate it to the constructor of the inherited class.

Implementing the required behavior

Once initialised, we can move to the implementation of the response to the put() call. In fact a response to a put() call passes by two phases which are:

  • The pre-put phase.
  • The confirm-put phase.

The pre-put phase

This phase is dedicated for setting the proper environment to the insertion of the resource into the bag, that includes the conditions to be validated befors that. In the present exemple we don't have too many conditions to be verified, only the fact that the recover value to be inserted is valid. Then we can generate a storage-id for that ressorce and add it to the pre-put-table, table in which value are regestred before being inserted into the bag.

 def bagaction_put(self, value):

        assert len(value) == self.arity        <------ Used to test the validity of the value to be inserted ------------
        storage_id = LINC.Bags.tools.generate_storage_id()   <----- Generate the storage-id ---------
        self.table_of_pre_put_operations[storage_id] = value
        return storage_id

The confirm-put phase

In this phase we will implement the required behavior while calling the put() operation and then insert the ressorce in the bag.

This behavior will be defined whithin the bagaction_confirm_put method.

First we need to verify if there is a directory named ./log/ in which we will save the log files of the costumers, if it doesn't exist we will create it.

def bagaction_confirm_put(self, storage_id):

        log_dir = os.path.dirname(__file__) + "/log/"
        if not os.path.exists(log_dir):
            os.mkdir(log_dir)

Then, we will recover the value of the ressorce to be inserted using the storage_id got as parameter and convert it to a list, so that we can begin the treatement.

      value = self.table_of_pre_put_operations[storage_id]
      ressource = list(value)

In the next step, we will test if this is the costumer first trip or not and based on that we will modify the log file and calculate the amount of money spent then save it in the file.

      stub_val = self.builtins.get_bag("Travel")
      list_ress = stub_val.get_resource_list(["*", "*", "*"], 0)
      log_file = os.path.join(log_dir,ressource[2] + '_log')
      nbr_res = 0
      i = 0
      while i< len(list_ress):
                     res = list_ress[i]
          if ressource[2] == res [2]:
              nbr_res += 1
          i += 1
      total_price = str((nbr_res + 1)*1.5) + ' euros'
      self.builtins.Tracer.print_warning("bagaction confirm %s", total_price)
      if nbr_res > 0 :
          fr = open(log_file, "r")
          # on recupere le contenu de fichier et on lui supprime la derniere lignes
          txt = ''.join(fr.readlines()[:-2])
          fr.close()
          fw=open(log_file, "w")
          txt = txt + ressource[0] + 4* '\t'+ ressource[1] + 3*'\t' + "1.5 euros" + 2*'\n'
          txt = txt + "total_spent" + 2* '\t'  + total_price
          # on ecrit la modification
          fw.write(txt)
          fw.close()
      else :
          fw = open(log_file, "w")
          txt = "departure" + 2* '\t' + "arrival" + 2* '\t' + "price" + '\n'
          txt = txt + ressource[0] + 4* '\t'+ ressource[1] + 3*'\t' + total_price + 2*'\n'
          txt = txt + "total_spent" + 2* '\t'  + total_price
          fw.write(txt + "\n")
          fw.close()

Finally, we will delete the storage-id from the pre-put table, add the ressorce and then signal it.

      del self.table_of_pre_put_operations[storage_id]
      self.storage_manager.add_resource(value)
      self.builtins.signal_new_resources(["Travel"])

Run the scripts

You should run the initialization and application scripts with the following command:

 python quinoa.py --start_objects All --run_scripts All

When you are done, please stop the application with the following command:

python quinoa.py --kill All