You are on page 1of 63

Oracle® Retail Predictive

Application Server

Extension Development Guide -


Volume 1, RPAS DB Server
Extensions
Release 16.0
November 2019
Note: The following is intended to outline our general product
direction. It is intended for information purposes only, and may
not be incorporated into any contract. It is not a commitment to
deliver any material, code, or functionality, and should not be
relied upon in making purchasing decisions. The development,
release, and timing of any features or functionality described for
Oracle’s products remains at the sole discretion of Oracle.
Contents
Introduction .............................................................................................................................. 1
Summary of Basic Requirements........................................................................................... 2
Developer Skill-Sets .......................................................................................................................... 2
Installation of RPAS .......................................................................................................................... 2
Operating System Platforms ........................................................................................................... 2
Access to a Korn Shell Command-Line Environment ................................................................. 2
UNIX Platforms .............................................................................................................................. 2
Windows Platforms ....................................................................................................................... 2
The GNU Make (gmake) Utility ..................................................................................................... 2
RPAS Platform.......................................................................................................................... 3
applib .................................................................................................................................................. 3
bin ....................................................................................................................................................... 3
domain................................................................................................................................................ 3
devkit .................................................................................................................................................. 3
include ................................................................................................................................................ 3
boost ................................................................................................................................................. 4
cppunit............................................................................................................................................. 4
rpas................................................................................................................................................... 4
rtkapp .............................................................................................................................................. 4
lib ...................................................................................................................................................... 4
doc .................................................................................................................................................... 4
Patches and Release Notes .................................................................................................... 5
The RPAS API ................................................................................................................................... 5
Base Classes .................................................................................................................................... 5
Overview of Extension Development ................................................................................... 10
Configurations, Domains and Extensions ................................................................................... 10
Developing an Extension ............................................................................................................... 10
Function or Java Expression? ........................................................................................................ 11
Building Extensions with the RPAS Makefile System........................................................ 12
Directory Structure ...................................................................................................................... 12
Environment Variables................................................................................................................ 12
Makefile Variables ....................................................................................................................... 12
Placement of Makefile Include Files .......................................................................................... 15
Sample Makefile ........................................................................................................................... 15
Using the RPAS Makefile ............................................................................................................ 15
Writing RPAS Functions ....................................................................................................... 17
Standard Functions ......................................................................................................................... 17
Custom Functionality ..................................................................................................................... 17
Derive from ExpressionFunction<T> ........................................................................................ 17
Derive from DynamicExpressionFunction ............................................................................... 18
Derive from ParseNode/EvalNode........................................................................................... 19
Derive from MultiResultFunctionParseNode/MultiResultFunctionEvalNode .................. 19
Implementing a Function using ParseNode/EvalNode ......................................................... 20
Implementing a Multi-Result Function
(MultiResultFunctionParseNode/MultiResultFunctionEvalNode) ..................................... 23
Named Parameters ...................................................................................................................... 25
RPAS Java Expressions ........................................................................................................ 25
Characteristics and Limitations .................................................................................................... 25
Implementation ............................................................................................................................... 26
Running Java Expressions ............................................................................................................. 26
Debugging Java Expressions ......................................................................................................... 26
Memory Management for Java Expressions ............................................................................... 27
Increasing the Default Heap Size of the JVM ........................................................................... 27
Explicit Memory Clean-up.......................................................................................................... 27
Example ............................................................................................................................................ 28
Measures ................................................................................................................................. 32
General Measure Properties ....................................................................................................... 32
Measure Loading Properties ...................................................................................................... 35
Virtual Measures ............................................................................................................................. 36
Editing ........................................................................................................................................... 36
Protection ...................................................................................................................................... 36
Deferred Calculation ................................................................................................................... 36
Non-Materialized Measures .......................................................................................................... 37
Display-Only Non-Materialized Measures .............................................................................. 37
Token Measures .............................................................................................................................. 37
Assumptions ................................................................................................................................. 37
C++ API for Setting Token Measures ........................................................................................ 37
Measure Attributes ......................................................................................................................... 38
Measure Attribute Interface........................................................................................................ 38
Attribute Controlled Measure Interface.................................................................................... 39
MeasureStore Class Library .......................................................................................................... 41
Workbooks and Workbook Templates................................................................................. 42
Workbook Templates ..................................................................................................................... 42
Template Factory ............................................................................................................................ 43
BaseInfo ............................................................................................................................................ 44
DynamicTemplate........................................................................................................................... 44
Dynamic Wizard ............................................................................................................................. 44
CustomTemplateRegistrationFactory .......................................................................................... 44
Appendix: Configuration Repository ................................................................................... 48
Template Configuration ................................................................................................................. 48
File Format .................................................................................................................................... 48
Object: Group................................................................................................................................ 49
Object: Template .......................................................................................................................... 49
Object: Sheet.................................................................................................................................. 50
Object: Window ............................................................................................................................ 50
Object: HierMod ........................................................................................................................... 51
Object: PositionQuery.................................................................................................................. 51
Object: Profile ............................................................................................................................... 52
Object: DynamicHier ................................................................................................................... 52
Object: Axis ................................................................................................................................... 52
Object: Tab .................................................................................................................................... 52
Object: Measure ............................................................................................................................ 52
Object: Wizard .............................................................................................................................. 53
Object: Widget .............................................................................................................................. 54
Object: Menu ................................................................................................................................. 55
Object: MenuItem ......................................................................................................................... 55
Object: Logger............................................................................................................................... 55
Example ......................................................................................................................................... 56
Introduction
The Oracle Retail Predictive Application Server (RPAS) platform provides flexibility for its
customers, including a framework for handling extensions (in library or executable form) to
its core functionality.
To develop extensions, Oracle Retail provides a software development kit (SDK) with each
release. The kit consists of a collection of C++-based “include” headers, shareable object
libraries, and documentation. Together, these development components enable RPAS
customers to develop, test, and deploy additional behaviors in their RPAS-based production
environments.
Great care must be taken to maintain the functional integrity and performance of the total
solution when developing these extensions to RPAS. Where appropriate, this development
process should mirror the development of RPAS as much as possible.
This document outlines the requirements and procedures for building extensions through a
process that conforms to Oracle Retail standards. In particular, this document describes the
software tools required for each platform, including instructions for how to procure and
build them when necessary. It also covers the commands, variables, and file structures of the
RPAS build system. Finally, the document describes the contents of the RPAS distribution.

Note: The configuration detailed in this document involves the


RPAS extension framework API, to facilitate the development of
custom solution plug-ins. Defects may be logged against this
framework. Note, however, that in accordance with Oracle
Software Technical Support Policies, Oracle Support may not
provide fixes for issues resulting from such custom
modifications, as the problem may not be demonstrable in our
standard Support environments. Please see the official
product/program documentation for the technologies that are
fully supported.

For questions and concerns about this document, or to begin a


discussion with other implementers on this topic, please visit the
Oracle Retail User Communities.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 1


Summary of Basic Requirements
This section describes the basic requirements for developing RPAS extensions.

Developer Skill-Sets
An RPAS extensions developer must have sound knowledge of the C++ programming
language in its modern, standardized form. In particular, familiarity with the C++ Standard
Template Library (STL) is essential.
The developer should also have a working knowledge of the utilities mechanisms provided
by a POSIX-compliant command-line interface (CLI) environment to develop software. This
CLI is supplied by default in most UNIX-based operating systems, and it can be added to a
Windows-based environment.

Installation of RPAS
Refer to the sections “Installing on UNIX and Linux Environments” or “Installing on a
Windows Environment” in the Oracle Retail Predictive Application Server Installation Guide for
information on the installation of RPAS.

Operating System Platforms


The development of RPAS takes place on relatively recent versions of the operating systems
that Oracle explicitly supports. Below is information about these operating systems. Refer to
the section “Hardware and Software Requirements” in the Oracle Retail Predictive Application
Server Installation Guide for information on supported operating systems for the current
release of RPAS.

Access to a Korn Shell Command-Line Environment


UNIX Platforms
All UNIX-based platforms that Oracle supports have the Korn shell (ksh) installed by
default. All of these platforms are set up by default to associate this particular shell with
newly created user accounts.

Windows Platforms
The Cygwin environment, which is a software requirement for RPAS on a Microsoft
Windows environment, contains an optional Korn shell component. This component must be
installed in the environment for Microsoft Windows systems.

The GNU Make (gmake) Utility


The RPAS Makefile system is highly complex. For example, one of its capabilities is a run-
time determination of the platform upon which a library or executable is being built. Because
of the need to perform advanced build management functions, Oracle depends on GNU
Make (gmake), an enhanced open source version of the standard UNIX-based build
management utility, make.

2 Oracle Retail Predictive Application Server


RPAS Platform
This section describes the contents of the core RPAS distribution. See the directory listing
relative to the setting of the RPAS_HOME variable.

applib
This subdirectory contains standard and custom RPAS extension libraries. These shared
libraries are typically registered to an RPAS domain with a regtemplate or regfunction
command. However, some libraries are actually support libraries upon which the registered
libraries are directly dependent.

bin
This subdirectory contains all RPAS-related executable binaries and shell scripts, including
commands for creating, manipulating, and querying RPAS domains. This directory also
includes the two main RPAS server daemons (DomainDaemon and RpasDbServer).

domain
This subdirectory contains all of the “bootstrap” files necessary for creating an RPAS domain
from scratch. It includes the minimal set of necessary database files. It also includes several
fixed-width flat files that are used to initialize the core hierarchies, dimensions, positions,
and measures, as well as a set of localized messages.

devkit
This subdirectory contains RPAS Makefile system files and a subdirectory called “include,”
which is described below. The devkit directory also contains the examples subdirectory. This
subdirectory contains examples of extensions written using the Java expression framework.

include
This subdirectory contains the header files that are necessary for the development of RPAS
extensions. This directory has separate sub directories for each functional cluster of header
files. Oracle Retail recommends including only this directory (as opposed to each individual
subdirectory) in an extension Makefile. The headers may be accessed with the standard
C/C++ notation. For example:
#include <rpas/String.h>
The top-level include directory contains only one file, FlexLexer.h. This file is normally
included with flex, which is an open source-based lexical analyzer. Only the Parser.h header
in the rpas subdirectory includes FlexLexer.h, which should never be directly included in
RPAS extension code.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 3


Because each include subdirectory covers a unique functional area, those directories are
described below:

boost
This directory contains header files from Boost, which is an open source-based set of
extensions to the standard C++ libraries. Although Boost headers installed on your
development platform prior to the installation of RPAS may be used, Oracle Retail supports
only the usage of version 1.30.2. Specifically, Oracle cannot guarantee the behavior of RPAS
extensions built with Boost version 1.31 or higher. Because Oracle does not use or distribute
any Boost-related libraries, Oracle does not redistribute any of the Boost non-header source
files.

cppunit
This directory contains header files from CppUnit, which is a C++ unit testing
framework.

rpas
This directory contains header files that collectively provide the RPAS API. There are several
subdirectories within this directory as well.

rtkapp
This directory contains header files that provide the API for the DynamicTemplate class,
which is used to construct an RPAS workbook template from a configuration file.

lib
This subdirectory contains the RPAS library and the libraries it depends on, as well as,
including the Zlib compression library. The lib directory also contains the
oracleRPASUtils.jar library that provides the interfaces classes used by Java Expressions.

doc
This subdirectory contains the JavaDoc description of the classes that make up the Java
expression APIs for development of RPAS Java Expressions. The index file for the JavaDocs
for the oracleRpasUtils package is found within the java subdirectory of the doc directory.

4 Oracle Retail Predictive Application Server


Patches and Release Notes
Oracle Retail provides a series of patches to add critical functionality to RPAS and remedy
defects found within the release. A set of release notes that explains these changes also is
included. The release notes include:
 The impact on the application programming interface (API)
 Necessary upgrades to compilers and other development utilities
 Other aspects of RPAS behavior
The RPAS extensions developer’s must ensure that the installation of RPAS has the latest
changes applied and to change RPAS extensions code accordingly. Oracle Retail strives to
minimize changes to the currently existing API, but there may be occasional circumstances
when an element of the API must be changed.

The RPAS API


Oracle Retail supplies RPAS with a C++-based application programming interface (API)
suitable for creating custom extensions for an RPAS-based solution. The API allows
developers to create custom functions through a number of publicly-available classes. Other
supporting classes in the RPAS hierarchy may change interfaces, but Oracle intends to keep
the classes listed herein as stable as possible, changing them only when absolutely necessary.

