Persistent write protection
In order to prevent other users or applications from updating a certain instance, instances might be write protected in different ways. ODABA supports automatic write protection in case of pessimistic locking, but also persistent write protection or context enabled write protection.
Write locks are called in order to manage concurrency problems when updating instances or collections (database entries) in the database. In the worst case, database entries are locked, when being stored in a transaction. Since each modification goes through a transaction, database entries are always passed to a transaction after being modified, which automatically locks the instance or collection.
This is usually an internal transaction, which automatically starts, when calling a updating property handle function (e.g. Property ::insert() ). Practically, any property handle function may start an internal transaction when changing the instance selection for an updated instance. When updating values in an instance, changes are stored, when the instance is saved explicitly calling Property ::save() or when the instance selection changes, e.g. when selecting another instance calling Property ::get() .
Since changing an instance may cause changes in a number of database entries, changes are stored to an internal transaction before being written to the database. In case of conflicts, the internal transaction aborts and changes are not stored to the database.
As long as database entries are part of a transaction, database entries are locked and cannot be updated by other processes or threads. After all database entries in a transaction have been written to the database, database entries will be unlocked.
This may cause a large number of instances locked when the application starts a transaction in order to perform a series of changes in one transaction. Hence, it is suggested to avoid long transactions in order to reduce lock conflicts.
When opening a property handle in write mode ( Write ), instances are automatically locked when being selected. Thus, using write property handles forces pessimistic locking. Pessimistic locking prevents other property handles from changing valued for the locked instance, which is saver than optimistic. When opening the property handle or changing the access mode to Update , values for the same instance might be changed in different property handles (or applications or threads). Detecting an update conflict usually causes a database error (SDB-Error 67) and refuses storing the latest update. In order to overwrite the updates made asynchroneously, one may call Property ::save( true ).
In order to prevent an instance from being updated also when the process has been finished, it might be write protected persistently. When being write protected persistently, no other user or application is able to update such an instance by accident.
Enabling persistent write protection is possible by changing the Property ::writeProtected() property after selecting an instance in the property handle. For being able to change the write protected state, the instance has to be available in write mode ( Property ::canUpdate() == true ). After enabling persistent write protection, the instance cannot be updated until the persistent write protection state will be changed to false, again.
In order to check, whether the instance cannot be updated because of persistent write protection, the write protection state might be checked.
... fragment ( Property &person ) {
bool wpt = false;
person.get("Miller|Paul");
if ( !person.canUpdate() )
if ( wpt = person.writeProtected() )
person.writeProtected(false); // reset persistent write protection
if ( person.canUpdate() ) {
// ... do changes
}
if ( wpt )
person.writeProtected(true); // restore persistent write protection
}
In order to control user specific access rights, usually context functions ( doAfterRead() event handler) are used. ODABA supports a common base type for relevant object types ( __OBJECT ), from which other object types may inherit.
Object instances inheriting from __OBJECT can be linked to a user or user group ( ADM_User ), which is a supported system data type. Typically, instances are linked to the user or user group creating the object instance. When reading the object instance later on, the TypeContext ::doAfterRead() handler defined for the __OBJECT data type may check, whether the user currently locked in is authorized to update the instance read or not. By changing the TypeContext ::readOnly() state to false , updating the instance becomes impossible.
When changing the TypeContext ::visible() state to false , the instance will even not be shown to the application. Thus, context handler provide a simple mean for user specific access control on object instance level.
The example below shows, how we can achieve in an application, that only users, which have created an object are able to update it. Users logged in with an invalid user name cannot create or change any sort o data.
int8 tCtx__OBJECT::doAfterOpen() {
// int64 user_id is member of the context class.
Option user_name("__USER");
Property users(database(),"ADM_User",Read);
try {
user_id = 0;
if ( user_name != "" )
if ( users.tryGet(user_name.get()) ) {
user_id = users.loid(); // one might also look for the upper user group
} catch ( ... ) {
;
}
return true;
}
// reject creation for unregistered users before create
int8 tCtx__OBJECT::doBeforeCreate() {
return user_id ? true : false;
}
// set owner after being created
int8 tCtx__OBJECT::doAfterCreate() {
Property &owner = property().property("owner");
if ( user_id )
owner.insert(Option("__USER").get()); // register owner
return true;
}
// check owner after read
int8 tCtx__OBJECT::doAfterRead() {
bool read_only = user_id ? false : true;
if ( !read_only ) {
Property &owner = property().property("owner");
if ( owner.tryGet(0) && owner.loid() != user_id )
read_only = true;
}
readOnly(read_only); // read only for other users
return true;
}