Skip to main content
Each solution defines its own measures, but the common layer provides the infrastructure for registering and accessing them. Understanding this infrastructure is useful when adding custom measures or modifying existing ones. Prerequisites: Familiarity with the Atoti Configuration API cube model and the Copper measure API.

How are measures defined?

The Copper measure API is a fluent interface for defining measure calculations as CopperMeasure objects. The code examples on this page use Copper to construct and transform measures.

How are measures organized?

The common layer exposes three constructs for working with measures. IMeasuresConfigurator is the interface on the cube used to register new measures. Call cubeManagerConfigurator.cube(name).measures() to obtain an instance, then use addMeasure() to register a CopperMeasure. The measure factory pattern allows solutions to define reusable, parameterized measure templates. A factory implements IMeasureFactory<T> where T is an IMeasureFactoryInput describing the inputs. Each factory is identified by a unique plugin key returned by its key() method. A factory registered under the same plugin key for the cube replaces the existing one, which allows overriding the default calculation logic of a built-in measure. For a full reference of all common factory plugin keys, see Measures reference. ISolutionCube.measures() is the accessor for retrieving a solution’s measure collections programmatically. Use it inside an IModelConfigurer to obtain existing measures and build new ones on top of them.

How to add or modify measures

Via configuration properties

Some measure parameters can be adjusted using configuration properties without writing Java code. For example, confidence levels and display currencies affect how certain measures are computed and displayed. See Configuration properties for supported options.

Via the Java API

To add new measures programmatically, build a CopperMeasure and register it via IMeasuresConfigurator in an IModelConfigurer. The pattern has three elements:
  • Obtain an existing measure via ISolutionCube.measures()
  • Transform it using the Copper API
  • Register it via IMeasuresConfigurator.addMeasure(name, measure), which names the measure through its first argument
The following example illustrates this pattern by adding a measure that computes the maximum value from an aggregated scenario vector. It uses Copper.combine(...) together with the .map(BiConsumer, type) operator: the mapper receives a reader for the combined inputs and a writer for the result. The reader exposes the underlying vector via readVector(int), and the result is written with writer.writeDouble(...). The output literal type is declared with ILiteralType.DOUBLE:

How to register a custom measure factory

Register a custom measure factory by declaring a MeasureFactoryRegistration bean. Spring’s component scanning discovers the bean and registers the factories it wraps. Because the factory is instantiated by Spring, it can depend on other Spring-managed beans:
Factories can be registered globally (for all cubes) or per cube. Pass a Map<String, Collection<IMeasureFactory<?>>> keyed by cube name for the latter. Per-cube factories take precedence over globally registered ones. To replace a built-in factory, register the replacement for the cube. See How to override a factory. Multiple factories can be registered in a single MeasureFactoryRegistration bean.

How to rename measures

Measures defined by a solution can be renamed without modifying Java code. Add name overrides to application.yml:
Use square brackets for measure names that contain spaces.
For details on defining and customizing measures using the Atoti Configuration API, see the measures reference.