Content


Cibet Concepts

Control Events

A control event is a context specific action that is performed on a Resource like a persistence entity or an object's method. This could be a persistence event on a domain object or a method call on an object. These actions are monitored in its specific context by Cibet. That means for example, that a database update can be done in a dual control context or a restoring context or in a simple update context. The following event types are the basic ones:

  • INSERT

    A newly created domain object which is not already in the database is persisted. This event corresponds to an SQL INSERT into the database. With JPA this is done with the EntityManager.persist() method.

  • UPDATE

    An already persistent object is updated in the database. This event corresponds to an SQL UPDATE in the database. With JPA this is done either implicitly when the object is already in the persistent context and the transaction is committed or with the EntityManager.merge() method if the object is not in the persistent context.

  • DELETE

    A persistent object is removed from the database. This event corresponds to an SQL DELETE in the database. With JPA this is done with the EntityManager.remove() method.

  • SELECT

    A persistent entity is selected from the database. This event corresponds to an SQL SELECT statement. With JPA this is effectuated with the EntityManager.find() methods.

  • INVOKE

    This event is the execution of a service or business process. For example, the business process to update some entity in the database may also include sending an information message to an external system. You don't want the message to be sent if the update is set under dual control, but only when it is released. An INVOKE event could be a method call in a service class or an http request on a URL.

  • RELEASE, RELEASE_INSERT, RELEASE_UPDATE, RELEASE_DELETE, RELEASE_SELECT, RELEASE_INVOKE

    when a dual control mechanism is applied on a business case it is not executed instantaneously but is postponed. A second user must release or reject the business case. These events are the release actions on insert, update, delete or invoke business methods.

  • FIRST_RELEASE, FIRST_RELEASE_INSERT, FIRST_RELEASE_UPDATE, FIRST_RELEASE_DELETE, FIRST_RELEASE_SELECT FIRST_RELEASE_INVOKE

    when a six-eyes dual control mechanism is applied on a business case, a third person must release the business case. In this case the second user does not issue a release event but a first release event. These events are the first release actions on insert, update, delete or invoke business cases.

  • REJECT, REJECT_INSERT, REJECT_UPDATE, REJECT_DELETE, REJECT_SELECT, REJECT_INVOKE

    when a dual control mechanism is applied on a business case it is not executed instantaneously but is postponed. A second user must release or reject the business case. These events are the reject actions on insert, update, delete or invoke business cases.

  • REDO

    An archived service call invocation can be redone as all necessary metadata are stored with the archive. The redo event is the re-execution of a service call or the re-sending of an http request from an archive with exactly the same parameters.

  • RESTORE

    The state of an entity can be restored from an archived persistence business case. Restoring is either an update persistence action if the entity still lives in the database or an insert if the entity has been deleted.

These are the events that can be used for configuration. The events are organized in a hierarchical order. This means, if a configuration is defined for an event in the upper hierarchy, it applies also for all sub-events. The event hierarchy is shown in the following figure.

Hierarchy of control events

Sensors

The sensors are the part of the Cibet framework that must be integrated into an application in order to detect and control resources and business events and allow Cibet to evaluate setpoints and apply actuators. The following sensors are included in Cibet:

  • EJB3

    This sensor detects control for EJB3.0 method invocations

  • JPA

    This sensor detects control for JPA database persistence actions like select, insert, update and delete of domain entities.

  • POJO

    This sensor detects control for invocations of public methods on any POJO. For dual control actuators and the REDO of a POJO method invocation the object on which to invoke the method must be recreated. Cibet supports the following object instantiation mechanisms:

    • Default constructor
    • Constructor with String parameter
    • Static Singleton method without parameter
    • Static Singleton method with String parameter
    • Static method in a factory class
    • Factory method in a factory class with default constructor
    • Factory method in a singleton factory class
    • Instantiation as Spring bean
  • SERVLET

    This sensor allows controlling of http requests.

  • JDBC

    This sensor detects control for plain JDBC requests. Insert, update and delete in database tables can be controlled with the help of this sensor.

Actuators

Actuators are one of the central concepts around which Cibet is built. An actuator implements the procedure that Cibet applies in order to control an entity or a business process. The following actuators are pre-defined in Cibet:

  • INFOLOG

    Before and after the control event a message is logged in INFO level to the standard log channel. This actuator is for debugging and testing purpose and should not be applied in production. The logged data are not encrypted.

  • TRACKER

    All controls executed by Cibet can be tracked and monitored as Event Results. The tracked data include applied sensor, setpoints and actuators, control result, user and so on. Nested control events are also tracked. When for example a method call is controlled by Cibet and within the execution of the method a nested persistence action is also controlled the second Event Result is a child of the first one.

    This actuator writes all results of intercepted business cases into the database into table cib_eventresult.

  • ARCHIVE

    Persistence actions and method invocations are archived. That means, it is logged which user executes what action at what time. In case of an updating persistence action on an entity the state of the entity is archived. For a new (insert) persistence action, the state of the newly created entity is archived. For a delete persistence action, the last state of the removed entity is archived. From the archived state, the entity could be reconstructed at any time. For a method invocation archive, the method parameters and, if applicable the constructor parameters of the object on which the method has been invoked are archived as well as the method result. The method could be re-invoked with the same parameters at any time from an archive. For an http request, the URL with headers and query parameters are archived and can be re-executed.

    The archive entries in the database are secured against manipulation to make them audit-proof and revision safe. It is not possible to silently modify existing archives or to delete or add archive records without detection by Cibet control. Though it is not possible to prohibit entirely such manipulations except if an encrypted database is used all fraudulent actions can be detected.

  • FOUR_EYES

    With the FOUR_EYES actuator a dual control mechanism is applied. That means, if a user executes a persistence action or method invocation it will not be done directly in the productive environment. Instead, the action is stored and postponed. Only if a second user accepts and releases the action, it is performed in the productive system. If the second user rejects the action it will not be performed. The following figure shows the work flow of a four-eyes controlled process.

    1. A user submits a business case which can be one of the before mentioned control events.
    2. Cibet sensors detect that the business case is under dual control and postpones it. All relevant business case and context data are persisted in a cache.
    3. A second user is responsible to check and release or reject postponed business cases
    4. He searches for unreleased business cases and validates them
    5. When the validation is positive he may release the business case, otherwise he rejects it and the following step 6 is not executed.
    6. The business case is executed in the productive system, i.e. a persistence action is executed or a method is called on an object or a http request is sent.
    7. The cache of postponed business cases is updated.

    Concerning the submitting and releasing users the following rules are applied:

    • the two users must be different
    • submitting user cannot release
    • if submitting user rejects, the action will be discarded
    • if releasing user release the action it will become productive
    • if releasing user rejects the action it will be discarded

    Let's make an example: User A changes the account number in an account object which is controlled by FOUR_EYES actuator. The updated state of the object is stored in Cibet but the object itself is not updated. Later on user A remembers that not only the bank account but also the bank name must be changed. If he tries to update the account with the changed bank name he will receive a warning that an unreleased update exists already for this account. He has two choices now:

    • He rejects the first update of the account number. While release must be done by a different user than the one who executed the controlled action, rejection can be done also by the same user. After rejection he changes account number and bank name and persists the update which will be postponed. Now user B accepts and releases the account change and the update is performed in the productive database.
    • He waits until user B has accepted and released the change of the account number. After that user A can change the bank name and persist the update. Now user B accepts and releases the bank name change and the update is performed in the productive database.
  • SIX_EYES

    The SIX_EYES actuator applies an even higher dual control concept than the FOUR_EYES actuator using the same mechanism. Additionally to FOUR_EYES a third user must release the controlled action in order to get it productive. The applied rules are as follows:

    • the three users must be different
    • submitting user cannot release
    • if submitting user rejects, the action will be discarded
    • if first and second releasing user release the action it will become productive
    • if first releasing user releases and second releasing user rejects the action will be discarded
    • if first releasing user rejects the action will be discarded at once without any action of a second releasing user
  • TWO_MAN_RULE

    This is another dual control actuator that imposes an additional requirement (see Two-man rule). The controlled event executed by a submitting user must not only be released by a second user, but with this actuator applied the submitting and releasing user must be present at the same time. This means that the event must be authorized by two users simultaneously.

    This control scheme can be observed for example at the cash desk of a supermarket when a customer wants to return a purchased product. While the cashier remains logged in into the terminal, a second employee has to authorize the cash return.

    The following rules are applied:

    • the submitting and releasing users must be different
    • if at submitting time a second releasing user is authenticated the action will be executed at once.
    • If at submitting time no second releasing user is authenticated the action will be postponed.
    • Only the submitting user can request the release of a postponed action. If at that time a second releasing user is authenticated the action will be executed.
    • Any user can reject a postponed action. The action will be discarded.
  • PARALLEL_DC

    This is one more dual control scheme where the same business case is executed in parallel by one or more users. A releasing user compares the input parameters and the results and releases one of the postponed variants. An example of such a scenario is for example in an accountancy application when one employee physically counts all the valuables and arrives at a balance without knowledge of the balance expected by the paperwork balance. A second employee compiles the transaction information using supporting paperwork and tabulates the teller or store balance. A third employee then compares their findings. If a discrepancy exists, the two employees each verify the others work in an attempt to reconcile the difference. If the employees agree upon a balance and identify that a loss has occurred, they would then follow their appropriate procedures. If there is no difference the third employee releases the business case and the two employees sign the paperwork indicating the agreed upon balance.

    This actuator allows defining if the users who execute the business case must be different, if there should be a time lag between the executions and how many executions must be done at minimum before the business case could be released. The releasing user must always be a different user.

  • SPRING_SECURITY

    This actuator integrates Spring Security . It allows defining access control rules for persistence actions, method invocations or http requests on URLs. Users and groups are managed by Spring Security as well as the evaluation of permissions and access rules. In a standalone Spring Security application the permissions are configured in the Spring config file or with annotations in the classes. With Cibet permissions are defined in Cibet setpoints. The integration with Cibet allows a much more fine grained permission definition and a lot more possibilities. For example you can allow a dual control method invocation to one group of users and the release to another group. Or you may grant the update of a payment object to a user only when the amount does not change. Please have a look here for more information about the integration of security frameworks like Spring Security into Cibet.

  • SHIRO

    This actuator integrates Apache Shiro. It allows defining access control rules for persistence actions, method invocations or http requests on URLs. Users and groups are managed by Shiro as well as the evaluation of permissions and access rules. In a standalone Shiro application the permissions are configured programmatically or with annotations in the classes. With Cibet, permissions are defined in Cibet setpoints. The integration with Cibet allows a much more fine grained and flexible permission definition and a lot more possibilities. For example you can allow a dual control method invocation to one group of users and the release to another group. Or you may grant the update of a payment object to a user only when the amount does not change. Please have a look here for more information about the integration of security frameworks like Shiro into Cibet.

  • LOCKER

    LOCKER actuator allows the temporary locking of an event on a resource like persisting an entity, releasing of a dual control event, invocation of a method or requesting a URL. Only the user who sets the lock can execute that event. This actuator is not a replacement for a sophisticated authorization framework like Spring Security or Apache Shiro. There is no concept of roles and groups. While an authorization framework defines static permissions which are applied to groups or roles, LOCKER actuator allows defining dynamic permissions applied to a user. LOCKER can be applied when a user wants to make a reservation for a resource so that no other user can do an action on it.

Actuators can be combined. For example in most cases it makes sense to combine a FOUR_EYES or a SIX_EYES actuator with the ARCHIVE actuator in order to follow up the life cycle of objects. On the other hand it makes no sense to combine FOUR_EYES and SIX_EYES actuators. 4 + 6 is not 10 in this case!

It is also possible to extend the pre-installed actuators and define own actuators (see Implementing Own Actuators)

Setpoints and Controller

Setpoints are another central concept of Cibet. The Cibet controller decides by comparing the actual control event context with registered setpoints. If a setpoint matches the context the actuators of this setpoint are executed. The actual control event context could comprise all data and metadata of the event, for example:

  • event type
  • tenant
  • logged in user
  • affected domain object
  • state of the object
  • affected service call
  • service call parameters
  • service call context
  • Cibet context properties
  • time
  • environment conditions
  • URL
  • http headers
  • http querystring and form parameters

A setpoint defines which of these parameters should be evaluated with what parameters in order to decide if the actuators of this setpoint should be executed. Setpoints can be defined in cibet-config.xml configuration file or in program code with the Configuration API. Here is an example of xml configuration:

  <setpoint id="Account Control 1">
    <controls>
      <event>UPDATE</event>
      <target>com.app.accounting.Account</target>
    </controls>
    <actuator name="FOUR_EYES"/>
    <actuator name="ARCHIVE"/>
  </setpoint>

This setpoint defines that updating of Account objects shall be set under four eyes principle and the state changes be archived. A setpoint definition can be regarded as an IF THEN statement: IF the controls match the actual runtime context THEN the given actuators are applied.

The same setpoint as above could also be defined in program code:

   Setpoint sp = new Setpoint("Account Control 1");
   sp.setEvent(ControlEvent.UPDATE);
   sp.setTarget(Account.class.getName());
   sp.addActuator(cm.getActuator(FourEyesActuator.DEFAULTNAME));
   sp.addActuator(cm.getActuator(ArchiveActuator.DEFAULTNAME));
   Configuration.instance().registerSetpoint(sp);

