Package com.orchestranetworks.addon.dqid


package com.orchestranetworks.addon.dqid

Provides classes and interfaces to define, publish and execute indicators.

Indicator creation - A basic example

To add a custom indicator, its definition must first be created. Since {addon.label} v1.5.2, you can declare an indicator definition and its execution in the same class by extending the DefaultDECIndicator or DefaultWorkflowIndicator class. For example:

        public final class TestDefaultDECIndicator extends DefaultDECIndicator
        {
                private static final String NUMBER_OF_RECORDS_PARAM = "numberOfRecords";
                private static final String LAST_MODIFICATION_TIME_PARAM = "lastModificationTime";
        
                public String getCode()
                {
                        return "TestDefaultDECIndicator"; // the code must be unique
                }
        
                public DECType getDECType()
                {
                        return DECType.TABLE;
                }
        
                public Set<FrequencyType> getPossibleComputationFrequencies()
                {
                        Set<FrequencyType> frequencyTypes = new HashSet<FrequencyType>();
                        frequencyTypes.add(FrequencyType.ON_DEMAND);
                        return frequencyTypes;
                }
        
                public List<ParameterDefinition> getOutputParametersDefinition()
                {
                        List<ParameterDefinition> outputs = new ArrayList<ParameterDefinition>();
        
                        UserMessageString nbOfRecordsMsg = UserMessage.createInfo("Number of records");
                        outputs.add(new ParameterDefinition(NUMBER_OF_RECORDS_PARAM, SchemaTypeName.XS_INT, nbOfRecordsMsg, nbOfRecordsMsg));
        
                        UserMessageString lastModificationTimeMsg = UserMessage.createInfo("Last modification time");
                        outputs.add(new ParameterDefinition(LAST_MODIFICATION_TIME_PARAM, SchemaTypeName.XS_DATETIME, lastModificationTimeMsg, lastModificationTimeMsg));
        
                        return outputs;
                }
                
                public BigDataDefinition getBigDataDefinition()
                {
                        BigDataDefinition bigDataDefinition = new BigDataDefinition();
                        bigDataDefinition.addPathAssociation(BigDataDefinition.NbRecordsTable, NUMBER_OF_RECORDS_PARAM);
                        bigDataDefinition.addPathAssociation(BigDataDefinition.LastUpdateDate, LAST_MODIFICATION_TIME_PARAM);
                        return bigDataDefinition;
                }
                
                // since v2.0.0, it is recommended to declare FlatDataDefinition instead of BigDataDefinition
                public FlatDataDefinition getFlatDataDefinition()
                {
                        FlatDataDefinition flatDataDefinition = new FlatDataDefinition();
                        flatDataDefinition.addPathAssociation(FlatDataDefinition.Integer1, NUMBER_OF_RECORDS_PARAM);
                        flatDataDefinition.addPathAssociation(FlatDataDefinition.Datetime1, LAST_MODIFICATION_TIME_PARAM);
                        return flatDataDefinition;
                }
        
                public IndicatorReport execute(DECIndicatorExecutionContext executionContext) throws DQIdException
                {
                        IndicatorReport report = new IndicatorReport();
        
                        // sequence: 1, order: 1
                        Integer number = Integer.valueOf(100);
                        IndicatorValue numberOfRecordsValue = new IntegerValue(number, 1, 1); // built-in IndicatorValue
                        // deprecated since v2.0.0
                        // numberOfRecordsValue.setBigDataReportField(BigDataDefinition.NbRecordsTable, number);
                        numberOfRecordsValue.setFlatDataFieldValue(FlatDataDefinition.Integer1, number);
                        report.addValue(numberOfRecordsValue);
        
                        // sequence: 1, order: 2
                        Date date = new Date();
                        IndicatorValue modificationTimeValue = new IndicatorTestValue(date, 1, 2); // custom IndicatorValue
                        // deprecated since v2.0.0
                        // modificationTimeValue.setBigDataReportField(BigDataDefinition.LastUpdateDate, date);
                        modificationTimeValue.setFlatDataFieldValue(FlatDataDefinition.Datetime1, date);
                        report.addValue(modificationTimeValue);
        
                        return report;
                }
        
        }
        

