Skip to main content

Advanced topics

This topic discusses miscellaneous information on topics that are not mandatory for using Copper in a project, but can be useful in certain situations.

Cross compatibility with non-Copper definitions

Defining measures using both Copper and the classic ways (like the builders or using the measure definitions) should work seamlessly.

Copper is also able to handle non-Copper analysis levels and hierarchies, under the condition that the property in IAxisLevelDescription#ANALYSIS_LEVEL_TYPE_PROPERTY be specified in the properties of the levels of the Analysis Hierarchy. This is because Copper is not aware of the level types for the analysis hierarchies defined outside its context's scope (like Java-based analysis hierarchies defined in the Cube description). in such cases you need to provide that information in the hierarchy description properties.

Since the information is to be provided in the hierarchy description, it can either be specified by the Analysis Hierarchy's description provider :

@Override
public IAnalysisHierarchyDescription getDescription() {
final var desc =
new AnalysisHierarchyDescription(TimeBucketHierarchy.PLUGIN_TYPE, "Maturity", true);
final var levelDesc = new AxisLevelDescription("Maturity term category", null);
levelDesc.putProperty(
IAxisLevelDescription.ANALYSIS_LEVEL_TYPE_PROPERTY, String.valueOf(Types.TYPE_STRING));
final var levelDescTwo = new AxisLevelDescription("Maturity term bucket", null);
levelDescTwo.putProperty(
IAxisLevelDescription.ANALYSIS_LEVEL_TYPE_PROPERTY, String.valueOf(Types.TYPE_STRING));
desc.addLevel(levelDesc);
desc.addLevel(levelDescTwo);
return desc;
}

Or via the Analysis Hierarchy fluent builder's withCustomizations(Consumer<IHierarchyDescription>) method :

.withDimension(SCALING_HIERARCHY)
.withAnalysisHierarchy(ScalingHierarchy.PLUGIN_KEY)
.withCustomizations(
hierarchyDescription -> {
hierarchyDescription.setName(SCALING_HIERARCHY);
hierarchyDescription.setAllMembersEnabled(false);
final IAxisLevelDescription lvl = new AxisLevelDescription();
lvl.setLevelName(SCALING_HIERARCHY);
lvl.putProperty(
IAxisLevelDescription.ANALYSIS_LEVEL_TYPE_PROPERTY,
String.valueOf(Types.TYPE_LONG));
hierarchyDescription.addLevel(lvl);
})

Using the testing framework

Atoti Server comes with utility functions to ease the writing of tests. These utilities come in the form of another module:

<groupId>com.activeviam</groupId>
<artifactId>atoti-server-test</artifactId>
<version>6.1.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>

Defining a test environment

Defining the application to test follows the same standard practices as one would use in a regular application.

final IDatastoreSchemaDescription datastoreSchema =
StartBuilding.datastoreSchema()
.withStore(
StartBuilding.store()
.withStoreName("A")
.withField("id", ILiteralType.INT)
.asKeyField()
.withField("group", ILiteralType.INT)
.withField("value", ILiteralType.DOUBLE)
.build())
.build();
final ISelectionDescription selection =
StartBuilding.selection(datastoreSchema)
.fromBaseStore("A")
.withAllReachableFields()
.build();
final IActivePivotInstanceDescription cube =
StartBuilding.cube("C")
.withCalculations(
context -> {
Copper.sum("value").withName("value.SUM").publish(context);
})
.withSingleLevelDimensions("group")
.build();
final ApplicationWithDatastore application =
StartBuilding.application()
.withDatastore(datastoreSchema)
.withManager(
StartBuilding.managerDescription()
.withSchema()
.withSelection(selection)
.withCube(cube)
.build())
.withoutBranchRestrictions()
.buildAndStart();
application
.getDatastore()
.edit(
transaction ->
transaction.addAll(
"A",
IntStream.range(0, 10)
.mapToObj(i -> new Object[] {i, i % 3, i})
.collect(Collectors.toList())));

In this snippet, we define the underlying Datastore and a single cube on top of it. After starting the application, we populate the Datastore with some data.

Using the testers

The testing framework provides a tester for the cube, which can be obtained by calling CubeTester.from(<manager>). It will allow to run a MDX query against the created cube.

final CubeTester cubeTester = CubeTester.from(application.getManager());
final ICellSetResult result =
cubeTester
.mdxQuery()
.withMdx(
"""
SELECT
[group].[group].[group].Members ON ROWS,
{[Measures].[value.SUM]} ON COLUMNS
FROM [C]
""")
.withContextValues(new QueryMonitoring().enableExecutionTimingPrint())
.run();

Then, we can easily access the various cells of the result and test their values.

final ICellSetTester tester = result.getTester();
Assertions.assertThat(tester.getCellsCount()).isEqualTo(3);
final LevelIdentifier groupLevel = LevelIdentifier.simple("group");
final ICellByCoordinatesFinder cellTester = tester.findCell();
final double g0Value = (double) cellTester.coordinate(groupLevel, 0).getCell().getValue();
Assertions.assertThat(g0Value).isEqualTo(18d, Offset.offset(0.01d));
final double g1Value = (double) cellTester.coordinate(groupLevel, 1).getCell().getValue();
Assertions.assertThat(g1Value).isEqualTo(12d, Offset.offset(0.01d));

We can also test for empty cells.

assertNull(cellTester.coordinate(groupLevel, 5).getCell().getValue());