All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.api.attribute.StructureAttributeService Maven / Gradle / Ivy

There is a newer version: 17.25.3
Show newest version
package com.almworks.jira.structure.api.attribute;

import com.almworks.integers.LongList;
import com.almworks.jira.structure.api.attribute.loader.*;
import com.almworks.jira.structure.api.attribute.subscription.AttributeSubscriptionService;
import com.almworks.jira.structure.api.auth.StructureAuth;
import com.almworks.jira.structure.api.error.StructureException;
import com.almworks.jira.structure.api.forest.ForestService;
import com.almworks.jira.structure.api.forest.ForestSpec;
import com.almworks.jira.structure.api.forest.item.ItemForest;
import com.almworks.jira.structure.api.forest.raw.Forest;
import com.almworks.jira.structure.api.item.ItemIdentity;
import com.almworks.jira.structure.api.row.RowManager;
import com.almworks.jira.structure.api.row.SuperRootRow;
import com.almworks.jira.structure.api.settings.AttributeSensitivitySettings;
import com.atlassian.annotations.Internal;
import com.atlassian.annotations.PublicApi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 

{@code 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 {@link 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 {@code 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 {@link 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 {@link ForestSpec} as a parameter, then forest-dependent attributes (like totals) will be cached, * but if you're passing arbitrary {@link 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, {@code 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 {@code StructureAttributeService}, you can use {@code -1} (or {@link 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 {@code 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 {@link ForestService}. Attribute service will try * to load faster values first, so you can use {@link #loadAttributeValues} with {@link 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 * {@link 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 {@link AttributeValue#undefined()}. The aggregating values like totals, which * may aggregate values from multiple rows, will behave according to {@link 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 {@link StructureAuth#sudo}, 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 * {@link 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 {@link #getConsistentAttributeValues} methods, but know that this is a costly operation, because * the loading will happen at least two times.

* *

Methods that receive {@code ItemForest} or {@code Forest} as a parameter (instead of {@code 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 AttributeSpec * @see ForestSpec * @see RowValues * @see CoreAttributeSpecs * @see AttributeSubscriptionService * @see AttributeSensitivitySettings */ @PublicApi public interface StructureAttributeService { /** *

This method loads values for the given sets of attributes and rows. The values are delivered by calling {@link AttributeValuesReceiver#receiveValues} * method of the provided receiver.

* *

The forest is identified by {@link ForestSpec}, and the forest-dependent values will be cached. You can pass both secured and unsecured * forest specs (see {@link 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 {@code strictSpec} parameter to {@code 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 {@link 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.

* * @param spec forest spec of the forest * @param strictSpec if true, do not optimize forest spec * @param rows rows to be loaded * @param attributes attributes to be loaded * @param receiver the receiver of data and metadata */ void loadAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable LongList rows, @Nullable Collection> 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 ({@code strictSpec} is false). * See {@link #loadAttributeValues(ForestSpec, boolean, LongList, Collection, AttributeValuesReceiver)} * for details. * * @param spec forest spec of the forest * @param rows rows to be loaded * @param attributes attributes to be loaded * @param receiver the receiver of data and metadata */ default void loadAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection> attributes, @NotNull AttributeValuesReceiver receiver) { loadAttributeValues(spec, false, rows, attributes, receiver); } /** *

This method loads values for the given sets of attributes and rows. The values are delivered by calling {@link AttributeValuesReceiver#receiveValues} * method of the provided receiver.

* *

The forest is provided to this method as {@link ItemForest}. Since the forest is not identified by {@link 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 {@link 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.

* * @param itemForest forest to load value for * @param rows rows to be loaded * @param attributes attributes to be loaded * @param receiver the receiver of data and metadata */ void loadAttributeValues(@Nullable ItemForest itemForest, @Nullable LongList rows, @Nullable Collection> attributes, @NotNull AttributeValuesReceiver receiver); /** *

This method loads values for the given sets of attributes and rows. The values are delivered by calling {@link AttributeValuesReceiver#receiveValues} * method of the provided receiver.

* *

The forest is provided to this method as {@link Forest}. The rows in the forest are supposed to be actual rows, managed by * {@link RowManager}. Since the forest is not identified by {@link 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 {@link 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.

* * @param forest forest to load value for * @param rows rows to be loaded * @param attributes attributes to be loaded * @param receiver the receiver of data and metadata */ void loadAttributeValues(@Nullable Forest forest, @Nullable LongList rows, @Nullable Collection> attributes, @NotNull AttributeValuesReceiver receiver); /** *

Loads and returns attribute values as {@link RowValues}. This may be a more convenient method than {@link #loadAttributeValues}, * 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 {@link ForestSpec}, and the forest-dependent values will be cached. You can pass both secured and unsecured * forest specs (see {@link 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 {@code strictSpec} parameter to {@code 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.

* * @param spec forest spec of the forest * @param strictSpec if true, do not optimize forest spec * @param rows rows to be loaded * @param attributes attributes to be loaded * @param metaConsumer the receiver of metadata, can be null * @return value matrix, which contains the values for requested attributes and rows */ @NotNull RowValues getAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable LongList rows, @Nullable Collection> attributes, @Nullable Consumer metaConsumer); /** * Retrieves values for the given sets of attributes and rows. * A convenience method that assumes that the passed forest spec can be optimized ({@code strictSpec} is false) and does not accept * meta-information. * See {@link #getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer)} * for details. * * @param spec forest spec of the forest * @param rows rows to be loaded * @param attributes attributes to be loaded * @return value matrix, which contains the values for requested attributes and rows */ @NotNull default RowValues getAttributeValues(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection> attributes) { return getAttributeValues(spec, false, rows, attributes, null); } /** *

Returns attribute values for the given matrix of Rows and Attributes. The value is retrieved for each * {@code (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 {@link ForestSpec}, use * {@link #getAttributeValues(ForestSpec, LongList, Collection)}.

* *

The rows in the forest are supposed to be actual rows, managed by {@link RowManager}.

* * @param forest forest that contains the given rows * @param rows a collection of row IDs for which the values are needed * @param attributes a collection of attribute specifications for the values that are needed * @return value matrix, which contains the values for requested attributes and rows */ @NotNull RowValues getAttributeValues(@Nullable Forest forest, @Nullable LongList rows, @Nullable Collection> attributes); /** *

Returns attribute values for the given matrix of Rows and Attributes. The value is retrieved for each * {@code (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 {@link ForestSpec}, use * {@link #getAttributeValues(ForestSpec, LongList, Collection)}.

* *

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

* * @param forest forest that contains the given rows and information about each row * @param rows a collection of row IDs for which the values are needed * @param attributes a collection of attribute specifications for the values that are needed * @return value matrix, which contains the values for requested attributes and rows */ @NotNull RowValues getAttributeValues(@Nullable ItemForest forest, @Nullable LongList rows, @Nullable Collection> attributes); /** *

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 * {@link 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 {@link ConsistentRowValues} also contains the forest snapshot.

* *

The forest is identified by {@link ForestSpec}, and the forest-dependent values will be cached. You can pass both secured and unsecured * forest specs (see {@link 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 {@code strictSpec} parameter to {@code true}.

* *

To identify the rows to be loaded, pass {@code 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.

* * @param spec forest spec of the forest * @param strictSpec if true, do not optimize forest spec * @param rowsSupplier a function that provides a list of rows to be loaded, given the current forest * @param attributes attributes to be loaded * @return a matrix with consistent values, plus the forest that was used to load them and the rows provided by {@code rowsSupplier} * @throws StructureException if consistent load fails due to rapid updates in Jira or in the forest */ @NotNull ConsistentRowValues getConsistentAttributeValues(@Nullable ForestSpec spec, boolean strictSpec, @Nullable Function rowsSupplier, @Nullable Collection> 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 ({@code strictSpec} is false). See {@link #getConsistentAttributeValues(ForestSpec, boolean, Function, Collection)} for details. * * @param spec forest spec of the forest * @param rowsSupplier a function that provides a list of rows to be loaded, given the current forest * @param attributes attributes to be loaded * @return a matrix with consistent values, plus the forest that was used to load them and the rows provided by {@code rowsSupplier} * @throws StructureException if consistent load fails due to rapid updates in Jira or in the forest */ @NotNull default ConsistentRowValues getConsistentAttributeValues(@Nullable ForestSpec spec, @Nullable Function rowsSupplier, @Nullable Collection> attributes) throws StructureException { return getConsistentAttributeValues(spec, false, rowsSupplier, attributes); } /** *

Loads item-based values for the given items.

* *

The passed attributes are supposed to be forest-independent (like {@link CoreAttributeSpecs#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.

* * @param itemIds items to be loaded * @param attributes attributes to be loaded * @return an item-based matrix of values */ @NotNull ItemValues getItemValues(@Nullable Collection itemIds, @Nullable Collection> attributes); /** *

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 {@link #getItemValues}.

* *

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

* * @param attribute attribute * @return true if the attribute is item-based */ boolean isItemAttribute(@Nullable AttributeSpec attribute); /** *

Loads the values for the given rows and attributes, plus provides an instance of {@link 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.

* * @param forest forest that contains the given rows and information about each row * @param rows rows to be loaded * @param attributes attributes to be loaded * @param baseForestSpec forest spec to be provided to the attribute loading context as the base (see {@link AttributeLoaderContext#getBaseStructureId()}) * @return requested values and the checking interface */ @Internal @NotNull RowValuesWithUpdateChecker getAttributeValuesWithUpdateChecker(@Nullable ItemForest forest, @Nullable LongList rows, @Nullable Collection> attributes, @Nullable ForestSpec baseForestSpec); /** *

Loads the values for the given rows and attributes, plus provides an instance of {@link 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.

* * @param spec forest spec of the forest * @param rows rows to be loaded * @param attributes attributes to be loaded * @return requested values and the checking interface */ @Internal @NotNull RowValuesWithUpdateChecker getAttributeValuesWithUpdateChecker(@Nullable ForestSpec spec, @Nullable LongList rows, @Nullable Collection> attributes); /** *

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

* *

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

* * @param spec forest spec of the forest * @param rows rows that have been or about to be loaded * @param attributes attributes to be loaded * @param loadMeta metadata with versions that was previously received (see {@link #getAttributeValues(ForestSpec, boolean, LongList, Collection, Consumer)}, for example) * @return true if there may be an update for at least one of the given rows and attributes */ @Internal boolean hasUpdate(@Nullable ForestSpec spec, @Nullable LongList rows, Collection> attributes, ValuesMeta loadMeta); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy