Interface StructureAttributeService


@PublicApi public interface StructureAttributeService

StructureAttributeService provides a unified way to retrieve data for items. The data can include item fields, aggregate values, attributes defined by Structure extensions, and more. Most of the per-issue or per-row data that Structure displays in the structure grid is provided through attributes.

An attribute is an abstraction that lets the consumer of this service not care about what type of value is needed, where it comes from, how to calculate it, how to cache it and what type of items is this value defined for.

Furthermore, attribute system is extensible, so a third-party add-on can use Attributes SPI to define their own attribute or extend how existing attributes work for a new type of items. See AttributeLoaderProvider for details.

Bulk Processing

This service is intended to be used for retrieving values for multiple attributes of multiple rows in one go. Whenever you need to retrieve multiple values, try to minimize the number of calls to StructureAttributeService for best performance.

Caching

Attributes service will do its best to cache the results and not recalculate values when it's not needed. If a value does not depend on the forest (for example, an issue's key), it will be reused when displaying this value in different forests. If a value is dependent on user's locale, the cache will store per-locale values in the cache. For comprehensive information, see AttributeLoader about how attribute loaders are defined.

All caching happens behind the scenes, so the client code does not need to worry about it. Remember, however, that if you call methods that receive ForestSpec as a parameter, then forest-dependent attributes (like totals) will be cached, but if you're passing arbitrary ItemForest, these attributes will be calculated every time. (Attributes that are not dependent on the forest will be cached in either case.)

Row mapping and missing rows

Normally, if you request a value for a row ID that is not in the forest or that is missing from the system, you'll get an undefined value in return. However, StructureAttributeService additionally checks if the row is a copy of another row, made by a generator. In that case the calculation is performed for the original row. The resulting value can be retrieved from the result by the row ID that was requested, so this conversion is transparent to the caller.

Note that the values for forest-independent attributes may actually load for rows that are not in the forest at all, as long as the rows exist in the system. For example, they might already be deleted from the forest.

Super-root row

Whenever you pass row IDs to StructureAttributeService, you can use -1 (or SuperRootRow.SUPER_ROOT_ROW_ID) to get the values calculated for the "super-root", a fictional row at the very top of the forest, having the actual forest roots as its children.

This allows you to calculate totals and other aggregates across the whole forest. Any attributes that are not based on aggregates will have undefined value for the super-root.

Performance and receiver interface

Initial loading of values may take an arbitrarily long time. It depends on how quickly the loaders will provide values and, for forest-dependent attributes, how quickly the forest will be provided by ForestService. Attribute service will try to load faster values first, so you can use loadAttributeValues(com.almworks.jira.structure.api.forest.ForestSpec, boolean, com.almworks.integers.LongList, java.util.Collection<? extends com.almworks.jira.structure.api.attribute.AttributeSpec<?>>, com.almworks.jira.structure.api.attribute.AttributeValuesReceiver) with AttributeValuesReceiver to extract some values before everything else is loaded.

Once the values are loaded and cached, subsequent calls to load the same values should execute very quickly. When some items or the forest change, only those values that are affected will be recalculated.

Subscriptions

When you need to load values with a timeout, or when you need to monitor a specific set of values, you can use AttributeSubscriptionService.

Permissions

All values are calculated based on the current user's permissions. If the user does not have access to a particular item, they will not receive any value or will receive AttributeValue.undefined(). The aggregating values like totals, which may aggregate values from multiple rows, will behave according to AttributeSensitivitySettings.

Once it is determined that the user can have access to the value, that value is shared with all other users who have the same access.

You can override security checks with StructureAuth.sudo(com.atlassian.jira.user.ApplicationUser, boolean, com.almworks.jira.structure.api.util.CallableE<R, E>), but that is not recommended, since the system may not use caches in such case.

Consistency

By default, the values loaded from the service are eventually consistent. More specifically, if there are no item updates and no forest updates after time T1, then the service will provide consistent values at some time T2 > T1 and will continue providing consistent values at least until there's a new update in Jira or in structures.

Given constant flow of changes, the values may be temporarily inconsistent, but if you continue loading them (for example, through AttributeSubscriptionService), they will become consistent at some point.

An example of inconsistency: total story points number is calculated over the whole forest, and the total includes story points from a certain issue twice. This may happen in a rare case when multiple users calculate the total at the same time, and at the same time an issue is moved from one place in the forest to another.

If it is critical to get consistent values, use getConsistentAttributeValues(com.almworks.jira.structure.api.forest.ForestSpec, boolean, java.util.function.Function<com.almworks.jira.structure.api.forest.item.ItemForest, com.almworks.integers.LongList>, java.util.Collection<? extends com.almworks.jira.structure.api.attribute.AttributeSpec<?>>) methods, but know that this is a costly operation, because the loading will happen at least two times.