Note: In all RPAS classes, the templates and variables mentioned


below are assumed to reside within the rpas C++ namespace,
and they have a corresponding C++ header file in
$RPAS_HOME/devkit/include/rpas unless otherwise
specified.

Base Classes
String
The String class is perhaps the most used class in the RPAS API, because several methods of
the other RPAS classes expect String parameters. Its functionality is a superset of the C++
STL class std::string. For example, you can perform an “addition” operation on a String to
achieve string concatenation:
String s1(“This string”);
s1 += “ has been added to.”);
In addition to the normal comparison and substring extraction methods, a number of utility
methods for converting to and from other data types (such as int, double, and bool) are
provided. There is also a method that is specifically geared for filling in the parameters of a
locale-specific message label.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 5


StringConvertException
This exception-handling class is intended for catching problems when converting a String
object to/from another type. For example,
try
{
int i = String(“This is not a number”).toInt();
}
catch (StringConvertException &e)
{
// error-handling here
}

File
The File class encapsulates the lower-level, operating system-specific file descriptor handle,
which identifies a unique I/O session performed on a particular file or directory. Methods
for opening, closing, reading, writing, and erasing the file are provided and typically
encapsulate the operating system’s file descriptor API. However, File goes a few steps
further by providing methods that can list, or even copy, the contents of a file directory. File
also provides several methods to lock, unlock, and check the lock status of the associated file.

FilePath
FilePath is typically associated with File, but it is also used by several other classes. The
FilePath class encapsulates the fully-qualified name path for a file residing in a file system
employing a hierarchical directory structure. This type of structure is indigenous in UNIX- or
Windows-based operating environments.
A key advantage of the FilePath class is the automatic handling of the character sequence for
separating directory hierarchies (for example, / in UNIX-style environments, and \ in DOS
and Windows). The FilePath class can also “flatten” a path where shorthand references to a
current or parent directory are taken out to create a fully-qualified filename. Methods are
provided to manipulate the filename extension (for instance, “.txt”) and query a file path’s
directory and base filename.

ArgReader
The ArgReader class encapsulates the contextual list of arguments that were specified as part
of the invocation of a new RPAS process. For example, when
loadMeasure –d /home/rpas/myDomain –meas TwPStrCnt
is invoked, the loadMeasure process can create an ArgReader object that will hold the –d and
–meas parameters and allow easier access to the arguments that are referenced by the
traditional C-style argc/argv method. The ArgReader class can be used to stipulate the valid
parameters and switches (which are parameterless directives such as –noWarn) and dictate
which of those must be specified on the command line.
Though much of the argument processing logic (aside from that specified above) must be
performed by the creator of the ArgReader object, ArgReader automatically handles one type
of argument: the –loglevel class, which specifies the level of granularity for statements
generated through the Logger class (see below).

InvalidArgException
This exception will be thrown if the ArgReader object notices a parameter or switch in the
process’s parameter list that was not previously registered with the ArgReader object.

6 Oracle Retail Predictive Application Server


DateTime
The DateTime class encapsulates the operating environment-specific API for representing a
specific point in time (timestamp) down to the millisecond. After the DateTime object obtains
a valid value, special accessor methods can adjust the timestamp on any level of granularity,
from years down to milliseconds. DateTime objects may also be compared for equality and
chronological order; they may also be queried for leap-year status.
By default, a DateTime object captures the current value of the system’s internal clock;
however, DateTime objects also are frequently used as RPAS position values.

DateTimeComponents
This companion class to DateTime allows a developer to access (in a read-only and a read-
write fashion) the individual date and time elements of a DateTime timestamp. The levels
that can be accessed are year, month, day within the month, hour, minute, second, and
millisecond. A DateTime object can generate a DateTimeComponents object, and a
DateTimeComponents object can generate a DateTime object.

DateTimeFormat
The DateTimeFormat class (also a companion class to the DateTime class) formats a
DateTime timestamp and generates a character String value suitable for display to users and
for parsing by back-office batch processes. A suitable default format is provided; however,
developers can provide alternate formats to further refine the display ability of the
timestamp.
The timestamp display format is specified as a String object containing one or more of the
following placeholder tokens into which the appropriate timestamp values are substituted.
All values are formatted as two digits unless otherwise specified:
 %Y – Four-digit Gregorian calendar year
 %y – Two least-significant digits of year (for example, 04 is 2004)
 %m – Month (or example, 01 is January)
 %B – Varying-length full name of the month
 %h – Three-character abbreviation of the month name
 %d – Day of the month (or example, 01 is the first day of the month)
 %H – Hour of the day (or example, 22 represents 10 PM)
 %M – Minutes past the hour
 %S – Seconds past the current minute
 %s – Three-digit milliseconds past the current second
 %% – Treated as a single-character percent literal

Extension Development Guide - Volume 1, RPAS DB Server Extensions 7


The format string can also contain non-token characters to enhance the readability of the
formatted string.
For example, a format string of
%D %h %y
will yield the formatted string 02 Jul 04 for a timestamp occurring on July 2, 2004, and a
format string of
%m/%D/%Y %H:%M:%S.%s
will yield the formatted string 07/02/2004 10:30:00.001 for a timestamp of one millisecond
past 10:30 AM on the same day.

Logger
The Logger class (the Logger singleton object accessor rpas::logger()) can be used to generate
non-interactive, “back-channel” feedback regarding the behavior and performance of one or
more sections of C++ code. The logger() accessor behaves like an output stream, so it can
initiate a chain of stream insertion operations. For example,
logger() << debug << “The query returned” << i
<< endl;

Note: The debug operand at the beginning of the insertion


operation is one of several logger stream modifiers available.
They allow developers to specify the granularity of the log
message (basic, none, error, warning, information, debug, and
profile with basic and none for always-logged to profile for
logging at the deepest logging level). For controlling the
formatting of the output stream, all standard stream modifiers
are supported.

RpasVersionMacros.h header
Various C-preprocessor-style macros are defined in the header file RpasVersionMacros.h so
that libraries and binary executables will have a foolproof mechanism for self-discovering
the version of RPAS for which they were built. There are two constructors for the
RpasVersion object. One is designed for the RPAS Library and does not include the
parameter shown below that contains “Additional” in the name. For libraries and code
written outside of RPAS that requires additional version information, this additional
information can be provided as shown in the example. The parameters to the constructor
that have “Additional” in their name provide the version information for the library or code
written outside of RPAS while the other parameters provide the RPAS library version that
this library or code was complied against.
To include this information into a custom extension library, a C++ source file with the
following contents must be included. The developer can substitute more appropriate text in
the “name of your library here” space:
#include "rpas/RpasVersion.h"
namespace
{
static rpas::RpasVersion
rpasVersion
(
"name of your library here",
ADDITIONAL_VERSION_MAJOR,
ADDITIONAL_VERSION_REVISION,
ADDITIONAL_VERSION_MODTIME,

8 Oracle Retail Predictive Application Server


ADDITIONAL_VERSION_BUILD_DATE,
RPAS_VERSION_MAJOR,
RPAS_VERSION_REVISION,
RPAS_VERSION_MODTIME,
RPAS_VERSION_BUILD_DATE,
RPAS_VERSION_CLIENT_API,
RPAS_VERSION_MSPL_API,
RPAS_VERSION_COM_MSG_FORMAT
);
}
extern "C" const rpas::RpasVersion& __LibraryVersion()
{
return rpasVersion;
};

To display the version information in a binary, the code would look similar to this:
if (args["version"] == ArgReader::TRUE_ARG())
{
RpasVersion rpasVersion
(
"name of your binary here",
ADDITIONAL_VERSION_MAJOR,
ADDITIONAL_VERSION_REVISION,
ADDITIONAL_VERSION_MODTIME,
ADDITIONAL_VERSION_BUILD_DATE,
RPAS_VERSION_MAJOR,
RPAS_VERSION_REVISION,
RPAS_VERSION_MODTIME,
RPAS_VERSION_BUILD_DATE,
RPAS_VERSION_CLIENT_API,
RPAS_VERSION_MSPL_API,
RPAS_VERSION_COM_MSG_FORMAT
);

logger() << basic << endl << rpasVersion


<< endl;

return 0;
}

Extension Development Guide - Volume 1, RPAS DB Server Extensions 9


Overview of Extension Development
This section provides a brief explanation of extension development. It is assumed the user is
familiar with the terms used throughout this section.

Configurations, Domains and Extensions


From a configuration, you can make a domain and then write an extension in C++ from
scratch, assuming you have the RPAS header files. Finally, the compiled extension is
registered with a domain.
While creating a configuration, you can call an expression that does not exist yet, and then
use the
-rf (register function) option of rpasInstall to register the extensions as you build the domain.
However, it is also possible to fully create a domain first, while an extension is still under
development or being created by a separate build process. In this case a user can make a
domain, write an extension, call the regfunction utility to register it with the domain, and
then do one of the following:
 go back to the config tool(s), add a call to the expression in a rule or rule group, and
use rpasInstall again.
 use mace on the command line to call an expression against the domain without
saving that expression as part of a rule group.
This process may seem a little circular. Just remember that extension libraries have to be
registered with a domain, not a configuration.

Developing an Extension
Before it can be used, an extension must be:
1. Written
2. Compiled
3. Linked
4. Installed
5. Registered
Writing the extension requires writing C++ code and deciding what type of extension is
required: function or special expression. Compilation, linking, and installation can be done
all at once using gmake. This is covered in the next section.
Registration is done using the regfunction utility provided in the RPAS_HOME/bin folder.

10 Oracle Retail Predictive Application Server


Function or Java Expression?
RPAS extensions are functions or Java expressions. Before beginning development of an
extension, determine which of these is more likely to suit your needs. In general, functions
get more help from the RPAS framework; Java expressions are more flexible. Types of
expressions are described later in this document. The following is a list of some of the major
differences between functions and Java expressions.
 Number of times an extension is called
Functions are called for every LHS base intersection that has at least one populated
LHS measure. The iteration over these intersections is handled by mace. A Java
expression is called just once for a domain and must handle its own iteration.
 Control over iteration
Functions have no control over the order of processing for relevant base
intersections. Java expressions (theoretically) have full control over iteration.
 RPAS classes that must be extended
Java Expressions are created by providing an implementation of the RpasExpression
interface. Function development may involve inheriting from between one and four
RPAS classes, depending on the level of sophistication needed. See Writing RPAS
Functions.
 Constraints on measure dimensionality
A Java expression may read and write values of any dimensionality in a domain. The
types do not need to match or have any particular relationship. Functions are more
constrained, in that all LHS measures must have the same base intersection. In
addition, all RHS measures for a function are automatically spread or rolled up by
mace to the same base intersection level before values are seen by the function. All
spread and rolled-up values are also persisted to disk. This can result in significant
increase to the memory usage of the program, both on disk and in memory.
 Constraints on input measure type
For a function, all RHS measures must be of the same type as the first (primary) LHS
measure, or be of a type that can be automatically converted to it. Currently only
numeric types can be automatically converted to one another. Special expressions
are free of this constraint.
 Expected complexity
Functions are generally expected to be small and simple. Some guidelines
recommend avoiding too many data members (especially of type rpas::String or
those even more complicated) in classes that implement functions.
 Nesting and stacking
Functions may take one another as inputs, as long as the output measure type of one
is appropriate as a measure input type for another. Java expressions may not call
other functions or Java expressions, or have any other interaction with other parts of
the configuration. They may only take measures, intersection modifiers, and scalars
as arguments.
 Syntax
In the rule defining the extension call, special expressions are called using <- and
functions are called using = .

Extension Development Guide - Volume 1, RPAS DB Server Extensions 11


Building Extensions with the RPAS Makefile
System
The development of RPAS extensions involves the usage of certain Makefiles that Oracle
Retail has provided in each RPAS distribution. This section describes how to incorporate
these Makefiles into your own project’s Makefile to build extensions in a standard, consistent
manner.
This section explains how to set up your development project’s Makefile to include unit tests
to help assess the quality of the functional components for each written extension. Oracle
Retail strongly recommends that the unit-testing paradigm be employed as an integral part
of RPAS extensions development. The use of CppUnit for the unit-testing framework also is
recommended.