Next, you can create a custom indicator value to store execution results if necessary. For example:

        public final class IndicatorTestValue extends IndicatorValue
        {
                private final Date date;

                public IndicatorTestValue(Date date, int sequence, int order)
                {
                        super(sequence, order);
                        this.date = date;
                }

                public String formatDataType()
                {
                        return Date.class.getSimpleName();
                }

                public String formatValue()
                {
                        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        return df.format(this.date);
                }

                public Object getValue()
                {
                        return this.date;
                }
        }
        

Finally, the indicator must be registered with the add-on in order to be available in the configuration:

        IndicatorDefinitionCatalog.add(new TestDefaultDECIndicator());
        

 

By extending the default indicator classes, you can also apply multiple implementations on the same definition. For example, declare an abstract indicator definition as the following:

        public abstract class AbstractNumberOfRecordsIndicator extends DefaultDECIndicator
        {
                protected static final String NUMBER_OF_RECORDS_OUTPUT_PARAM = "numberOfRecords";
        
                public DECType getDECType()
                {
                        return DECType.TABLE;
                }
        
                public Set<FrequencyType> getPossibleComputationFrequencies()
                {
                        Set<FrequencyType> frequencyTypes = new HashSet<FrequencyType>();
                        frequencyTypes.add(FrequencyType.ON_DEMAND);
                        return frequencyTypes;
                }
        
                public List<ParameterDefinition> getOutputParametersDefinition()
                {
                        List<ParameterDefinition> outputs = new ArrayList<ParameterDefinition>();
                        UserMessageString nbOfRecordsMsg = UserMessage.createInfo("Number of records");
                        outputs.add(new ParameterDefinition(NUMBER_OF_RECORDS_OUTPUT_PARAM, SchemaTypeName.XS_INT, nbOfRecordsMsg, nbOfRecordsMsg));
                        return outputs;
                }
        
                public BigDataDefinition getBigDataDefinition()
                {
                        BigDataDefinition bigDataDefinition = new BigDataDefinition();
                        bigDataDefinition.addPathAssociation(BigDataDefinition.NbRecordsTable, NUMBER_OF_RECORDS_OUTPUT_PARAM);
                        return bigDataDefinition;
                }
                
                // since v2.0.0, it is recommended to declare FlatDataDefinition instead of BigDataDefinition
                public FlatDataDefinition getFlatDataDefinition()
                {
                        FlatDataDefinition flatDataDefinition = new FlatDataDefinition();
                        flatDataDefinition.addPathAssociation(FlatDataDefinition.Integer1, NUMBER_OF_RECORDS_OUTPUT_PARAM);
                        return flatDataDefinition;
                }
        
        }
        

Then extend the above class to handle the business in different scenarios:

        public final class NumberOfRecordsWithErrorsIndicator extends AbstractNumberOfRecordsIndicator
        {
        
                public String getCode()
                {
                        return "NumberOfRecordsWithErrors";
                }
        
                public IndicatorReport execute(DECIndicatorExecutionContext executionContext) throws DQIdException
                {
                        IndicatorReport report = new IndicatorReport();
        
                        Integer number = Integer.valueOf(50);
                        IndicatorValue numberOfRecordsValue = new IntegerValue(number, 1, 1);
                        // deprecated since v2.0.0
                        // numberOfRecordsValue.setBigDataReportField(BigDataDefinition.NbRecordsTable, number);
                        numberOfRecordsValue.setFlatDataFieldValue(FlatDataDefinition.Integer1, number);
                        report.addValue(numberOfRecordsValue);
        
                        return report;
                }
        
        }
        
        public final class NumberOfRecordsWithWarningsIndicator extends AbstractNumberOfRecordsIndicator
        {
        
                public String getCode()
                {
                        return "NumberOfRecordsWithWarnings";
                }
        
                public IndicatorReport execute(DECIndicatorExecutionContext executionContext)
                        throws DQIdException
                {
                        IndicatorReport report = new IndicatorReport();
        
                        Integer number = Integer.valueOf(80);
                        IndicatorValue numberOfRecordsValue = new IntegerValue(number, 1, 1);
                        // deprecated since v2.0.0
                        // numberOfRecordsValue.setBigDataReportField(BigDataDefinition.NbRecordsTable, number);
                        numberOfRecordsValue.setFlatDataFieldValue(FlatDataDefinition.Integer1, number);
                        report.addValue(numberOfRecordsValue);
        
                        return report;
                }
        
        }
        

 

