> ## Documentation Index
> Fetch the complete documentation index at: https://docs.activeviam.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cube Filters

## Introduction

In Atoti, it is possible to restrict the view of a cube by granting access to a specific set of members.
This is achieved using:

* Atoti entitlements: They provide a way to configure `ISubCubeProperties` context values characterizing the resources an authenticated user
  can access (see [Atoti Security](../security/atoti_access_control) section for more details).
* Cube Filters: They represent filters that can hide members of an Atoti cube for a given query.

In this article, we explain the use case behind cube filters and detail how these objects can be manipulated to reach the wanted result.

> Query filters generation is closely tied to the original MDX. Nevertheless, for the sake of clarity this topic is
> out of this article's scope. More details can be found in the [MDX Filtering](../mdx/filtering) page.

## Post-Processors and CubeFilters

Editing filters can be very handy, especially when working in an environment relying on automated MDX generation through
a UI or using a third-party component that may not offer the possibility of editing the final MDX.
An MDX generated filter may result in hiding the very members you want to do calculations on, thus producing confusing results.

Atoti Server allows post-processors to manipulate `ICubeFilter`s to remove such member restrictions.
This can be done using the Prefetcher API of the post-processors. `PrefetchRequest`s are requests emitted
by post-processors to notify the core product to prefetch the given aggregates on a given location with a defined scope
expressed using an `ICubeFilter`. Those requests are typically created by the `IPrefetcher` of a post-processor.
`IPrefetchRequest`s also hold a context that can be used to store computations made in the prefetcher, so that they can
be used within the post-processor's `compute()` method.

> **Note:** When building a Prefetch Request, the `ILocation` corresponding to the required data for the post-processor
> computation can be different from the one from the query. One should make sure to provide all the required locations to the `IPrefetchRequest`.

In a post-processors chain, filters are propagated along the chain. This mean that each post-processor may edit the cube filter
it received and pass it along to the underlying post-processors.

### Use Case

A classic example would be the calculation of day-to-day differences. The following query calculates the differences
of `contributors.COUNT` between each day and the day before.

```MDX theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
WITH
 Member Measures.YesterdayName AS [Time].[HistoricalDates].CurrentMember.NextMember.Name
 Member Measures.Diff AS [Time].[HistoricalDates].CurrentMember - [Time].[HistoricalDates].CurrentMember.NextMember
SELECT
  NON EMPTY {
    [Measures].[contributors.COUNT],
    [Measures].[Diff],
    [Measures].[YesterdayName]
  } ON COLUMNS,
  NON EMPTY {
    [Time].[HistoricalDates].[AsOfDate].Members
  } ON ROWS
  FROM [EquityDerivativesCube]

```

The above query yields the following result:

<Frame>
  <img src="https://mintcdn.com/activeviam/KszPZqdDnmT6EpJc/engine/java-sdk/6.1/assets/core/day-to-day-diff.png?fit=max&auto=format&n=KszPZqdDnmT6EpJc&q=85&s=9191cc2ec18cd1afe7874e1dcbdf1248" alt="" width="694" height="132" data-path="engine/java-sdk/6.1/assets/core/day-to-day-diff.png" />
</Frame>

Now let us change the initial MDX by selecting only the 24th and 25th of September.

```MDX theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
WITH
 Member Measures.YesterdayName AS [Time].[HistoricalDates].CurrentMember.NextMember.Name
 Member Measures.Diff AS [Time].[HistoricalDates].CurrentMember - [Time].[HistoricalDates].CurrentMember.NextMember
SELECT
  NON EMPTY {
    [Measures].[contributors.COUNT],
    [Measures].[Diff],
    [Measures].[YesterdayName]
  } ON COLUMNS,
  NON EMPTY {
    [Time].[HistoricalDates].[AsOfDate].Members
  } ON ROWS
  FROM (
    SELECT
    {
      [Time].[HistoricalDates].[AsOfDate].[Mon Sep 25 02:00:00 CEST 2017],
      [Time].[HistoricalDates].[AsOfDate].[Sun Sep 24 02:00:00 CEST 2017]
    } ON ROWS
    FROM [EquityDerivativesCube]
  )
```

The result becomes:

<Frame>
  <img src="https://mintcdn.com/activeviam/KszPZqdDnmT6EpJc/engine/java-sdk/6.1/assets/core/day-to-day-diff-filter.png?fit=max&auto=format&n=KszPZqdDnmT6EpJc&q=85&s=5279dcaa6ffa59077b59e17ddb736738" alt="" width="697" height="98" data-path="engine/java-sdk/6.1/assets/core/day-to-day-diff-filter.png" />
</Frame>

Filtering introduced a result change for the 24th of September. This is because the previous day has been hidden by the
Subselect, and so is not accessible through the top SELECT.

While it is possible to rewrite the MDX query without filtering to get the expected result, it can't be done by pure UI interactions.
Even worse, the user may be completely stuck if the UI does not provide a way to edit the underlying MDX query (Excel, for example).

For such use cases, the `ICubeFilter` API provides the capability to directly edit the resulting *Cube Filters*,
ignoring the query filter when needed and allowing more granular control over the queried space.

