Flip Programming Guide

 

Writing a DocumentValidator

This chapter will describe the use of flip::DocumentValidator with the Flip framework.

Overview

The Flip framework offers a flip::DocumentValidator template class which is the basis to write a document validator. A document validator receives model changes and perform logical validation.

Logical validation follows the structural validation handled by the Flip framework and will dynamically ensure that the model is in a consistent state. When the logical validation is running on the server side, it will accept or refuse a transaction. When a transaction is accepted, it will be broadcasted to the other clients and the originator will receive a notification that its transaction was accepted. When a transaction is refused, it is rollbacked on the server side, and a notification is sent to the originator for him to rollback the transaction as well.

By convention, the document validator source file will reside with the other model files.

In the following, it will be assumed that the model has the format listed in the listings below. In particular, Root will be the root class of the model. The listings skip all functions just to show the needed members for clarity.

The example is an hypothetic taxi company, where the taxis can change of color. The taxi company has a boss, which can be happy or upset. The taxi company also maintains the total number of courses since the beginning of time.

class Boss
:  public ohm::flip::Object
{
public:
   bool                 did_happyness_change () const;
private:
   ohm::flip::Bool      _happy_flag;
};
class Taxi
:  public ohm::flip::Object
{
public:
   bool                 did_color_change () const;
private:
   ohm::flip::Int64     _color;
};
typedef ohm::flip::Collection <Taxi>   TaxiColl;
class Root
:  public ohm::flip::Object
{
private:
   ohm::flip::Int64     _nbr_taxi_course;
   Boss                 _boss;
   TaxiColl             _taxi_coll;
};

Setting Up

To set up the validator, we have to make a DocumentValidator for the Root class. For that we make a class that inherits from ohm::flip::DocumentValidator which template parameter is Root .

ListingDocumentValidator declaration

class DocumentValidator
:  public ohm::flip::DocumentValidator <Root>
{
public:
                  DocumentValidator ();
   virtual        ~DocumentValidator () {}
   // inherited from ohm::flip::DocumentValidator <model::Root>
   virtual void   validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Root & root);
};

Then the client will bind the validator to the Flip ohm::flip::DocumentServer . At this point any model change to the document will be notified through the validate virtual method.

Parsing the Model Tree

Parsing the model tree itself is done in the exact same way as for the DocumentObserver .

Before we put write the implementation of validate we are going to add some private methods to keep the code clean

ListingDocumentObserver declaration

class DocumentValidator
:  public ohm::flip::DocumentValidator <Root>
{
public:
                  DocumentValidator ();
   virtual        ~DocumentValidator () {}
   // inherited from ohm::flip::DocumentValidator <model::Root>
   virtual void   validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Root & root);
private:
   void           validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Boss & boss);
   void           validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Taxi & taxi);
}

The code to handle each class is always to check changes for its direct basic typed members. To keep the code clean, each compound object should have its own validate method as illutrated here.

Listingvalidate (Root &) definition

void  DocumentObserver::validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Root & root)
{
   if (root.did_nbr_taxi_course_change ())
   {
      // validate the number of taxi course
   }
   if (_boss.is_invalid ())
   {
      // _boss contains a modification
      validate (_boss);
   }
   if (_taxi_coll.is_invalid ())
   {
      // the taxi collection contains a modification, check every taxi
      TaxiColl::iterator it = _taxi_coll.begin ();
      const TaxiColl::iterator it_end = _taxi_coll.end ();
      for (; it != it_end ; ++it)
      {
         Taxi & taxi = *it;
         if (taxi.is_invalid ())
         {
            validate (taxi);
         }
      }
   }
}

Note:In theory, every call to is_invalid can be omited. But this is done for speed.

Listingvalidate (Boss &) definition

void  DocumentObserver::validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Boss & boss)
{
   // nothing to validate
}

Listingvalidate (Taxi &) definition

void  DocumentObserver::validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Taxi & taxi)
{
   if (taxi.did_color_change ())
   {
      // validate the new taxi color
   }
}

Validating the Model Tree

A model tree is considered as validated if the document validator did not issue any error during the model tree parsing. In particular, the model tree is considered as not validated as soon as one error is reported.

Reporting errors is made through the ValidationReportWriter & report method parameter.

Here we are going to consider that validating the Taxi color is to ensure that the color number shoult be between 0 and 6 (note that we should have maybe use an Enum to do this, as the bound check would have been done in the Flip structural validation)

Listingvalidate (Taxi &) definition, continued

void  DocumentObserver::validate (flip::ValidationReportWriter & report, archi::Int32 user_id, Taxi & taxi)
{
   if (taxi.did_color_change ())
   {
      archi::Int64 color = taxi.get_color ();
      if ((color < 0) || (color > 6))
      {
         // this is an error, report
         report.print_logical_error ("Taxi: _color out of bound (%1%)").arg (color);
      }
   }
}

The report supports formatted strings. See Flip Reference: ValidationReportWriter Class Reference for more informations.