Methods that receive ItemForest or Forest as a parameter (instead of ForestSpec) are consistent with the forest, because the forest is constant and doesn't change during the loading. (The issues in Jira may change though.)

Threads and thread-safety

All methods of this service are synchronous and will not offload work to any other thread. The methods are thread-safe and the service is optimized for concurrent use.

See Also:
  • Method Details

    • loadAttributeValues

      void loadAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @NotNull AttributeValuesReceiver receiver)

      This method loads values for the given sets of attributes and rows. The values are delivered by calling AttributeValuesReceiver.receiveValues(com.almworks.jira.structure.api.attribute.AttributeSpec<T>, com.almworks.jira.structure.api.attribute.ValueColumn<java.lang.Long, T>) method of the provided receiver.

      The forest is identified by ForestSpec, and the forest-dependent values will be cached. You can pass both secured and unsecured forest specs (see ForestSpec.secure(String)) - if you use a secured spec, the system may optimize it and use an unsecured one so the calculated values may be shared with other users. If you'd like the spec to be used exactly as passed, set strictSpec parameter to true.

      In any case, the system will make sure that the resulting values do not contain data that the current user must not see. The difference between using a strictly secured and unsecured spec is whether multi-row values (like aggregates) will account for the items that exist in the forest but are invisible for the user.

      Performance and outdated values

      The values will be sent to the receiver as soon as they are available. The receiver may get values for the same attributes and rows multiple times. An implementation of AttributeValuesReceiver would typically store the values in a buffer and allow concurrent access to that buffer so the values can be accessed while the loading is still ongoing.

      Parameters:
      spec - forest spec of the forest
      strictSpec - if true, do not optimize forest spec
      rows - rows to be loaded
      attributes - attributes to be loaded
      receiver - the receiver of data and metadata
    • loadAttributeValues

      default void loadAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @NotNull AttributeValuesReceiver receiver)
      Loads values for the given sets of attributes and rows. A convenience method that assumes that the passed forest spec can be optimized (strictSpec is false). See loadAttributeValues(ForestSpec, boolean, LongList, Collection, AttributeValuesReceiver) for details.
      Parameters:
      spec - forest spec of the forest
      rows - rows to be loaded
      attributes - attributes to be loaded
      receiver - the receiver of data and metadata
    • loadAttributeValues

      void loadAttributeValues(@Nullable ItemForest itemForest, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @NotNull AttributeValuesReceiver receiver)

      This method loads values for the given sets of attributes and rows. The values are delivered by calling AttributeValuesReceiver.receiveValues(com.almworks.jira.structure.api.attribute.AttributeSpec<T>, com.almworks.jira.structure.api.attribute.ValueColumn<java.lang.Long, T>) method of the provided receiver.

      The forest is provided to this method as ItemForest. Since the forest is not identified by ForestSpec, the forest-dependent values calculated with this method cannot be cached. Item-based values, which are not dependent on the forest, will be cached.

      The system will check whether the user has access to the items referenced in the passed forest. It will make sure that the resulting values do not contain data that the current user must not see.

      Performance and outdated values

      The values will be sent to the receiver as soon as they are available. The receiver may get values for the same attributes and rows multiple times. An implementation of AttributeValuesReceiver would typically store the values in a buffer and allow concurrent access to that buffer so the values can be accessed while the loading is still ongoing.

      Parameters:
      itemForest - forest to load value for
      rows - rows to be loaded
      attributes - attributes to be loaded
      receiver - the receiver of data and metadata
    • loadAttributeValues

      void loadAttributeValues(@Nullable Forest forest, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @NotNull AttributeValuesReceiver receiver)

      This method loads values for the given sets of attributes and rows. The values are delivered by calling AttributeValuesReceiver.receiveValues(com.almworks.jira.structure.api.attribute.AttributeSpec<T>, com.almworks.jira.structure.api.attribute.ValueColumn<java.lang.Long, T>) method of the provided receiver.

      The forest is provided to this method as Forest. The rows in the forest are supposed to be actual rows, managed by RowManager. Since the forest is not identified by ForestSpec, the forest-dependent values calculated with this method cannot be cached. Item-based values, which are not dependent on the forest, will be cached.

      The system will check whether the user has access to the items referenced in the passed forest. It will make sure that the resulting values do not contain data that the current user must not see.

      Performance and outdated values

      The values will be sent to the receiver as soon as they are available. The receiver may get values for the same attributes and rows multiple times. An implementation of AttributeValuesReceiver would typically store the values in a buffer and allow concurrent access to that buffer so the values can be accessed while the loading is still ongoing.

      Parameters:
      forest - forest to load value for
      rows - rows to be loaded
      attributes - attributes to be loaded
      receiver - the receiver of data and metadata
    • getAttributeValues

      @NotNull RowValues getAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @Nullable Consumer<ValuesMeta> metaConsumer)

      Loads and returns attribute values as RowValues. This may be a more convenient method than loadAttributeValues(com.almworks.jira.structure.api.forest.ForestSpec, boolean, com.almworks.integers.LongList, java.util.Collection<? extends com.almworks.jira.structure.api.attribute.AttributeSpec<?>>, com.almworks.jira.structure.api.attribute.AttributeValuesReceiver), since there's no need to implement the receiver. However, the calling code will not be able to use any values before everything is loaded.

      The forest is identified by ForestSpec, and the forest-dependent values will be cached. You can pass both secured and unsecured forest specs (see ForestSpec.secure(String)) - if you use a secured spec, the system may optimize it and use an unsecured one so the calculated values may be shared with other users. If you'd like the spec to be used exactly as passed, set strictSpec parameter to true.

      In any case, the system will make sure that the resulting values do not contain data that the current user must not see. The difference between using a strictly secured and unsecured spec is whether multi-row values (like aggregates) will account for the items that exist in the forest but are invisible for the user.

      The returned value is immutable.

      Parameters:
      spec - forest spec of the forest
      strictSpec - if true, do not optimize forest spec
      rows - rows to be loaded
      attributes - attributes to be loaded
      metaConsumer - the receiver of metadata, can be null
      Returns:
      value matrix, which contains the values for requested attributes and rows
    • getAttributeValues

      @NotNull default RowValues getAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes)
      Retrieves values for the given sets of attributes and rows. A convenience method that assumes that the passed forest spec can be optimized (strictSpec is false) and does not accept meta-information. See getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer) for details.
      Parameters:
      spec - forest spec of the forest
      rows - rows to be loaded
      attributes - attributes to be loaded
      Returns:
      value matrix, which contains the values for requested attributes and rows
    • getAttributeValues

      @NotNull RowValues getAttributeValues(@Nullable Forest forest, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes)

      Returns attribute values for the given matrix of Rows and Attributes. The value is retrieved for each (row, attribute) pair from the collections of rows and attributes passed as parameters.

      The values are not cached because this method accepts an arbitrary forest. If you need to calculate values for a structure or other forest that can be identified with ForestSpec, use getAttributeValues(ForestSpec, LongList, Collection).

      The rows in the forest are supposed to be actual rows, managed by RowManager.

      Parameters:
      forest - forest that contains the given rows
      rows - a collection of row IDs for which the values are needed
      attributes - a collection of attribute specifications for the values that are needed
      Returns:
      value matrix, which contains the values for requested attributes and rows
    • getAttributeValues

      @NotNull RowValues getAttributeValues(@Nullable ItemForest forest, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes)

      Returns attribute values for the given matrix of Rows and Attributes. The value is retrieved for each (row, attribute) pair from the collections of rows and attributes passed as parameters.

      The values are not cached because this method accepts an arbitrary forest. If you need to calculate values for a structure or other forest that can be identified with ForestSpec, use getAttributeValues(ForestSpec, LongList, Collection).

      This method lets you calculate values for temporary rows, which are not yet known to RowManager. By providing an instance of ItemForest, you have StructureAttributeService bypass going to row manager for row data. Typically, negative row ID numbers are used for temporary rows.

      Parameters:
      forest - forest that contains the given rows and information about each row
      rows - a collection of row IDs for which the values are needed
      attributes - a collection of attribute specifications for the values that are needed
      Returns:
      value matrix, which contains the values for requested attributes and rows
    • getConsistentAttributeValues

      @NotNull ConsistentRowValues getConsistentAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable Function<ItemForest,LongList> rowsSupplier, @Nullable Collection<? extends AttributeSpec<?>> attributes) throws StructureException

      Performs consistent loading of values for the given rows and attributes. It is guaranteed that the loaded values represent a consistent "snapshot" of the requested values at a certain point in time.

      More specifically, for any values A and B in the result, if A is loaded first and B is loaded second, it is guaranteed that after B has finished loading, A hasn't changed. The guarantee eliminates the possibility of temporarily inconsistent values as described in StructureAttributeService.

      To achieve consistent loading, the service will perform additional checks and may load the same values multiple times. In a highly active system, the method may execute for an arbitrary long time or result in an exception if consistent loading was not possible because of the constantly changing data.

      Note that ConsistentRowValues also contains the forest snapshot.

      The forest is identified by ForestSpec, and the forest-dependent values will be cached. You can pass both secured and unsecured forest specs (see ForestSpec.secure(String)) - if you use a secured spec, the system may optimize it and use an unsecured one so the calculated values may be shared with other users. If you'd like the spec to be used exactly as passed, set strictSpec parameter to true.

      To identify the rows to be loaded, pass rowsSupplier function instead of a collection of rows. This is needed because the forest may change in parallel with the loading and there's no guarantee the calling code may have the same forest as the one seen by the implementation of this method.

      Parameters:
      spec - forest spec of the forest
      strictSpec - if true, do not optimize forest spec
      rowsSupplier - a function that provides a list of rows to be loaded, given the current forest
      attributes - attributes to be loaded
      Returns:
      a matrix with consistent values, plus the forest that was used to load them and the rows provided by rowsSupplier
      Throws:
      StructureException - if consistent load fails due to rapid updates in Jira or in the forest
    • getConsistentAttributeValues

      @NotNull default ConsistentRowValues getConsistentAttributeValues(@Nullable ForestSpec spec, @Nullable Function<ItemForest,LongList> rowsSupplier, @Nullable Collection<? extends AttributeSpec<?>> attributes) throws StructureException
      Performs consistent loading of values for the given rows and attributes. A convenience method that assumes that the passed forest spec can be optimized (strictSpec is false). See getConsistentAttributeValues(ForestSpec, boolean, Function, Collection) for details.
      Parameters:
      spec - forest spec of the forest
      rowsSupplier - a function that provides a list of rows to be loaded, given the current forest
      attributes - attributes to be loaded
      Returns:
      a matrix with consistent values, plus the forest that was used to load them and the rows provided by rowsSupplier
      Throws:
      StructureException - if consistent load fails due to rapid updates in Jira or in the forest
    • getItemValues

      @NotNull ItemValues getItemValues(@Nullable Collection<ItemIdentity> itemIds, @Nullable Collection<? extends AttributeSpec<?>> attributes)

      Loads item-based values for the given items.

      The passed attributes are supposed to be forest-independent (like SharedAttributeSpecs.SUMMARY). Passing a forest-based attribute to this method will not result in an exception, but it is not defined what the loaded values will be.

      This method will use item-based cache for the values.

      Parameters:
      itemIds - items to be loaded
      attributes - attributes to be loaded
      Returns:
      an item-based matrix of values
    • isItemAttribute

      boolean isItemAttribute(@Nullable AttributeSpec<?> attribute)

      Checks if the attribute is based only on items, which means the value will not depend on a particular position of the item in the forest. Such attributes may be used with getItemValues(java.util.Collection<com.almworks.jira.structure.api.item.ItemIdentity>, java.util.Collection<? extends com.almworks.jira.structure.api.attribute.AttributeSpec<?>>).

      An attribute may change to item-based or to forest-based during runtime, if extension apps are installed.

      Parameters:
      attribute - attribute
      Returns:
      true if the attribute is item-based
    • getAttributeValuesWithUpdateChecker

      @Internal @NotNull RowValuesWithUpdateChecker getAttributeValuesWithUpdateChecker(@Nullable ItemForest forest, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @Nullable ForestSpec baseForestSpec)

      Loads the values for the given rows and attributes, plus provides an instance of AttributeUpdateChecker, which can be used to detect that the values may have changed and another loading is needed.

      This method is used internally by the generator engine to calculate intermediate values during forest generation.

      Parameters:
      forest - forest that contains the given rows and information about each row
      rows - rows to be loaded
      attributes - attributes to be loaded
      baseForestSpec - forest spec to be provided to the attribute loading context as the base (see AttributeContext.getBaseStructureId())
      Returns:
      requested values and the checking interface
    • getAttributeValuesWithUpdateChecker

      @Internal @NotNull RowValuesWithUpdateChecker getAttributeValuesWithUpdateChecker(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes)

      Loads the values for the given rows and attributes, plus provides an instance of AttributeUpdateChecker, which can be used to detect that the values may have changed and another loading is needed.

      This method is used internally by the generator engine to calculate values during forest transformation.

      Parameters:
      spec - forest spec of the forest
      rows - rows to be loaded
      attributes - attributes to be loaded
      Returns:
      requested values and the checking interface
    • hasUpdate

      @Internal boolean hasUpdate(@Nullable ForestSpec spec, @Nullable LongList rows, Collection<? extends AttributeSpec<?>> attributes, ValuesMeta loadMeta)

      This method checks if there may have been updates in the system since some previous loading of the given attributes.

      If this method returns false, loading the values for the given rows and attributes will provide the same values as before ( when the provided loadMeta was received). If this method returns true, the loading may result in different values.

      Parameters:
      spec - forest spec of the forest
      rows - rows that have been or about to be loaded
      attributes - attributes to be loaded
      loadMeta - metadata with versions that was previously received (see getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer), for example)
      Returns:
      true if there may be an update for at least one of the given rows and attributes