Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
OTPattern/PerThreadRole
Contents
Intent
A team should adapt a certain base behaviour in a per-thread manner, where team state may differ for different control flows.
Motivation
Many teams intercept more than one join point in the base application and more often than not the team should preserve some state between interceptions.
In order to detect which interceptions should be seen as part of the same behavioural context object identity is not always sufficient, because different join points may be triggered at different base objects which are not always linked in a way that supports easy detection of object groups.
If the connection between different interceptions is actually best described by a control flow dependency the team should provide access to a reference object that represents the current control flow thus supporting to easily maintain per-thread state.
Structure
The team selects one role as the representative for a control flow. This is the role that observes the join point anchoring the observed control flow. The team provides a per-thread reference to an instance of this role class.
Participants
This pattern involves
- One ContextTeams spanning the overall context
- One ContextRole representing the individual per-thread contexts
- Any number of SecondaryRoles which may or may not be sub-divided into ObserverRoles and ActuatorRoles.
Collaboration
The ContextTeam holds the per thread reference like this
public team class ContextTeam { ThreadLocal<ContextRole> context = new ThreadLocal<ContextRole>(); protected class ContextRole ... }
The ContextRole intercepts the anchoring join point and registers itself at the ContextTeam for the duration of this join point:
... protected class ContextRole playedBy Base1 { // state variables: protected SomeType someState; // (un)registration: register <- replace anchorJoinpoint; callin void register() { ContextTeam.this.context.set(this); try { base.register(); } finally { ContextTeam.this.context.set(null); ContextTeam.this.unregisterRole(this, ContextRole.class); } } }
SecondaryRoles can now
- use availability of a ContextRole in their guards to restrict their activation to the given control flow
- access state variables of ContextRole (write and read) for passing information from one join point to the other
... protected class ObserverRole playedBy Base2 base when (ContextTeam.this.context.get() != null) { observer <- after observationPoint; void observer() { ContextTeam.this.context.get().someState = ... // some state computation } } protected class ActuatorRole playedBy Base3 base when (ContextTeam.this.context.get() != null) { actuate <- after triggerPoint when (someConditionUsing(ContextTeam.this.context.get())); void actuate() { ContextRole context = ContextTeam.this.context.get(); if (someOtherConditionUsing(context)) someActionUsing(context); } }
Note, how this pattern seemlessly supports the ObserversMediatorActuators architectural pattern.
Implementation
The above implementation makes access to the enclosing team explicit using ContextTeam.this.context
throughout. Of course the abbreviated use of context
is possible, too.
If a guard accesses state of the ContextRole this is doubly secured:
- the top-level guard checkes this reference for
null
- if the top-level guard were omitted, any
NullPointerException
within the guard would simply cause the guard to evaluate to false (see OTJLD §5.4.c).
Known Uses
This pattern was first observed in the org.objectteams.otdt.pde.ui
plugin in team class org.objectteams.otdt.pde.validation.BundleValidation
(source in SVN). Here role BundleCheckingContext
is the per-thread context role.
Related Patterns
This pattern is well suited as a supporting pattern for the ObserversMediatorActuators archtectural pattern.
For some situations an even simpler solution can be achieved using AssociateByNesting.