Directory Structure
A Makefile must be stored in a directory before it is set up. Oracle Retail recommends
placing each extension to be developed in its own development directory. If multiple
extensions are being developed for a particular solution, each of these directories could
reside in a parent directory where a centralized Makefile can recursively invoke the Makefile
of each extension’s subdirectory.
Oracle Retail also recommends creating two subdirectories within each extension’s
development directory as follows:
 One containing the source code for the extension
 One containing the source code for any unit tests associated with the extension
The advantage to this method is that the same Makefile can manage the builds for both of
these subdirectory types.
Consider the following sample directory specifications:
/home/janedoe/RPASSolution
/home/janedoe/RPASSolution/Extension1
/home/janedoe/RPASSolution/Extension1/src
/home/janedoe/RPASSolution/Extension1/unittest
/home/janedoe/RPASSolution/Extension2
/home/janedoe/RPASSolution/Extension2/src
/home/janedoe/RPASSolution/Extension2/unittest

Environment Variables
The RPAS_HOME variable indicates the fully qualified directory location of the particular
distribution of RPAS on the system where extensions are to be developed. This should
already be set if RPAS was configured for use in your UNIX account or Windows command
shell.

Makefile Variables
The following Makefile variables are used by the RPAS Makefile system to ease the process
of developing RPAS extensions. See Sample Makefile for the proper assignment syntax of
these variables.

12 Oracle Retail Predictive Application Server


RPAS_HOME
This variable serves the same purpose as the environment variable. See Environment
Variables. If this variable does not exist in the Makefile, the gmake command uses the
environment variable. If the variable exists in the Makefile and in the environment, the
setting in the Makefile overrides the environment variable during the gmake invocation.

TARGET
This variable can be set to one of two possible values:
 binary for executable images
 shared for shareable intermediate object libraries

TARGET_BINARY
When the TARGET variable is set to binary, this variable should contain the base filename of
the executable image target to be created.

BASE_TARGET_NAME, TARGET_SHARED
When the TARGET variable is set to shared, these variables are used to indicate the filename
of the shared object library target to be created. The BASE_TARGET_NAME contains the
name of the library without any extension; TARGET_SHARED should contain
BASE_TARGET_NAME plus the $(SHARED_LIB_SUFFIX) variable to indicate the filename
extension for the current platform (.dll for Windows, .so for AIX and Solaris, .sl for HPUX).
RPAS recommends use of the $(LIB_PREFIX_NEVERD) variable to indicate the prefix for the
filename. Doing so yields “lib” for the UNIX platforms and is empty for Windows. The
NEVERD indicates you do not wish to have a “d” prefix in the filename, even when building
a debug version of the code. NEVERD allows a domain with your library registered in it to
work with either a debug or a release build of the code. The string in the middle
(MyTemplateLib in the example above) represents the unique, developer-chosen part of the
library name.
Using the information in the Makefile (see Sample Makefile), gmake will determine that the
library name is libMyTemplateLib.so on AIX and MyTemplateLib.dll on Windows.

INSTALL_SHARED_LIB_DIR
This variable should contain the directory location where a target shared object library file is
to be installed. For most development situations, this should be set to
$(RPAS_HOME)/applib.
The system administrator for the development platform should configure this directory to
allow developers to insert extension libraries, but not to overwrite the other libraries in the
directory. If this is not possible, a private copy of the RPAS distribution must be created for
this purpose.

INCDIR
This variable should contain a space-separated list of include directives, where each directive
is in this form: -includeDirectory
The includeDirectory represents the location of a directory that the C++ compiler should use
for looking for header files. The RPAS Makefile system will automatically include the
directories for the standard RPAS includes, so only application-specific directories should be
added here.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 13


LIBDIR, LIBS
If your library or application requires linking against other shared or static libraries besides
the standard RPAS libraries, you may specify them in the LIB variable and where to find
them in the LIBDIR variable. In the example above, we are asserting that a directory called
/my/other/project/libs contains a file called libMyOtherLib.so (on UNIX), or
MyOtherLib.dll (on Windows).

OBJS
This variable should contain a space-separated list of intermediate object filenames. The
filenames may be specified relative to the directory location of the Makefile. The RPAS
Makefile system uses this variable for determining the source-to-object and the object-to-
target dependencies.

DEVKIT
For nearly all RPAS application and extension library development, the DEVKIT variable
should be set to True. This indicates to the RPAS Makefile system that your RPAS include
files, and shared libraries are coming from an RPAS_HOME release build (rather than from a
checked-out copy of the RPAS source tree).

USES_CORE
There are several variables for selecting the level of RPAS library support that your library or
application requires. For nearly all application and library developers, USES_CORE is the
correct level. It links your code against the RPAS support libraries for system/platform
dependencies (RpasBase), the RPAS database system (RpasArray), and domain, measure,
and workbook capabilities (RpasCore).
If you want to build a very simple utility that requires only the RPAS system/platform
support, you may specify USES_BASE. If your application accesses RPAS databases and
arrays but does not need domain- or measure-level access, you may specify USES_ARRAY.
The USES level is cumulative, so if you specify USES_CORE, you get the BASE and ARRAY
support automatically. There is no need to specify all three.

14 Oracle Retail Predictive Application Server


Placement of Makefile Include Files
The following applies to makefile.arch and makefile.rules files.

Makefile.arch
Makefile.arch is an include file that heuristically discovers the platform on which gmake is
being invoked. It sets certain internal critical variables accordingly. This file should always
be included after the setting of RPAS_HOME, and the TARGET* series of variables.
However, it should also be included before the setting of INSTALL_SHARED_LIB_DIR,
INCDIR, and OBJS.

Makefile.rules
Makefile.rules is an include file generates the basic dependency rules. It supplies the gmake
invocation with its eventual targets. It should always be included at the end of a Makefile.
Note that both Makefile.arch and Makefile.rules (and other files subsequently included by
them) will be located in the “devkit” directory of any official RPAS release. You may assume
that they are located at $RPAS_HOME/devkit/Makefile.arch and
$RPAS_HOME/devkit/Makefile.rules.

Sample Makefile
The following sample represents the contents of a Makefile for a typical extension
development situation, where a shared library is being developed. See Makefile Variables for
explanations of the variables shown in this example.

# Sample Makefile for a shared library build


TARGET = shared
BASE_TARGET_NAME = $(LIB_PREFIX_NEVERD)MyTemplateLib
TARGET_SHARED = $(BASE_TARGET_NAME)$(SHARED_LIB_SUFFIX)
DEVKIT = true
USES_CORE = true
include $(RPAS_HOME)/devkit/Makefile.arch
INSTALL_SHARED_LIB_DIR = $(INSTALL_DOMAIN_LIB_DIR)
INCDIR += -I.
LIBDIR += $(L)/my/other/project/libs
LIBS += $(LFLAG_PRE)MyOtherLib$(LFLAG_POST)
OBJS = \
rpas/MyCustomTemplate.o \
rpas/MyCustomWizardPage.o
include $(RPAS_HOME)/devkit/Makefile.rules

Using the RPAS Makefile


The project can be built when the development directory structure has been set up with the
appropriate Makefiles to properly manage the build process.

Note: If you have a release version of RPAS, you must also build
a release version of your custom library.

To streamline the building process, Oracle Retail has provided a series of gmake-based
keywords to build each required component of the extensions’ development, testing, and
deployment. These keywords drive the generation and placement of build-time products.
The most common ways of using these keywords are summarized below.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 15


Note that keywords may be combined. For example, “gmake clean install” and “gmake clean
release install.”

gmake all
This builds a version of the RPAS extension library or executable within the development
directory for each extension. This type of build contains debugging information but no
optimizations.

gmake install
This is the same as above, except that it copies the library or executable to the default
installation target directory and makes permissions adjustments as needed. This type of
build is suitable for problem resolution and normal testing.

gmake release (all|install)


This builds an optimized-for-performance version of the RPAS extensions library or
executable. This type of build is suitable for performance testing and deployment. It is not
suitable for debugging since it contains no debugging information.

gmake clean
This removes any intermediate and final products of the build process, which allows for a
complete build of extensions and unit tests from scratch.

16 Oracle Retail Predictive Application Server


Writing RPAS Functions
This section explains how to write RPAS functions.

Standard Functions
See the Appendix C of the Oracle Retail Predictive Application Server Configuration Tools User
Guide (RPAS Rule Functions Reference Guide) for a description of standard functions.

Custom Functionality
RPAS supports four techniques for implementing custom functions and procedures. This
section contains a brief summary of each technique.

Derive from ExpressionFunction<T>


This is simple to implement, because there is just one class to create. In addition, it:
 Insulates you from working directly with the RPAS calculation engine
 Is good for data-driven functions working with a single data type
 Supports full flexibility to use other operations in the same expression
 Supports vector mode and/or dropping the calendar dimension (function with
vector arguments returning non-vector result)
 Provides no access to underlying intersection or dimension information
All arguments and the return value must be the same type. Following is an example:
class SliceVarFn : public ExpressionFunction<double>
{
public:
SliceVarFn(){}
virtual ~SliceVarFn() { }
virtual rpas::String functionName() const
{ return "ut_slicevar"; }
virtual int minArgCount() const
{ return 2; }
virtual int maxArgCount() const
{ return 2; }
virtual ExpressionFunctionBase* clone() const
{ return new SliceVarFn(*this); }
virtual bool canDropCalendarDimension() const
{ return true; }
virtual void eval
( const typename ExpressionFunction<double>
ArgumentVectorType& args,
typename ExpressionFunction<double>
ResultVectorType& results
) const
{
// Just populate the first result. Typically with
// this function we will be dropping the
// calendar dimension anyway.
results.at(0) = args.at(0).at(static_cast
<SimpleVectorWrapper<double>::size_type>(
args.at(1).at(0)));
}

Extension Development Guide - Volume 1, RPAS DB Server Extensions 17


virtual SemanticInformation::EvaluationModeType
evaluationMode() const
{
return SemanticInformation::VECTOR;
}
};

Derive from DynamicExpressionFunction


This is a specialized version of ExpressionFunction, which supports mixed types for
arguments and return values. Types may be determined at runtime.
 This can be used to implement simple function name overloading. For example, you
can write a max function that returns reals if you pass it reals, strings if you pass it
strings, and so on.
 Functions coded this way typically run 10%-15% slower than the equivalent function
using ExpressionFunction.
Example
class DecompressMeasureFn : public
DynamicExpressionFunction
{
public:
DecompressMeasureFn() { }
virtual ~DecompressMeasureFn() { }
virtual String functionName() const
{ return "ut_decompressMeasure"; }
virtual int minArgCount() const
{ return 1; }
virtual int maxArgCount() const
{ return 1; }
virtual MeasureType returnType() const
{ return MeasureType:: real(); }
virtual bool convertType(int argIndex)
{ return false; }

virtual ExpressionFunctionBase* clone() const


{
return new DecompressMeasureFn(*this);
}
virtual bool checkArgType(
const MeasureType& argType,
const int argNum)
{
switch(argNum)
{
case 0:
// Nothing to check - we can handle any
// type.
// Remember the type of argument 1 – this
// will be the return type.
_returnType = argType;
break;
default:
return false;
}
return true;
}
virtual void eval
(

18 Oracle Retail Predictive Application Server


Const
DynamicExpressionFunction::ArgumentVectorType&
args,
DynamicExpressionFunction::ResultVectorType&
results
) const
{
// Loop over argument 1, populating the
// corresponding entries in the
// output vector with the last non-error value
// entry from the input.
ArrayCell lastNonErrorValue = errorValue();
for (ArgumentType::size_type i = 0;
i < args.at(0).size();
++i)
{
ArrayCell currentValue = args.at(0).at(i);
if (currentValue != errorValue())
{
lastNonErrorValue = currentValue;
}

results.at(i) = lastNonErrorValue;
}
}
virtual SemanticInformation::EvaluationModeType
evaluationMode() const
{
return SemanticInformation::VECTOR;
}
};

Derive from ParseNode/EvalNode


This is the low-level RPAS API for implementing functions.
 It has access to intersection and dimension information as well as the error and
ignore channels (for example, to report an error that is trapped by a prefer
statement).
 Like ExpressionFunction, functions written this way can be combined with other
operations in an expression.
 It should only be used if ExpressionFunction and DynamicExpressionFunction do
not meet your requirements.
 The RPAS “first” keyword is implemented this way. See
/rpas/11/AppLibs/RpasFunctions/rpas/IndexFirst*.h and IndextFirst*.cpp.

Derive from MultiResultFunctionParseNode/MultiResultFunctionEvalNode


This is similar to ParseNode/EvalNode, but it is intended for implementing functions that
return multiple results. It supports multiple return values ─ one primary and any number of
secondaries. It is an efficient way to compute groups of closely related values within a single
function, rather than having one single-valued function per result.
 Each return value can be a different type. Some return values can have a calendar
dimension and some cannot. Computation of each secondary result can be
individually controlled by changing the expression text (using named arguments).
 Although not fully automated, a helper function assists in implementing vector
mode and dropping the calendar dimension.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 19


 It can be partially combined with other operations in an expression. Each function
argument can be an expression, but the result must be assigned directly to the left-
hand side.
Examples are as follows:

Function Location and description

sumdiff /rpas/11/RpasTestUtility/rpas/SumDiff*.h and SumDiff*.cpp.


Very simple multi-result function.
getlastdigit /rpas/11/Libraries/Expression/unittest/GetLastDigitExpr.cpp.
Multi-result function returning three results, each of a different
type.
priceprofile1/priceprofile2 /rpas/11/Libraries/Expression/unittest/PriceProfileExpr.cpp:
Two samples in one. Both multi-result functions return three results,
some with a calendar dimension and some without. priceprofile1
drops the calendar dimension for its primary result. priceprofile2,
which derives from priceprofile1, does not drop the calendar
dimension.

Implementing a Function using ParseNode/EvalNode


At the most complex level, the implementation of a function requires implementing four
classes deriving from:
 ParseNodeFactory
 ParseNode
 EvalNodeFactory
 EvalNode
The following is a brief explanation of the life cycle of an expression:
All expressions start as strings (for example, “A = B + C” or “A = lag(B)”). The Parser object
parses these strings. If no syntax errors are encountered, a Parse Tree is built. This tree is
then checked for semantic errors. If none are found, the Expression is considered to be valid.
A valid expression may then be evaluated in either full (data load/batch mode) or
incremental (workbook update) mode. Either way, the ParseTree is fed through a collection
of EvalNodeFactories. An Eval Tree is generated, which is used to perform the calculations.
For example, assume that A, B, and C are real valued measures in a domain with a
“sku_str_week” intersection.
The expression “A = B + C” is transformed into a parse tree:
=
/ \
“A = B + C”  A +
/ \
B C
where = is an AssignParseNode with two children: a VarParseNode for measure A, and a
BinaryOpParseNode for an addition. The BinaryOpParseNode also has two children:
VarParseNodes for measures B and C.
Once this tree is built from the expression string, the recursive legalSubTree algorithm is run
on it to detect semantic errors. This algorithm is a recursive, post-order tree traversal of the
tree that stores the intersection and the type of the node before return.

20 Oracle Retail Predictive Application Server


In the above example, the legalSubTree algorithm is executed on the AssignParseNode that
calls the legalSubTree function on the VarParseNode for A. This node retrieves the type and
base intersection of measure A and returns True (successful validation). If measure A did not
exist, the function would have returned false.
Upon the return from VarParseNode A, the AssignParseNode calls the legalSubTree function
on the BinaryOpParseNode for +, which calls the legalSubTree function on the
VarParseNodes for B and C. Each of these stores its intersection and type, which is retrieved
from the MeasureStore that holds them and returns True (because they exist).
Upon the return from the VarParseNode’s legalSubTree function, the BinaryOpParseNode
queries them for their intersections and types. If the types and the intersections are
compatible (that is, they are the same type, and one intersection is above or equal to the
other), the BinaryOpParseNode stores its intersection and type and returns.
Finally, the AssignParseNode queries the BinaryOpParseNode and the VarParseNode for A
for their types and intersections. If they are valid (they are the same type, and intersection A
is below or equal to intersection of BinaryOpParseNode +), it returns True.
When the calling expression receives a True from the legalSubTree function, it knows that:
 the sub-tree can be legally evaluated at the base.
 each node in the tree knows its type.
 each node in the tree knows its intersection.
When the expression is asked to evaluate at some intersection, two phases occur in the tree.
The first uses the parse tree’s general-purpose query device to determine which instances of
the measure need to exist to allow calculation.
Once these arrays are generated, the parse tree is used by the Expression class’ buildEvalTree
method to build a tree to evaluate this expression at the given intersection. The resulting eval
tree exists only for the length of the evaluation and is then discarded. The tree is specific to
the expression and the intersection at which to evaluate the expression.
Forming the tree occurs by invoking the getEvalNode function of the ParseNode. This
function uses the stored EvalNodeFactory pointer to construct an eval node, passing itself as
an argument to the factory. Each factory invokes the call on the children of the node to get
their EvalNode implementations and finally returns the EvalNode for the ParseNode that
invoked the method.
Continuing with the example above, the getEvalNode is called on the AssignParseNode

Note: This is handled by the root class ParseNode and should


not be overridden.

This function calls the AssignEvalNodeFactory function getEvalNode using the pointer
stored in the ParseNode from the AssignParseNode’s chaining constructor. The factory then
calls getEvalNode on its children (VarParseNode A and BinaryOpParseNode +), which use
their factories and recursive invocation to build the eval tree.
All nodes in the eval tree are descendants of the EvalNode class, which implements two
principle recursive tree functions: eval and evalNA. Each node allocates a buffer of strongly
typed space to be used to hold the value of the calculation at that node.
Each node should be able to run in Single Cell mode (calculates one cell at a time) or Vector
mode (calculates a vector of cells at a time). Evaluation takes place using similar recursive
techniques. Both algorithms descend the tree traveling to the leaves of the tree (which are
either constants or VarEvalNodes), setting those EvalNode buffers to the correct values.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 21


Upon the return of all a node’s children’s eval calls, the parent calculates its value based on
the children’s calculated values, stores this result in its buffer, and returns. Finally, the
AssignEvalNode stores the result in the left hand side’s VarEvalNode, and eventually in the
array on disk.
Given this overview of the processes of the expression library, it is clear how a function fits
into the language. A function will become an internal node (or in some rare cases a leaf) in
the parse and eval trees. Consider the expression string, A = f(B,C + 1). It parses as follows:
=
/ \
A f
“A = f(B,C+1)”  / \
B +
/ \
C 1
Note the following about function f:
 The example takes two arguments. A function can take multiple arguments.
 Any type of ParseNode, including other functions, can be a child.
The following table describes the interfaces used to write your own function.

Interface Description

ParseNodeFactory This interface must be implemented to allow the parser to


recognize your function and create ParseNodes of the
appropriate type.
The factory stores the name of the function.
The factory is registered using a static interface on the Parser
class.
ParseNode This interface interacts with the expression to provide
information about the function before evaluation time.
The ParseNode stores a pointer to the EvalNode factory.
legalSubTree must be implemented.
EvalNodeFactory This interface converts a ParseNode into a corresponding
specific EvalNode.
It should allocate the eval node. Eval nodes are often, but not
always, implemented as templates (depending on whether they
need to support multiple types).

22 Oracle Retail Predictive Application Server


Interface Description

EvalNode This interface is used to calculate actual values.


It should allocate and de-allocate appropriate sized buffers.
Computed results should match the buffer type specified when
the eval node was initialized.
It should implement eval and evalNA.
Note: Custom functions are not allowed to generate an “ignore”
condition — only the built-in RPAS “if” function can do this. But
custom functions are responsible for propagating the ignore
channel from their child nodes (that is, the eval nodes
representing the arguments to the function).
Important: When an EvalNode is destroyed, it is responsible for
deleting its child eval nodes. For example, from RpasFunctions:
~AttributeEvalNode()
{
delete _child;
}

Implementing a Multi-Result Function


(MultiResultFunctionParseNode/MultiResultFunctionEvalNode)
Similar to ParseNode/EvalNode-based functions, multi-result functions are created by
creating four classes:

Interface Description

ParseNodeFactory Same as ParseNode/EvalNode-based functions.


MultiResultFunctionParseNode This interface interacts with the expression to
provide information about the function before
evaluation time.
MultiResultFunctionParseNode derives from
ParseNode.
In addition to its ParseNode-related
responsibilities, implementations of this
interface must implement
validateSecondaryResultLabel. This function
checks the left-hand side label to make sure it
matches one of the expected label values.
MultiResultFunctionEvalNodeFactory This interface converts a ParseNode into a
corresponding specific EvalNode.
MultiResultFunctionEvalNodeFactory derives
from EvalNodeFactory.
 It should allocate the eval node. Eval nodes
are often (but not always) implemented as
templates (depending on whether they
need to support multiple types).
Note: The “factory” function is named
buildMultiResultFunctionEvalNode instead of
getEvalNode, as on EvalNodeFactory.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 23


Interface Description

MultiResultFunctionEvalNode This interface is used to calculate actual values.


MultiResultFunctionEvalNode derives from
EvalNode.
 It should allocate and de-allocate
appropriate sized buffers.
 Computed results should match the buffer
type specified when the eval node was
initialized.
 In addition to its normal EvalNode-related
functionality, a class deriving from
MultiResultFunctionEvalNode implements
getEvalNode, acting as a factory for
SecondaryResultFunctionEvalNodes, which
serve as a temporary holding area for the
secondary function results assigned to a
measure. The class must retain a pointer to
each secondary eval node it creates to
implement eval and evalNA. It should
implement eval and evalNA, storing only
the secondary results that are actually being
stored. See the previous bullet point.
 If the multi-result function is designed to
support mixed intersections, verify this in
the constructor (that is, the vector results
have size > 1 and the non-vector results
have size = 1). RPAS does not check for this.
Important: When a
MultiResultFunctionEvalNode is destroyed, it is
responsible for deleting its child eval nodes.
For example, from Expression/unittest:
template <class T>
GetLastDigitEvalNode<T>::~GetLastDigitEvalNode
()
{
std::for_each(_children.begin(),
_children.end(), rpas::DeleteObject());
}

24 Oracle Retail Predictive Application Server


Named Parameters
On the left side of the equation, a variable may be preceded by a label.
Example:
frcst:f, int:i = autoes(sales)
In a function, a measure may be preceded by a label.
Example:
f = autoes(sales, alpha:a)

In both cases, the ParseNode will have the label attribute set to the value before the colon. If
no attribute is set, a numeric string corresponding to the position will be returned as the
label.

RPAS Java Expressions


RPAS Java Expressions provide a framework by which application writers can implement
algorithms that require complex custom iteration over existing data. Unlike RPAS functions,
Java expressions provide full control over the way source and destination data are accessed.
A Java expression is like a custom implementation bounded by RPAS parsing and expression
evaluation APIs.
Java expressions are built on top of the RPAS oracleRpasUtils library, which provides access
to APIs related to Measures, Cell Accessors, Iterators, and intersections. The RPAS
calculation engine manages when a procedure should be invoked, whether it is encountered
in a rule during a workbook calculation or in an expression during a batch run.

Characteristics and Limitations


From a user perspective, there are significant differences between an RPAS Java expression
and an RPAS function. Specifically, users should be aware of the following characteristics:
 Java expressions can only use measures, intersection modifiers, or literal constant
values as arguments. They cannot call other Java expressions or functions.
 While Java expressions fully control the evaluation/calculation process, the
calculation engine controls other aspects, such as protection processing and sequence
of calculations when the procedure is called.
 Java expressions cannot be used with functions, other Java expressions, keywords, or
modifiers.
 Because of their flexibility and the control that the developer has, Java expressions
can be used for a wider variety of special calculations and activities.
 Java expressions require a different syntax in the configuration: <- instead of = .

Extension Development Guide - Volume 1, RPAS DB Server Extensions 25


Implementation
Java expression writers must validate input arguments. The input can be a constant, an
intersection modifier (in the form of a dimension specification [hier].[dim]), or a measure
name. Due to the nature of Java expressions, users can control almost every aspect of the
evaluation cycle. There is not a standard way to implement a Java expression, except that
certain methods of the base interface must be implemented to provide basic functionality. To
write an RPAS Java Expression with minimal customization, the following steps are
recommended:
 Define the user’s Java expression type as a class implementing the RpasExpression
interface in the com.oracle.rpas.utils package.
 Implement the validate(Map map) method to verify inputs passed to the Java
expression in the form of a Map from argument labels to argument values.
 Implement the fullEvaluate(Map map) method to perform the full evaluation
operation for the Java expression.
 Implement the incrementalEvaluate(Map map) method to perform the incremental
