T
- type of the loaded value@PublicSpi
public interface AttributeLoader<T>
An AttributeLoader
contains code that loads values for a particular attribute, represented by AttributeSpec
.
Instances of AttributeLoader
are created by AttributeLoaderProvider
in response to a request with
a matching spec.
There are several types of attribute loaders, each represented by a separate interface extending
AttributeLoader
:
ItemAttributeLoader
loads the attribute value for an item (identified by ItemIdentity
),
without any knowledge of the structure / forest that contains this item. These values can be shared between
multiple structures.
SingleRowAttributeLoader
uses only row data for the row that is being calculated.AggregateAttributeLoader
uses the information calculated for children rows. Sum over a subtree is implemented with an aggregate.PropagateAttributeLoader
uses the information calculated for the parent row. Inheriting a value from parent is implemented
with a propagate.ScanningAttributeLoader
uses the information calculated for the row that immediately precedes the current row in the table.
A rolling total is implemented with a scanning loader.DerivedAttributeLoader
is calculated using only the dependencies. It does not need item or row information.Every loader must implement one and only one of the interfaces listed above.
Loaders may declare dependencies on other attributes by overriding getAttributeDependencies()
. To calculate a value, a loader may use the values of dependency attributes.
Attribute system guarantees that these values have been calculated and are available.
Dependencies are declared as a list of attribute specs. Each spec will be resolved to its own loader. If a spec is not resolved, or if there is a circular dependency, the attribute loader is considered faulty and the values won't be loaded.
An implementation of AttributeLoader
must be stateless. Typically, an instance of loader is created with some parameters
and references to the required services. The loader's main loading function may be called concurrently for different or for the same
items and rows.
An instance of loader is cached and reused to serve multiple requests coming from different users and for different forests. Unless
AttributeLoaderProvider
declares the loader non-cacheable, the loader should not assume that the current user at the loader
creation time will be the same as when loading function is called.
If a loader needs to store some information between the calls to the load function, use AttributeContext.putObject(java.lang.Object, java.lang.Object)
and AttributeContext.getObject(java.lang.Object)
.
When creating a loader for a value that requires doing several things, there's a question of whether to do everything in one loader, or create a chain of loaders with dependencies between them.
For example, an attribute that shows the issue release date based on the fixVersions
issue field and the planned release
date for those versions, could be implemented in two ways:
ItemAttributeLoader
, which get the value of the issue's fixVersions
and then accesses the versions service
to get the release date.ItemAttributeLoader
, loads the versions from fixVersions
field; the second, a
DerivedAttributeLoader
, receives the value from the first loader as a dependency and extracts the release date.In this example, the second option is clearly better. When deciding which implementation is better, consider the following.
fixVersions
field can be used to calculate the names of the versions. Retrieving the field value requires
getting the full Jira issue object, which is costly enough to justify the separation.)affectsVersions
field.Multi-row and derived loaders should be separated from getting values related to a particular item or row, which should be done by item
or single-row loaders. Multi-row and derived loaders should get input from dependencies and implement only the aggregation/propagation/scanning,
so that same inputs will always produce same results. (They can use StructureRow
and ItemIdentity
though -
see descriptions of the specific loader interface.)
If a loader uses some of the context information, such as the current user, it must declare
context dependencies by overriding getContextDependencies()
. The attribute system will then be aware of such dependency and implement correct caching and invalidation
for this and all depending attributes.
Note: sometimes the dependency on the user or user's locale is implicit – for example, if you render a web template to get an attribute result, the value would typically depend at least on the current user's locale.
One of the most important functions of the attribute subsystem is to cache the calculated values and invalidate / recalculate
them when needed. The loader may indicate how the values that it produces should be treated with getCachingStrategy()
.
The global trail allows the loader to declare specific items that, if changed, cause all values calculated for this attribute to be invalidated.
null
as the resultIt's possible to have multiple loaders in the system that load values for the same attribute. This typically means that each loader is able to load the value for specific types of items, or that the attribute was extended at some point to provide (or override) a value in some special case.
For example, SharedAttributeSpecs.SUMMARY
is a common attribute that represents an item in the Structure's main column.
There are multiple loaders for this attribute, each loading summary for a specific item type only.
The common contract for all types of loaders is that returning null
as the result of the calculation means
"I cannot work with this item/row, let some other loader try". However, if the loader is indeed responsible for
this item or row, but the value is missing or cannot be calculated for some reason, the loader should
return AttributeValue.undefined()
.
Since the system will try all loaders in sequence until some loader returns a non-null value, the order of loaders is important.
See AttributeLoaderProvider
about how to define in what order the attribute loaders are called.
(If you initialize CompositeAttributeLoader.create(AttributeSpec, List)
manually, the passed loaders will be invoked in the iteration order.)
The loaders are not supposed to interact with Attribute and Forest subsystems as client code. In particular, calling
StructureAttributeService.getAttributeValues(com.almworks.jira.structure.api.forest.ForestSpec, boolean, com.almworks.integers.LongList, java.util.Collection<? extends com.almworks.jira.structure.api.attribute.AttributeSpec<?>>, java.util.function.Consumer<com.almworks.jira.structure.api.attribute.ValuesMeta>)
or ForestService.getForestSource(com.almworks.jira.structure.api.forest.ForestSpec)
from within a loading function
may lead to re-entrancy issues and unexpected results, exceptions or endless cycle.
Typically, attribute system receives requests to load attributes for a number of rows or items. It may be more efficient for a loader to perform some bulk action before per-item or per-row loading function is called. This is called preloading and both item and row-based loaders support it.
Modifier and Type | Method and Description |
---|---|
default Set<AttributeSpec<?>> |
getAttributeDependencies()
Returns attributes that need to be loaded prior to calling this loader's loading function.
|
AttributeSpec<T> |
getAttributeSpec()
Returns the spec for which this loader loads values.
|
default AttributeCachingStrategy |
getCachingStrategy()
Indicates how the values provided by this loader should be cached.
|
default Set<AttributeContextDependency> |
getContextDependencies()
Indicates which context values are used to calculate the value of the attribute.
|
default TrailItemSet |
getGlobalTrail()
Returns global trail.
|
@NotNull AttributeSpec<T> getAttributeSpec()
Returns the spec for which this loader loads values.
The returned value must be the same throughout the lifetime of the object.
@Nullable default Set<AttributeSpec<?>> getAttributeDependencies()
Returns attributes that need to be loaded prior to calling this loader's loading function.
The returned value must be the same throughout the lifetime of the object.
null
if there are noneAttributeLoaderContext.getDependencyValue(com.almworks.jira.structure.api.attribute.AttributeSpec<V>)
@Nullable default Set<AttributeContextDependency> getContextDependencies()
Indicates which context values are used to calculate the value of the attribute.
For example, if the calculated value depends on the current user, the return value from this method must include
AttributeContextDependency.USER
.
The returned value must be the same throughout the lifetime of the object.
null
if there are none@Nullable default AttributeCachingStrategy getCachingStrategy()
Indicates how the values provided by this loader should be cached.
The returned value must be the same throughout the lifetime of the object.
null
has the same effect as AttributeCachingStrategy.MAY
@Nullable default TrailItemSet getGlobalTrail()
Returns global trail. When an item from a global trail changes, all values calculated for this attribute will be invalidated and will need to be recalculated.
The returned value must be the same throughout the lifetime of the object.
null
if noneCopyright © 2023 ALM Works. All Rights Reserved.