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

@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.

Types of loaders

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.
  • 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.
    • SingleRowAttributeLoader uses 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.
      • 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.

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's fixVersions and then accesses the versions service to get the release date.
  • Two loaders: the first, an 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.

  • Can the intermediate result be used as a dependency for loading some other value? And is calculating it relatively costly? (In our example, 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.)
  • 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, 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.)

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.

See Also:
  • Method Details

    • getAttributeSpec

      @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.

      Returns:
      spec being loaded
    • getAttributeDependencies

      @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.

      Returns:
      the set of attribute dependencies, or null if there are none
      See Also:
    • getContextDependencies

      @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.

      Returns:
      the set of context dependencies or null if there are none
    • getCachingStrategy

      @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.

      Returns:
      caching strategy, null has the same effect as AttributeCachingStrategy.MAY
    • getGlobalTrail

      @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.

      Returns:
      global trail set, or null if none