evaluation operation for the Java expression.
Implementations using the validate(Map map), fullEvaluate(Map map), and
incrementalEvaluate(Map map) methods can leverage the RPAS Java Expression APIs
provided within the oracleRpasUtils package. The package provides a number of
interfaces that can be used to evaluate the Java expression.

Running Java Expressions


It is not necessary to register individual Java expressions. However, the RPAS Java
functionality is optional; the RpasJavaExpression function library must be registered during
installation or through a subsequent call to the regfunction server utility to evaluate Java
expressions.
It is only necessary to make the Java expression available to RPAS by placing its location
(along with the path to RPAS_HOME/lib/oracleRpasUtils.jar) within the
RPAS_JAVA_CLASSPATH. Once this is done, any rule group executed through mace or
through the RpasDBServer can execute the Java expression.
Rules may make use of Java expressions by calling the javaexpression procedure. This
procedure takes as arguments the name of the Java expression class along with any other
arguments the Java expression requires. All arguments to the javaexpression procedure must
be labeled; the labels serve as keys to retrieve the arguments within the Java expression. For
example:
destMeas:lhsMeasure <- javaexpression(class:”myJavaExpression”,
srcMeas1:rhsMeasure1, srcMeas2:rhsMeasure2)
This expression will perform a calculation using the myJavaExpression extension. All calls to
validate, fullEvaluate and incrementalEvaluation for this Java expression will receive a Map
containing the key/value pairs destMeas->lhsMeasure, srcMeas1->rhsMeasure1 and
srcMeas2->rhsMeasure2.

Debugging Java Expressions


Because Java expressions are launched from within the RPAS native application, a Java
debugger must be attached to the process once it has been spawned. To facilitate debugging,
RPAS has defined the environmental variable RPAS_JAVA_DEBUG_PORT. When this

26 Oracle Retail Predictive Application Server


variable is defined, RPAS suspends execution upon beginning evaluation of a Java
expression so that a debugger can be attached to the port specified.

Memory Management for Java Expressions


The Java Virtual Machine has the ability to manage memory through an automated garbage
collection process. For many Java applications, this garbage collection is sufficient to prevent
issues related to memory usage. However, due to the nature of the interfaces contained
within the Java Special Expression framework, the automated garbage collection provided
by the JVM may not prevent expressions from exhausting available memory without
additional effort on the part of the expression.
The reason for this is a result of the underlying implementation of the Java Special
Expression framework. Calls made to the APIs of the framework are translated through a
Java Native Interface (JNI) to the C++ libraries used by RPAS. Many of these operations,
such as instantiation of measure instances or cell accessors, require the C++ libraries to
allocate memory in addition to the memory allocated for the Java classes in the framework.
Because these C++ allocations occur beyond the JNI boundary, the JVM garbage collector is
unable to automatically free them as it frees memory allocated within the Java classes. As a
result, allocated memory within the native libraries will accumulate over the course of the
execution of a Java Special Expression.
All memory allocated within the C++ libraries will be freed at the conclusion of the Java
Special Expression. However, for expressions that require large numbers of instantiations or
that operate over a large amount of measure data, the allocated C++ memory can exhaust the
maximum allowable heap size of the JVM.
There are two options available to prevent issues related to the allocation of memory within
the native libraries when writing Java Special Expressions. The first option is to instruct the
JVM to reserve a larger amount of memory when executing expressions. The second option
is to make use of methods present in many APIs to explicitly free the memory allocated
within the native libraries.

Increasing the Default Heap Size of the JVM


RPAS has defined an environment variable named RPAS_JAVA_MAXHEAPSIZE. When this
variable is set, the value it is given is passed along to the JVM when a Java Special
Expression is executed to instruct the JVM to allow a large maximum memory space. The
value specified for the variable should be in the format of the number of megabytes of
memory to request followed by the letter m (such as 1024m).
In many cases, changing the maximum heap size of the JVM will be sufficient to prevent
memory exhaustion issues. The JVM used to execute the Java Special Expression must
allocate a significant amount of memory in order to support the use of the JNI that allows the
RPAS C++ libraries to process the calls made through the APIs.
This allocation results in a noticeably smaller amount of memory available to execute the
calls made through the APIs. By increasing the maximum heap size from the default, it is
possible to allocate enough memory to support the JNI allocations while still reserving an
acceptable amount of free heap for the execution of the expression.

Explicit Memory Clean-up


For some expressions, it may not be sufficient to raise the maximum heap size. This is
generally true of Java Special Expressions that make use of a large number of iterators and

Extension Development Guide - Volume 1, RPAS DB Server Extensions 27


cell accessors. For these expressions, it is necessary to free the native memory allocated by
calls through the JNI explicitly.
Allocated C++ memory can be freed by calling the dispose method of the Java Special
Expression class that resulted in its allocation. When working with Java Special Expressions
that instantiate a large number of CellAccessor, Cursor, IterativeAccessor or
RandomCellAccessor objects, the expression should call the dispose method of the classes
when they are no longer needed.
By making use of the dispose functionality of the accessor classes, Java Special Expressions
can free native allocations of memory in a manner similar to the way the JVM garbage
collector frees allocations made within the JVM. Efficient de-allocation of will allow the
recycling of heap memory and allow even expressions that make large numbers of
allocations to execute without exhausting available memory.

Example
This example shows how a custom Java expression must be invoked when writing rules,
given that destMeas, srcMeas, and cellCountMeas are measures in the domain. Because of
the use of argument labels, the order of arguments to javaexpression is not important.
However, it is still necessary to differentiate between left side and right side measures.
destMeasure:destMeas <- javaexpression(class:”copyMeasure”, srcMeasure:srcMeas,
popMeasure:cellCountMeas)
The class for copyMeasure could be implemented as follows:

public class copyMeasure implements RpasExpression


{
public void validate(Map map)
{
if (!map.containsKey("srcMeasure"))
{
invalidArg("Missing label 'srcMeasure'");
}
if (!map.containsKey("dstMeasure"))
{
invalidArg("Missing label 'dstMeasure'");
}

if (!map.containsKey("popMeasure"))
{
invalidArg("Missing label 'popMeasure'");
}

DimensionalMeasure srcMeasure = (DimensionalMeasure) map.get("srcMeasure");


DimensionalMeasure dstMeasure = (DimensionalMeasure) map.get("dstMeasure");
ScalarMeasure popMeasure = (ScalarMeasure) map.get("popMeasure");

if (srcMeasure.isLhs())
{
invalidArg("srcMeasure is not on right handle side");
}

if (dstMeasure.isRhs())
{
invalidArg("dstMeasure is not on left handle side");
}

28 Oracle Retail Predictive Application Server


if (popMeasure.isRhs())
{
invalidArg("popMeasure is not on left handle side");
}

if (srcMeasure.getName().equalsIgnoreCase(dstMeasure.getName()))
{
invalidArg("srcMeasure and dstMeasure must be two different measures");
}

MeasureType srcType = srcMeasure.getType();


MeasureType dstType = dstMeasure.getType();
MeasureType type = popMeasure.getType();

if (0 != srcType.compareTo(dstType))
{
invalidArg("source and destination measure must be of the same type");
}

if (!type.isInteger())
{
invalidArg("popMeasure must be integer scalar measure");
}

Intersection srcIntersection = srcMeasure.getBaseIntersection();


Intersection dstIntersection = dstMeasure.getBaseIntersection();

if (!srcIntersection.isConforming(dstIntersection))
{
invalidArg("src and dst intersections are not conforming");
}
}

public void fullEvaluate(Map map)


{
evaluate(map, false);
}

public void incrementalEvaluate(Map map)


{
evaluate(map, true);
}

private void invalidArg(String msg)


{
throw new InvalidArgumentException(msg);
}

private void evaluate(Map map, boolean inc)


{
ProfileTimer timer = RpasUtil.getProfileTimer();

timer.start();

DimensionalMeasure srcMeasure = (DimensionalMeasure) map.get("srcMeasure");


DimensionalMeasure dstMeasure = (DimensionalMeasure) map.get("dstMeasure");
ScalarMeasure popMeasure = (ScalarMeasure) map.get("popMeasure");

MeasureType srcType = srcMeasure.getType();


MeasureData srcData = srcMeasure.getMeasureDataAtBase();

Extension Development Guide - Volume 1, RPAS DB Server Extensions 29


MeasureData dstData = dstMeasure.getMeasureDataAtBase();

IterativeAccessor srcIter = srcData.getPopulatedIterator(inc);


int popCount = 0;

if (!inc)
{
dstData.fill(srcData.getNaValue());
}

if (map.containsKey("useObject"))
{
srcType = MeasureType.String;
}

// Date and integer are both store as real


if ((srcType.isReal()) || (srcType.isDate()) || (srcType.isInteger()))
{
// Real/double case
if (dstData.needsSubCubeIterator(srcIter))
{
IterativeAccessor subIter = dstData.getSubCubeIterator(srcIter);
CellAccessor accessor = dstData.getCellAccessor(subIter, inc);

while (srcIter.next())
{
double cell = srcIter.getCellAsDouble();

while (subIter.next())
{
popCount++;

accessor.setCellToDouble(cell);
}
}
}
else
{
CellAccessor accessor = dstData.getCellAccessor(srcIter, inc);

while (srcIter.next())
{
popCount++;
accessor.setCellToDouble(srcIter.getCellAsDouble());
}
}
}
else if (srcType.isBoolean())
{
// Boolean case
if (dstData.needsSubCubeIterator(srcIter))
{
IterativeAccessor subIter = dstData.getSubCubeIterator(srcIter);
CellAccessor accessor = dstData.getCellAccessor(subIter, inc);

while (srcIter.next())
{
boolean cell = srcIter.getCellAsBoolean();

while (subIter.next())
{

30 Oracle Retail Predictive Application Server


popCount++;

accessor.setCellToBoolean(cell);
}
}
}
else
{
CellAccessor accessor = dstData.getCellAccessor(srcIter, inc);

while (srcIter.next())
{
popCount++;
accessor.setCellToBoolean(srcIter.getCellAsBoolean());
}
}
}
else
{
// Object case.
if (dstData.needsSubCubeIterator(srcIter))
{
IterativeAccessor subIter = dstData.getSubCubeIterator(srcIter);
CellAccessor accessor = dstData.getCellAccessor(subIter, inc);

while (srcIter.next())
{
Object cell = srcIter.getCell();

while (subIter.next())
{
popCount++;

accessor.setCell(cell);
}
}
}
else
{
CellAccessor accessor = dstData.getCellAccessor(srcIter, inc);

while (srcIter.next())
{
popCount++;

accessor.setCell(srcIter.getCell());
}
}
}

popMeasure.setCellToInteger(popCount);

Logger logger = RpasUtil.getLogger();

logger.logInformationMsgEndl("CopyMeasure copied " + popCount + " cells");

timer.stop();

timer.logElapseTime("CopyMeasure");
}
}

Extension Development Guide - Volume 1, RPAS DB Server Extensions 31


Measures
Measures must be registered. The following is a reference for the measure properties that are
supported in RPAS.

General Measure Properties


Property Description Updateable

label Label of measure. Defaults to measure name. Updateable


Label of measure for display in client (in grid
and measure dialogs). There is no maximum
size limit, but you should keep grid display
limitations in mind when creating a measure
label.
(History: displayed in wb, but not in measure
dialogs.)
description Potentially long, descriptive summary of Updateable
measure.
type Base types: int, real, string, date, Boolean. The Not updateable
numeric representations of these types are 1, 2,
3, 4, and 6 respectively.
Note: This is a required property for adding a
measure.
navalue Suggested value for unpopulated (na) cells. Not updateable
Defaults according to type. Initially, the base
array of a measure is defined with this naval.
This is subject to change to optimize the
compression factor.
baseint Base intersection for measure. Concatenation of Not updateable
all dimension names (4 characters, suffixed by
“_” as necessary). Dimensions are ordered
according to the hierarchy to which they
belong. In [Measure]SFX array, baseint is
prefixed by I (simply I for scalar measures).
Note: This is a required property for adding a
measure.
defagg Default aggregation method. See Rule Updateable
Functions document for master list.
Note: Changes to defagg will only be reflected
in new or rebuilt workbooks.
defspread Default spread method. Updateable
Note: Changes to defspread will only be
reflected in new or rebuilt workbooks.
allowedaggs Permitted aggregation methods. Updateable
Syntax is MAX MIN TOTAL.

32 Oracle Retail Predictive Application Server


Property Description Updateable

basestate Editable at base level: read, write. Updateable


