final class ChangeBroker extends java.lang.Object implements Scopeable
RecordBuffer
to defer creating a placeholder
snapshot as long as possible;
Persistence
query methods to disable auto-flushing
of the active Hibernate session when possible;
AdaptiveQuery
to invalidate its current result set
when necessary.
RecordChangeListener
objects register to receive notifications for one or
more DMO types, using the addListener(com.goldencode.p2j.persist.event.RecordChangeListener)
method. A listener will
only receive notifications for changes to DMOs of the types for which they
registered. Beyond this, no other filtering is performed. Listeners are
maintained in a scoped dictionary, so there is never a need to remove a
listener explicitly; when the scope in which the listener was registered
is closed, it is released automatically.
Change notifications are driven by the stateChanged(com.goldencode.p2j.persist.event.RecordChangeEvent.Type, com.goldencode.p2j.persist.RecordBuffer, com.goldencode.p2j.persist.Persistable, com.goldencode.p2j.persist.Persistable, java.util.Map<java.lang.String, java.util.List<java.lang.Integer>>, boolean)
method. It
is anticipated that this only will be invoked by RecordBuffer
objects, as user code calls setter methods on DMO proxies. A state change
causes a RecordChangeEvent
to be broadcast to all listeners registered against the
type of DMO which was modified. "Type" in this sense corresponds to the
DMO interface.
This class also enlists a Hibernate Interceptor
to listen for
Hibernate session events. The context local Persistence
instance installs this interceptor into each Hibernate Session
it opens. Whenever a DMO state change occurs, the implementation class of
the modified DMO is stored in a set of pending flush types. When the
interceptor is notified of a session flush event, this set is cleared.
As an optimization, the Persistence
class consults with the
ChangeBroker
to determine whether a session flush is required
before a query is executed. If not, it disables auto-flushing for the
query, which preempts considerable, unnecessary work.
The interceptor has another responsibility associated with temp tables.
Temporary DMOs can be removed from the database via a bulk delete, which
bypasses the BufferManager
's DMO reference counting mechanism.
As a result, a bulk delete can leave records associated with the open
Hibernate session, which can result in much more flushing effort than
necessary, in certain situations. During a flush event, the interceptor
detects DMOs which are not in use and schedules these for eviction from the
session. The eviction takes place immediately after the flush event is
finished.
Because it relies upon the RecordBuffer
as the source for all
state change events, ChangeBroker
is not useful in non-legacy
client contexts (i.e., where external code accesses the services of the
Persistence
class directly). For the most part, this class
is transparent for these contexts. One exception is that the auto-flush
optimization technique described above would be harmful if allowed to
proceed naturally, because flushing would always be disabled since state
changes would never be reported, thus it would always seem flushing is not
required. To prevent this problem, this class detects when it is not in
legacy mode and disables this optimization accordingly in such cases.
Modifier and Type | Class and Description |
---|---|
private static class |
ChangeBroker.ListenerSet
An ordered set of listeners (iterates in the order added to the set), which optionally
links to other sets of listeners up and down the stack of open scopes.
|
(package private) class |
ChangeBroker.SessionInterceptor
An implementation of a Hibernate
Interceptor which listens
for session flush events and clears the enclosing instance's pending
flush status whenever a flush occurs. |
Modifier and Type | Field and Description |
---|---|
private static ContextLocal<ChangeBroker> |
context
Context local instance of this class
|
private java.util.Map<Persistence,ChangeBroker.SessionInterceptor> |
interceptors
Objects which are notified of activity in Hibernate session
|
private boolean |
legacyMode
Is this client context using legacy services?
|
private ScopedDictionary<java.lang.Class<?>,ChangeBroker.ListenerSet> |
listeners
Sets of listeners which have registered interest in DMO property changes
|
private static java.util.logging.Logger |
LOG
Logger
|
private java.util.Map<java.lang.Object,java.util.Map<java.lang.Class<?>,ChangeBroker.ListenerSet>> |
procListeners
Map of saved listeners, per persistent procedure referent.
|
Modifier | Constructor and Description |
---|---|
private |
ChangeBroker()
This class can only be constructed privately.
|
Modifier and Type | Method and Description |
---|---|
(package private) void |
addListener(RecordChangeListener listener)
Register a listener to receive DMO property change notifications.
|
(package private) void |
addListener(RecordChangeListener listener,
int depth)
Register a listener to receive DMO property change notifications.
|
(package private) void |
forcePendingFlush(Persistence persistence,
java.lang.Class<?> dmoClass)
Force a Hibernate session flush to occur before the next query against
the given DMO class is executed.
|
(package private) void |
forcePendingFlush(RecordBuffer buffer)
Force a Hibernate session flush to occur before the next query against
the given buffer is executed.
|
(package private) static ChangeBroker |
get()
Retrieve the context-local instance of this class, instantiating it
first if necessary.
|
(package private) ChangeBroker.SessionInterceptor |
getInterceptor(Persistence persistence)
Retrieve the
Interceptor instance to be installed in the
next Hibernate Session to be opened against the specified
database. |
(package private) static void |
initialize()
Register with the
TransactionManager a factory object which creates instances of this
class, so that they can be registered to receive notifications of
runtime scope start and finish events. |
(package private) void |
markDirtyDMO(RecordChangeEvent event)
Track the DMO specified in the event as dirty until it is flushed.
|
(package private) boolean |
removeFromGlobal(RecordChangeListener listener)
Remove a listener from global scope.
|
(package private) void |
removeListener(RecordChangeListener listener)
Remove the specified listener from all scopes.
|
(package private) boolean |
requiresFlush(Persistence persistence,
java.lang.String entityName)
Report whether a Hibernate session level flush would be required before
processing a query which expects results of type
dmoClass . |
void |
scopeDeleted()
Notification that the instantiating procedure where the scope was opened was deleted.
|
void |
scopeFinished()
Pop the current scope from the listeners dictionary whenever a runtime
scope is closed.
|
void |
scopeStart()
Add a scope to the listeners dictionary whenever a new runtime scope
opens.
|
(package private) void |
stateChanged(RecordChangeEvent.Type type,
RecordBuffer buffer,
Persistable dmo,
Persistable snapshot,
java.util.Map<java.lang.String,java.util.List<java.lang.Integer>> properties,
boolean setPendingFlush)
Broadcast a notification that a DMO's state has changed.
|
private static final java.util.logging.Logger LOG
private static final ContextLocal<ChangeBroker> context
private final ScopedDictionary<java.lang.Class<?>,ChangeBroker.ListenerSet> listeners
private final java.util.Map<java.lang.Object,java.util.Map<java.lang.Class<?>,ChangeBroker.ListenerSet>> procListeners
private final java.util.Map<Persistence,ChangeBroker.SessionInterceptor> interceptors
private boolean legacyMode
private ChangeBroker()
static ChangeBroker get()
static void initialize()
TransactionManager
a factory object which creates instances of this
class, so that they can be registered to receive notifications of
runtime scope start and finish events.
This method should be invoked once during the server bootstrap phase.
public void scopeStart()
scopeStart
in interface Scopeable
public void scopeFinished()
scopeFinished
in interface Scopeable
public void scopeDeleted()
This implementation is a no-op.
scopeDeleted
in interface Scopeable
void addListener(RecordChangeListener listener)
This method is tolerant of being called multiple times for the same listener in the same scope; the listener will only be registered once, and therefore will receive only one notification per distinct event.
listener
- Listener object to be added.void addListener(RecordChangeListener listener, int depth)
depth
parameter. The
listener will receive notifications of any changes to DMOs of the types
for which it is registered, as they occur in the specified scopes or in
the nested ones. The listener is deregistered automatically
when the scopes end.
When we call this function with a depth
less than the
number of innermost scope, we actually want to register it in some outer
scope, but since registrations at inner scopes will hide those at outer
scopes during a lookup, we should register it in the several scopes
if needed.
This method is tolerant of being called multiple times for the same listener in the same scope; the listener will only be registered once, and therefore will receive only one notification per distinct event.
If the buffer is opened in a procedure executed persistent, the buffer has to survive when the procedure code is finished until the prosistent procedure is deleted. In this case the buffer is stored in the GLOBAL scope (depth = 0).
listener
- Listener object to be added.depth
- 1-based number of the most outer scope in which we want to register the listener
(starting from the very outermost scope). Use 0 for GLOBAL scope when the buffer
is opened in a persistent procedure.boolean removeFromGlobal(RecordChangeListener listener)
If this buffer was not open in global scope (from a persistent procedure) this is a no-op and
the returned value is false
.
listener
- The listener to be removed.true
if the listener was successfully removed from global scope.void removeListener(RecordChangeListener listener)
listener
- The listener to be removed.void forcePendingFlush(RecordBuffer buffer)
buffer
- Buffer whose DMO type must be registered for a pending flush.void forcePendingFlush(Persistence persistence, java.lang.Class<?> dmoClass)
persistence
- Persistence service object.dmoClass
- DMO implementation class for which flushing must occur.void markDirtyDMO(RecordChangeEvent event)
event
- Record change event.void stateChanged(RecordChangeEvent.Type type, RecordBuffer buffer, Persistable dmo, Persistable snapshot, java.util.Map<java.lang.String,java.util.List<java.lang.Integer>> properties, boolean setPendingFlush) throws PersistenceException
buffer
will receive the event.type
- Event type: insert, update, or delete.buffer
- Record buffer which originated the event.dmo
- Post-modification state of the affected DMO (in the
case where the modification is the deletion of the DMO from
the database, this will be the object instance which was
deleted).snapshot
- A deep copy of the DMO which reflects the state of the DMO as
it was first set into the record buffer. For a newly created
record, this will contain the primary key assigned to the
record. All other properties will be set to their default
values.properties
- A map whose keys are the names of those properties which have
been modified and whose values are lists of the indexes at
which those values are stored (indexed properties only). For
a simple property, the matching value will be
null
.PersistenceException
- if any error occurs within a listener while processing a
change event. Exceptions will be deferred until all listeners
have had a chance to process the event. If more than one
listener throws an exception, only the first will be rethrown
by this method; however, all will be logged.boolean requiresFlush(Persistence persistence, java.lang.String entityName)
dmoClass
.
A flush is required if any change has been made to a DMO of this type
in the current context, since the last time a session flush occurred.
If dmoClass
is null
, this method determines
that a flush is required if a DMO of any type has been modified
since the last time a session flush occurred.
This method will always recommend a flush when executing in a non-legacy client context.
persistence
- Persistence object which is the key under which the
interceptor object is stored.entityName
- DMO implementation class for which to check for pending flush,
or null
to check for any class.true
if a flush is required, else
false
.ChangeBroker.SessionInterceptor getInterceptor(Persistence persistence)
Interceptor
instance to be installed in the
next Hibernate Session
to be opened against the specified
database.persistence
- Persistence object which is the key under which the
interceptor object is stored.SessionInterceptor
instance.