Indicator creation - A full-featured example

The following example covers in-depth all properties of an IndicatorDefinition instance:

        public final class IndicatorDurationDefinition implements IndicatorDefinition
        {
                private static final String TIME_PARAM = "TimeOutput";
                private static final String DURATION_PARAM = "DurationOutput";
        
                public IndicatorDurationDefinition()
                {
        
                }
        
                public String getCode()
                {
                        return "Indicator-Duration";
                }
        
                public UserMessage getLabel()
                {
                        return UserMessage.createInfo("Time duration in interval format");
                }
        
                public UserMessage getDashboardLabel()
                {
                        return UserMessage.createInfo("Time duration");
                }
        
                public UserMessage getDescription()
                {
                        return UserMessage.createInfo("This indicator checks if the value of a Datetime field is within a specified time range "
                                + "and displays the time range from that time to the execution time in interval format.");
                }
        
                public Indicator getImplementation()
                {
                        return new IndicatorDurationImpl(); // this implementation class is defined below
                }
        
                public DECType getDECType()
                {
                        return DECType.FIELD;
                }
        
                public DECSubType getDECSubType()
                {
                        // return null if the indicator doesn't have any D.E.C. sub-types
                        return FieldDECSubType.DATE; // this indicator can only be configured and executed on fields with the Date type
                }
        
                public List<ParameterDefinition> getInputParametersDefinition()
                {
                        // return null if the indicator doesn't have any input parameters
                        List<ParameterDefinition> inputs = new ArrayList<ParameterDefinition>();
        
                        // numeric input
                        UserMessage maxNbOfOutcomesLabel = UserMessage.createInfo("Maximum number of outcomes");
                        UserMessage maxNbOfOutcomesDescription = UserMessage.createInfo("The maximum of outcomes (also the Big data records) for an execution.");
                        ParameterDefinition maxNumberOfOutputs = new ParameterDefinition(
                                "maxNumberOfOutcomesInput",
                                SchemaTypeName.XS_INTEGER,
                                maxNbOfOutcomesLabel,
                                maxNbOfOutcomesDescription,
                                String.valueOf(1));
                        // optional constraint for an input parameter
                        ParameterConstraint parameterConstraint = new ParameterConstraint();
                        parameterConstraint.setUnboundedSupported(true);
                        maxNumberOfOutputs.setParameterConstraint(parameterConstraint);
                        inputs.add(maxNumberOfOutputs);
        
                        // datetime inputs
                        UserMessage fromTimeLabel = UserMessage.createInfo("From");
                        UserMessage fromTimeDescription = UserMessage.createInfo("The start time of the time range. Only considered if the configured periodicity is 'None'.");
                        ParameterDefinition fromDate = new ParameterDefinition(
                                "fromTimeInput",
                                SchemaTypeName.XS_DATETIME,
                                fromTimeLabel,
                                fromTimeDescription);
                        inputs.add(fromDate);
        
                        UserMessage toDateLabel = UserMessage.createInfo("To");
                        UserMessage toDateDescription = UserMessage.createInfo("The end time of the time range. Only considered if the configured periodicity is 'None'.");
                        ParameterDefinition toDate = new ParameterDefinition(
                                "toTimeInput",
                                SchemaTypeName.XS_DATETIME,
                                toDateLabel,
                                toDateDescription);
                        inputs.add(toDate);
        
                        // enumeration input with interval formats
                        UserMessage intervalFormatLabel = UserMessage.createInfo("Interval format");
                        UserMessage intervalFormatDescription = UserMessage.createInfo("The interval format for the 'Duration' output.");
        
                        Set<PredefinedValue> formats = new HashSet<PredefinedValue>();
                        formats.add(new PredefinedValue(IntervalType.DAY_HOUR.getCode(), IntervalType.DAY_HOUR.getLabel()));
                        formats.add(new PredefinedValue(IntervalType.DAY_HOUR_MINUTE.getCode(), IntervalType.DAY_HOUR_MINUTE.getLabel()));
        
                        PredefinedValue defaultFormat = new PredefinedValue(IntervalType.DAY_HOUR.getCode()); // default selected option of the list
                        ListConstraint listConstraint = new ListConstraint(formats, Collections.singleton(defaultFormat));
        
                        ParameterDefinition intervalFormat = new ParameterDefinition(
                                "intervalFormatInput",
                                SchemaTypeName.XS_STRING,
                                intervalFormatLabel,
                                intervalFormatDescription,
                                listConstraint,
                                HTMLComponent.DROPDOWNBOX_COMPONENT); // HTML type for the input
                        inputs.add(intervalFormat);
        
                        return Collections.unmodifiableList(inputs);
                }
        
                public List<ParameterDefinition> getOutputParametersDefinition()
                {
                        List<ParameterDefinition> parameterDefinitions = new ArrayList<ParameterDefinition>();
        
                        UserMessage timeLabel = UserMessage.createInfo("Time in range");
                        UserMessage timeDescription = UserMessage.createInfo("Displays the datetime value within the time range.");
                        ParameterDefinition timeOutput = new ParameterDefinition(
                                IndicatorDurationDefinition.TIME_PARAM,
                                SchemaTypeName.XS_DATETIME,
                                timeLabel,
                                timeDescription);
                        parameterDefinitions.add(timeOutput);
        
                        UserMessage durationLabel = UserMessage.createInfo("Time duration in interval");
                        UserMessage durationDescription = UserMessage.createInfo("Displays the time range from a certain time to the execution time in interval format.");
                        ParameterDefinition durationOutput = new ParameterDefinition(
                                IndicatorDurationDefinition.DURATION_PARAM,
                                SchemaTypeNameExtension.INTERVAL,
                                durationLabel,
                                durationDescription);
                        parameterDefinitions.add(durationOutput);
        
                        return parameterDefinitions;
                }
        
                public BigDataDefinition getBigDataDefinition()
                {
                        BigDataDefinition bigDataDefinition = new BigDataDefinition();
                        bigDataDefinition.addPathAssociation(BigDataDefinition.OverseeDateValue, IndicatorDurationDefinition.TIME_PARAM);
                        bigDataDefinition.addPathAssociation(BigDataDefinition.LifeTimeOfDataspace, IndicatorDurationDefinition.DURATION_PARAM);
                        return bigDataDefinition;
                }
                
                // since v2.0.0, it is recommended to declare FlatDataDefinition instead of BigDataDefinition
                public FlatDataDefinition getFlatDataDefinition()
                {
                        FlatDataDefinition flatDataDefinition = new FlatDataDefinition();
                        flatDataDefinition.addPathAssociation(FlatDataDefinition.Datetime1, IndicatorDurationDefinition.TIME_PARAM);
                        flatDataDefinition.addPathAssociation(FlatDataDefinition.Interval1, IndicatorDurationDefinition.DURATION_PARAM);
                        return flatDataDefinition;
                }
        
                public Set<FrequencyType> getPossibleComputationFrequencies()
                {
                        // supports both the on-demand and the real-time computations
                        Set<FrequencyType> frequencyTypes = new HashSet<FrequencyType>();
                        frequencyTypes.add(FrequencyType.ON_DEMAND);
                        frequencyTypes.add(FrequencyType.REAL_TIME);
                        return frequencyTypes;
                }
        
                public Set<ProbeType> getPossibleProbes()
                {
                        // returns null if the indicator runs on-demand
                        return Collections.singleton(ProbeType.TRIGGER); // supports probes for a real-time indicator
                }
        
                public WatchdogInformation getWatchdogInformation()
                {
                        // return null if the indicator doesn't have any periodicities or thresholds
                        WatchdogInformation information = new WatchdogInformation();
                        Set<PeriodicityType> possiblePeriodicities = new HashSet<PeriodicityType>();
                        possiblePeriodicities.add(PeriodicityType.DAILY);
                        possiblePeriodicities.add(PeriodicityType.TOLERANCE); // TOLERANCE or 'None' means using the configured time range
                        information.setPossiblePeriodicityOfControl(Collections.unmodifiableSet(possiblePeriodicities));
                        return information;
                }
        
                public UserMessage getStorageProcedureInformation()
                {
                        return UserMessage.createInfo("Any results are stored.");
                }
        
                // deprecated since v2.0.0
                public boolean hasManyOutcomesStoredInBigDataTable()
                {
                        return true; // has multiple outcomes because there may be many datetime values within a time ranges
                }

                public boolean hasManyOutcomesStoredInReportingTable()
                {
                        return true; // has multiple outcomes because there may be many datetime values within a time ranges
                }
        
                public LinkedRecordDefinition getLinkedRecordDefinition()
                {
                        // return null if the indicator doesn't store any records
                        LinkedRecordDefinition linkedRecordDefinition = new LinkedRecordDefinition(UserMessage.createInfo("Records with time duration greater than one day will be stored"));
                        UserMessage label = UserMessage.createInfo("Duration greater than one day.");
                        UserMessage description = UserMessage.createInfo("Records with time duration greater than one day will be stored.");
                        linkedRecordDefinition.addOption(new ParameterDefinition(
                                "DurationLinkedRecordOption",
                                SchemaTypeName.XS_BOOLEAN,
                                label,
                                description,
                                Boolean.TRUE.toString()));
                        return linkedRecordDefinition;
                }
        }
        