### Building CubeFilters

`ICubeFilter`s are immutable objects that once built cannot be modified. An `ICubeFilter` can only be obtained by calling the `ICubeFilterBuilder.build()` method.

There are two approaches for building a Cube Filter:

* From scratch using `CubeFilter.builder()`. This call will provide an `ICubeFilterBuilder` with no access restrictions by default.
* From an existing `ICubeFilter` using  `ICubeFilter.edit()`. This will provide a builder including a **copy** of the permissions of the input `ICubeFilter`.

The fluent `ICubeFilterBuilder` API allows you to:

* Specify access to some measure with `ICubeFilterBuilder.includeMeasure(String measureName)`.

* Grant or deny access to some members with various `ICubeFilterBuilder.includeMembers(...)` methods  or
  `ICubeFilterBuilder.excludeMembers(...)` methods, respectively. The input of these methods specify the dimension, hierarchy, and the member path of the required members.
  Alternatively, `ICubeFilterBuilder.includeMembersWithConditions(...)` methods and `ICubeFilterBuilder.excludeMembersWithConditions(...)`
  support, on top of the member path specification, `ICondition` conditions to express more complex filtering rules.

* Intersect a given filter with another pre-existing filter with `ICubeFilterBuilder.intersect(ICubeFilter filter)`.

* Clear restrictions on a given hierarchy using `ICubeFilter.clear(...)` methods.

The final `ICubeFilter` instance can be obtained by calling the `ICubeFilterBuilder.build()` method.

> If the path to the members to grant access to is incomplete, then all members below are granted.

**Examples:**

```java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
CubeFilter.builder()
    .includeMembers(new HierarchyIdentifier("Booking", "Desk"), "AllMember", "DeskA", "Book3")
    .build();
```

Creates a filter that has access to measures with a member path "AllMember/DeskA/Book3".

```java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
CubeFilter.builder()
    .excludeMembersWithConditions(
        new HierarchyIdentifier("Booking", "Desk"),
        new NotCondition(new EqualCondition("DeskA")))
    .build();
```

Creates a filter that has access to measures of members that do not correspond to "DeskA".

### TrendPostProcessor Example

The following example illustrates the usage of the `CubeFilter` API in a Trend post-processor.

The `CubeFilter` built when defining the `IPrefetcher` relative to the post-processor (see `init()` and `createPrefetchers()`)
will dynamically allow access to the correct dates through the filter created in `computeFilter()`. The `computeLocation()` method
will include the location of all the data needed for performing the post-processor's computations.

