Interface AttributeLoader<T>
- Type Parameters:
T- type of the loaded value
- All Known Subinterfaces:
AggregateAttributeLoader<T>,DerivedAttributeLoader<T>,ItemAttributeLoader<T>,MultiRowAttributeLoader<T>,PropagateAttributeLoader<T>,RowAttributeLoader<T>,ScanningAttributeLoader<T>,SingleRowAttributeLoader<T>
- All Known Implementing Classes:
AbstractAggregateLoader,AbstractAttributeLoader,AbstractDerivedAttributeLoader,AbstractItemAttributeLoader,AbstractNaiveDistinctAggregateLoader,AbstractPropagateLoader,AbstractScanningLoader,AbstractSingleRowAttributeLoader,AttributeLoaderAdapter,BaseAttributeLoader,BaseDerivedAttributeLoader,BaseItemAttributeLoader,BaseSingleRowAttributeLoader,BiDerivedAttributeLoaderBuilder.BuiltBiDerivedLoader,CompositeAttributeLoader,DelegatingAggregateAttributeLoader,DelegatingAttributeLoader,DelegatingDerivedAttributeLoader,DelegatingItemAttributeLoader,DelegatingPropagateAttributeLoader,DelegatingRowAttributeLoader,DelegatingScanningAttributeLoader,InheritedValueLoader,IssueAttributeLoader,ItemClassAttributeLoader,ItemTypeAttributeLoader,LongSumLoader,NumberSumLoader,ReducingAggregateLoader,ScanningLongSumLoader,ScanningNumberSumLoader,SimpleDerivedAttributeLoader,SingleDependencyReducingAggregateLoader,UniDerivedAttributeLoaderBuilder.BuiltDerivedLoader
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.
Types of loaders
There are several types of attribute loaders, each represented by a separate interface extending
AttributeLoader:
ItemAttributeLoaderloads the attribute value for an item (identified byItemIdentity), without any knowledge of the structure / forest that contains this item. These values can be shared between multiple structures.- Row attribute loaders use forest information to calculate their value. Therefore, the result provided by a row loader
will be specific to the given forest.
SingleRowAttributeLoaderuses only row data for the row that is being calculated.- Multi-row loaders use row data for the row that is being calculated, but also for some additional rows.
AggregateAttributeLoaderuses the information calculated for children rows. Sum over a subtree is implemented with an aggregate.PropagateAttributeLoaderuses the information calculated for the parent row. Inheriting a value from parent is implemented with a propagate.ScanningAttributeLoaderuses the information calculated for the row that immediately precedes the current row in the table. A rolling total is implemented with a scanning loader.
DerivedAttributeLoaderis 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.
Dependencies
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.
State, concurrency, loader caching
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).
Discreteness and purity
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:
- A single
ItemAttributeLoader, which get the value of the issue'sfixVersionsand then accesses the versions service to get the release date. - Two loaders: the first, an
ItemAttributeLoader, loads the versions fromfixVersionsfield; the second, aDerivedAttributeLoader, 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.
- Can the intermediate result be used as a dependency for loading some other value? And is calculating it relatively costly?
(In our example,
fixVersionsfield 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.) - Can the derived result be used with some other intermediate attribute? (In our example, the derived attribute can be used to
calculate the release date based on any other attribute that provides versions, for example,
affectsVersionsfield.
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.)
Context dependencies
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.
Value caching
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().
Global trail
The global trail allows the loader to declare specific items that, if changed, cause all values calculated for this attribute to be invalidated.
Composite loading and using null as the result
It'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.)
Reentrancy
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.
Preloading
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.
-
Method Summary
Modifier and TypeMethodDescriptiondefault Set<AttributeSpec<?>>Returns attributes that need to be loaded prior to calling this loader's loading function.Returns the spec for which this loader loads values.default AttributeCachingStrategyIndicates how the values provided by this loader should be cached.default Set<AttributeContextDependency>Indicates which context values are used to calculate the value of the attribute.default TrailItemSetReturns global trail.
-
Method Details
-
getAttributeSpec
Returns the spec for which this loader loads values.
The returned value must be the same throughout the lifetime of the object.
- Returns:
- spec being loaded
-
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.
- Returns:
- the set of attribute dependencies, or
nullif there are none - See Also:
-
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.
- Returns:
- the set of context dependencies or
nullif there are none
-
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.
- Returns:
- caching strategy,
nullhas the same effect asAttributeCachingStrategy.MAY
-
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.
- Returns:
- global trail set, or
nullif none
-