Next, the definition of the indicator value to be stored must be created. For example:

        public final class IndicatorTestValue extends IndicatorValue
        {
                private final Date date;

                public IndicatorTestValue(Date date, int sequence, int order)
                {
                        super(sequence, order);
                        this.date = date;
                }

                public String formatDataType()
                {
                        return Date.class.getSimpleName();
                }

                public String formatValue()
                {
                        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        return df.format(this.date);
                }

                public Object getValue()
                {
                        return this.date;
                }
        }
        

To execute the indicator, the definition of the implementation must first be created. The following example illustrates how to get and handle indicator configuration properties:

        public class IndicatorDurationImpl extends DECIndicator
        {
                private static final int TIME_OF_A_DAY = 1000 * 60 * 60 * 24;
        
                public IndicatorReport execute(DECIndicatorExecutionContext executionContext) throws DQIdException
                {
                        IndicatorDefinitionContext definitionContext = this.getIndicatorDefinitionContext();
        
                        // input parameter values
                        String maxNbOfOutcomeValue = definitionContext.getBespokeParameterValue("maxNumberOfOutcomesInput");
                        String fromTimeValue = definitionContext.getBespokeParameterValue("fromTimeInput");
                        String toTimeValue = definitionContext.getBespokeParameterValue("toTimeInput");
                        String intervalFormatValue = definitionContext.getBespokeParameterValue("intervalFormatInput");
                        String linkedRecordOption = definitionContext.getLinkedRecordInformation().getOption("DurationLinkedRecordOption");
        
                        int maxNbOfOutcomes;
                        try
                        {
                                maxNbOfOutcomes = Integer.parseInt(maxNbOfOutcomeValue);
                        }
                        catch (NumberFormatException ex)
                        {
                                throw new DQIdException(UserMessage.createError("Invalid maximum number of outcomes."));
                        }
        
                        // periodicity
                        Watchdog watchdog = definitionContext.getWatchdog();
                        PeriodicityType periodicityType = watchdog == null ? null : watchdog.getPeriodicityOfControl();
        
                        Date fromTime = null;
                        Date toTime = null;
                        // if the periodicity is 'None'
                        if (periodicityType.isTolerance())
                        {
                                if (fromTimeValue == null || toTimeValue == null)
                                {
                                        throw new DQIdException(UserMessage.createError("Invalid time."));
                                }
                                fromTime = FormatDataPolicy.parseDate(SchemaTypeName.XS_DATETIME, fromTimeValue);
                                toTime = FormatDataPolicy.parseDate(SchemaTypeName.XS_DATETIME, toTimeValue);
                        }
                        else
                        {
                                // other cases to check if a date value is within the periodicity
                        }
                        if (fromTime == null || toTime == null)
                        {
                                throw new DQIdException(UserMessage.createError("Invalid time."));
                        }
                        if (toTime.before(fromTime))
                        {
                                throw new DQIdException(UserMessage.createError("From time cannot be before to time."));
                        }
        
                        AdaptationTable table = executionContext.getExecutionEnvironment().getTable();
                        Path fieldPath = definitionContext.getField();
                        Date executionTime = new Date();
                        IntervalType intervalType = IntervalType.DAY_HOUR_MINUTE.getCode().equals(intervalFormatValue) ? IntervalType.DAY_HOUR_MINUTE : IntervalType.DAY_HOUR;
                        boolean saveLastValueOnlyInReporting = definitionContext.saveLastValueOnlyInFlatDataTable();
                        boolean storeAll = !Boolean.getBoolean(linkedRecordOption);
        
                        IndicatorReport indicatorReport;
                        if (executionContext.isOnDemand()) // on-demand execution
                        {
                                indicatorReport = this.executeOnDemand(
                                        executionContext,
                                        table,
                                        fieldPath,
                                        fromTime,
                                        toTime,
                                        executionTime,
                                        intervalType,
                                        maxNbOfOutcomes,
                                        saveLastValueOnlyInReporting,
                                        storeAll);
                        }
                        else // real-time execution
                        {
                                String currentRecordXPath = executionContext.getCurrentRecordXPathPredicate();
                                indicatorReport = this.executeRealtime(
                                        executionContext,
                                        table,
                                        fieldPath,
                                        currentRecordXPath,
                                        fromTime,
                                        toTime,
                                        executionTime,
                                        intervalType,
                                        maxNbOfOutcomes,
                                        saveLastValueOnlyInReporting,
                                        storeAll);
                        }
                        if (indicatorReport == null)
                        {
                                return null;
                        }
        
                        // indicates that the watchdog is used as an alert to save the indicator report
                        indicatorReport.setAlertRaisedByWatchdog(true);
                        return indicatorReport;
                }
        
                private IndicatorReport executeOnDemand(
                        DECIndicatorExecutionContext executionContext,
                        AdaptationTable table,
                        Path fieldPath,
                        Date fromTime,
                        Date toTime,
                        Date executionTime,
                        IntervalType intervalType,
                        int maxNbOfOutcomes,
                        boolean saveLastValueOnlyInReporting,
                        boolean storeAll) throws DQIdException
                {
                        IndicatorReport indicatorReport = new IndicatorReport();
                        int sequence = 1; // sequence always starts with 1
                        RequestResult requestResult = table.createRequest().execute();
                        try
                        {
                                Adaptation record = null;
                                while ((record = requestResult.nextAdaptation()) != null)
                                {
                                        Date datetime = record.getDate(fieldPath);
                                        if (datetime.before(fromTime) || datetime.after(toTime) || datetime.after(executionTime))
                                        {
                                                continue;
                                        }
                                        if (sequence > maxNbOfOutcomes)
                                        {
                                                break;
                                        }
                                        this.addIndicatorValues(
                                                executionContext,
                                                indicatorReport,
                                                datetime,
                                                executionTime,
                                                intervalType,
                                                sequence,
                                                saveLastValueOnlyInReporting,
                                                record.toXPathPredicateString(),
                                                storeAll);
                                        sequence++;
                                }
                        }
                        finally
                        {
                                requestResult.close();
                        }
                        return indicatorReport;
                }
        
                private IndicatorReport executeRealtime(
                        DECIndicatorExecutionContext executionContext,
                        AdaptationTable table,
                        Path fieldPath,
                        String currentRecordXPath,
                        Date fromTime,
                        Date toTime,
                        Date executionTime,
                        IntervalType intervalType,
                        int maxNbOfOutcomes,
                        boolean saveLastValueOnlyInReporting,
                        boolean storeAll) throws DQIdException
                {
                        if (maxNbOfOutcomes <= 0 || AddonStringUtils.isEmpty(currentRecordXPath))
                        {
                                return null;
                        }
                        RequestResult requestResult = table.createRequestResult(currentRecordXPath);
                        try
                        {
                                Adaptation record = requestResult.nextAdaptation();
                                if (record == null)
                                {
                                        return null;
                                }
                                Date datetime = record.getDate(fieldPath);
                                if (datetime.before(fromTime) || datetime.after(toTime) || datetime.after(executionTime))
                                {
                                        return null;
                                }
                                IndicatorReport indicatorReport = new IndicatorReport();
                                this.addIndicatorValues(
                                        executionContext,
                                        indicatorReport,
                                        datetime,
                                        executionTime,
                                        intervalType,
                                        1,
                                        saveLastValueOnlyInReporting,
                                        record.toXPathPredicateString(),
                                        storeAll);
                                return indicatorReport;
                        }
                        finally
                        {
                                requestResult.close();
                        }
                }
        
                private void addIndicatorValues(
                        DECIndicatorExecutionContext executionContext,
                        IndicatorReport indicatorReport,
                        Date datetime,
                        Date executionTime,
                        IntervalType intervalType,
                        int sequence,
                        boolean saveLastValueOnlyInReporting,
                        String recordPredicate,
                        boolean storeAll) throws DQIdException
                {
                        // indicator values
                        IndicatorValue datetimeValue = new IndicatorTestValue(datetime, sequence, 1); // IndicatorTestValue was defined before
                        // deprecated since v2.0.0
                        // datetimeValue.setBigDataReportField(BigDataDefinition.OverseeDateValue, datetimeValue);
                        datetimeValue.setFlatDataFieldValue(FlatDataDefinition.Datetime1, datetimeValue);
                        indicatorReport.addValue(datetimeValue);
        
                        long duration = executionTime.getTime() - datetime.getTime();
                        // IntervalValue is an available implementation class of IndicatorValue
                        IndicatorValue intervalValue = new IntervalValue(BigDecimal.valueOf(duration), sequence, 2, intervalType);
                        // deprecated since v2.0.0
                        // datetimeValue.setBigDataReportField(BigDataDefinition.LifeTimeOfDataspace, BigDecimal.valueOf(duration));
                        datetimeValue.setFlatDataFieldValue(FlatDataDefinition.Interval1, BigDecimal.valueOf(duration));
                        indicatorReport.addValue(intervalValue);
        
                        // handle saving last value in the Flat data table
                        if (saveLastValueOnlyInReporting)
                        {
                                // deprecated since v2.0.0
                                // indicatorReport.setPrimaryKeyOfPreviousBigDataReportBySequence(sequence, this.getPrimaryKeyOrNullOfExistingIndicatorReportInBigDataTable(executionContext, sequence));
                                indicatorReport.setPrimaryKeyOfPreviousReportingRecordBySequence(sequence, this.getFlatDataAccessor().getPrimaryKeyOrNullOfLatestRecord(executionContext, sequence));           
                        }
        
                        // linked record - check if duration is greater than a day 
                        if (!storeAll && duration <= TIME_OF_A_DAY)
                        {
                                return;
                        }
                        LinkedRecord linkedRecord = new LinkedRecord();
                        linkedRecord.addRelevantRecordPredicate(recordPredicate);
                        indicatorReport.addLinkedRecord(sequence, linkedRecord);
                }
        }
        

Finally, the indicator must be registered with the add-on in order to be available in the configuration:

        IndicatorDefinitionCatalog.add(new IndicatorDurationDefinition());