Provides the means for intercepting database operations such as data reading and modification.

Functionalities is something similar to the database triggers. Each functionality can be associated with only one database table ({@link com.supportwizard.dictionary.SWTable SWTable}) instance. When the table is about to be modified it broadcasts corresponding event to all interested functionalities. Moreover, before the modification takes place concerned functionality is asked whether this modification is permitted. If at least one functionality forbids the modification then it must not be performed. Functionalities are also notified on data reading. Thus they can modify the data in some way e.g. add virtual fields, modify field values or add auxiliary objects. Look at the {@link com.supportwizard.functionalities.SWFunctionalityLogic SWFunctionalityLogic} for complete list of available callbacks.

Functionalities are invoked in the hardcoded order, however this order can be tuned using {@link com.supportwizard.functionalities.SWFunctionalitiesOrder SWFunctionalitiesOrder} class that allows to set per-method priorities. If someone will require to define priorities dynamically, then I will put priorities into some entity bean..

Functionalities can communicate with each other during the event dispatching in the scope of this event and sometimes in the scope of pair event, like recordAdding--recordAdded. See {@link com.supportwizard.functionalities.SWFunctionalityLogicBus} for details.

Functionalitiy can exist in two different states: enabled and disabled. After creation all functionalities are enabled. While functionality is disabled it doesn't receive any notifications. Functionality's state can be changed with {@link com.supportwizard.functionalities.SWFunctionality#setEnabled(boolean) SWFunctionality.setEnabled(boolean)} method.

Functionalities are organized in the following way: Each functionality is a BMP entity bean. Their remote interfaces must extend {@link com.supportwizard.functionalities.SWFunctionality SWFunctionality} interface. Another constraint is that they must have these finders:

To simplify the life of functionalities creators there is {@link com.supportwizard.functionalities.SWFunctionalityBase SWFunctionalityBase} class which contains all necessary BMP logic and some other useful features.

Note that functionality itself doesn't contain callback methods. They are located in the separate {@link com.supportwizard.functionalities.SWFunctionalityLogic SWFunctionalityLogic} interface because JBoss application server currenly supports only TRANSACTION_SERIALIZABLE isolation level for concurrent thransactions on the same entity bean. It means that concurrent modifications of some table can't be successfully multisequenced and moreover can cause a deadlock. Moving all callbacks to separate java class solves the problem. However there is {@link com.supportwizard.functionalities.SWFunctionalityLogicBase SWFunctionalityLogicBase} class that contains empty implementations of all interface methods, so you can subclass it and override only methods you are interested in.

Thus every functionality is represented by a couple of classes (not counting remote and home interfaces): SWxxxFunctionalityBean and SWxxxFunctionalityLogic. Here's the division of their responsibilities:

SWxxxFunctionalityBean

SWxxxFunctionalityLogic

Here's a simplified diagram of event dispatching:

As you see, a set of functionalities interested in particular event is cached inside the broadcaster. Since evey callback may have it's own functionalities ordering, there is separate cache for every callback. This cache is cleared if functionalities are added to/removed from a table or when they are enabled/disabled. The latter case is handled inside of SWFunctionalityBase, so if you ever decide to implement some functionality from scratch, remember to call SWTableRelatedCachesWrapper.clean(tableID). Note that getFunctionalityLogic is not cached because functionalities doesn't guarantee that logic instance is reusable. Of course, this call can take significant time, especially if broadcaster and functionality are on different cluster nodes, so may be in the future I will add local interfaces to functionalities and make getFunctionalityMethod a local call. But currently we use only a single node, and JBoss optimizes such calls automatically, so there is almost no performance loss.

What steps must be done to create a new functionality

How to add and remove a functionality

If functionality inherits SWFunctionalityBase or correctly fulfills it's responsibilities then adding is very straightforward: call SWxxxFunctionalityHome.create() and that's it! Romoval is a bit more complex: you shouldn't use SWxxxFunctionality.remove(). {@link com.supportwizard.functionalities.SWFunctionalityLogic#getTablesToDropOnRemove Here's} an explanation why. Functionality can be removed using two different ways: either drop the table it is associated with using SWDictionaryDDL or utilize {@link com.supportwizard.functionalities.ejb.SWFunctionalitiesRemoverBean SWFunctionalitiesRemover}.

Exceptions handling

If some functionality encounters application-level error it should throw {@link com.supportwizard.functionalities.SWFunctionalityException SWFunctionalityException}. System-level exceptions should be either processed by the functionality itself or be wrapped into SWFunctionalityException.

All exceptions thrown by functionalities during event multicasting will be assembled into one big ChainedException which will be finally thrown to the client right after the event will be sent to all functionalities. In other words all interested functionalities will receive the event regardless of exceptions thrown (I'm still unsure if this behaviour is correct... ).

Performance considerations

The only operation that can take significant time is event broadcasting. Here's how it works in the case of cache miss:
  1. Client creates new instance of SWFunctionalityEventBroadcaster and gently asks it to broadcast some event E associated with the table T.
  2. SWFunctionalityEventBroadcaster searches through the database to get all enabled functionalilies that are interested in events on T. This involves only one simple SQL command: SELECT * FROM swfunctionalities WHERE Enabled=true AND SWTableID=T.ID
  3. SWFunctionalityEventBroadcaster instantiates all found functionalities, sorts them using {@link com.supportwizard.functionalities.SWFunctionalitiesOrder SWFunctionalitiesOrder} and consequently sends E to each of them.
So everything depends on how long will it take for each functionality to process E plus time to sort them. SWFunctionalitiesOrder uses standart Collections.sort() routine which implements QuickSort algorithm, so in the worst case it will take O(N^2) while on average it will take O(N*log(N)). In general assuming that all functionalities will process E in constant time sending one event costs O(N*log(N)) where N is number of enabled functionalities associated with T.

If your functionality is interested in import process and uses corresponding callbacks, then it must work as fast as possible, postponing serisous job to latter recordModifying calls (sort of lazy initialization). In the other case import of a large data array will take too much time.

Caveats

Remember to disable functionalities before making any DB modifications that you don't want to be intercepted and don't use ejbRemove() to kill functionalities, use either SWFunctionalitiesRemover or SWDictionaryDDL instead. Also don't forget to override {@link com.supportwizard.functionalities.SWFunctionalityLogic#getTablesToDropOnRemove SWFunctionalityLogicBasegetTablesToDropOnRemove()} if your functionality requires to drop tables on removal.

Related documents

Database schema
Design justification
Full class diagrams