Default to read.

aggstate Editable at aggregate levels: read, write. Updateable


Default to read.
db Database where measure is stored in domain Not updateable
(in future, a partitioned database which
comprises many partitions).
If database is NA, the measure is not stored.
It specifies the relative path from domain root.
Syntax is data/test.
viewtype Indicates if the measure is calculated when Updateable
viewed.
If the view type is not None, the measure must
not be materialized (that is, not stored in
workbook or domain) and must appear on the
left side of only one expression in a rule group
(that is, the view expression). Synchronized
view types are maintained immediately at cell
edit time.
View types (and their corresponding integer
values) include:
 none (0) – measure is stored
 view_only (1) – measure is calculated
when viewed (aggstate and basestate must
be read-only)
 sync_first_lag (2) – period 1 from first
measure (no calendar), periods 2..N from
second measure 1..N-1 (lag) [for instance
bop  os & eop]
 sync_lead_last (3)– periods 1..N-1 from
first measure 2..N (lead), period N from
second measure (no calendar) [for instance,
eop  bop & cs]
 sync_first (4) – get period 1 from measure
(similar to first agg type along calendar
dimension) [for instance os  bop]
 sync_last (5) – get period N from measure
(equivalent to last agg type along calendar
dimension) [for instance cs  eop]
syncwith Comma separated list of measures used for Updateable
synchronization. Depends on view type.
insertable Indicates whether measure is visible to user for Updateable
inclusion in workbooks. This is used in
addition to measure security. Defaults to True.
refreshable Indicates whether measure is refreshable in a Updateable
workbook.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 33


Property Description Updateable

range Specify suitable range for the measure at edit Updateable


time. For picklist measures (indicated with
ui_type property), these are the
displayed/possible values.
For numeric values, the syntax is
Lower Bound : Upper Bound.
For strings, it is the maximum length.
For picklist measures (both numeric and
string), values can be explicitly listed as
a,b,c,d…
Labels can be specified for each value such as
valuea(labela) and valueb(labelb), where the
labels are displayed in the user interface.
ui_type Indicates whether the measure is a picklist. Updateable
materialized There are two possible values for materialized: Not updateable
Persistent: a "normal" measure
Display: A measure that is only calculated
when it is displayed (for instance, in the client).
Only the displayed values are calculated, rather
than the whole measure, which significantly
improves performance with these measures.
Display-only measures have three major
restrictions:
 Must have agg type "recalc"
 Cannot appear on the RHS of any rule
 Cannot be edited.
lowerbound A measure that provides minimum bounding Not updateable
values for this measure’s data cells.
upperbound A measure that provides maximum bounding Not updateable
values for this measure’s data cells.
tokenmeas Values are True or False to indicate whether the Not updateable
measure is a token measure.
materialized Determines the computation status of the Updateable
measure in the client. For example, does the
measure need to be calculated whenever
anything that affects it is changed, or only
when it is to be displayed?
fnhbi Use this flag in a global environment if a high Not updateable
base intersection is to be forced to live in the
partitioned domains, as opposed to the master
domain.

34 Oracle Retail Predictive Application Server


Property Description Updateable

scriptname Name of the script to run. Updateable


hybridaggspec To be used in conjunction with the defagg of Updateable
“hybrid.” Use this to specify different
aggregation methods along different
hierarchies of the same measure.

Notes

When a recalc measure is registered, its aggstate and basestate


are set to a user-specified value no matter what is specified for
the defspread field.

When a measure with defagg PopCount or NobCount is


registered, this measure's defspread is set to NONE; aggstate is
set to read only no matter what the user specifies for this field.
For measures that do not fit into the above descriptions, if their
defspread is NONE, their aggstate is always read only.

Measure Loading Properties


Property Description Updateable

loadint Intersection at which the measure is loaded (for Not updateable


ovr, inc loads).
Default to baseint.
Changing loadint will make it impossible to
back out previous data loads.
filename File used to load measure (suffix of ovr, inc, Updateable
clr).
Default to measure name.
start Start column of data (defaults to column after Updateable
dimensions).
width Width of data (defaults by type). Updateable
Note: For Boolean types, width can be >= 1, but
only the first character is read.
loadstokeep History of loads (that may be backed out). Updateable
Default to 6.

loadagg Used when aggregating from load intersection. Updateable


stageonly When True, data is not loaded into measure, Updateable
but left in a staging array for custom
processing.
Default to False.
clearint Intersection at which to clear measure data (for Updateable
clr loads).

Extension Development Guide - Volume 1, RPAS DB Server Extensions 35


Property Description Updateable

purgeage Limits the history of data in a measure along Updateable


the time dimension (purge occurs during load
measure process).
Default to NA.

Virtual Measures
In RPAS, measures such as Opening Stock (OS), Begin of Period (BOP), End of Period (EOP),
and Closing stock (CS) have a predefined relationship that is always True for predictive
applications. Assuming that data is stored from a starting point to an ending point, this
relationship is characterized as follows:
 OS is equal to the first element of BOP across the time dimension.
 The relationship also states that nth + 1 element of BOP is equal to the nth element of
EOP across the time dimension.
 The relationship also states that the last element of EOP across the time dimension is
equal to CS.
To support this predefined relationship between OS, BOP, EOP, and CS, the RPAS 11 client
supports the concept of virtual measures. Virtual measures are defined by a set of rules that
allow the client to calculate the data for virtual measures from stored measures. This set of
rules is created to implement the relationship between OS, BOP, EOP, and CS. For any
implementation, both OS and EOP are stored measures with BOP and CS virtual measures,
or BOP and CS are stored measures with EOP and OS virtual measures.

Editing
If a virtual measure is edited, the edit will be translated to the corresponding cell address of
the stored measure that is related to the virtual measure. The calculation engine cannot
handle virtual measures being changed.

Protection
When the read/write state of a virtual measure is calculated, the cell address of the stored
measure corresponding to the cell address of the virtual measure is used.

Deferred Calculation
When the deferred calculation queue is applied to a visible grid, all stored measures that
have a cell edit that corresponds to a visible virtual measure address are displayed in the
visible cell of the virtual measure.

36 Oracle Retail Predictive Application Server


Non-Materialized Measures
Non-materialized measures are temporary or transient; they are always assumed to be of
recalc aggregation type. Non-materialized measures enhance performance because they:
 are calculated only when required.
 are not persisted to the domain or workbook.
Expressions for normal (materialized) measures are always calculated when the rule is
effected and the expression is selected. This is not the case with non-materialized measures,
which are only calculated when required (for example, during the fetch process for display-
only non-materialized measures).

Display-Only Non-Materialized Measures


RPAS supports display only non-materialized measures. They provide significant
performance improvements when used for performance indicators that cannot be
manipulated and are view only. The following requirements must be observed to utilize
display only non-materialized measures.
 The measure cannot appear on the right-hand side of any expression.
 The measure must be registered with Recalc aggregation type and follow the rules
for recalc measures.
 The measure must be registered with no db.
 The measure must be registered with materialized type “display.”

Token Measures
Example:
@a = @b + c / d , where a and b are token measures.

Assumptions
 Token measures are string-type scalar measures. They cannot appear on the right-
hand side or the left-hand side of expressions without the indirection operator @.
 Token measures cannot be aggregated or calculated. An expression including non-
indirected token measures (without @ prefix) will not be valid. Expression parser
will not validate such expressions.
 Each token measure must be initialized with the name of a valid non-token measure.
This must be done before running processes that create tokenized expressions.
Examples of such processes are alert-manager and domain configuration processes.
 RPAS provides a C++ api and a utility called regTokenMeasure for assigning values
to token measures. This api will register non-existing token measures and populate
them with the provided values.
 Token measures do not need to be registered by RPAS applications. Registration of
these measures is done internally by regTokenMeasure utility. Refer to the domain
utility regTokenMeasure.

C++ API for Setting Token Measures


MeasureStoreTools class contains the interface for registering token measures.
MeasureStoreTools::registerTokenmeasure(MeasureStore& store,
const std::map<String, String>&= tokenValuePairs);

Extension Development Guide - Volume 1, RPAS DB Server Extensions 37


The input map is a collection of token-value pairs while the first argument is the name of the
token measure and the second argument is the name of the reference measure.

Measure Attributes
Several RPAS applications require the ability to register sets of mostly identical measures
that slightly vary from each other based on application specific attributes. RPAS supports
this ability by introducing the concept of “measure attribute” (that is, single level roll-ups
along data hierarchy above the measure dimension). Measure attributes are valid dimensions
in the domain and their name should not consist of more than four characters. The interfaces
for dynamic registration of measure attributes and using a set of attributes to allow existence
testing, creation, and retrieval of measures are defined.

Measure Attribute Interface


The basic RPAS measure hierarchy is the data hierarchy. By default, it no longer contains
roll-ups at creation time. Instead, applications can register the desired one level higher
aggregates of measures as needed. For efficiency, it is recommended that all such attributes
be registered during the application’s initial registration into the RPAS base.
Attributes can be any useful higher level-grouping device. Examples of potential attributes
are role, version, metric, forecast level, and forecast date. To promote readability, it is further
enforced that attributes should not share a name with the information components that are
associated with a measure (for example, label, type, baseint, navalue, defagg, defspread,
allowedaggs, range, refreshable, db, insertable, basestate, aggstate, stageonly, filename,
loadint, clearint, loadstokeep, start, width, loadagg, purgeage, viewtype, syncwith,
description, and ui_type).
Finally, because the attributes are higher-level roll-ups of the measure hierarchy, each
attribute must be registered with a default value. The default value will be the higher-level
position that any measure will belong to when no specific value is assigned to that measure
for that attribute.
Registration and removal of attributes are through the MeasureStore.

Function Description

void registerAttribute( Register a measure attribute


const AliName& attName,
const AliName& defVal,
const String& attributeLabel,
const String& defValueLabel)
void unregisterAttribute( Unregister a measure attribute
const AliName& attName)

38 Oracle Retail Predictive Application Server


Once the attribute is registered, two functions are used to manipulate the positions present in
the dimension.

Function Description

void addAttributePosition Add an measure attribute position


(const AliName& attName,
const AliName& posName,
const String& posLabel)

void removeAttributePosition Unregister a measure attribute position. The


(const AliName& attName, lastMeasName is the last measure that rolls up to
const AliName& posName, posName, but it is going to change to another
const String& lastMeasName = "") rollup for the attribute.

A position may be added for an attribute multiple times, although it will only appear once in
the dimension position list for the attribute. With the exception of the lastMeasName
measure (if it is not empty string), any attempt to remove an attribute position that is in use
as a roll-up for a measure will throw a MeasureAttributeException exception.

Attribute Controlled Measure Interface


One general basic type call is supported in this interface. This call mimics a section of the
more traditional measure registration interface. In fact, the primary difference between the
attribute based interface and the existing interfaces is the use of an attribute-value pair map
instead of a measure name as the vehicle for referring to a measure. For convenience, the
following type is added to the MeasureProperties class:
typedef std::map<AliName, AliName> MeasAttrCollection;
Based on this type, the following procedures are available to register a measure through
MeasureStore:

Function Description

Measure& registerMeasure Register a measure where the attributes for this


(const MeasureProperties& measure can be prior set by calling rollups (const
propertiesMap); MeasAttrCollection & rollups) function on the
propertiesMap object.

The parameter propertiesMap encapsulates the information components associated with a


measure. Three components—measure name, type, and baseintersection—are required for
constructing such an object. Other information components will be set to default values if
they are not explicitly provided. If the name component in the propertiesMap object is an
empty string, it will be set to a unique measure name at registration time.
Removal of measures uses the following interface:

Function Description

void unregisterMeasures Remove all measures that fit the description


(const MeasAttrCollection&
description)

Extension Development Guide - Volume 1, RPAS DB Server Extensions 39


Retrieval of measures uses the following interfaces:

Function Description

String::VectorT Remove all measures that fit the description.


lookupMeasures
(const MeasAttrCollection&
description)
String::NoCaseSetT Return the names of all the measures that do not
measuresWithoutAttribute roll up to the specified attribute position.
(const AliName& attName, const
AliName& attValue)
String::NoCaseSetT Return the names of all the measures that roll up
measuresWithAttribute to the specified attribute position.
(const AliName& attName, const
AliName& attValue)

All of these functions could issue a MeasureAttributeException to indicate a bad map with
the details manifested in the exception message.