```java theme={"languages":{"custom":["/engine/python-sdk/0.9/languages/pycon.tmLanguage.json"]}}
/* (C) ActiveViam 2020
 * ALL RIGHTS RESERVED. This material is the CONFIDENTIAL and PROPRIETARY
 * property of ActiveViam Ltd. Any unauthorized use,
 * reproduction or transfer of this material is strictly prohibited
 */
package com.activeviam.biz.pivot.filtering.impl;
import com.activeviam.activepivot.core.ext.api.postprocessing.fwk.AAdvancedPostProcessor;
import com.activeviam.activepivot.core.ext.api.postprocessing.fwk.APrefetcher;
import com.activeviam.activepivot.core.impl.api.cube.hierarchy.HierarchiesUtil;
import com.activeviam.activepivot.core.impl.api.location.LocationUtil;
import com.activeviam.activepivot.core.intf.api.contextvalues.subcube.ICubeFilter;
import com.activeviam.activepivot.core.intf.api.cube.hierarchy.IAxisHierarchy;
import com.activeviam.activepivot.core.intf.api.cube.hierarchy.IAxisMember;
import com.activeviam.activepivot.core.intf.api.cube.metadata.ILevelInfo;
import com.activeviam.activepivot.core.intf.api.location.ILocation;
import com.activeviam.activepivot.core.intf.api.postprocessing.IAdvancedAggregatesRetriever;
import com.activeviam.activepivot.core.intf.api.postprocessing.IAggregatesRetrievalResult;
import com.activeviam.activepivot.core.intf.api.postprocessing.IPostProcessor;
import com.activeviam.activepivot.core.intf.api.postprocessing.IPostProcessorCreationContext;
import com.activeviam.activepivot.core.intf.api.postprocessing.IPrefetcher;
import com.activeviam.database.api.types.ILiteralType;
import com.activeviam.tech.core.api.exceptions.ActiveViamException;
import com.activeviam.tech.core.api.filtering.impl.InCondition;
import com.activeviam.tech.core.api.registry.AtotiExtendedPluginValue;
import com.activeviam.tech.core.internal.util.ArrayUtil;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
 * <b>TrendPostProcessor</b><br>
 * TrendPostProcessor does the difference between today and the day before if no measure retrieved
 * from the day before then return null.
 *
 * @author ActiveViam
 */
@AtotiExtendedPluginValue(intf = IPostProcessor.class, key = TrendPostProcessor.PLUGIN_KEY)
public class TrendPostProcessor extends AAdvancedPostProcessor {
  protected static final long serialVersionUID = 2722596108421670864L;
  public static final String PLUGIN_KEY = "TREND";
  public static final String LEVEL_DESCRIPTION_PROPERTY = "LEVEL";
  public static final String HIERARCHY_DESCRIPTION_PROPERTY = "HIERARCHY";
  protected static final String INCLUDE_PREFETCHER = "INCLUDE_PREFETCHER";
  protected ILevelInfo levelInfo = null;
  protected IAxisHierarchy hierarchy = null;
  public TrendPostProcessor(
      final String name, final IPostProcessorCreationContext creationContext) {
    super(name, creationContext);
  }
  @Override
  public void init(final Properties properties) throws ActiveViamException {
    properties.setProperty(OUTPUT_TYPE_PROPERTY, ILiteralType.DOUBLE);
    super.init(properties);
    final String levelDescription = properties.getProperty(LEVEL_DESCRIPTION_PROPERTY);
    this.levelInfo = HierarchiesUtil.getLevel(getActivePivot(), levelDescription);
    // get the IHierarchy needed instance
    final String hierarchyDescription = properties.getProperty(HIERARCHY_DESCRIPTION_PROPERTY);
    this.hierarchy =
        (IAxisHierarchy) HierarchiesUtil.getHierarchy(getActivePivot(), hierarchyDescription);
  }
  @Override
  protected List<IPrefetcher<?>> initializePrefetchers(final Properties properties) {
    return ArrayUtil.mutableList(
        new APrefetcher(INCLUDE_PREFETCHER, this.measuresProvider) {
          @Override
          protected Collection<String> computeMeasures(final ILocation location) {
            return underlyingMeasures;
          }
          @Override
          protected Collection<ILocation> computeLocations(final ILocation location) {
            return List.of(TrendPostProcessor.this.computeLocation(location));
          }
          @Override
          protected ICubeFilter computeFilter(
              final ILocation location, final ICubeFilter queryFilter) {
            return TrendPostProcessor.this.computeFilter(location, queryFilter);
          }
        });
  }
  protected ICubeFilter computeFilter(final ILocation location, final ICubeFilter queryFilter) {
    final Object value = LocationUtil.getCoordinate(location, levelInfo);
    ICubeFilter filter = queryFilter;
    if (value == null) {
      // we deal with a range location here, we add all the required
      // members and edit the filter
      final List<? extends IAxisMember> members =
          queryFilter.retrieveMembers(hierarchy, levelInfo.getOrdinal());
      final Set<Date> membersToInclude = new HashSet<>();
      for (final IAxisMember member : members) {
        final Date date = (Date) member.getDiscriminator();
        membersToInclude.add(date);
        membersToInclude.add(TrainingUtils.retrievePreviousDate(date));
      }
      filter =
          queryFilter
              .edit()
              .includeMembersWithConditions(
                  hierarchy.getHierarchyInfo().getIdentifier(), new InCondition(membersToInclude))
              .build();
    }
    return filter;
  }
  protected ILocation computeLocation(final ILocation location) {
    final Object value = LocationUtil.getCoordinate(location, levelInfo);
    if (value instanceof Date) {
      // we deal with a point location here
      // build range location with previous date,
      // use a Set having current and previous dates
      final Set<Object> dates = new HashSet<>();
      dates.add(value);
      dates.add(TrainingUtils.retrievePreviousDate((Date) value));
      return createModifiedLocation(location, dates);
    }
    // nothing to do here as we deal with a range location
    return location;
  }
  protected ILocation createModifiedLocation(final ILocation location, final Object membersToAdd) {
    if (LocationUtil.isAtLevel(location, levelInfo)) {
      final Object[] newMembers = LocationUtil.copyPath(location, levelInfo.getHierarchyInfo());
      newMembers[levelInfo.getOrdinal()] = membersToAdd;
      return LocationUtil.createModifiedLocation(
          location, levelInfo.getHierarchyInfo(), newMembers);
    }
    return location;
  }
  @Override
  public void compute(final ILocation location, final IAdvancedAggregatesRetriever retriever) {
    final IAggregatesRetrievalResult results = retriever.retrieveAggregates(INCLUDE_PREFETCHER);
    final int measureForResults = results.getMeasureId(underlyingMeasures.get(0));
    results.forEachPoint(
        new int[] {measureForResults},
        (pointReader, rowId) -> {
          final int rowIdPrevious = results.getRow(retrievePreviousDateLocation(pointReader));
          final Double currentValue;
          final Double previousValue;
          if (rowIdPrevious != -1) { // -1 if the point does not exist
            currentValue = (Double) results.read(rowId, measureForResults);
            previousValue = (Double) results.read(rowIdPrevious, measureForResults);
            if (currentValue != null && previousValue != null) {
              retriever.write(retriever.addPoint(pointReader), currentValue - previousValue);
            }
          }
          return true;
        });
  }
  protected ILocation retrievePreviousDateLocation(final ILocation currentLocation) {
    final Date currentDate = (Date) LocationUtil.getCoordinate(currentLocation, levelInfo);
    return createModifiedLocation(currentLocation, TrainingUtils.retrievePreviousDate(currentDate));
  }
  @Override
  public String getType() {
    return PLUGIN_KEY;
  }
}
```
