@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.
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.
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.)
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.
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.
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.
When you need to load values with a timeout, or when you need to monitor a specific set of values, you can use
AttributeSubscriptionService
.
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.
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.)
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.
Modifier and Type | Method and Description |
---|---|
RowValues |
getAttributeValues(Forest forest,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes)
Returns attribute values for the given matrix of Rows and Attributes.
|
RowValues |
getAttributeValues(ForestSpec spec,
boolean strictSpec,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
Consumer<ValuesMeta> metaConsumer)
Loads and returns attribute values as
RowValues . |
default RowValues |
getAttributeValues(ForestSpec spec,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes)
Retrieves values for the given sets of attributes and rows.
|
RowValues |
getAttributeValues(ItemForest forest,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes)
Returns attribute values for the given matrix of Rows and Attributes.
|
RowValuesWithUpdateChecker |
getAttributeValuesWithUpdateChecker(ForestSpec spec,
LongList rows,
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. |
RowValuesWithUpdateChecker |
getAttributeValuesWithUpdateChecker(ItemForest forest,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
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. |
ConsistentRowValues |
getConsistentAttributeValues(ForestSpec spec,
boolean strictSpec,
Function<ItemForest,LongList> rowsSupplier,
Collection<? extends AttributeSpec<?>> attributes)
Performs consistent loading of values for the given rows and attributes.
|
default ConsistentRowValues |
getConsistentAttributeValues(ForestSpec spec,
Function<ItemForest,LongList> rowsSupplier,
Collection<? extends AttributeSpec<?>> attributes)
Performs consistent loading of values for the given rows and attributes.
|
ItemValues |
getItemValues(Collection<ItemIdentity> itemIds,
Collection<? extends AttributeSpec<?>> attributes)
Loads item-based values for the given items.
|
boolean |
hasUpdate(ForestSpec spec,
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.
|
boolean |
isItemAttribute(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.
|
void |
loadAttributeValues(Forest forest,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
AttributeValuesReceiver receiver)
This method loads values for the given sets of attributes and rows.
|
void |
loadAttributeValues(ForestSpec spec,
boolean strictSpec,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
AttributeValuesReceiver receiver)
This method loads values for the given sets of attributes and rows.
|
default void |
loadAttributeValues(ForestSpec spec,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
AttributeValuesReceiver receiver)
Loads values for the given sets of attributes and rows.
|
void |
loadAttributeValues(ItemForest itemForest,
LongList rows,
Collection<? extends AttributeSpec<?>> attributes,
AttributeValuesReceiver receiver)
This method loads values for the given sets of attributes and rows.
|
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.
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.
spec
- forest spec of the foreststrictSpec
- if true, do not optimize forest specrows
- rows to be loadedattributes
- attributes to be loadedreceiver
- the receiver of data and metadatadefault void loadAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes, @NotNull AttributeValuesReceiver receiver)
strictSpec
is false).
See loadAttributeValues(ForestSpec, boolean, LongList, Collection, AttributeValuesReceiver)
for details.spec
- forest spec of the forestrows
- rows to be loadedattributes
- attributes to be loadedreceiver
- the receiver of data and metadatavoid 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.
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.
itemForest
- forest to load value forrows
- rows to be loadedattributes
- attributes to be loadedreceiver
- the receiver of data and metadatavoid 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.
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.
forest
- forest to load value forrows
- rows to be loadedattributes
- attributes to be loadedreceiver
- the receiver of data and metadata@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.
spec
- forest spec of the foreststrictSpec
- if true, do not optimize forest specrows
- rows to be loadedattributes
- attributes to be loadedmetaConsumer
- the receiver of metadata, can be null@NotNull default RowValues getAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection<? extends AttributeSpec<?>> attributes)
strictSpec
is false) and does not accept
meta-information.
See getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer)
for details.spec
- forest spec of the forestrows
- rows to be loadedattributes
- attributes to be loaded@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
.
forest
- forest that contains the given rowsrows
- a collection of row IDs for which the values are neededattributes
- a collection of attribute specifications for the values that are needed@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.
forest
- forest that contains the given rows and information about each rowrows
- a collection of row IDs for which the values are neededattributes
- a collection of attribute specifications for the values that are needed@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.
spec
- forest spec of the foreststrictSpec
- if true, do not optimize forest specrowsSupplier
- a function that provides a list of rows to be loaded, given the current forestattributes
- attributes to be loadedrowsSupplier
StructureException
- if consistent load fails due to rapid updates in Jira or in the forest@NotNull default ConsistentRowValues getConsistentAttributeValues(@Nullable ForestSpec spec, @Nullable Function<ItemForest,LongList> rowsSupplier, @Nullable Collection<? extends AttributeSpec<?>> attributes) throws StructureException
strictSpec
is false). See getConsistentAttributeValues(ForestSpec, boolean, Function, Collection)
for details.spec
- forest spec of the forestrowsSupplier
- a function that provides a list of rows to be loaded, given the current forestattributes
- attributes to be loadedrowsSupplier
StructureException
- if consistent load fails due to rapid updates in Jira or in the forest@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.
itemIds
- items to be loadedattributes
- attributes to be loadedboolean 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.
attribute
- attribute@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.
forest
- forest that contains the given rows and information about each rowrows
- rows to be loadedattributes
- attributes to be loadedbaseForestSpec
- forest spec to be provided to the attribute loading context as the base (see AttributeContext.getBaseStructureId()
)@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.
spec
- forest spec of the forestrows
- rows to be loadedattributes
- attributes to be loaded@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.
spec
- forest spec of the forestrows
- rows that have been or about to be loadedattributes
- attributes to be loadedloadMeta
- metadata with versions that was previously received (see getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer)
, for example)Copyright © 2023 ALM Works. All Rights Reserved.