For an existing measure, the following functions of Measure class can be used to set and
retrieve measure attribute information on the basis that measure attributes and positions
were properly registered and added. Otherwise, MeasureAttributeException exception will
be thrown.

Function Description

void setRollups Specify the hierarchy roll up information


(const for a measure.
MeasureProperties::MeasAttrCollection&
description)
void setRollup Make a measure to roll up to a specific
(const AliName& attName, const AliName& measure attribute position.
posName)
MeasureProperties::MeasAttrCollection Retrieve the hierarchy roll up
getRollups() const information of a measure.
AliName getRollup Retrieve the position a measure rolls up
(const AliName& attName) const to for a specified measure attribute
dimension.

40 Oracle Retail Predictive Application Server


MeasureStore Class Library
In RPAS, the MeasureStore class and its subclasses (Domain and Workbook) form the basis
for the manipulation of data stored in RPAS and the construction of viewable subsets of that
data. The domain represents the primary data storage of RPAS, and a workbook represents a
temporary data cache for both display and manipulation purposes. Within a single process,
there can only be one Domain object and one Workbook object in existence simultaneously.
A single Domain object exists for the duration of a process. Several workbooks may exist
within the lifetime of a single process.
A measure store (associated with either a domain or a workbook) contains interfaces to all
the primitives necessary to manipulate and use measures. These tasks include:
 Registering and unregistering measures
 Navigating hierarchies and dimensions
 Retrieving and setting the properties of existing measures
 Retrieving arrays that correspond to a measure instance
The first step to performing any of the above tasks is to get a reference to the appropriate
measure store. Three static functions are used to retrieve the appropriate references:

Function Description

MeasureStore& Returns a reference to the active workbook


MeasureStore::current(); measure store, if one exists. Otherwise, it returns a
reference to the domain measure store.
Domain& Domain::current(); Returns a reference to the domain measure store.

Workbook *Workbook::current(); Returns a reference to the active workbook


measure store. If there is no active workbook,
returns NULL.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 41


Workbooks and Workbook Templates
Most user interaction from the RPAS client is done using workbook templates and
workbooks. A workbook template can be used to configure and build a workbook that is
opened in the RPAS client or pre-built using the Auto Workbook Build process.
The implementation of most of the standard workbook templates in RPAS can be
accomplished through configuration tools. See the RPAS Configuration Tools Guide for
information about how to configure a workbook. For customization, coding must be done in
C++. This includes having a custom wizard process (where wizards are simple hierarchy
selections) or a custom workbook display (where the windows/tabs displayed within the
workbook depend on the wizard selections).

Workbook Templates
The Dynamic Template Library takes care of the static workbooks (where the non-
hierarchical content of the displayed workbook does not depend on the wizard selections)
built with standard two-tree wizard pages (simple ranging down of the hierarchies in the
workbook). It also takes care of the display aspect of the custom wizards. To minimize the
coding effort, custom workbook templates should be built on the functionality defined by the
Dynamic Template Library.
The Dynamic Template Library was developed as a configuration aid for deployment of
workbooks. It uses configuration files (flat text files outputted from the Configuration tools)
and converts them into the workbook templates. See Appendix: Configuration Repository for
the layout of such configuration files.

42 Oracle Retail Predictive Application Server


The following figure describes the code layout for custom workbook development.
RPAS DB Server Classes

Workbook Template Factory Workbook Template

1 Wizard Page
Win Info Widget Info

0..*
0..* 0..*

Meas Sheet Wizard Info

Contains

Template Registration Factory Template Factory -Creates Dynamic Template Dynamic Wizard

1 1

1 Custom Template Factory Custom Dynamic Template

Custom Template Registration Factory Custom Dynamic Wizard

Custom Workbook Template Classes

The Template Registration Factory registers and creates templates. It searches the
repos/templates (see Appendix: Configuration Repository) directory for tmpl.cfg files. Each
tmpl.cfg is an individual workbook template residing in the <workbook template group
name>/<workbook template name>/tmpl.cfg. All configuration files are loaded at once
when the client establishes connection with the domain.

Template Factory
The Template Factory parses all individual tmpl.cfg files and builds individual info objects
and relationships within these objects. It creates a String to String Vector map to contain all
of the attributes that are in the cfg files and passes it to the constructor of the appropriate
BaseInfo derived class.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 43


BaseInfo
BaseInfo is the base class for all information classes. It contains name and attribute map
members. It provides the generic functionality for handling attributes defined in the config
file. Examples of info classes are TemplateInfo, MeasSheetInfo, WinInfo, WizardInfo, and
WidgetInfo.

DynamicTemplate
This template converts the parsed information objects into RPAS objects and implements the
RPAS workbook template with them. It is derived from the Workbook Template Class.

Dynamic Wizard
This class is derived from the RPAS WizardPage class and is responsible for converting the
parsed WizardInfo into WizardPage object and the WidgetInfo objects into proper RPAS
controls. It does not contain any processing logic for the controls. It is the derived custom
dynamic wizard’s responsibility to define the processing of these custom wizards.

CustomTemplateRegistrationFactory
Typically, it inherits all of its functionality from its baseclass TemplateRegistrationFactory
except for two methods: getTemplateFactory and getTemplateFilePath. The following is a
sampleCustomTemplateRegistrationFactory.
CustomTemplateRegistrationFactory class

class CustomTemplateRegistrationFactory
: public rtkapp::cnfg::TemplateRegistrationFactory
{
TemplateRegistrationFactory::TemplateFactoryCPtr
getTemplateFactory() const
{
return TemplateFactoryCPtr( new CustomTemplateFactory() );
}
FilePath getTemplateFilePath() const
{
FilePath filePath = DomainState::instance().pathToDomain();
filePath.addSub( "repos" );
filePath.addSub( "customtemplates" );
return filePath;
}
}

44 Oracle Retail Predictive Application Server


