final class ForeignNuller
extends java.lang.Object
breakLinkages(java.util.List<com.goldencode.p2j.persist.ForeignNuller>, com.goldencode.p2j.persist.Persistable)
-- nulls out all foreign key references to a given DMO.
The nullification of these foreign references is necessary to prevent database errors caused by foreign key constraints when a record is deleted without first deleting dependent records related to the primary record by a foreign key. This may happen for one of two reasons:
Both of the above situations is possible and natural in Progress, due to the lack of database-level, referential integrity enforced by foreign key constraints. In a converted application running in the P2J environment, the introduction of foreign key relations between tables necessitates the special delete-time handling provided by this class.
Construction of an instance of this class is expensive, since it involves multiple method lookups using reflection. For this reason, instances of this class are cached across client contexts. Context-local state for each instance is managed within an instance of an inner class.
The actual breaking of linkages is also expensive in that it requires a database query to find all records which refer to a particular record by foreign key. However, the query uses the refering record's foreign key column, which is indexed, to make this lookup as fast as possible. Nevertheless, a tight loop of many deletes requiring this service will be quite expensive. An alternative to this expense would be to use the inverse association between the referent record and all of its refering records. This would require only a method call at linkage breakage time to collect refering DMOs. However, for this option to work, the DMO graph which holds set of associated records (one-to-many) or a single associated record (one-to-one) would have to be kept up to date in real time. This would require the record buffer to update these inverse associations each time a legacy foreign key property in the referent DMO is updated. This would probably add a more pervasive (and noticable) cost to the overall system. So,the lazy approach of looking up refering records at referent delete time was chosen instead.
RecordBuffer.delete()
Modifier and Type | Class and Description |
---|---|
private static class |
ForeignNuller.Context
Context local state maintained for the enclosing class.
|
private static class |
ForeignNuller.State
Object which stores a reference to a local DMO and its lock status
before its foreign reference is changed.
|
Modifier and Type | Field and Description |
---|---|
private static java.util.Map<RelationInfo,ForeignNuller> |
cache
Cache of nuller objects, keyed by RelationInfo instances
|
private ContextLocal<ForeignNuller.Context> |
context
Context local list of states managed by this object
|
private java.lang.reflect.Method |
foreignGetter
Method of local DMO which gets referent (foreign) DMO
|
private java.lang.reflect.Method |
foreignSetter
Method of local DMO which sets referent (foreign) DMO
|
private java.lang.String |
hql
HQL statement used to retrieve refering DMOs given a referent
|
private java.lang.reflect.Method |
localSetter
Method of foreign DMO which sets refering (local) DMO
|
private java.lang.String |
lockTarget
Name of table for which a lock must be acquired
|
private Persistence |
persistence
Persistence service object which performs record locking/unlocking
|
private boolean |
unique
Is relation uniquely constrained at the local table?
|
Modifier | Constructor and Description |
---|---|
private |
ForeignNuller(RecordBuffer buffer,
RelationInfo info)
Private constructor which is only accessed by the
instances(com.goldencode.p2j.persist.RecordBuffer)
factory method. |
Modifier and Type | Method and Description |
---|---|
private boolean |
acquireLocalLocks(Persistable referent)
Acquire an exclusive record lock for each DMO which references
referent by a foreign key. |
private boolean |
acquireLock(Persistable referer,
Persistable referent,
ForeignNuller.Context ctx)
Acquire an exclusive lock on
referer , then optionally
confirm that referer still has a foreign reference to
referent . |
(package private) static void |
breakLinkages(java.util.List<ForeignNuller> instances,
Persistable referent)
Set to
null all foreign references to referent
from related records. |
private void |
breakLinkages(Persistable referent)
Update the foreign key references from all DMOs currently refering to
referent , such that they will instead refer to
null . |
private static java.lang.String |
generateHQL(RelationInfo info)
Generate the HQL statement which will be used for the resolution of
refering records to referent record (via foreign key).
|
private void |
getReferers(Persistable referent,
ForeignNuller.Context ctx)
Compose a list of DMOs which refer to the
referent DMO via
foreign key. |
(package private) static java.util.List<ForeignNuller> |
instances(RecordBuffer buffer)
Retrieve a list of
ForeignNuller instances, one for each
inverse relation between the DMO represented by the business interface
dmoIface and another DMO which references this record via
a foreign key. |
private java.util.Iterator<Persistable> |
referers(Persistable referent,
ForeignNuller.Context ctx)
Return an iterator on all of the local DMOs related to the given,
foreign DMO.
|
private void |
resetLocalLocks()
Reset all refering DMOs' record locks to their previous states (before
an exclusive lock was acquired).
|
private static final java.util.Map<RelationInfo,ForeignNuller> cache
private final ContextLocal<ForeignNuller.Context> context
private final Persistence persistence
private final java.lang.String lockTarget
private final java.lang.String hql
private final java.lang.reflect.Method localSetter
private final java.lang.reflect.Method foreignGetter
private final java.lang.reflect.Method foreignSetter
private final boolean unique
private ForeignNuller(RecordBuffer buffer, RelationInfo info) throws PersistenceException
instances(com.goldencode.p2j.persist.RecordBuffer)
factory method.buffer
- RecordBuffer which defines the persistence service object and
database for this object.info
- Descriptor of the relation between the local and foreign DMOs.PersistenceException
- if a method cannot be found using reflection.static java.util.List<ForeignNuller> instances(RecordBuffer buffer) throws PersistenceException
ForeignNuller
instances, one for each
inverse relation between the DMO represented by the business interface
dmoIface
and another DMO which references this record via
a foreign key. Instances are first retrieved from a cross-context
cache, if present in the cache. Otherwise, they are lazily created and
stored in the cache.buffer
- Record buffer which holds the DMO representing a database
record which is about to be deleted, which may be referenced
via foreign key by other records.ForeignNuller
instances. May be empty,
but will not be null
.PersistenceException
- if there is an error creating a ForeignNuller
instance.static void breakLinkages(java.util.List<ForeignNuller> instances, Persistable referent) throws PersistenceException
null
all foreign references to referent
from related records. If there are no foreign key references to
referent
, this method returns immediately.
All records which are related to referent
are collected,
locked exclusively, and updated to null out their foreign reference back
to the record represented by referent
. Locks on these
methods are restored to their previous state when this operation is
complete.
No change is made unless all refering records can be locked. This method will attempt to obtain the locks repeatedly, until either all locks are obtained, or the user aborts locking.
instances
- ForeignNuller
instances which will be used to
break foreign key references to referent
.referent
- DMO representing a database record which is about to be
deleted, which may be referenced via foreign key by other
records.PersistenceException
- if any reflection error occurs getting dependent records or
setting foreign references;
if there was an error locating the refering DMOs.private static java.lang.String generateHQL(RelationInfo info)
info
- Relation descriptor.private boolean acquireLocalLocks(Persistable referent) throws PersistenceException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException
referent
by a foreign key. Each local DMO which so
references referent
is retrieved by invoking a getter
method for that record (in a one-to-one relation) or for a collection
of records (in a one-to-many relation).
A context-local list of state objects is stored for this
ForeignNuller
object (because it can be used across client
contexts). One ForeignNuller.State
object is added to this
list for each local DMO successfully locked. This context local list
will be removed when the current scope is closed.
referent
- DMO referenced by one or more refering DMOs via foreign key.true
if all required locks were acquired properly;
else false
.PersistenceException
- if there was an error locating the refering DMOs.java.lang.IllegalAccessException
- if the referent DMO's getter method for local DMO(s) is
inaccessible.java.lang.reflect.InvocationTargetException
- if the referent DMO's getter method for local DMO(s) throws an
exception.private boolean acquireLock(Persistable referer, Persistable referent, ForeignNuller.Context ctx) throws java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException
referer
, then optionally
confirm that referer
still has a foreign reference to
referent
. Create a new instance of the inner class
ForeignNuller.State
, which remembers the local DMO and the type
of lock held before acquiring the exclusive lock, and add it to the
states
list.referer
- DMO to be locked exclusively.referent
- DMO which should be referenced by referer
after
it is locked.ctx
- Current context for this object. Contains a list to which a
new State
instance will be added after the locking
and reference confirmation take place.true
if the lock was acquired AND the confirmation
of the referer to referent relationship was successful or
unnecessary; false
if the lock was acquired but
the confirmation failed.java.lang.IllegalAccessException
- if the local DMO's getter method for the foreign DMO is
inaccessible.java.lang.reflect.InvocationTargetException
- if the local DMO's getter method for the foreign DMO throws an
exception.private void resetLocalLocks()
State
objects which indicate what
the pre-lock status was. If in a transaction, the lock may be
downgraded if appropriate to the previous state, but it will not be
released until the end of the transaction.private void breakLinkages(Persistable referent) throws PersistenceException, java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException
referent
, such that they will instead refer to
null
. At the same time, remove the inverse association
from the referent record to the refering DMO, to maintain the correct
state on the other end of the bidirectional association (if indeed there
was such an inverse reference at all).referent
- DMO being deleted.PersistenceException
- if there was an error locating the refering DMOs.java.lang.IllegalAccessException
- if the refering DMO's setter method for the referent DMO is
inaccessible.java.lang.reflect.InvocationTargetException
- if the refering DMO's setter method for the referent DMO throws
an exception.private void getReferers(Persistable referent, ForeignNuller.Context ctx) throws PersistenceException
referent
DMO via
foreign key. This list is cached within a context local state object
for the life of the process which breaks these foreign key linkages, so
that it does not need to be retrieved separately for each stage of the
linkage break process (lock acquisition, DMO updates, lock reset). Only
the first call to this method during this cycle triggers the actual
database access; subsequent calls use the cached list.referent
- DMO to which zero or more refering DMOs have a foreign key
link.ctx
- Context local state object which stores the list of found DMOs.PersistenceException
- if an error occurs looking up the refering DMOs in the
database.private java.util.Iterator<Persistable> referers(Persistable referent, ForeignNuller.Context ctx) throws PersistenceException
referent
- DMO on the inverse end of the foreign key association.ctx
- Current context for this object, which maintains the list of
found referers.referent
via
foreign key.PersistenceException
- if there was any error locating the refering DMOs.