The Cibet Controller executes evaluation of setpoints and makes decisions by applying instances of the Control interface. The Control instances are the counterpart of the setpoint definitions.

Installation

The easiest way to make use of Cibet is to add a Maven dependency into your pom.xml. Just add

  <dependency>
        <groupId>com.logitags</groupId>
        <artifactId>cibet</artifactId>
        <version>the current version</version>
  </dependency>

to the pom.xml of your project. The SQL scripts to set up the database tables are included in the cibet archive in folder sql.

The current cibet release can also be downloaded from the cibet web page (http://www.logitags.com/cibet) or from the Cibet sourceforge project page (https://sourceforge.net/projects/cibet/).

The complete list of dependencies and transitive dependencies to add to your classpath can be found here. It must be checked which of these dependencies are actually necessary. In any case, you need commons-logging. As Cibet has a component-based architecture further installation depends on which sensors and actuators you want to apply in your application. The following chapters about sensors and actuators list all installation requirements.

Using Cibet

One of the greatest advantages of the Cibet framework is that it is non-intrusive. Existing applications can be enhanced by Cibet control functionality with changing only a few lines of existing code. No change of existing domain objects, manager classes or database schemes is necessary. In many cases no modification at all of existing code is necessary. Nearly all control and actuator functionality can be added by configuration. All other Cibet code is optional and only necessary if the additional functionalities of Cibet should be applied.

The Cibet Context

Scopes

Cibet retrieves and stores properties and data that are used internally in a context. Developers can use the Cibet context to make application specific data accessible to the Cibet framework. One example for a context property is the logged in user that must be made known to Cibet in order to apply controllers and actuators.

There exist three scopes of the Cibet context:

  • Request scope: Data in request scope are only available within one http request. It can be accessed with com.logitags.cibet.core.context.Context.requestScope()
  • Session scope: Data in session scope are available within the same http session. Cibet session scope is linked to the http session. It can be accessed with com.logitags.cibet.core.context.Context.sessionScope()
  • Application scope: Data in application scope are available in all threads and sessions. It can be accessed with com.logitags.cibet.core.context.Context.applicationScope()

All three contexts have generic methods to set, get and remove any user-defined property. For scope-specific additional properties with explicit getter and setter methods see the API Javadoc of RequestScope, SessionScope and ApplicationScope.

The Cibet session scope context is thread-safe. Every logged in user has an own session scope context. In a web application each http request runs in a different thread. Therefore, the Cibet session context must be reset on each request from the http session. The linking of the Cibet session context to the http session is done by the com.logitags.cibet.core.context.CibetContextFilter. This http filter is responsible for setting the Cibet context on each request.

In order to make use of the CibetContextFilter, add the following to the web.xml:

<filter>
    <filter-name>cibetContextFilter</filter-name>
    <filter-class>com.logitags.cibet.core.context.CibetContextFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>cibetContextFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Please note that you don't have to configure CibetContextFilter if you use SERVLET sensor with the CibetFilter (see chapter SERVLET sensor). The CibetFilter inherits from CibetContextFilter and contains already its functionality.

User and Tenant

In order to apply controller and actuators Cibet must know in any case the unique id of the user who executes the business process. The user id must be set into the session scope context to be accessible by Cibet. Normally, this can be done after the user has been authenticated during the login process. In many cases, Cibet can detect the logged in user by itself and nothing has to be done here.

Cibet detects the logged in user by applying implementations of the AuthenticationProvider interface in the following sequence:

  1. If Apache Shiro libraries are in the classpath, the authenticated user is taken from Shiro SecurityUtils
  2. If Spring Security libraries are in the classpath, the authenticated user is taken from Spring Security SecurityContextHolder
  3. If HttpServletRequest.getUserPrincipal() is not null, the user name is taken from that Principal
  4. If in an EJB container the SessionContext.getCallerPrincipal() is not equal to 'guest' or 'ANONYMOUS', the user name is taken from that Principal

If Cibet cannot detect the user name by one of these algorithms, there are two alternatives:

  1. Setting the user name directly into the Cibet context. This is best done after login, when the user has been authenticated:
      public void login(String user, String password) {
         // authentication 
         ...
         Context.sessionScope().setUser(user);
      }
    
  2. Registering an own implementation of the AuthenticationProvider interface. This can be done by code or by configuration. In code execute
      Configuration.instance().registerAuthenticationProvider(new MyCustomAuthProvider());
    

    Alternatively, add in cibet-config.xml:

      <authenticationProvider>
        <class>com.myapp.MyCustomAuthProvider</class>
      </authenticationProvider>
    

    A custom AuthenticationProvider can be configured with properties:

      <authenticationProvider>
        <class>com.myapp.MyCustomAuthProvider</class>
        <properties>
          <myProp>any</myProp>
        </properties>
      </authenticationProvider>
    

    In this case the implementation must have a property myProp with getter and setter according to the Java beans convention.

In a multi-tenant system, the tenant to whom the user belongs must be set into the Cibet session context. The Cibet standard AuthenticationProvider implementations cannot detect the tenant automatically. Therefore the tenant must always be set by one of the two above mentioned alternatives:

  1. Setting the tenant directly into the Cibet context. This is best done after login, when the user has been authenticated:
            public void login(String user, String password) {
               // authentication 
               ...
               Context.sessionScope().setTenant(tenant);
            }
    
  2. Registering an own version of the AuthenticationProvider interface that implements the getTenant() method. It is also possible to inherit from one of the Cibet AuthenticationProviders and implement the getTenant() method. Registration can be done by code or by configuration like demonstrated above.

EntityManager

Many Cibet functionalities depend on a database. Cibet can be used in applications that are based on JPA database access but also in applications based on plain JDBC and DataSources. Internally, Cibet uses JPA to persist and query entities.

In JPA based applications an instance of the EntityManager interface serves as API for persistence operations. The persistence unit must be defined in the persistence.xml of the application and the EntityManager that Cibet should use must be provided to the Cibet request scope context. Two possibilities exist to make an EntityManager available to the request scope context:

  • Automatic setting with CibetContextFilter or CibetFilter
  • Manual setting

Automatic Setting of the EntityManager

JPA applications

This is the non-intrusive and the standard way to make an EntityManager available to Cibet. All that has to be done is to declare CibetContextFilter or CibetFilter in the web.xml like pointed out above and define a persistence unit in the persistence.xml. The only requirement regards the naming of this persistence unit. In a Java EE application, the transaction-type must be JTA and the unit name must be Cibet, while in a Java SE application the transaction-type must be RESOURCE_LOCAL and the unit name must be CibetLocal.

In a Java EE environment the EntityManager is injected into CibetEEContext EJB. In most cases Cibet will detect the JNDI name of this EJB itself, but in some environments it can't find out if it is not a standard name. If you observe an error in the log that Cibet failed to retrieve the JNDI name of CibetEEContext EJB you can set it as a parameter of the CibetContextFilter:

  <filter>
     <filter-name>cibetContextFilter</filter-name>
     <filter-class>com.logitags.cibet.core.context.CibetContextFilter</filter-class>
     <init-param>
        <param-name>EJB_JNDI_NAME</param-name>
        <param-value>CibetEEContextEJBLocal</param-value>
     </init-param>
   </filter>

The Cibet entities must be declared in the persistence unit with the <jar-file> tag. Add

<jar-file>cibet-<version>.jar</jar-file>

to the Cibet / CibetLocal persistence unit. Please note that according to Java EE specification jar files are specified relative to the directory or jar file that contains the root of the persistence unit. It can be quit tricky to find out what the root is, depending on your archive type (ear or war) and the persistence provider. Read my entry in java.net Glassfish forum and see Java EE specification for details and examples.

Transaction management of Cibet / CibetLocal EntityManager is different in EE and SE applications:

  • In an EE application, the Cibet EntityManager will join an active JTA transaction. It is therefore necessary, that the controlled business case is executed in a transacted environment.
  • In an SE application the CibetContextFilter will start and commit a transaction for the CibetLocal EntityManager. The CibetLocal EntityManager is independent from a transacted environment. If an application transaction is rolled back during execution of a business case, the CibetLocal transaction should also be rolled back with
        Context.requestScope().setRollbackOnly(true);
    

JDBC applications

In JDBC based applications there is no meaning of a persistent unit in a persistence.xml file. Instead, JDBC applications will use a DataSource. Because Cibet works internally with JPA, Cibet provides for such applications an implementation of the EntityManager interface that will be used as a bridge between JDBC and JPA. You have to include the Java persistence API into the classpath. Like in JPA applications CibetContextFilter or CibetFilter must be declared in web.xml in order to use automatic JdbcBridgeEntityManager.

All that has to be done now is to declare a DataSource with name CibetJDBC in JNDI context java:comp/env/jdbc. If CibetContextFilter or CibetFilter detect such a DataSource, it assumes being in a JDBC application and uses it for the JdbcBridgeEntityManager.

Additionally, an implementation of com.logitags.cibet.sensor.jdbc.IdGenerator interface must be published with name CibetIdGenerator in the JNDI context java:comp/env/bean. This implementation is used to generate unique ids as primary keys for the internal Cibet entities. An example of such a configuration could be:

  <Resource name="jdbc/CibetJDBC" auth="Container"
     type="javax.sql.DataSource"
     maxActive="100" maxIdle="30" maxWait="10000"
     username="root" password="xxxxxx"
     driverClassName="com.mysql.jdbc.Driver"
     url="jdbc:mysql://192.168.1.64:3306/test"/>

  <Resource name="bean/CibetIdGenerator" auth="Container"
     type="com.logitags.cibet.sensor.jdbc.bridge.TimeBasedIdGenerator"
     factory="org.apache.naming.factory.BeanFactory" />               

com.logitags.cibet.sensor.jdbc.TimeBasedIdGenerator uses System.currentTimeMillis() to generate a pseudo unique value. Each start of the VM sets the current value to System.currentTimeMillis() and each request for an ID increases the actual value by one. This implementation is not applicable when more than 1000 IDs are requested per second and the application is often restarted.

Manual Setting of the EntityManager

An EntityManager can be set manually into the Cibet request scope context. An EntityManager set manually overwrites the EntityManager set automatically in CibetContextFilter or CibetFilter:

  EntityManager em;
  Context.requestScope().setEntityManager(em);

As CibetContext is thread safe each thread will have its own EntityManager instance. Please note that the EntityManager must be set new on each request. CibetContext will not keep it in a session because EntityManager implementations are often not serializable.

In JDBC applications an instance of com.logitags.cibet.sensor.jdbc.JdbcBridgeEntityManager must be set into the request scope.

JdbcBridgeEntityManager is an EntityManager that translates between the JPA API and the JDBC API. This class provides two constructors:

public JdbcBridgeEntityManager(Connection conn, IdGenerator idGenerator)

Parameters are the JDBC connection and an implementation of the com.logitags.cibet.sensor.jdbc.IdGenerator interface that is used to generate unique ids as primary keys. Please be aware that the connection is neither committed nor closed within Cibet. The main application is responsible for committing / rolling back and closing the connection!

public JdbcBridgeEntityManager(DataSource ds, IdGenerator idGenerator)

Parameters are an SQL DataSource and an implementation of the com.logitags.cibet.sensor.jdbc.IdGenerator interface that is used to generate unique ids as primary keys. Cibet will use JDBC connections created from this DataSource. Please be aware that the connection is closed within Cibet but not committed. The main application is responsible for committing the connection! Normally, the DataSource is a container managed resource and the transaction is committed or rolled back by the container.

Set the JdbcBridgeEntityManager into the Cibet context before accessing the database. But don't forget to commit and close the connection if you use the constructor that takes java.sql.Connection as parameter.

  EntityManager em = new JdbcBridgeEntityManager(connection);
  
  // set the EntityManager into the Cibet context
  CibetContext.setEntityManager(em, idgen);

  ...
  connection.commit();
  connection.close();

With JdbcBridgeEntityManager all sensors and actuators can be used the same way as with a JPA EntityManager. Even the JPA sensor can be used, though it makes not much sense to use JdbcBridgeEntityManager and JPA in the same application. In this case the JdbcBridgeEntityManager must be set in the Cibet context, and in the application the CibetEntityManager must be used.

If JdbcBridgeEntityManager should be used with the JPA sensor, some additional implementation effort has to be done. This is described in the following chapter.

Using JPA sensor in a JDBC application

If you don't use a database access sensor like JPA together with the JdbcBridgeEntityManager, this chapter can be skipped. A JDBC application will work with JdbcBridgeEntityManager right out of the box.

If you want to use JPA sensor in a JDBC application (whatever sense that makes), you have to give Cibet some extra information. JdbcBridgeEntityManager translates the JPA API into calls to the JDBC API. Cibet does not know however, how the entities of your application are persisted; it does not know primary keys, table and column names. Therefore this functionality must be implemented by you. Cibet provides the interface com.logitags.cibet.sensor.jdbc.EntityDefinition and an abstract implementation com.logitags.cibet.sensor.jdbc.AbstractEntityDefinition, from which you can inherit an own implementation per entity that you want to persist. The interface defines the following methods:

  void persist(Connection jdbcConnection, Object obj) throws SQLException;

This method will be called when the EntityManager.persist(Object) is called. Implement this method with an INSERT SQL statement. An implementation of this method must not commit or close the connection.

  <T> T merge(Connection jdbcConnection, T obj) throws SQLException;

This method will be called when the EntityManager.merge(Object) method is called. Implement this method with an UPDATE SQL statement and return the updated object. An implementation of this method must not commit or close the connection.

  void remove(Connection jdbcConnection, Object obj) throws SQLException;

This method will be called when the EntityManager.remove(Object) method is called. Implement this method with a DELETE SQL statement. An implementation of this method must not commit or close the connection.

  <T> T find(Connection jdbcConnection, Class<T> clazz, Object primaryKey);

This method will be called when the EntityManager.find(Class, Object) method is called. Implement this method with a SELECT SQL statement. An implementation of this method must not commit or close the connection.

  List<Object> createFromResultSet(ResultSet rs) throws SQLException;

This method will be called when one of the EntityManager.createQuery(String), createNamedQuery(String) or createNativeQuery(String) methods is called and a getResultList() or getSingleResult() is executed on the returned Query object. The ResultSet contains the result of the query. From each record in the ResultSet an object must be created and returned in a list.

  Map<String, String> getQueries();

returns a map of the names of all defined queries of the entity, mapped to the native SQL statement. The name could be

  1. the name of a JPA named query
  2. the native SQL query itself (if it is a native query)
  3. a JPA query (if it is an on-the-fly query)

Example: Entity Contract is persisted in table app_ctrct and defines a JPA named query:

  @NamedQuery(name = "SEL_CTRCT", query = "SELECT c FROM Contract c WHERE c.contractNumber = :number")

In order to use this query with EntityManager.getNamedQuery(), the getQueries() method in ContractEntityDefinition implementation of EntityDefinition must return a map which contains the native SQL statement for this named query:

  String sql = "SELECT id, ctrctNumber, create_date, reference FROM app_ctrct 
     WHERE ctrctNumber = ?";
  map.put("SEL_CTRCT", sql);

If the same query is used also as a native query with EntityManager.getNativeQuery(), the following entry must be added into the returned map:

  map.put(sql, sql);

If the same query is used as a JPA query with the EntityManager.getQuery() method, add

  map.put("SELECT c FROM Contract c WHERE c.contractNumber = :number", sql);

You have to implement one EntityDefinition for each entity that you want to persist with the EntityManager API. These implementations must be registered in JdbcBridgeEntityManager. You can do this with a static registering method in JdbcBridgeEntityManager:

  JdbcBridgeEntityManager.registerEntityDefinition(
      MyJdbcEntity.class, 
      new MyJdbcEntityDefinition());

Integrating a sensor

EJB3 sensor

Requirements:

The javaee library must be in the classpath.

Configuration:

EJB service methods can be controlled by Cibet. With ARCHIVE scheme this means that an archive entry with class, method name and parameters is written. If the invoked method's return is not void the result value is stored with the archive entry. With dual control schemes this means that the method will not be invoked but suspended until a second user released the INVOKE action.

There is only one thing to do in order to let Cibet control EJB method invocations: Declare the CibetInterceptor interceptor in your EJB:

  @Stateless
  @Remote
  @Interceptors(com.logitags.cibet.sensor.ejb3.CibetInterceptor.class)
  public class MyEJBImpl implements MyEJB {
  ...

JPA sensor

Requirements:

The JPA API library must be in the classpath. It is included in the javaee library. If JdbcBridgeEntityManager is used in CibetContext, some additional implementation effort has to be done (though it makes no real sense to use mixed JDBC and JPA in one application). This extra work is explained in chapter Using JPA sensor in a JDBC application.

Configuration:

No code change is necessary. Just use the Cibet persistence provider in the persistence unit that shall be controlled. The original provider of your persistence framework must be set in property com.logitags.cibet.persistence.provider:

   <persistence-unit name="myAPP-PU" transaction-type="JTA">
      <provider>com.logitags.cibet.sensor.jpa.Provider</provider>
      ...
      <properties>
         <property name="com.logitags.cibet.persistence.provider"
          value="org.eclipse.persistence.jpa.PersistenceProvider"/>
      </properties>
   </persistence-unit>
  Remark: The alternative way of applying the JPA sensor described in earlier Cibet versions is still 
  working. If you use an instance of CibetEntityManager with
  
      EntityManager cibetEM = new CibetEntityManager();
  
  one of 'Cibet' or 'CibetLocal' persistence units will be used for persistence operations, 
  depending on which one has been configured in the persistence.xml (see chapter Automatic Setting 
  of the EntityManager).

There are some aspects to consider when working with Cibet persistence provider:

  1. do not rely on entities being in the managed state when using Cibet persistence provider to find entities. In contrast to standard EntityManager implementations entities are generally detached from the persistence context. This means that the following code will not update the Account object in the database assuming that the transaction ends in this example when the method returns:
    public void updateAccount(String accountNumber, int amount) {
       Account account = cibetEM.find(Account.class, accountNumber);
       Account.setBalance(account.geBalance() + amount);
    }
    

    Instead the entity must be merged again into the persistence context to make the update persistent:

    public void updateAccount(String accountNumber, int amount) {
       Account account = cibetEM.find(Account.class, accountNumber);
       account.setBalance(account.geBalance() + amount);
       cibetEM.merge(account);
    }
    
  2. The fact that entities are always in the detached state implies that lazy loading will not work. Therefore, the FetchType.Lazy annotation is ignored by the Cibet EntityManager and all associated entities of an entity will be loaded in Eager mode. Performance issues must be taken into account if entities contain large collections. It is not a good idea to control entities with the Cibet EntityManager that have many associations like for example an enterprise entity with a collection of its employees.
  3. Queries are not controlled by Cibet. If you define a query for a batch update the entities are updated in the database regardless of the controller configuration:
      // all accounts are updated immediately, no archive is written, no 4-eyes or 6-eyes control
      txn.begin(); 
      Query query = cibetEM.createQuery("UPDATE Account SET amount = amount + 10");
      query.executeUpdate();
      txn.commit();
    

    The persistence methods controlled by Cibet are find(...), persist(object), merge(object) and remove(object) only.

POJO sensor

Requirements:

The POJO sensor is based on aspectj. Therefore the aspectj runtime library must be in the classpath. It can be included with the following Maven dependency:

  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.9</version>
  </dependency>

Cibet uses aspectj's runtime weaving of application classes. Therefore the runtime weaver must be set as a java agent when your application is started. This can be achieved by adding the following parameter to the java start command:

  java -javaagent:<path_to>/aspectjweaver-1.6.9.jar

When dual control mechanisms or the REDO functionality are applied and the invoked class is a Spring bean you need the Spring libraries on the classpath. In your Spring config file add the following lines:

  <context:component-scan base-package="com.logitags.cibet"/>
  <aop:config proxy-target-class="true"/>

Configuration:

There is only one thing to do in order to let Cibet control POJO method invocations: Declare an interceptor in your POJO with the com.logitags.cibet.sensor.pojo.CibetIntercept annotation:

  @CibetIntercept
  public class MyPojoClass {
  ...

The @CibetIntercept annotation can be set on class or on method level. If set on class level, all public methods of that class are under Cibet control; if set on method level only this method is under Cibet control.

When dual control actuators or the REDO functionality out of an archived method invocation are applied the POJO instantiation mechanism must be declared. Cibet provides three factory classes:

  @CibetIntercept(factoryClass = PojoFactory.class)
  public class MyPojoClass {

This is the default. The PojoFactory must be used if the MyPojoClass object is instantiated with the default constructor or as a Singleton. If the constructor or Singleton method takes a String parameter it can be also set in the Intercept annotation:

  @CibetIntercept(factoryClass = PojoFactory.class, param="123")
  public class MyPojoClass {

If the MyPojoClass object is instantiated with the help of a factory class, the FactoryFactory class must be used and the actual factory class must be set in the param value:

  @CibetIntercept(factoryClass = FactoryFactory.class, param="my.app.MyFactory")
  public class MyPojoClass {

The FactoryFactory class will search in MyFactory for a method that will return an instance of the MyPojoClass class. You can also set the factory method explicitly:

  @CibetIntercept(factoryClass = FactoryFactory.class, 
             param="my.app.MyFactory.createMyPojoClass()")
  public class MyPojoClass {

If the object on which to invoke the method is a Spring bean the SpringBeanFactory must be set:

  @CibetIntercept(factoryClass = SpringBeanFactory.class)
  public class MyPojoClass {

If the object is declared with a bean id in the Spring config, this id can be set in the param value:

  @CibetIntercept(factoryClass = SpringBeanFactory.class, param="myPojo")
  public class MyPojoClass {

SERVLET sensor

Requirements:

The servlet sensor needs the apache httpcomponents library. It can be included with the following Maven dependency:

<dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.1</version>
</dependency>

This sensor needs also the servlet API 2.5

Configuration:

The servlet sensor is in fact a servlet filter. In order to install the filter add to your web.xml something like this:

  <filter>
    <filter-name>cibetFilter</filter-name>
    <filter-class>com.logitags.cibet.sensor.servlet.CibetFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>cibetFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

Please note that you don't have to configure CibetContextFilter if you use SERVLET sensor with the CibetFilter (see chapters The Cibet Context and Automatic Setting of the EntityManager). CibetFilter inherits from CibetContextFilter and contains already its functionality.

JDBC sensor

Requirements:

A JDBC driver of your preferred database must be in the classpath. JSqlParser must be in the classpath. Download it from the JSqlParser website or add it with the Maven dependency

  <dependency>
          <groupId>net.sf.jsqlparser</groupId>
          <artifactId>jsqlparser</artifactId>
          <version>0.7.0</version>
  </dependency>

Configuration:

JDBC calls can be intercepted and controlled by Cibet. The sensor is implemented as a JDBC driver with class name com.logitags.cibet.sensor.jdbc.driver.CibetDriver. The connection URL has the following format:

jdbc:cibet:<native sql driver class>:<native URL>

where <native sql driver class> is the class name of the original JDBC driver and <native URL> is the original connection URL without leading jdbc:. For an Oracle database the URL would be for example like this:

jdbc:cibet:oracle.jdbc.OracleDriver:oracle:thin:@192.168.1.64:1521:xe

If your application works with plain JDBC and you want to control JDBC calls by Cibet you normally would set JdbcBridgeEntityManager as EntityManager in Cibet context (see chapter JDBC Applications).

Not all JDBC queries can be controlled. Generally, only INSERT, UPDATE and DELETE statements are controlled. Batch updates and deletes are not controlled. Furthermore, only updates and deletes where the primary key column is in the WHERE clause can be controlled. Tables with multi-column primary keys are not supported.

RESTORE event is not supported for JDBC sensor.

Setpoint Configuration

Configuration of Cibet consists of parameterizing actuators if necessary and defining setpoints. This can be done either in the configuration file cibet-config.xml which must be in the classpath or in code. If for example a setpoint should be defined in code, do something like

  Setpoint setpoint1 = new Setpoint("sp1");
  ...
  Configuration.instance().registerSetpoint(setpoint1);

Definition of setpoints

The general structure of a setpoint is:

  <setpoint>
    <controls>
    ...
    </controls>
    <actuator />
    <actuator />
    ...
  </setpoint>

The controls elements represent the IF part where the conditions are defined for which the following declared actuators should be applied.

Unique identifier

Each setpoint has a unique identifier by which it is identified during runtime and in the log files.

  XML:
  
    <setpoint id="sp1">
    ...

  Code:
    Setpoint setpoint1 = new Setpoint("sp1");

Setpoint inheritance

Setpoints can inherit from other setpoints. This allows a significant reduction of configuration code in complex control scenarios. Only the control definitions which is the IF condition are inherited. The actuators or the THEN part is not inherited.

XML:
  
  <setpoint id="Basic">
    ...
  </setpoint>

  <setpoint id="sp1" extends="Basic">
    ...
  </setpoint>

Code:
  Setpoint basicSP = new Setpoint("Basic");
  Setpoint setpoint1 = new Setpoint("sp1", basicSP);

If e.g. in setpoint Basic a target control element and some other controls are defined and in setpoint sp1 the same target should be controlled but with other conditions, the target element must not be repeated in sp1. If the parent setpoint contains a control that you don't want apply in a child setpoint just set an empty control in the child setpoint.

Please see the scenarios for controlling a domain object and controlling a service call for complete examples for definition of setpoints.

Input tolerance

The Cibet principle of 'Keep it simple' is applied in the Setpoint API. The methods for adding a control accept input in various styles. If you want for example set a method control for methods doThis() and makeThat() you can achieve this in the following ways:

  sp.setMethod("doThis()", "makeThat()"); 
  sp.setMethod("doThis(), makeThat()"); 

The result is the same. So if you are unsure about how something works in Cibet just try it out! If you don't find it out by intuition, please tell us what we can approve to make it simpler.

In the following all existing Control implementations and setpoint definitions are described. The Controller evaluates them in their declared order.

Tenant Control

Evaluates the tenant in the setpoint against the tenant which is set in the context. Evaluates also tenant hierarchies. In tenant hierarchies the tenant ids are separated by a |. If no setpoints for the actual tenant exists the Control checks if setpoints for one of the parents up to the default tenant exist. This is an optional control and can be omitted if the application is not tenant-specific.

XML:

  <!-- setpoint matches for the German client of IBank -->
  <tenant>IBank|Germany</tenant>

  <!-- setpoint matches for the German and French client of IBank -->
  <tenant>IBank|Germany, IBank|France</tenant>

  
Code:

  setpoint.setTenant("IBank|Germany");  

  setpoint.setTenant("IBank|Germany", "IBank|France");

Cibet supports multi-tenant systems. A multi-tenant application hosts several clients or tenants where each client has its own view on the system without interference between the clients. Each tenant can view only his own data and accesses the application without influence on the other tenants. Of cause this could be achieved by deploying an own instance of the application and database independently for each tenant but often this means much higher effort for maintenance and support. Therefore it is easier, especially if there are many clients to host, to install them all on one instance of the application and to use one database for all. One possibility to keep data visibility restricted to the tenant is to assign to each domain object (and table record) the tenant to whom it belongs.

In Cibet multi-tenancy is achieved through introduction of a tenant attribute to all internal Cibet domain objects. Cibet supports also tenant hierarchies. What does that mean?

Let's say you run an application for a big international bank with name IntBank. The bank has national branches and in some countries subsidiaries. The bank's business rules allow headquarters to see data of all branches and subsidiaries. The branches may see only their national data including the data of the national subsidiaries. The subsidiaries at last can see only their local data. You may have the following tenant hierarchy for IntBank:

Example for a tenant hierarchy

The Cibet tenant attribute is a concatenation of the tenant ids separated by a minus. In the example the tenant attributes may then be:

Head Head|France, Head|US, Head|Germany, Head|GB Head|US|California, Head|US|Midwest, Head|US|New_York

If an application is not tenant-specific Cibet uses '__DEFAULT' as default for the tenant attribute.

Event Control

Evaluates the event(s) in the setpoint against the current event. A Setpoint can be defined for multiple events.

XML:

  <!-- matches for UPDATE, INSERT and DELETE persistence operations -->
  <event>UPDATE, INSERT, DELETE</event>
  
  <!—- matches all entity persistence operations -->
  <event>PERSIST</event>
  
  
Code:

  setpoint.setEvent(ControlEvent.UPDATE.name(), 
                    ControlEvent.INSERT.name(), 
                    ControlEvent.DELETE.name());
                    
  setpoint.setEvent(ControlEvent.PERSIST.name());                    

Target Control

Evaluates the target for this setpoint. The target could be a class name of a domain object when its persistence should be controlled, the class name of a service class if a service call should be controlled or a URL if a http request should be controlled. In the context of JDBC sensor the target is the table name which is controlled. The target element can contain more than one target name. The target element accepts patterns with *. Here are some examples:

XML:

      <!-- a concrete full qualified class name -->
      <target>com.app.accounting.Account</target>
      
      <!-- all classes of a package -->
      <target>com.app.client.*, com.app.gui.*</target>

      <!-- all classes of the service package that start with Txn -->
      <target>com.app.service.Txn*</target>

      <!-- a concrete URL -->
      <target>http://www.logitags.de/cibet/documentation.html</target>

      <!-- all resources in cibet/ -->
      <target>http://www.logitags.de/cibet/*</target>
      
  
Code:

  setpoint.setTarget("com.app.accounting.Account");
  setpoint.setTarget("com.app.client.*", "com.app.gui.*");
  setpoint.setTarget("com.app.service.Txn*");
  setpoint.setTarget("http://www.logitags.de/cibet/documentation.html");
  setpoint.setTarget("http://www.logitags.de/cibet/*");

State Change Control

Evaluates the state of object attributes or the state of columns of an updated table (JDBC). This evaluation is executed only for UPDATE control events, for other events this control is ignored. The control compares the actual state of the object under control against its persistent state. The stateChange element can contain more than one property name. The control has an attribute 'exclude', which defines how the comparison should be applied:

  • exclude=false: This is the default. The setpoint matches if at least one of the given properties is modified.
  • exclude=true: The setpoint matches if at least one property is modified that is not in the given list.

Attributes in associated objects of the controlled object can be declared with the point notation in stateChange elements. If for example an object has an attribute addr of type Address which in turn has a String attribute city the transitive property is written as addr.city

Example classes:

  public class Customer {
    private String name;
    private boolean active;
    private Address addr;
    ...
  }

  public class Address {
    private String city;
    private String email;
    private Contact contact;
    ... 
  }
  
  public class Contact {
    private String telephone;
    private String telephone2;
    private String telephone3;
    ...
  }
  
XML:

  <!-- exclude Customer state changes of active property and
       transitive property telephone3 in Contact object -->
  <target>com.app.Customer</target>
  <stateChange exclude="true">active, addr.contact.telephone3</stateChange>

  <!-- include Address state changes of email property and all 
       properties of associated Contact object --> 
  <target>com.app.Address</target>
  <stateChange>email, contact</stateChange>
  
Code:

  setpoint.setStateChange(true, "active", "addr.contact.telephone3");

  setpoint.setStateChange("email", "contact");

StateChange control will not work if uncommitted modifications exist on the controlled target entity. Because the dirty check is done in a second transaction a LockAcquisitionException could occur.

Method Control

Evaluates the actual executed method against the method configured in the setpoint. The Method Control can be applied for Java method invocations as well as for http requests. In the latter case the http method is evaluated. The method element accepts patterns as method name. A setpoint can be defined for multiple methods. Here are some examples:

XML:

  <!-- a concrete method without parameters -->
  <method>doSomething()</method>
  
      
  <!-- a concrete method with parameters -->
  <method>doSomething(String, com.logitags.cibet.TEntity, int)</method>
        
  <!-- all overloaded methods of the same name -->
  <method>doSomething</method>

  <!-- all methods starting with do or make -->
  <method>do*, make*</method>

  <!-- http POST method -->
  <method>POST</method>

  
Code:

  setpoint.setMethod("doSomething()");
  setpoint.setMethod("doSomething(String, com.logitags.cibet.TEntity,
                      int)");
  setpoint.setMethod("doSomething");
  setpoint.setMethod("do*", "make*");
  setpoint.setMethod("POST");

Invoker Control

For events detected by JPA, JDBC, EJB3 and POJO sensors this control evaluates the direct and indirect invoker methods in the current thread against the invokers configured in the setpoint. For INVOKE events from a SERVLET sensor, the invoker is looked up in the server chain from where the request originated. This is the remote host IP or DNS name. If the request has been passed through one or more proxy servers the list of invokers is constructed from the HTTP_X_FORWARDED_FOR header if present. An invoker chain looks something like:

client, proxy1, proxy2, proxy3

The Invoker Control checks in this case if the request has been invoked or passed through one of the given IPs.

The control has an attribute 'exclude', which defines how the comparison should be applied:

  • exclude=false: This is the default. The setpoint matches if the actual event has been invoked directly or indirectly by one of the given methods or an http request has passed through one of the given IPs.
  • exclude=true: The setpoint matches if the actual event has not been invoked directly or indirectly by one of the given methods or a http request has not passed through one of the given IPs.

This control allows for example, that an actuator is executed for a business case if it is called from the GUI, but is not when called from a batch process.

The invoker element accepts patterns and multiple tokens. Here are some examples:

XML:

  <!-- no control if invoked by any method of class BatchProcessor -->
  <invoker exclude="true">com.app.batch.BatchProcessor</invoker>
        
  <!-- control only if invoked by any method of any class in the 
       com.root package or from any method of any class in the
       webservice package -->
  <invoker>
    com.root.*,
    com.app.webservice.*
  </invoker>

  <!-- control only if invoked by receive method in class
       AccountService. The receive method can be overloaded,
       method signature is not evaluated -->
  <invoker>com.app.web.AccountService.receive()</invoker>

  <!-- control only if http request comes from an IP or
       has passed through a proxy with this IP -->
  <invoker>192.168.1.45</invoker>

  <!-- control only if http request comes from an IP range or
       has passed through a proxy within this range -->
  <invoker>192.168.*</invoker>
  
Code:

  setpoint.setInvoker(true, "com.app.batch.BatchProcessor");
  setpoint.setInvoker("com.root.*, com.app.webservice.*");
  setpoint.setInvoker("com.app.web.AccountService.receive()");   
  setpoint.setInvoker("192.168.1.45");
  setpoint.setInvoker("192.168.*");

Condition Control

Evaluates JavaScript conditions. The condition element can contain any JavaScripts statements that return a boolean value at the end. The setpoint matches if the condition evaluates to true. The condition could also be externalized into a file. This is more convenient if several conditions must be concatenated or you want to use functions. In order to read the condition from a file start the condition with 'file:' and give then the location of the file. The location can be set absolute or as a classpath entry.

The following variables can be used in a condition definition:

  • $REQUESTSCOPE: the context request scope
  • $SESSIONSCOPE: the context session scope
  • $APPLICATIONSCOPE: the context application scope
  • $EVENT: the actual control event

Additional variables are available depending on the resource that is being controlled.

EJB and POJO method invocations:

  • $TARGET: the actual object which is invoked
  • $TARGETTYPE: the class name of the object which is invoked
  • $PARAM0, $PARAM1 ... $PARAMn: method parameter objects in declared order

JPA:

  • $TARGET: the actual persistent object
  • $TARGETTYPE: the class name of the persistent object
  • $ + simple class name: the actual persistent object (same as TARGET)
  • $PRIMARYKEY: the unique id value of the persistent object

JDBC:

  • $TARGET: the SQL statement
  • $TARGETTYPE: the table name in the SQL statement
  • $PRIMARYKEY: the primary key value in the WHERE clause of the SQL statement
  • $COLUMNS: the parameters of an SQL statement as a list of SqlParameter

HTTP requests:

  • $TARGETTYPE: the requested URL
  • $HTTPATTRIBUTES: map of http attributes
  • $HTTPHEADERS: map of http headers
  • $HTTPPARAMETERS: map of http querystring or form parameters
XML:

  <!-- This is a special setpoint for Werner -->
  <condition>$SESSIONSCOPE.getUser() == 'Werner'</condition>
          
  <!-- this setpoint matches only for Test environment (the property 
       must have been set before) -->
  <condition>$APPLICATIONSCOPE.getProperty('environment').equals('Test')</condition>
          
  <!-- this setpoint matches if an object is in the Cibet context under key
       currentSession and the getCounter() method 
       of this object returns 7 -->
  <condition>
    $SESSIONSCOPE.getProperty("currentSession") != null 
    &amp;&amp; 
    $SESSIONSCOPE.getProperty("currentSession").getCounter() == 7 
  </condition>
          
  <!-- setpoint matches if the getCounter() method of the actual object
       under control of type AccountManager yields > 10 -->
  <condition>$AccountManager.getCounter() &gt; 10</condition>
            
  <!-- setpoint matches if first parameter of the actual 
       method under control has value 2 -->            
  <condition>$PARAM0 == 2</condition>
            
  <!-- setpoint matches if second parameter of the actual method under
       control which is of type Date is in the past -->
  <condition>
    importPackage(java.util);
    var DATE = new Date();
    println('todays date: ' + DATE);
    println('Date parameter: ' + $PARAM1);
    $PARAM1.before(DATE);
  </condition>
            
  <!-- setpoint matches if third parameter is null -->            
  <condition>$PARAM2 == null</condition>

  <!-- setpoint matches if a String http attribute equals to Hase, a
       boolean http header value is true and an int http 
       parameter is 67 -->
  <condition> 
    $HTTPATTRIBUTES.get('p1')=='Hase' &amp;&amp;
    $HTTPHEADERS.get('head1') == true  &amp;&amp;
    $HTTPPARAMETERS.get('param1') == 67
  </condition>

  <!-- setpoint matches if the condition in file testscript2.js which is
       in the classpath is fulfilled -->
  <condition>file:testscript2.js</condition>

  <!-- setpoint matches if the condition in file testscript1.js which is
       located under the given URL is fulfilled -->
  <condition>
    file:C:\projects\cibet/src/test/resources/testscript1.js
  </condition>
              
  
Code:

  setpoint.setCondition("$SESSIONSCOPE.getUser() == 'Werner'");
  setpoint.setCondition("$APPLICATIONSCOPE.getProperty('environment').equals('Test')");
  setpoint.setCondition("$SESSIONSCOPE.getProperty(\"currentSession\") != null
     && $SESSIONSCOPE.getProperty(\"currentSession\").getCounter() == 7");    
  setpoint.setCondition("$AccountManager.getCounter() > 10");
  setpoint.setCondition("$PARAM0 == 2");
  setpoint.setCondition("importPackage(java.util); var DATE = new Date(); 
      println('todays date: ' + DATE); println('Date parameter: ' 
      + $PARAM1); $PARAM1.before(DATE);");
  setpoint.setCondition("$PARAM2 == null");
  setpoint.setCondition("$HTTPATTRIBUTES.get('p1')=='Hase' &&
      $HTTPHEADERS.get('head1') == true  &&
      $HTTPPARAMETERS.get('param1') == 67");
  setpoint.setCondition("file:testscript2.js");
  setpoint.setCondition(
      "file:C:\projects\cibet/src/test/resources/testscript1.js");

Implementing Custom Controls

Own custom Control implementations can be registered in the configuration file or with the Configuration API. In cibet-config.xml add a control element and set the class tag. If the Control implementation uses own properties, they can be defined with property tags. The implementation must have a setter method with a name following the Java Beans convention and taking a String argument as parameter:

XML:

  <control name="MYCONTROL">     
    <class>com.company.MyControl</class>
    <properties>
      <myAttribute>someValue</myAttribute>
    </properties>
  </control>
  
Code:

  Configuration.instance().registerControl(new MyControl());

Control implementations must implement interface com.logitags.cibet.controller.controls.Control provide a default constructor and must provide a unique name which is returned by method getName(). It is recommended to inherit from AbstractControl and overwrite the methods the custom control is interested in. This ensures that the custom control works also in case of future interface enhancements.

In a setpoint the custom control is applied with the <customControl> tag.

For example you want a business case be executed only when some system object is in a special state. A custom Control may check custom context parameters. Own context properties can be set into application-, session- or request scope which provide methods to set, get and remove a property.

Register a custom SystemStateControl and set the state into the context before executing the business case:

  Configuration.instance().registerControl(new SystemStateControl());
  ...
  Context.requestScope().setProperty("SYSTEM_STATE", systemObject.getState());

Implement the evaluate(Object controlValue, EventMetadata metadata) method of SystemStateControl something like:

  If (Context.requestScope().getProperty("SYSTEM_STATE") == OK) {
    return true;
  } else {
    return false;
  }

The method hasControlValue() should return always true in this case as the evaluation is not dependent on the value of the <customControl> tag. Apply the control in a setpoint like this:

  <setpoint id="K1">
    <controls>
      <customControl name="MYSystemStateControl" />
    </controls>
    <actuator name="INFOLOG"/>
  </setpoint>

If the value of the <customControl> tag is a comma separated list of states for which the control should evaluate true SystemStateControl must be implemented like this:

  • The resolve() method parses the comma separated list of states into a List object. The implementation of AbstractControl does right this.
  • The parameter of the hasControlValue() method is the List object. It must return true if the list is not null and contains at least one element. The implementation of AbstractControl does right this.
  • The first parameter of the evaluate() method is the List object. Implement it something like this:
      If (((List)controlValue).contains(Context.requestScope().getProperty("SYSTEM_STATE"))) {
        return true;
      } else {
        return false;
      }
    

    In the setpoint the control will then be applied like this:

      <setpoint id="K1">
        <controls>
          <customControl name="MYSystemStateControl">OK, PENDING</customControl>
        </controls>
        <actuator name="INFOLOG"/>
      </setpoint>
    

Controls in the context of the sensor

The meaning of the described controls can differ according to the applied sensor. The following table lists how controls are applied in the context of the different sensors and what values are allowed.

Actuator Configuration

In the setpoints only the names of actuators are declared:

XML:

  <actuator name="SPRING_SECURITY" />

Code:

  Actuator act = Configuration.instance().getActuator(
      SpringSecurityActuator.DEFAULTNAME);
  sp.addActuator(act);

Each built-in actuator has a default name.

Some actuators have properties which can be adjusted. This is done in the configuration file in actuator elements or in code by setting the properties directly. The SPRING_SECURITY actuator for example needs rules for authorization. If there is only one rule which should be applied in the whole application it can be defined as follows:

XML:

  <cibet>

    <!-- set the preAuthorize property of the built-in default 
         SpringSecurityActuator instance -->
    <actuator name="SPRING_SECURITY">
      <properties>
        <preAuthorize>hasRole( 'WALTER')</preAuthorize>
      </properties>
    </actuator>

    <setpoint>
    ...
  </cibet>

Code:

  SpringSecurityActuator act = (SpringSecurityActuator)
       Configuration.instance().getActuator(
       SpringSecurityActuator.DEFAULTNAME);
  act.setPreAuthorize("hasRole('WALTER')");

If there are more rules to apply what normally is the case you have to instantiate another SpringSecurityActuator:

XML:

    <actuator name="PermitAuthorizer">
      <class>
        com.logitags.cibet.actuator.springsecurity.SpringSecurityActuator
      </class>
      <properties>
        <permitAll></permitAll>
      </properties>
    </actuator>

Code:

  SpringSecurityActuator permitter = new
       SpringSecurityActuator("PermitAuthorizer");
  permitter.setPermitAll(true);
  Configuration.instance().registerActuator(permitter);

Working with actuators

INFOLOG actuator

Description: Writes a message in INFO level to the standard log channel before and after the control event. For tracking and tracing.

Default name: INFOLOG

Class: com.logitags.cibet.actuator.info.InfoLogActuator

Requirements: None

TRACKER actuator

Description: Writes all results of intercepted business cases into the database into table cib_eventresult. See chapter Post- Checking Control Results.

Default name: TRACKER

Class: com.logitags.cibet.actuator.info.TrackerActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySQL and Oracle database management systems.

FOUR_EYES actuator

Description: With the FOUR_EYES actuator a dual control mechanism is applied. If a user executes a persistence action or method invocation it will not be done directly in the productive environment. Instead, the action is stored and postponed. Only if a second user accepts and releases the action, it is performed in the productive system. If the second user rejects the action it will not be performed.

Default name: FOUR_EYES

Class: com.logitags.cibet.actuator.dc.FourEyesActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySQL and Oracle database management systems.

Properties:

Property Data type Default Description
throwPostponedException boolean false throw an Exception when this actuator is applied
jndiName String null jndi name of EJB under control
sendAssignNotification boolean true Send a notification to assigned user
sendReleaseNotification boolean true Send a release notification to the initiating user
sendRejectNotification boolean true Send a reject notification to the initiating user

When a dual control actuator is set in a setpoint the persistence action or service call will be suspended and postponed. If a business case is suspended can be checked by querying the EventResult object returned by the method

  EventResult result = Context.requestScope().getExecutedEventResult();

The executionStatus property of EventResult will be POSTPONED, otherwise EXECUTED (see also chapter Post- Checking Control Results).

It is also possible to let Cibet throw an Exception when the business case is postponed. For this the throwPostponedException attribute must be set to true in the actuator, either in the Cibet config file or in code. The property can be set generally for the actuator or for a single setpoint (see Actuator configuration):

XML:

   <actuator name="FOUR_EYES">
      <properties>
         <throwPostponedException>true</throwPostponedException>
      </properties>
   </actuator>

Code:

  FourEyesActuator ac = (FourEyesActuator)
       Configuration.instance().getActuator("FOUR_EYES");
  ac.setThrowPostponedException(true);

If a business case is set under dual control a PostponedException will then be thrown:

  try {
          entityManager.insert(transaction);
  } catch (PostponedException e) {
    ...
  }

For http requests which are under dual control the behavior is different. In http protocol it is not possible to transmit an exception. The request result is communicated by response codes. Therefore, the value of the throwPostponedException property has normally no effect in intercepted http requests. Instead, a response code 202 (ACCEPTED) is returned.

Release or Rejection of a FOUR_EYES intercepted business case is a post control functionality and is described in chapter Releasing Dual Control Events.

The jndiName property of the FOUR_EYES actuator must not be set normally. When a dual control actuator is applied on an EJB3 method, the release method must look up the EJB from JNDI context. JNDI names are not standardized in Java EE specification <3.1. When an EJB service is invoked in a release or redo action Cibet tries to find out the JNDI name of the EJB applying various strategies. If the JNDI name cannot be figured out and an exception is thrown, it must be configured explicitly in property jndiName.

XML:

  <actuator name="FOUR_EYES">
    <properties>
      <jndiName>
        com.logitags.cibet.javaee.CibetTestEJBImpl/remote
      </jndiName>
    </properties>
  </actuator>

Code:

  FourEyesActuator ac = (FourEyesActuator)
       Configuration.instance().getActuator("FOUR_EYES");
  ac.setJndiName("com.logitags.cibet.javaee.CibetTestEJBImpl/remote");

SIX_EYES actuator

Description: The SIX_EYES actuator implements a dual control concept like the FOUR_EYES actuator. Additionally a third user must release the controlled action in order to get it productive.

Default name: SIX_EYES

Class: com.logitags.cibet.actuator.dc.SixEyesActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySQL and Oracle database management systems.

Properties:

Property Data type Default Description
throwPostponedException boolean false throw an Exception when this actuator is applied
jndiName String null jndi name of EJB under control
sendAssignNotification boolean true Send a notification to assigned user
sendReleaseNotification boolean true Send a release notification to the initiating user
sendRejectNotification boolean true Send a reject notification to the initiating user

SIX_EYES actuator inherits from FOUR_EYES actuator and has the same properties and behavior.

TWO_MAN_RULE actuator

Description: A dual control actuator that imposes an additional requirement. The controlled event executed by a user must not only be released by a second user, but the initiating and releasing user must be present at the same time. This means that the event must be authorized by two users simultaneously.

Default name: TWO_MAN_RULE

Class: com.logitags.cibet.actuator.dc.TwoManRuleActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySQL and Oracle database management systems.

Properties:

Property Data type Default Description
throwPostponedException boolean false throw an Exception when this actuator is applied and no second user is authenticated
jndiName String null jndi name of EJB under control
removeSecondUserAfterRelease boolean false The second authenticated user is removed from context after release if set to true
sendAssignNotification boolean true Send a notification to assigned user
sendReleaseNotification boolean true Send a release notification to the initiating user
sendRejectNotification boolean true Send a reject notification to the initiating user

TWO_MAN_RULE actuator inherits from FOUR_EYES actuator and has the same properties and behavior with some exceptions:

As two users must be authenticated at the same time, the second authenticated user must be set into the session scope context with method setSecondUser(). If at the time of executing the controlled event a second user is set into the context, the event is released and executed directly. If no second user is set into the context the event is postponed and the presence of a second user is checked during release. The release of a TWO_MAN_RULE controlled event requires that the same user who initiated the event must be logged in and be set into session scope with setUser() method and a second user must be set into session scope with setSecondUser().

With the property removeSecondUserAfterRelease it can be controlled whether the second user should be removed automatically from the context after release. Set this property to true to have a higher security. If several events should be released in one use case, set this property to false so that the second user must not authenticate himself after each release. After the last release the second user must be removed from context manually.

Authentication of the second user is out of scope of Cibet. It depends on the security and access system that is applied how this is achieved. Most security frameworks do not allow out of the box authentication of a second user. For the Spring Security and Apache Shiro frameworks Cibet provides functionality to log on and off a second user while the first user is still authenticated in the current session/thread:

  // For Spring Security use Spring bean SpringSecurityService:
  try {
     Authentication request = new UsernamePasswordAuthenticationToken(
        name, password);
     SpringSecurityService man = applicationContext.getBean(SpringSecurityService.class);
     man.logonSecondUser(request);
  } catch(AuthenticationException e) {
     System.out.println("Authentication failed: " + e.getMessage());
  }


  // For Apache Shiro use ShiroService:
  try {
     AuthenticationToken token = new UsernamePasswordToken(name, password);
     ShiroService man = new DefaultShiroService();
     man.logonSecondUser(token);
  } catch (AuthenticationException e) {
     System.out.println("Authentication failed: " + e.getMessage());
  }

After the two-man rule use case has been executed the second user should be logged off. If the property removeSecondUserAfterRelease of TwoManRuleActuator has been set to true this is done automatically after release. If not it can be done with SpringSecurityService / ShiroService:

  man.logoffSecondUser();

When instead of the logged in user the second user should be authorized with SPRING_SECURITY actuator the property secondPrincipal of SPRING_SECURITY actuator must be set to true. Likewise, if the second user should be authorized with SHIRO actuator the property secondPrincipal of SHIRO actuator must be set to true. In this case the authorization rules are not applied on the user who is logged into the current session but on the user who has logged in with the above method logonSecondUser().

The executionStatus of the EventResult object retrieved by

  EventResult result = Context.requestScope().getExecutedEventResult();

is always POSTPONED, regardless of whether a second user was present and the business case has been released directly or no second user was present and the business case has been postponed. That is because if such a business case is released directly it consists of two control events, the originating one, e.g. UPDATE or INVOKE and the RELEASE event. The first one is POSTPONED while the second event will be EXECUTED. This is reflected by the parent – child relationship of EventResult (see also chapter Post- Checking Control Results):

  // parent
  result.getEvent() --> INVOKE
  result.getExecutionStatus() --> POSTPONED

  // child
  result.getChildResults().get(0).getEvent() --> RELEASE
  result.getChildResults().get(0).getExecutionStatus() --> EXECUTED

PARALLEL_DC actuator

Description: This actuator realizes a dual control scheme where a business case is executed more than once, optionally by different users, without having any impact on the system. Another user compares the results together with the input parameters and decides which of the variants to reject and which to release.

Default name: PARALLEL_DC

Class: com.logitags.cibet.actuator.dc.ParallelDcActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySql and Oracle database management systems.

Properties:

Property Data type Default Description
throwPostponedException boolean false throw an Exception when this actuator is applied
jndiName String null jndi name of EJB under control
sendAssignNotification boolean true Send a notification to assigned user
sendReleaseNotification boolean true Send a release notification to the initiating user
sendRejectNotification boolean true Send a reject notification to the initiating user
executions int 1 Minimum number of executions of the business case before it can be released
timelag int 0 Time lag between subsequent executions in seconds
differentUsers boolean true The users who execute the business case must be different

This actuator requires that the controlled business case is structured into parts that don't have any impact on the system and parts that have an impact. The parts with no impact are executed always when a user executes the business case but the parts which have an impact are postponed and executed only when the business case is released. Naturally, this actuator cannot be applied on all resources. The above requirement could for example not be fulfilled by resources monitored by JPA sensor. The actuator makes on the other hand sense for controlling methods (POJO sensor), EJBs (EJB sensor) or servlets (SERVLET sensor).

The resource parts which have an impact on the system can be differentiated by querying the isPostponed flag in the Request context. Let's make an example: The following method is controlled by PARALLEL_DC:

  public CalculationResult calculateBalance(List<Valuable> vals, List<Transaction> trans) {
    // now do the calculations which have no impact
    CalculationResult result = doDetailedCalculations(vals, trans);

    // now this is the part which has an impact. It is executed 
    // only when the business case has been released. 
    if(!Context.requestScope().isPostponed()) {
      // store the results in the database
      entityManager.persist(result);
      // send the results to another application
      sendToBillingSystem(result);
    }

    return result;
  } 

If a user calls now this method only the calculation is done and returned, but it is not stored nor send to the billing application. The business case is postponed and stored as DcControllable object in the database. If another user calls the method, optionally with another set of parameters, the business case is again postponed and stored as DcControllable object. The two DcControllable objects belong to the same business case instance and are linked by the case ID. DcControllables for the same business case instance are assigned the same case ID. The case ID can be retrieved from the EventResult object (see also chapter Post- Checking Control Results) like this:

  CalculationResult result = calculator.calculateBalance(vals, trans);
  EventResult er = Context.requestScope().getExecutedEventResult();
  Assert(er.getExecutionStatus() == ExecutionStatus.POSTPONED);
  String caseId = er.getCaseId();

If another user wants to execute the same business case and link it to the first one he has to set this case ID into the Request context before calling the method:

  // ... another user 
  Context.requestScope().setCaseId(caseId);
  CalculationResult result = calculator.calculateBalance(vals, trans);

If he doesn't set the case ID into the Request context, a new case ID is created and the postponed DcControllable object opens a new instance of the calculateBalance() business case. The actuator parameter 'differentUsers' defines if executing the business case with the same case ID must be done by different users or if the same user can execute it more than once. The parameter 'timelag' defines if there must be a minimum time lag between subsequent executions.

The method can now be called by other users and when the same case ID is set into the Request context, always another linked DcControllable object is created and stored in the database.

Eventually, someone has to decide which calculation is the good one and release the business case so that the result is stored in the database and the billing system is informed. The business case can only be released when it has been executed (and postponed) at least the minimum number defined by the actuator parameter 'executions'. The releasing user can retrieve all DcControllable objects with the same case ID:

  DcService service = new DefaultDcService();
  List<DcControllable> list = service.loadByCaseId(caseId);
  for (DcControllable co : list) {
    // check and compare the CalculationResult objects
    CalculationResult res = (CalculationResult) co.getResource().getResultObject();
    // check and compare the method parameters
    List<Valuable> vals = co.getResource().getParameters().get(0).getValue;
    List<Transaction> trans = co.getResource().getParameters().get(1).getValue;
  }

  // now decide which one to release
  entityManager.getTransaction().begin();
  CalculationResult finalResult = (CalculationResult) service.release(entityManager, list.get(1), comment);
  entityManager.getTransaction().commit();

  // alternatively, a DcControllable from the list can also be rejected:
  service.reject(entityManager, list.get(0), comment);

When one DcControllable variant is released, all other DcControllable instances with the same case ID are automatically rejected.

The procedure is a little different when PARALLEL_DC is applied on a resource monitored by SERVLET sensor due to the fact that it is a remote call on a URL. In contrast to other dual control actuators, the http response code is not 202 (ACCEPTED) when the request is postponed but 200 (OK) and the response body can be retrieved. The EventResult can be retrieved with

        String evReHeader = response.getFirstHeader(CibetFilter.EVENTRESULT_HEADER).getValue();
        EventResult result = CibetUtil.decodeEventResult(evReHeader);

Setting the case ID into the client's request scope context will not have the desired effect because the server doesn't know something about it. Instead the case ID must be set into the http request as header with name CIBET_CASEID. This could for example be done like this when using apache-httpClient:

  HttpGet g = new HttpGet(url);
  g.setHeader(HttpRequestInvoker.CASEID_HEADER, caseId);
  HttpResponse response = client.execute(g);

ARCHIVE actuator

Description: The controlled event is archived. In case of an updating persistence action on an entity the state of the entity is archived. For a new (insert) persistence action, the state of the newly created entity is archived. For a delete persistence action, the last state of the removed entity is archived. For a method invocation archive, the method parameters and, if applicable the constructor parameters of the object on which the method has been invoked are archived as well as the method result. For an http request, the URL with headers and query parameters are archived.

Default name: ARCHIVE

Class: com.logitags.cibet.actuator.archive.ArchiveActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySql and Oracle database management systems.

As an additional library commons-codec is necessary:

  <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.4</version>
  </dependency>

Properties:

Property Data type Default Description
integrityCheck boolean true create message digest for each archive
secretKey String 2366Au37nBB.0ya? secret key for creating message digest
jndiName String null jndi name of EJB under control

The ArchiveService API provides methods to load archives for a certain domain class, primary key of domain object, method name or case id. All load methods are tenant- specific. Archives with the same case id belong to a common business case, e.g. a dual control INSERT and its release archives.

The compare methods work same as described in chapter Releasing Dual Control Events. Comparing archives makes only sense for state changing archives, not for service archives. Methods exist for comparing two archives, an archive with the actual domain object or two arbitrary objects.

Configuring and checking archive integrity is post- control functionality and is described in chapter Checking Archive Integrity.

The main significance of the ARCHIVE actuator is that the archived business cases can be redone respective restored. This is described in chapter Redo and Restore of archived Events.

SPRING_SECURITY actuator

Description: This actuator integrates Spring Security authorization. It allows defining access control rules for persistence actions, method invocations or http requests on URLs.

Default name: SPRING_SECURITY

Class: com.logitags.cibet.actuator.springsecurity.SpringSecurityActuator

Requirements:

The Spring Security libraries and its dependent libraries must be on the classpath:

  <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>3.0.3.RELEASE</version>
  </dependency>
  <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>3.0.3.RELEASE</version>
  </dependency>

In your Spring config file add the following line:

  <context:component-scan base-package="com.logitags.cibet"/>

Properties:

Property Data type Default Description (see Spring Security documentation)
denyAll boolean false deny all access
permitAll boolean false permit all access
postAuthorize String null define a post authorize rule
postFilter String null define a post filter rule
preAuthorize String null define a pre authorize rule
preFilter String null define a pre filter rule
rolesAllowed String null define allowed roles
secured String null define allowed roles
throwDeniedException boolean false throw an Exception when this actuator is applied and access is denied
urlAccess String null allowed roles or expression for accessing an URL. Corresponds to the <intercept-url> element in the Spring configuration file
secondPrincipal boolean false Authorization applied on second principal if true

In order to use this actuator for method authorization enable annotation-based security in your application with the <global-method-security> element in the Spring configuration. Read in Spring Security documentation about using the <global-method-security> element. With the <global-method-security> element access control can be defined by one of three kinds of annotations:

  1. Spring Security native annotation @Secured
  2. JSR250 annotations @DenyAll, @PermitAll, @RolesAllowed
  3. Expression-based annotations @PreAuthorize, @PreFilter, @PostAuthorize, @PostFilter

In a Cibet controlled application these annotations can be used too but in order to make use of the enhanced control mechanisms of Cibet the access control configuration must be done in the cibet configuration file or in code. The configuration elements have the same names as the annotations and are parameterized in the same way as in Spring Security. Some examples:

XML:

  <actuator name="AllPermitter">
    <class>
      com.logitags.cibet.actuator.springsecurity.SpringSecurityActuator
    </class>
    <properties>
      <permitAll/>
      <!--
        <preAuthorize>hasRole( 'WALTER')</preAuthorize>
        <secured>ROLE_BaseUsers</secured>
        <preAuthorize>
          hasPermission(#contact, 'admin') and hasRole(ROLE_ADMIN)
        </preAuthorize>
        <urlAccess>
          IS_AUTHENTICATED_ANONYMOUSLY
        </urlAccess>
        Throw Exception when permission denied:
        <throwDeniedException><throwDeniedException>
                  This actuators permissions apply to the second logged in principal:
                  <secondPrincipal />
      -->
    </properties>
  </actuator>

Code:

  SpringSecurityActuator act = new SpringSecurityActuator("AllPermitter");
  act.setPermitAll(true);
  // act.setPreAuthorize("hasRole( 'WALTER')");
  // act.setSecured("ROLE_BaseUsers");
  // act.setPreAuthorize("hasPermission(#contact, 'admin') and 
                          hasRole(ROLE_ADMIN)");
  // act.setUrlAccess("IS_AUTHENTICATED_ANONYMOUSLY");
  // act.setThrowDeniedException(true);
  // act.setSecondPrincipal(true);
  Configuration.instance().registerActuator(act);

Please note that the Cibet principle of being tolerant against user input is applied also here: The properties accept any kind of ' or " or even without any apostrophe and any empty spaces, Cibet corrects everything automatically.

The urlAccess property is for defining rules for accessing a URL. It corresponds to the

<intercept-url pattern="/**" access="ROLE_USER" />

elements in a Spring Security configuration file. In order to define URL access rules in the urlAccess property set in the Spring Security configuration file at least:

<sec:http />

If you want to use expressions in the urlAccess property like

<urlAccess>hasRole('ROLE_USER')</urlAccess>

set in the Spring Security configuration file

<sec:http use-expressions="true"/>

instead. Please refer to the Spring Security documentation for any details on how to configure access rules.

When the authorization should be applied on the second user in the release of a TWO_MAN_RULE controlled event the property secondPrincipal must be set to true. In this case the authorization rules are not applied on the Authorization object of the logged in user which is stored in SecurityContextHolder.context but on the Authorization object stored in Context.sessionScope().

When a Spring Security actuator is applied in a setpoint the persistence action or service call will be granted or denied. If a business case is denied can be checked by querying the EventResult object returned by the method

  EventResult result = Context.requestScope().getExecutedEventResult();

The status property of EventResult will be DENIED, otherwise EXECUTED (see also Post- Checking Control Results).

It is also possible to let Cibet throw an Exception when the business case is denied. For this the throwDeniedException attribute must be set to true in the Spring Security actuator, either in the Cibet config file or in code (see also chapter Actuator configuration).

XML:

  <actuator name="SPRING_SECURITY">
    <properties>
      <throwDeniedException>true</throwDeniedException>
    </properties>
   </actuator>

Code:

  SpringSecurityActuator ssa = (SpringSecurityActuator)
     Configuration.instance().getActuator("SPRING_SECURITY");
  ssa.setThrowDeniedException(true);

If a business case is denied a DeniedException will then be thrown:

  try {
          entityManager.insert(transaction);
  } catch (DeniedException e) {
          ...
  }

For http requests which are secured with SpringSecurityActuator the behavior is different. In http protocol it is not possible to transmit an exception. The request result is communicated by response codes. Therefore, the value of the throwDeniedException property has normally no effect in intercepted http requests. Instead, a response code 403 (FORBIDDEN) is returned if the access is denied.

SHIRO actuator

Description: This actuator integrates Apache Shiro authorization. It allows defining access control rules for persistence actions, method invocations or http requests on URLs.

Default name: SHIRO

Class: com.logitags.cibet.actuator.shiro.ShiroActuator

Requirements:

The Apache Shiro libraries and its dependent libraries must be on the classpath, at least the core library:

  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.1</version>
  </dependency>

Properties:

Property Data type Default Description (see Apache Shiro documentation)
hasAllRoles String authorized if the Subject is assigned all of the specified comma/semicolon separated roles
isPermittedAll String authorized if the Subject is permitted all of the specified semicolon separated permissions
requiresAuthentication Boolean false requires the current Subject to have been authenticated during their current session
requiresGuest Boolean false requires the current Subject to be a "guest", that is, they are not authenticated or remembered from a previous session
requiresUser Boolean false requires the current Subject to be an application user, a Subject either known due to being authenticated during the current session or remembered from a previous session
throwDeniedException boolean false throw an Exception when this actuator is applied and access is denied
secondPrincipal boolean false Authorization applied on second principal if true

Performing authorization in Shiro can be done in 3 ways: Programmatically, by annotations and by JSP/GSP tags.

In a Cibet controlled application there is a fourth possibility: In the Cibet configuration file. With Cibet integration a much more flexible distribution of permissions is possible. See here for some examples what can be done with Shiro - Cibet integration that cannot be done by standalone Shiro. In order to configure Shiro with Cibet an instance of ShiroActuator must be configured. The properties of ShiroActuator have the same names as the equivalent methods in Shiro’s Subject class. Some examples:

  XML:

  <actuator name="AllPermitter">
    <class>com.logitags.cibet.actuator.shiro.ShiroActuator</class>
    <properties>
      <requiresGuest/>
      <!—
      Other permission possibilities:
        <hasAllRoles>SEC_ROLE; USER_ROLE; ADV_ROLE</hasAllRoles>
        <requiresUser>true</requiresUser>
        <isPermittedAll>
          lightsaber:* ;
          jaeger:schiess:*
        </isPermittedAll>
      Throw Exception when permission denied:
        <throwDeniedException><throwDeniedException>
      This actuators permissions apply to the second Subject:
        <secondPrincipal />
      -->
    </properties>
  </actuator>

  Code:

  ShiroActuator act = new ShiroActuator("AllPermitter");
  act.setRequiresGuest(true);
  // act.setHasAllRoles("SEC_ROLE; USER_ROLE; ADV_ROLE");
  // act.setRequiresUser(true);
  // act.setIsPermittedAll("lightsaber:*; jaeger:schiess:*");
  // act.setThrowDeniedException(true);
  // act.setSecondPrincipal(true);
  Configuration.instance().registerActuator(act);

When the authorization should be applied on the second user in the release of a TWO_MAN_RULE controlled event the property secondPrincipal must be set to true. In this case the authorization rules are not applied on the Subject object of the logged in user which is stored in the user session but on the Subject object stored in Cibet's session scope context accessible with getSecondPrincipal().

When a Shiro actuator is applied in a setpoint the persistence action or service call will be granted or denied. If a business case is denied can be checked by querying the EventResult object returned by the method

  EventResult result = Context.requestScope().getExecutedEventResult();

The status property of EventResult will be DENIED, otherwise EXECUTED (see also Post- Checking Control Results).

It is also possible to let Cibet throw an Exception when the business case is denied. For this the throwDeniedException attribute must be set to true in the actuator, either in the Cibet config file or in code. The property can be set generally for the actuator or for a single setpoint (see Actuator configuration)

  XML:

  <actuator name="SHIRO">
    <properties>
      <throwDeniedException>true</throwDeniedException>
    </properties>
  </actuator>

  Code:

  ShiroActuator ssa = (ShiroActuator) Configuration.instance().getActuator(“SHIRO”);
  ssa.setThrowDeniedException(true);

If a business case is denied a DeniedException will then be thrown:

  try {
    entityManager.insert(transaction);
  } catch (DeniedException e) {
    ...
  }

For http requests which are secured with ShiroActuator the behavior is different. In http protocol it is not possible to transmit an exception. The request result is communicated by response codes. Therefore, the value of the throwDeniedException property has normally no effect in intercepted http requests. Instead, a response code 403 (FORBIDDEN) is returned if the access is denied.

LOCKER actuator

Description: LOCKER actuator allows the temporary locking (reservation) of an event on a resource like persisting an entity, releasing of a dual control event, invocation of a method or requesting a URL. Only the user who sets the lock can execute the event until the lock is removed.

Default name: LOCKER

Class: com.logitags.cibet.actuator.lock.LockActuator

Requirements:

This actuator needs a database. The database scheme can be created with the SQL scripts <dbms>.sql included in the release. There exist scripts for Derby, MySQL and Oracle database management systems.

Properties:

Property Data type Default Description
throwDeniedException boolean false throw an Exception when this actuator is applied and access is denied
automaticLockRemoval boolean false If true, the lock is removed from the database after the first successful execution of the locked event
automaticUnlock boolean false If true, the lock is set to unlocked after the first successful execution of the locked event

First requirement for the locking functionality is that the resource that should be locked is under Cibet control. This is done by a setpoint configuration, for example:

  <setpoint id="id1">
    <controls>
      <event>UPDATE</event>
      <target>com.app.accounting.Account</target>
    </controls>
    <actuator name="LOCKER"/>
  </setpoint>

to set updates of Account entities under lock control. The LOCKER actuator checks now on each update of an Account object if it is locked by a user. When another user but the user who has set the lock tries to make an update of the account object, the action will be denied. If a business case is denied can be checked by querying the execute status in Cibet request scope context (see chapter Post- Checking Control Results) or by catching DeniedException if the thowDeniedException flag is set to true. Catching the DeniedException works the same as for SPRING_SECURITY actuator and SHIRO actuator and is described there.

LOCKER actuator checks only if a lock on a business case exists, the locking and unlocking itself is pre- respective post control functionality and is described in chapter Locking Business Cases.

If one of the properties automaticLockRemoval or automaticUnlock is set to true the lock will be removed immediately after the first successful execution of the locked event. This would be normally the case when the user who has set the lock or has made the reservation executes the business case. The difference between automaticLockRemoval and automaticUnlock is that in the first case the lock entry in table CIB_LOCKEDOBJECT is deleted while in the second case only the status of the entry is changed to unlocked. Therefore, the latter property allows a historization of applied and removed lock entries.

Implementing Own Actuators

Actuators are pluggable and it is possible to extend actuators or to define new ones with other business logic. Actuator logic is implemented in classes that implement interface com.logitags.cibet.core.Actuator. Actuator implementations can be declared in cibet-config.xml configuration file or dynamically registered with Configuration API.

In cibet-config.xml add an actuator element and set the class tag. If the actuator implementation uses own properties, they can be defined with property tags. The implementation must have a setter method with a name following the Java Beans convention and taking a String argument as parameter:

XML:

  <actuator name="Sub4Eyes">     
    <class>com.company.MyActuator</class>
    <properties>
      <myAttribute>someValue</myAttribute>
    </properties>
  </actuator>

Code:

  Actuator act = new MyActuator();
  act.setMyAttribute("someValue");
  Configuration.instance().registerActuator(new MyActuator());

Actuator implementations must have a default constructor and must provide a unique name which is returned by method getName(). This is the name that is set in the name attribute in cibet-config.xml. The other methods define logic which is executed before respective after a control event. It is recommended to inherit custom actuators from AbstractActuator and overwrite the methods the actuator is interested in. This ensures that the actuator works also in case of future interface enhancements.

Pre- and Post- Control Functionality

The following chapters describe Cibet functionality which enhance the basic control patterns described in the actuators chapter. Most of this functionality is optional but some is required in order to make the configured actuators useful.

Post- Checking Control Results

If a business case is controlled by a sensor, it can be summarized after execution of the business case which actuators have been applied and the actuator results. This can be done by the following method:

  EventResult result = Context.requestScope().getExecutedEventResult();

This method returns an object of type EventResult or null if no sensor has been applied or the intercepted event is still executing and the final result could not yet be determined.

The EventResult object contains the following information:

  • sensor: The sensor that intercepted the event
  • targetType: the target of the setpoint. This could be the entity class, the service class, the intercepted URL, database table or any other supported resource.
  • method: method name if a service method has been intercepted or http method, otherwise null.
  • caseId: the case id which is assigned to this business case
  • actuators: comma separated list of the applied actuators
  • setpoints: comma separated list of applied setpoint IDs
  • executionTime: timestamp of interception
  • event: the intercepted event
  • ExecutionStatus: result of the event execution. One of EXECUTING, EXECUTED, POSTPONED, REJECTED or DENIED
  • childResults: a list of child EventResult objects. If during execution of this event a second event is controlled by a setpoint, the result of the second event is added to this list. Empty list, if there is no child controlled event
  • parentResult: if this event is executed during execution of another parent event controlled by a setpoint, the result of the parent event is stored in this property. Null, if there is no parent controlled event

The last two properties need maybe some more explanation. If for example a service EJB method is controlled and within the implementation of this method the JPA persistence of an entity is controlled, we have a parent – child relationship. If the Context.requestScope().getExecutedResult() method is called after execution of the EJB method it will return an EventResult object representing the execution of the EJB method containing as a child an EventResult object representing the execution of the JPA method. If the Context.requestScope().getExecutedResult() method is called within the EJB service method after execution of the JPA persistence the returned EventResult object represents the JPA persistence object which has no child element in this case but has a parent EventResult representing the EJB method which is in status EXECUTING.

With the SERVLET sensor, the EventResult object cannot be retrieved from the client Request scope context because the EventResult is produced on the server. It is however transmitted in an encoded format in the http response as header with name CIBET_EVENTRESULT. It can be retrieved as follows:

  String evReHeader = response.getFirstHeader(CibetFilter.EVENTRESULT_HEADER).getValue();
  EventResult result = CibetUtil.decodeEventResult(evReHeader);

The EventResults can also be tracked in the database with the TRACKER actuator. If this actuator is applied on a business case, all EventResult objects including the child objects are stored into the database in table cib_eventresult (see chapter TRACKER actuator).

Pre- Checking Control Results

Sometimes it is desirable to know the expected control results before the business case is actually executed. For example on a web page a button to trigger a business case may be disabled if the current user is not allowed to execute it. It is often a better style to not offer an action instead of telling the user afterwards that he isn't allowed to do it.

However, with Cibet checking the expected results is not simply a matter of looking into the configuration. As Cibet control is not static but highly dynamic the result depends not only on the configuration but may depend on various other context parameters. It is therefore necessary to simulate the business case in the current context in order to check the applied actuators and control results. Cibet provides this functionality with the Play mode. When the system is in Play mode, the business case is not executed, nor are actuators applied or other impacts on the system are induced. It can be seen however which actuators will be applied and what will be the execution status of the business case.

A business case is executed in Play mode like this:

  Context.requestScope().startPlay();

  // now the business case is simulated:
  TEntity entity = persistTEntity();

  // and the Play mode is stopped:
  EventResult er = Context.requestScope().stopPlay();

Stopping the Play mode returns an EventResult object that can be checked like described in previous chapter Post- Checking Control Results.

When using the SERVLET sensor, the procedure is a little different because it is a remote request which runs not in the same thread. When sending an HTTP request the Play mode is started by adding the header CIBET_PLAYING_MODE with a value 'true':

  // add CIBET_PLAYING_MODE header
  postMethod.addHeader(CibetFilter.PLAYINGMODE_HEADER, "true");

  // send request
  HttpResponse response = client.execute(postMethod);

  // now the EventResult object can be retrieved like described in 
  // previous chapter Post- Checking Control Results
  String evReHeader = response.getFirstHeader(CibetFilter.EVENTRESULT_HEADER).getValue();
  EventResult result = CibetUtil.decodeEventResult(evReHeader);

It is not necessary to stop the Play mode after the http request. It is automatically stopped on the server side.

Please be aware that the Play mode has only the desired effect when the executed business case is controlled by a Cibet sensor. Methods and other actions which are not controlled by Cibet will be executed in Play mode!

Releasing Dual Control Events

If persistence or method invocation actions are suspended and postponed due to a dual control actuator a second user must check the actions and release or reject them. This is done with the DcService API. All suspended actions are represented by a subclass of DcControllable. Here is how unreleased actions can be found:

  // get a DcService instance by dependency injection, a Singleton, static instance 
  // or other mechanisms
  static DcService dcservice = new DefaultDcService();

  // find all unreleased DcControllables for the tenant set in 
  // session scope. Returns all unreleased objects if no tenant is set in context.
  List<DcControllable> list = dcservice.findUnreleased();
  
  // or be more specific: This method finds all unreleased DcControllables 
  // for the tenant set in session scope and entity BankAccount. Returns all unreleased 
  // objects of BankAccount if no tenant is set in context.
  list = dcservice.findUnreleased(BankAccount.class);

Of cause you can define any other query on the cib_dccontrollable table. Now you can display the data of the DcControllables and the controlled Resource object in a GUI to the user who wants to check and release the data. GUI is out of scope of Cibet because every application will have its own technology and layout. Use the getter methods of DcControllable to display the data, e.g.:

for (DcControllable obj : list) {
  // the class name of the entity or the object on which to invoke the method 
  String affectedClassName = obj.getTargetType();
  
  // the unique ID of DcControllable
  long uniqueCOId = obj.getResource().getDcControllableId();
  
  // the user who initiated the action
  String user = obj.getCreateUser();

  // the action on the entity (invoke, insert, update ...)
  String event = dObj.getControlEvent();

  // get the method name if any
  String methodName = obj.getResource().getMethod();
    
  // get the method or http parameters, http attributes and headers
  List<ResourceParameter> parameters = obj.getResource().getParameters(); 

  // get the unique id of the entity   
  String id = obj.getResource().getPrimaryKeyId();
    
  // get the persisted object
  Object entity = obj.getResource().getObject();
}

If the controlled event modifies the persistence state of an entity in the database the releasing user wants to compare the modified state with the productive unmodified state in order to see which attributes of the domain object have changed. The DcService interface provides functionality for comparing two objects of the same type:

  // ... find an unreleased object with DcService.findUnreleased() method
  DcControllable obj;
  // compare the state of the modified object with the actual state
  List<DiffValue> list = dcservice.compare(obj); 

The compare() method returns a list of DiffValue objects each of which represents a modified attribute. The following figure shows the class diagram of DiffValue:

Class diagram of DiffValue

An attribute value can be removed, added or modified. When a value has been removed only oldValue is set, when a value has been added only the newValue attribute is set. Removing or adding a value could be done either by setting it to or from null or by deleting or adding a value to a field of type Collection, Map or an array. The compare() method supports vertical and horizontal resolution of attribute differences. Vertical means that all non-static attributes which are not annotated with the javax.persistence.Transient or javax.persistence.Version annotation of the class itself and all its super classes in the class hierarchy are compared. Horizontal means, attributes of associated objects and of lists, maps and arrays are compared transitively. The transitives list attribute in DiffValue contains the transitive differences in the latter case.

Let's make an example. Following figure shows a simple class model of a person with a list of addresses:

Example class diagram

Here is an instance of the Person class:

  Lisa Smith
    
      Germany/Aachen
      Belgium/Bruxelles
      New York/USA

which will be modified to:

  Lisa Muller 
  
      Germany/Aachen
      Belgium/Kelmis

The compare() method will return a list of three DiffValue objects one of them having a transitive DiffValue object:

Result of the compare() method

After checking the data the user releases or rejects the event using the DcService interface:

  // release the event represented by the DcControllable.
  // The user may give a remark which will be stored in the release archive.
  
  EntityManager em;
  // transaction.begin();
  try {  
    Object result = dcservice.release(em, dcControllable, remark);
  } catch (ResourceApplyException e) {
        // if the release fails
        ...
  }
  // transaction.commit();
  
  // reject the event represented by the DcControllable.
  // The user may give a remark which will be stored in the reject archive.
  EntityManager em;
  // transaction.begin();
  dcservice.reject(em, dcControllable, remark);
  // transaction.commit();

The result of the release method is either the object on which a persistence event has been performed, the result of the method invocation or null. The release and reject methods must be executed within a transaction, either bean- or container- managed. If the method invocation throws an exception during release, or the database persistence action throws an exception the transaction is rolled back.

Checking Archive Integrity

Per default the archive entries are secured against fraudulent manipulation with checksums. All modifications of existing archives and deleting or adding of archives can be detected. If an encrypted database is used the generation of checksums can be switched off to increase performance. Integrity check functionality for the ARCHIVE scheme can be switched off in cibet-config.xml or in code:

XML:

  <actuator name="ARCHIVE">
    <properties>
      <integrityCheck>false</integrityCheck>
    </properties>
  </actuator>

Code:

  ArchiveActuator act = (ArchiveActuator)
     Configuration.instance().getActuator(ArchiveActuator.DEFAULTNAME);
  act.setIntegrityCheck(false);

Cibet uses a default secret key to generate checksums. If integrity check functionality is switched on, an own secret key should be set. Managing of passwords, keys or certificates is out of scope of Cibet. In any case, the secret key should be kept in a safe place like an encrypted configuration file or a hardware security box. Set your secret key in cibet-config.xml only if this file is safe and secured:

XML:

  <actuator name="ARCHIVE">
    <properties>
      <secretKey>my secret key</secretKey>
    </properties>
  </actuator>

Code:

  ArchiveActuator act = (ArchiveActuator)
     Configuration.instance().getActuator(ArchiveActuator.DEFAULTNAME);
  act.setSecretKey(secretKey);

Now the archive entries are secured. The archive integrity should be controlled regularly. In a productive system it is good practice to check integrity on a snapshot of the database in a consistent state. Check the archive integrity with ArchiveService API:

  // get an ArchiveService instance by dependency injection, a Singleton, 
  // static instance or other mechanisms
  static ArchiveService archiveService = new DefaultArchiveService();

  IntegrityCheck check = archiveService.checkArchiveIntegrity();

The IntegrityCheck object contains all information of the integrity check run. It is automatically stored in table CIB_INTEGRITYCHECK which allows to follow up the history of integrity check runs if they are done regularly. The IntegrityCheck.toString() method gives a report like this:

  Archive Integrity Check (Sat Nov 07 11:30:19 CET 2009): FAILURE
  total number of records: 15
  number of modified records: 0
  number of missing records: 6
  number of added records: 0
  Errors:
  1041: RECORD_MISSING
  1049: RECORD_MISSING
  1050: RECORD_MISSING
  1051: RECORD_MISSING
  1058: RECORD_MISSING
  1059: RECORD_MISSING

Redo and Restore of Archived Events

Archive entries created by the ARCHIVE actuator store the state of a method invocation, a http request or a domain object. Methods and http requests can be invoked a second time from the archive with the exact same parameters. This can be done with the redo method. A notice can be added as an explanation:

  // get an Archive that represents a method invocation resource, e.g. with
  // ArchiveService.loadArchivesByMethodName("AccountManager", "transfer");
   
  Object result = archiveService.redo(archive, "transferred again as bonus");

With the redo method the disclosure of sensible data to the executing user can be prevented as for example account and payment data in the above example. Controllers and actuators are applied also to repeated method invocations when a setpoint for the REDO event exists.

For redoing http requests there is a limitation: If a POST or PUT http request contains a body, the body is archived only when the request is postponed by a dual control actuator at the same time. This is because the body of an http request is streamed in and therefore once read it is consumed. It is therefore not possible to read the body in the SERVLET sensor and read it again in the receiving servlet.

If an archive contains the state of a selected, deleted or modified object this state can be restored with the restore method:

  // get an Archive that represents a persistence resource, e.g. with
  // ArchiveService.loadArchivesByCaseId("...");
  
  EntityManager em; 
  // transaction.begin(); 
  Object restoredObject = archiveService.restore(em, archive, "restored because user error");
  // transaction.commit();

Configured controls and actuators are applied to the restore event if a setpoint for the RESTORE event exists.

Assignment and Annotation of Dual Control Events

The release of business cases controlled by a dual control actuator can be directly assigned to a specific user. If a postponed business case is assigned, only the assigned user can execute the release. Rejection of business cases cannot be assigned.

Assignment will be automatically done if an approval user is set in the session scope with method

  Context.sessionScope().setApprovalUser(userName);

During execution of the dual control actuators, the session property approvalUser is evaluated and if it is not null, the postponed business case is assigned to that user. Don't forget to set the approval user null after execution of the business case if you don't want to assign subsequent other business cases to the same user.

In the SIX_EYES actuator there are two releases, both of which can be assigned. If during the initiation of the business case an approval user is set in session context, the first release will be assigned to that user. If during the first release an approval user is set in session context, the final release will be assigned to that user.

The initiating user can add a remark to the postponed business case to inform the releasing user about the context of the event. When in the request context a remark is found it will be automatically added to the metadata of the postponed business case. A remark can be added with method

  Context.requestScope().setRemark(remark);

A remark can also be added to the release and reject of a business case. In these cases, the remark is set in the methods of DcService and overwrites a remark set in the request scope context:

  Object release(EntityManager entityManager, DcControllable co, String remark)

  void reject(EntityManager entityManager, DcControllable co, String remark)

Notifications

Cibet can send notifications of control events. Notifications may be sent for the following events:

  • FIRST_ASSIGNED: A business case is controlled by a SIX_EYES process. The first releasing user has been assigned by the initiating user. He will be notified of the postponed business case.
  • FIRST_RELEASED: The first user has released a business case controlled by a SIX_EYES process. The initiating user will receive a notification.
  • ASSIGNED: A FOUR_EYES controlled business case has been assigned to a user for release or a SIX_EYES controlled business case has been assigned to a user for the second final release. He will be notified of the postponed business case.
  • RELEASED: A FOUR_EYES controlled business case has been released or a SIX_EYES controlled business case has been finally released. The initiating user receives a notification
  • REJECTED: a dual controlled business case (FOUR_EYES or SIX_EYES) has been rejected. The initiating user receives a notification

Sending of notifications requires three configurations:

  1. Registration of the notification provider
  2. Setting of the recipient address
  3. Activation of notifications in the actuators

Registration of the Notification Provider

The notification provider implements the protocol and technique how notifications are sent. Cibet provides two implementations:

com.logitags.cibet.core.notification.HttpNotificationProvider

sends notifications as a http POST requests. The request body contains all metadata and target data of the business case under dual control.

com.logitags.cibet.core.notification.EmailNotificationProvider

sends notifications as an email. The subject and email text can be customized. This provider has five properties:

  • smtpHost: SMTP server IP
  • smtpPort: SMTP server port
  • smtpUser: optional user name if the SMTP server requires authentication
  • smtpPassword: optional password if the SMTP server requires authentication
  • from: standard email address of the sender

It is possible to register own implementations of a Notification Provider, for example for SMS notifications. Custom implementations must implement interface com.logitags.cibet.core.notification.NotificationProvider, provide a default constructor and must define properties according to the Java Beans convention.

Registration of a notification provider can be done by code or by configuration. In code execute:

  Configuration.instance().registerNotificationProvider(
             new HttpNotificationProvider());

Alternatively add in cibet-config.xml:

  <notificationProvider>
    <class>
      com.logitags.cibet.core.notification.HttpNotificationProvider 
    </class>
  </notificationProvider>

The properties can be set like this in cibet-config.xml:

  <notificationProvider>
    <class>
      com.logitags.cibet.core.notification.EmailNotificationProvider 
    </class>
    <properties>
      <smtpHost>192.168.48.10</smtpHost>
      <smtpPort>25</smtpPort>
      <from>cibetNotifier@company.com</from>
    </properties>  
</notificationProvider>

Setting of the recipient address

The nature of the recipient address depends on the notification provider that has been registered. An EmailNotificationProvider requires an email address while an HttpNotificationProvider requires a URL. The recipient address is set into the session scope context. The session scope provides two methods:

Context.sessionScope().setUserAddress(String)

This is the address of the logged in user, who initiates a dual controlled business case. The address can be set after login when the user has been authenticated and his properties are known. The other method

Context.sessionScope().setApprovalAddress(String)

sets the address of the user who has been assigned for first or final release. The approval address must be set before the business case is executed or before a SIX_EYES controlled business case is released by the first user to notify the next user for the final release. Normally it makes sense to not only notify a user but also to assign the business case to him (see chapter Assignment and Annotation of Dual Control Events

Context.sessionScope().setApprovalUser(String)

Activation of notifications in the actuators

The last point to enable notifications is to activate it in the actuators. The Dual Control actuators FOR_EYES, SIX_EYES, PARALLEL_DC and TWO_MAN_RULE have the flags sendAssignNotification, sendReleaseNotification and sendRejectNotification. If set to true, an address is found in session scope and a NotificationProvider has been registered, the respective notification is sent. Per default these properties are true.

This three-step configuration allows a fine-grained tuning for what business cases and events notifications should be sent. It is for example possible to instantiate dual control actuators which send notifications, others that do not send any and apply them in different setpoints.

Customization of email notification templates

The default templates for email notifications are packed within the cibet.jar archive. If you want to customize or translate the email texts, create own templates and put them in the classpath of your application. The templates must have the following names:

<NotificationType>-emailsubject.vm for the email subject

<NotificationType>-emailbody.vm for the email body text

with <NotificationType> one of FIRST_ASSIGNED, FIRST_RELEASED, ASSIGNED, RELEASED and REJECTED according to the five possible notifications.

The templates are Velocity templates. Cibet puts all properties of class DcControllable into the Velocity context and are accessible by the same names as in the classes. Additionally, properties of the Resource class are set into Velocity context dependent on the resource type.

EJB / POJO method invocation:

  • targetType: class name of the object on which the method is invoked
  • method: method name which is invoked
  • resultObject: the return value of the method invocation, if any

JPA persistence:

  • targetType: class name of the persisted object
  • target: the persisted object
  • primaryKeyId: unique id of the persisted object

JDBC SQL statement:

  • targetType: table name in the SQL statement
  • target: the SQL statement
  • primaryKeyId: primary key id value in the SQL statement

HTTP servlet request:

  • targetType: requested URL
  • method: HTTP method of the request

For example the default template for the ASSIGNED event starts with

  Hello $approvalUser,


  A business case under dual control has been assigned to you for final approval. You may release 
  or reject the case. Please visit the dialogue for releasing/rejecting.

  The dual controlled business case is registered under id: $dcControllableId (case id: $caseId)


  control event:             $controlEvent

  controlled target:         $targetType 
  #if( $method.length() > 0 )
  ($method)
  #end

Please see the Velocity documentation for details on how to create these templates.

Locking Business Cases

The LOCKER actuator checks if a lock on a business case exists, the locking itself must be done before.

If a user wants to set a lock on a business case, he uses one of the lock methods of the Locker API. If a user wants e.g. set a lock on updates of a special account object he does something like:

  Account account = getAccount();
  Locker locker = new DefaultLocker();
  locker.lock(account, "UPDATE", null);

There exist also methods to set a lock on all instances of a class:

  locker.lock(Account.class, "UPDATE", null);

or on a method of a class:

  Method method = AccountManager.class.getMethod("createAccount");
  locker.lock(method, "INVOKE", "account creation reserved for me only!");

or on a URL:

locker.lock("http://www.mycompany.com/createAccount", "RELEASE", null);

If a lock exists already on the resource, an AlreadyLockedException will be thrown.

A lock can be unlocked, that means the lock is kept in the database for history reasons but the status is set to unlocked, or removed completely from the database. Unlock and removal of a lock exist in three different flavors:

  • methods unlockStrict() and removeLockStrict() in Locker API: Only the user who has locked the resource can execute these methods and unlock/remove.
  • methods unlock() and removeLock() in Locker API: Any user can execute these methods and unlock/remove
  • automaticUnlock and automaticLockRemoval: If property automaticUnlock of LockActuator is set to true, the lock status is set to unlocked when the user who has set the lock executes the locked action for the first time. If property automaticLockRemoval of LockActuator is set to true, the lock is removed from the database when the user who has set the lock executes the locked action for the first time. This method of unlocking is very convenient as it is done automatically. It is especially useful for deletes and dual control releases, because after executing the delete or release the lock is useless.

    Example: User John locks deletion of a Customer. No user except John can delete this customer now. If John actually deletes the customer the lock is automatically removed.

Hint: Often it is not a good idea to set a lock on a RELEASE event. If a lock on a RELEASE exists and a user rejects the dual control action the RELEASE lock would remain active though this is often not what is wanted. Though Cibet tries to reduce the developer's code from such processing functionality this kind of situations cannot be decided automatically. There are too many possibilities. The easiest way to solve this issue is to lock DC_CONTROL instead of RELEASE event. In this case the lock will be removed whether the action is rejected or released.