CustomTemplateFactory is derived from the TemplateFactory class. Typically, the
createTemplate() method is the only thing to override to return a CustomDynamicTemplate
object, as opposed to DynamicTemplate object. A typical CustomTemplateFactory could look
like the following
CustomTemplateFactory class
class CustomTemplateFactory: public TemplateFactory
{
DynamicTemplate* createTemplate(const TemplateInfoCPtr& cpTemplateInfo) const
{
return new CustomDynamicTemplate(cpTemplateInfo);
}
};
CustomDynamicTemplate is derived from the DynamicTemplate class and defines all
functionality that differs from a standard workbook template. Most coding for the custom
workbook is in this class. A typical CustomDynamicTemplate structure is defined below,
exluding the actual implementation details.
CustomDynamicTemplate class
class CustomDynamicTemplate : public DynamicTemplate
{
public:

// Constructor
CustomDynamicTemplate(const TemplateInfoCPtr& cpTemplateInfo);

// Destructor
virtual ~CustomDynamicTemplate();

virtual void initializeWizard();

// overload wizard traversal logic


// if the wizard process is static there is no need to
// overload these. On the other hand, if the next wizard is
// based on selections in the previous wizards or if
// something needs to be done while going to the previous
// page etc., the following methods will have to be overloaded
virtual WizardPage* moveToNextWizardPage();
virtual WizardPage* moveToPreviousWizardPage();
virtual WizardPage* getCurrentWizardPage();
virtual WizardPage* getPreviousWizardPage() ;

// overload wizard button states


virtual bool getFinishButtonState() ;
virtual bool getBackButtonState();
virtual bool getNextButtonState();

// prerange a hierarchy before presenting for selection


virtual bool rangeHierarchy(const String& id,
const String& hierName, String& dimName,
String::NoCaseSetT& positions);

// overload workbook window registration


virtual void registerWorkbookWindows(Workbook& wb);

// overload position hiding for various sheets


virtual void hidePositions(Workbook & wb);

WizardFinish* finishWizard();

Extension Development Guide - Volume 1, RPAS DB Server Extensions 45


// overload to register measures locally to the workbook
virtual void registerLocalMeasures(Workbook& wb);

protected:

// Overload to do something just before the workbook huild


virtual void preWorkbookBuild(Workbook& workBook);

// Overload to change the behavior once the workbook is built but before it is
displayed
virtual void postWorkbookBuild(Workbook& workBook);

// Overload to modify the rules in the workbook.


virtual void postMeasureRegistration(Workbook& workBook);

// Overload to modify the workbook hierarchies


virtual void describeDimensionDict(DimensionDictDescription& ddd);

// Overload to register the rule groups.


virtual void registerTemplate();
}
While these cover most of the methods that could be overloaded, only a few of these might
have to be overwritten to achieve the desired behavior of a custom workbook template.
The CustomDynamicWizard is derived from DynamicWizard. It defines the behavior that
differs from a WizardPage object. The structure of a CustomDynamicWizard is illustrated
below without the implementation details:
class CustomWizardPage : public WizardPage
{
public:
CustomWizardPage ();
virtual ~ CustomWizardPage ();

// initialize the page


virtual bool initialize();
// prepare the page before displaying for the first time
virtual bool setInitialState();
// validate the current wizard page
virtual bool validateResults(String& whyNot);
// Returns whether this wizard page should be visible.
/* Default implementation always returns true. */
virtual bool isVisible() const;
// Returns a list of widgets.
const WidgetInfoCPtrVector& getWidgets() const;
// Add Static Widgets...
virtual void addStaticWidget(WidgetInfo *pWidgetInfo);
// Add DynamicWidgets...
virtual void addDynamicWidget(WidgetInfo *pWidgetInfo);
// This method will force this wizard page to reinitialize
// itself each time the page is display, this is necessary
// when a successive page has dependencies on a previous
// page, and the user hits the back and next buttons.
void alwaysRefresh(bool b)
{
_bAlwaysRefresh = b;
}
bool alwaysRefresh()
{
return _bAlwaysRefresh;

46 Oracle Retail Predictive Application Server


}
// The "dependent" is the name of the control that is managed
// by the "controller..."
bool addDependency(const String& controller,
const String& dependent);
void filterSelections(String::SetT& preSelectedPos,
const String::SetT& allPos1);
void setInitialized()
{
_bInitialized = true;
}
bool initialized()
{
return _bInitialized;
}
virtual void describeDimensionDict(
DimensionDictDescription& ddd);
// to overload the saving and restoring behavior of
// the previous selections and data entered in wizards
virtual void saveControlDataArray();
virtual void restoreControlDataArray();
void saveControlDataArrayTo(const FilePath& path);
void restoreControlDataArrayFrom(const FilePath& path);
void clearControlsAndOptions();
bool clearTreeOptions(const String& controlName);
// The following methods though could be overloaded are
// less likely to be overloaded.
virtual void rollupChange(
const String& controlName,
const String& rollupName,
UIControlOptionSuperTree*& option);
virtual bool getRange(String& dimName,
String::NoCaseSetT& positions);
FilePath getSelectionFilePath(
const FilePath& parentDir) const;

Extension Development Guide - Volume 1, RPAS DB Server Extensions 47


Appendix: Configuration Repository
The configuration repository contains the domain configuration files. These files are located
in the repos directory within the domain. The following is the configuration repository
directory structure.
<domain>
data
scripts
src
input
output
setup
bin
users
repos
rules
rule.cfg
measures
meas.cfg
templates
<template group (old terminology: wbtg)>
<template name (old terminology: wbt)>
tmpl.cfg
<app-ext>templates
<template group (old terminology: wbtg)>
<template name (old terminology: wbt)>
tmpl.cfg
The templates directory contains two layers of subdirectories. The first level directory
represents the workbook template group. Within each template group directory, there are
directories with that template name. The directory with the template name contains a
tmpl.cfg file, which represents a single template. See Template Configuration for details. The
configuration tool searches for the specified template by looking in the template group
directory and then the template name directory. For example, if a template is named
UserCreate and it is in the AdminTemplate group, the configuration tool will look for the
template configuration file in the repos/templates/admintemplate/usercreate directory.
The <app-ext>templates is a replica of the templates directory, except that it serves as a
repository for the individual application extension’s template configuration files.

Template Configuration
The tables in this section describe object types and attributes.

File Format
Each configuration file represents one template. The template configuration file format is as
follows:
Template configuration file format
<object type> <name> = {
<attribute> = <value>…;
<attribute> = <value>…;
<attribute> = <value>…;
};

Extension Development Guide - Volume 1, RPAS DB Server Extensions 48


There are many possible object types. The following describes each object type and the
attributes it supports. The object types, names, and attributes are not case sensitive. In some
cases, the values are case sensitive. The order of the objects and the order of attributes within
an object are irrelevant. The identifier type indicates that it must reference an existing object
in the same configuration file. The list type indicates a comma-delimited list.

Object: Group
Attribute Type Required or Optional Description

Label String Required Label displayed to the user on the group tab.

Object: Template
Attribute Type Required or Optional Description

Group Identifier Required


Type String Required The type of template to create.
The only valid value is DynamicTemplate.
Derived libraries may support additional
values.
ImageEnabled Boolean Optional Whether the template should support image
display.

PreRangeMask Identifier Optional The name of the measure to be used for pre-
range masking within the wizards of the
template.
Label String Optional The label displayed to the user in the group
listbox.
If missing or empty, it defaults to the name.
LoadRule String Optional The name of the existing rule group in the
domain to use to load data into measures.

CalcRule String Optional The name of the existing rule group in the
domain to use to calculate data.

CommitRule String Optional The name of the existing rule group in the
domain to use to commit data.

RefreshRule Strings Optional A comma separated list of names of existing


rule groups in the domain to use to refresh
data.
HbiAccess Boolean Optional True if the template uses hbi (high base
intersection) measures. Hbi measures are
measures registered above the partitioning
dimension of a global domain.
DpmDimensions String Optional A comma separated list of dimensions for
which this template should allow Dynamic
Position Maintenance.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 49


Attribute Type Required or Optional Description

MaxDB Integer Optional The maximum number of databases for


workbook measures.

MaxMeas Integer Optional The maximum number of measures per


database.

Sheet List of This is only optional for derived templates


identifiers that overload the finishWizard function.
HidMeas List of A list of hidden measure names for a
strings workbook.
All measures in the rule group must be listed
either on a sheet or in this list.

Wizard List of Optional


identifiers

HierMod List of Optional


identifiers

DynamicHier List of Optional


identifiers

CustomMenu List of Optional Currently, only one menu is supported.


identifiers

Object: Sheet
Attribute Type Required or Optional Description

Label String Required Sheet label.


Intx String Required Valid sheet intersection.
Tags List of Optional Tags are used to apply actions (such as hide)
strings to groups of sheets.
Window List of Required Must contain at least one window per sheet.
identifiers
Measure List of Required Must contain at least one measure.
identifiers If the identifier matches a registered measure
in the domain, the measure object does not
have to exist.
Profile List of Required Must contain at least one profile. This is a list
identifiers of measure profiles available to the
worksheet.

Object: Window
Attribute Type Required or Optional Description

Label String Required A label that appears in the title bar.

50 Oracle Retail Predictive Application Server


Attribute Type Required or Optional Description

Measure List of Required Must match the name of a registered


strings measure.

Tab Identifier Required The tab on which the window will be


displayed.
XAxis List of Optional Axes for window’s x-axis.
identifiers
YAxis List of Optional Axes for window’s y-axis.
identifiers
ZAxis List of Optional Axes for window’s z-axis.
identifiers
Profile Identifier Required The name of the profile that is used by
default for this window. The profile must be
one of the profiles defined in the profile list
for the window’s worksheet.

Object: HierMod
Attribute Type Required or Optional Description

Hier String Optional The user label of the profile.


Graph String Optional Comma separated list of measures visible in
this profile. The order of identifiers is the
defined user order for the profile.

Object: PositionQuery
Attribute Type Required or Optional Description

Trigger Integer Required The trigger type of the Position Query.


TriggerName Identifier Required The name of the trigger element.
QueryName Identifier Required The user label of the Position Query.
QueryMeasure Identifier Required The name of the measure used to evaluate
the Position Query.
Condition String Required The condition on the Position Query. All
configured Position Queries will have an
empty value.
Parameter List of Required A list of the dimensions used to drive the
Identifiers Position Query.
Result Identifier Required The dimension populated as a result of the
Position Query.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 51


Object: Profile
Attribute Type Required or Optional Description

Label String Required The name of the hierarchy whose graph is to


be modified.
Measure List of Required Comma separated rollup pairs of
identifiers dimensions.

Object: DynamicHier
Attribute Type Required or Description
Optional

LabelMeasure Identifier Required The name of the label measure for the Dynamic Hier.
Measure Identifier Required The name of the measure for the Dynamic Hier.
MeasureHier Identifier Required The name of the measure hierarchy for the Dynamic
Hier.
MeasureDim Identifier Required The name of the measure dimension for the Dynamic
Hier.
Hier Identifier Required The name of the hierarchy for the Dynamic Hier.
Dim Identifier Required The name of the dimension for the Dynamic Hier.
ModifiedDim Identifier Required The name of the dimension modified by the
Dynamic Hier.

Object: Axis
Attribute Type Required or Optional Description

Hier String Optional A registered hierarchy in the domain.


Dim String Optional A dimension in the hierarchy.
Level Integer Optional A defined axis for window’s axis.

Object: Tab
Attribute Type Required or Optional Description

Label String Optional Label displayed to user.


Order Integer Optional Order of tab starting with 1.

Object: Measure
Attribute Type Required or Description
Optional

Label String Optional A label displayed to user.

52 Oracle Retail Predictive Application Server


Attribute Type Required or Description
Optional

Description String Optional A description of the measure.


Range Integer Optional The range of the measure.
LoadRangeMeas String Optional Picklist options from a measure.
BaseState String Optional Read/Write permission at base level.
AggState String Optional Read/Write permission at an aggregate level.
UIType String Optional Whether the measure is a regular measure or a picklist
measure.
SingleHierSelect String Optional A string describing the single hier select properties for
this measure, if the measure is set to be single hier
select.
LoadRange List of Optional
identifiers

Object: Wizard
Attribute Type Required or Description
Optional

Type String Required The type of wizard to create.


Valid values are DynamicWizard and
TwoTreeWizard.
Derived libraries may support additional values.
WizardLabel String Required The user label for the wizard as a whole. This
label is currently only used by the Fusion Client.
Hier String Optional A registered hierarchy in the domain.
This is used by the StandardTwoTreeWizard.
Dim String Optional A registered dimension for the hierarchy.
This is used by the StandardTwoTreeWizard.
SizeRestrictedDim Identifier Optional The dimension over which the wizard should
enforce a limit on the number of selected
positions.
MaxNumPositions Integer Optional The upper limit on the number of positions that
(Required if may be selected in the size restricted dimension.
SizeRestrictedDim
is set)
Llabel String Optional Left tree label.
Used by the StandardTwoTreeWizard.

Rlabel String Optional Right tree label.


Used by StandardTwoTreeWizard.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 53


Attribute Type Required or Description
Optional

BaseDim String Optional This is used by the StandardTwoTreeWizard.

Dependency String Optional Extension

Widget List of Optional


identifiers

Object: Widget
Attribute Type Required or Description
Optional

Type String Required The type of control to create.


Valid values are Text, Edit, Checkbox, Listbox, Radio,
Group, Dropdown, and Tree.
Derived libraries may support additional values.
Func String Required Valid values are Static and Dynamic.
If set to Static, the DynamicWizard class will process
the control.
If set to Dynamic, a derived class is expected to
process the control.
Label String Optional The name to use for the control.
(List of Defaults to the widget name if not specified. For a list
strings) box, the name of the list box will always be the name
of the control; the Label attribute can specify the
names for the list box contents.
The number of labels must equal the number of texts.
Style String Optional A space delimited string describing the style of the
control.
Valid values for style include left, right, center,
singleline, multiline, password, flip, and startgroup.
Values depend on the type of control.
Text String Optional Text displayed to the user.
(List of For a list box, this attribute can specify the text for the
strings) list box contents.
Align String Optional Deprecated.
Use Style instead.
LocX Integer Optional The X axis location of the control/widget.
LocY Integer Optional The Y axis location of the control/widget.
Width Integer Optional The width of the control/widget.

54 Oracle Retail Predictive Application Server


Attribute Type Required or Description
Optional

Height Integer Optional The height of the control/widget.


Action String Optional RDF Extension.

Object: Menu
Attribute Type Required or Description
Optional

Label String Optional Text that is displayed to the user.


It defaults to the name.
MenuItem List of identifiers Optional Items to appear in the menu.

Object: MenuItem
Attribute Type Required or Description
Optional

ID Integer Optional The order of the menu item.

Label String Optional The text that is displayed to the user.


Function String Optional Currently, only RuleGroupProcessor.
Args String Optional Comma separated list of rule group names or script
names.
It is asterisk prefixed if it is a script name.
Condition Identifier Optional The measure to be used as a condition on this menu
item. If the value of the measure is false, the menu
item will not execute.
Message Identifier Optional The measure to be used to pass a return message upon
completion of the menu item.
ASAP Boolean Optional If set to True, a commit group present in the args list
for the menu item will be executed as a commit asap.

RideEnabled Boolean Optional True if this menu item should be allowed to execute
when a RIDE session is active.

Object: Logger
Attribute Type Required or Description
Optional

Level String Required The level in which to set the logger object.
This is useful for debugging.
Valid values are All, Profile, Debug, Information,
Warning, and Error.

Extension Development Guide - Volume 1, RPAS DB Server Extensions 55


Example
group rpas_examples = {
label = “Rpas Examples”;
};
template t1 = {
label = “Template Test 1”;
group = rpas_examples;
hidmeas = MmWpSlsR;
sheet = sheet1;
wizard = w1, w2;
loadrule = load;
calcrule = calc;
commitrule = commit;
};
sheet sheet1 = {
label = “Sheet Test 1”;
win = window1;
measure = MmWpSlsC, MmWpSlsU, MmWpSlsAc;
intx = “clssweek”;
};
window window1 = {
label = “Window Test 1”;
measure = MmWpSlsC, MmWpSlsU;
tab = tab1;
xaxis = x1;
yaxis = y1;
zaxis = z1, z2;
};
wizard w1 = {
hier = PROD;
dim = “clss”;
};
wizard w2 = {
hier = CLND;
dim = “week”;
};
wizard w3 = {
type = extmeas;
};
wizard w4 = {
type = DynamicWizard;
widget = AddMeasures1, AddMeasures2, AddMeasures3,
AddMeasures4;
};
widget AddMeasures1 = {
type = "GROUP";
text = "Please select the additional forecasting measures to be included in this
workbook.";
align = "";
func = "static";
locx = 4;
locy = 5;
width = 42;
height = 54;
};
widget AddMeasures2 = {
label = "CHECK1";
type = "CHECKBOX";
text = "Upper Confidence Limit";
align = "";

56 Oracle Retail Predictive Application Server


func = "static";
locx = 9;
locy = 9;
width = 12;
height = 0;
};
widget AddMeasures3 = {
label = "CHECK2";
type = "CHECKBOX";
text = "System Baseline";
align = "";
func = "static";
locx = 9;
locy = 15;
width = 12;
height = 0;
};
widget AddMeasures4 = {
label = "CHECK3";
type = "CHECKBOX";
text = "Selected Methods";
align = "";
func = "static";
locx = 9;
locy = 21;
width = 12;
height = 0;
};
tab tab1 = {
label = “Tab 1”;
order = 1;
};
axis x1 = {
hier = PROD;
dim = “clss”;
level = 0;
};
axis y1 = {
hier = CLND;
dim = “week”;
level = 0;
};
axis z1 = {
hier = MEAS;
dim = “info”;
level = 0;
};

axis z2 = {
hier = LOC;
dim = “str”;
level = 1;
};

Extension Development Guide - Volume 1, RPAS DB Server Extensions 57


Oracle Corporation
World Headquarters
500 Oracle Parkway
Redwood Shores, CA 94065
U.S.A.

Worldwide Inquiries:
Phone: +1.650.506.7000
Fax: +1.650.506.7200
oracle.com

Copyright © 2019, Oracle and/or its affiliates. All rights reserved.

This document is provided for information purposes only and the


contents hereof are subject to change without notice.
This document is not warranted to be error-free, nor subject to any
other warranties or conditions, whether expressed orally or implied
in law, including implied warranties and conditions of merchantability
or fitness for a particular purpose. We specifically disclaim any
liability with respect to this document and no contractual obligations
are formed either directly or indirectly by this document. This document
may not be reproduced or transmitted in any form or by any means,
electronic or mechanical, for any purpose, without our prior written permission.
Oracle, JD Edwards, PeopleSoft, and Siebel are registered trademarks of Oracle
Corporation and/or its affiliates. Other names may be trademarks
of their respective owners.

You might also like