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.
findAll()
returns all functionalities of the same kind. For example
SWhistoryFunctionalityHome.findAll()
returns all history functionalities. findByTable(SWTable)
returns all functionalities of the same kind associated with
the given table. For example SWhistoryFunctionalityHome.findByTable(myTable)
returns
all history functionalities that should be notified on myTable
modification. findEnabledByTable(SWTable)
returns all enabled functionalities of the same kind associated with
the given table. For example SWhistoryFunctionalityHome.findEnabledByTable(myTable)
returns
all enabled history functionalities that should be notified on myTable
modification.
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:
ejbCreate()
and ejbRemove()
methods. ejbCreate()
adds a record to the swfunctionalities
table and to the swfuncmetadata
table (if there's no
metadata record there yet). ejbRemove
performs an opposite action.SWFunctionality
interface (to be preciese it should be implemented by SWxxFunctionality
,
the remote interface)SWFunctionalityLogic
interface
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.
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}.
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... ).
SWFunctionalityEventBroadcaster
and gently asks it to broadcast some event
E
associated with the table T
.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
SWFunctionalityEventBroadcaster
instantiates all found functionalities, sorts them using
{@link com.supportwizard.functionalities.SWFunctionalitiesOrder SWFunctionalitiesOrder} and consequently sends
E
to each of them.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.
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.