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.