com.almworks.jira.structure.api.attribute.StructureAttributeService Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
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 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 ({@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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 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 ({@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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> 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 extends AttributeSpec>> attributes, ValuesMeta loadMeta);
}