Provides classes and interfaces that enables the Java client to interface with the SAS multidimensional database (mddb).

## Using the MultidimensionalTableInterface

### Package Overview

MultidimensionalTableInterface

Properties

Core Classes

- Axis
- Dimension
- Hierarchy
- Level
- Member
- ParentNode

Interfaces

- SubsetInterface
- TotalInterface
- ComputedValuesInterface
- SortInterface
- RangeInterface
- BookmarkableInterface

Commands

Dialogs

Events

### See also

- Command Tips
- Data Tips
- Event Tips
- Initialization Tips
- Subclassing Tips

  Server Customizations

- Adding User Defined Actions to the MultidimensionalTable Model
- Using a User Written Subclass of EMDDB

### MultidimensionalTableInterface

The com.sas.sasserver.mdtable.MultidimensionalTableInterface enables the Java client to interface with the SAS multidimensional database (mddb). MultidimensionalTableInterface is used in conjunction with the Remote Object Class Factory (ROCF) functionality to make an instance of the appropriate proxy for communication with the SAS server. This interface uses the SAS SCL MDTABLE class to interact with the SAS OLAP Server.

MultidimensionalTableInterface implements a number of other interfaces to provide the functionality to retrieve information about the SAS mddb.

- The StaticMulitidimensionalCubeInterface provides the methods to query the SAS mddb for its physical makeup.
- The StaticMultidimensionalTableInterface and StaticFormattedDataInterface include methods to retrieve the current table layout as well as cell values.

The MultidimensionalTableInterface itself includes methods to define the datasource and the current table layout. This interface also provides support for sorting through the use of the popup action list and the more formal SortInterface. Additionally, one or more of the components that define the MultidimensionalTable implement the TotalInterface, SubsetInterface, ComputedValueInterface, and RangeInterface.

The MutidimensionalTableInterface implementation is made up of a number of subordinate classes. The multidimensional cube is rooted by the complete set of Dimensions that define the multidimensional datasource. Each Dimension is made up of a set of hierarchies. Each Hierarchy includes a set of levels that dictate the drill order (Country->Region; Year->Quarter->Month) and each Level includes a set of Member values. The multidimensional table is rooted with a set of Axes, each Axis is made up of a set of Dimensions. Each dimension then follows the same hierarchical structure of the multidimensional cube.

The multidimensional table is therefore a subset of the multidimensional cube with additional properties. These additional properties define which dimensions are currently selected for the row and which are selected for the column.

Cube | Dimensions | Hierarchies | Levels | Members
Cube | Geography | Geography | Country | Canada
Germany
U.S.A.
Total
Region | North
South
East
West
Time | YrQtrMon | Year | 1998
1999
2000
Quarter | Quarter 1
Quarter 2
Quarter 3
Quarter 4
Month | January
February
March
April
May
June
July
August
September
October
November
December
YrQtr | Year | 1998
1999
2000
Quarter | Quarter 1
Quarter 2
Quarter 3
Quarter 4

###### Cube Hierarchy

Table
Column Axis | Row Axis
Time | Geography | Product Line

###### Table Hierarchy

A multidimensional cube is a hierarchical set of data classifications. Each classification, known as a level, contains a set of values, known as members. For example, a level by the name of "Continents" would contain members named "North America", "South America", "Europe", "Africa", "Asia", "Australia", and "Antarctica".

A set of related levels is known as a dimension. For example, "Continents", "Countries", "Regions", and "Cities" could be grouped as a dimension identified as "Geography".

A multidimensional table is the result of crossing any number of (but usually two) mutually exclusive sets of dimensions of a multidimensional cube. These dimension subsets are known as axes, and the values that arise from crossing axes are known as cells. The axes are crossed by summarizing the data associated with each unique set of members (one from each level) along an axis with the datum associated with the corresponding unique member sets of the other axes.

The cells are also identified using a single zero-based integer known as an ordinal. The ordinals are assigned sequentially starting at zero by proceeding through the elements of the first axis while holding the other axes constant. After visiting the last element of the first axis, we advance to the second element of the second axis and resume the process. For example, a 3x3 two-dimensional table would be numbered like so: [{0, 1, 2}, {3, 4, 5}, {6, 7, 8}] with each set of {} corresponding to a row and each element of the set corresponding to a column. Therefore, cell (1,1) would be ordinal 0 and cell (2,2) would be ordinal 4:

| 1998 | 1999 | 2000
East | 0 | 1 | 2
West | 3 | 4 | 5
Total | 6 | 7 | 8

###### Cell Ordinals

Each axis is associated with a zero-based index: 0 for the x-axis, 1 for the y-axis, 2 for the z-axis, and so on. getAxisCount() indicates how many axes the table represents.

The data returned by this interface (for example, getCell) is assumed to be unformatted or raw data. MultidimensionalTableInterface also implements StaticFormattedDataInterface (getFormattedCell) which returns formatted data of type String.

A number of navigational actions are available from the CommandsInterface. This interface is implemented by the subordinate classes (such as Level, Member). The MultidimensionalTableView uses this interface to construct the popup menu. The commands currently supported include Drilldown, Up, Down, Left, Right, Expand, and Collapse. Other actions supported include, but are not limited to, Totals, Subsets, Computed Values, Sorting, Exception Highlighting, Show Detail Data, and Export to Excel.

The Level class also implements the TotalInterface, SubsetInterface, and ComputedValueInterface. Exception Highlighting is achieved through the getRanges and setRanges methods on the MultidimensionalTableInterface itself in conjunction with the StaticTableTypeStylesInterface of the com.sas.table.TableView.

### MultidimensionalTableInterface Properties

The MultidimensionalTableInterface includes a number of properties. Many properties are supported as a convenience to facilitate property linking. For example, the metabase, database, columnAxis, rowAxis, selectedStatistics, and selectedMeasures properties enable linking between simple selectors for each property, with the results ultimately driving one of the the setSource and/or initializeTable methods. The setSource() and initializeTable() signatures (when used without any parameters) retrieve the values of these properties and pass them to the parameterized version of the respective method.

For example,

    multidimensionalTableInterface1.setMetabase("SASHELP");
    multidimensionalTableInterface1.setDatabase("SASHELP.PRDMDDB");
    multidimensionalTableInterface1.setSource();

is the same as

    multidimensionalTableInterface1.setSource("SASHELP", "SASHELP.PRDMDDB");

The same is true for the initializeTable(). The following code...

    String colAxis[] = {"Time"};
    String rowAxis[] = {"Geographic","Product Line" };
    String measures[] = {"ACTUAL"};
    String statistics[] = {"SUM"};

    multidimensionalTableInterface1.setColumnAxis( colAxis );
    multidimensionalTableInterface1.setRowAxis( rowAxis );
    multidimensionalTableInterface1.setSelectedMeasures( measures );
    multidimensionalTableInterface1.setSelectedStatistics( statistics );
    multidimensionalTableInterface1.initializeTable();

is the equivalent of

    String axes[][] = { colAxis, rowAxis };
    multidimensionalTableInterface1.initializeTable(axes, null, measures, statistics);

Every effort has been made to automatically drive the setSource() and/or initializeTable() methods if all the required properties have been set. This synchronization takes place whenever a get method is called on the interface, such as getAxisCount().

Each of these properties sends out a PropertyChanged event. Some properties additionally send a ContentsChanged event. The com.sas.mdtable.MultidimensionalTableView listens for various events and reacts accordingly. When it receives a ContentsChanged event, for example, it begins to refresh by calling getAxisCount(). This get method proceeds to synchronize any client/server updates that are pending before retrieving and returning the axis count. This is true of most of the get methods. See Events for details on the events sent by the MultidimensionalTableInterface.

When setting properties that take Dimension, Hierarchy, Level, or Member (such as Measure and Statistic) references as strings, every effort should be made to use the name as opposed to the label. The name provides a uniqueness that is not guaranteed with the label. For example, use "ACTUAL" rather than "Actual Sales" for the Measure and "SUM" rather than "Sum" for the Statistic.

#### Label Property Information

Labels can be used when setting the selectedMeasures and selectedStatistic properties. This feature makes it convenient to link a selector to a property. However, when a label is used to set the property, the label will be returned when retrieving the value of a property. Therefore, it is possible for get methods to sometimes return names and other times return labels. To avoid this problem, have your selectors use Transforms to convert the label to the name, using the name to set the property value.

Use the getMeasureMembers and getStatisticMembers methods to assist in name to label conversions.

#### Case-Sensitive Property Information

Several properties are case-sensitive. The metabase and database properties are vulnerable to case sensitivity issues due to underlying technologies. Likewise, when using the dictionary returned by getAllLevels to retrieve a Level object, the dictionary keys are case-sensitive.

#### Noteworthy Properties

The following information details some noteworthy properties:

Application
  This property enables you to initialize the model using an existing SAS/EIS application and, therefore, eliminates the need to call initializeTable. The property expects a four-level SAS/EIS catalog entry name as input (for example, SASHELP.EISRG.N_WAY.EIS). The SAS/EIS application data will be used to initialize the columnAxis, rowAxis, selectedMeasures and selectedStatistic properties on the client.
CommandsMask
  This property enables you to disable popmenu items that you do not wish to surface to users.
CoordinatorEnabled
  This property enables you to take advantage of the SAS/EIS Coordinator to keep subsets and computed values of two different model instances synchronized with each other.
DefaultAction
  This property enables you to query the action that will be driven when a double click occurs.
Root
  The root property is surfaced as a result of implementing the TreeInterface. This enables the MultidimensionalTableInterface to easily be attached to a TreeView. The root of the model is considered to be the Cube Hierarchy. The Axis class also implements the TreeInterface should you have a need to represent the Axis hierarchies of the Table Hierarchy.
SlicerAxis
  This property enables you to define Levels that you want to use to slice your data, but which you do not want to appear in your table hierarchy. For example, you might want to view a single Year&rsquo;s (such as 1999) data, but not actually have the Time dimension or Year level displayed in your table. By setting the slicerAxis to Year, a single value subset will be set on the Year level. This property has never been fully supported. In the current implementation, setting the slicerAxis will subset the level to its first value. Currently, there are no methods in the interface that will allow you to slice on the &lsquo;next&rsquo; or &lsquo;previous&rsquo; value. Instead, you must use the SubsetInterface to set the desired slicer value. It is perceived that when using slicers you would add a Text or Label component to your applet to display the current slicer value. Additionally, webEIS enables you to display the slicer values by setting the template accordingly in the Database Connected Text component.
StatisticsPerMeasureSupported
  This property is mainly used by selectors to enable or disable the selection of different statistics per measure. The underlying technology of SAS Version 8 supports this feature, unlike SAS Version 6.
SubsetLevels
  This property is a read-only property that returns all the levels that are not currently part of the displayed table. These undisplayed levels can be subset to provide a slicer effect on the values that are displayed in the table.
TableLevels
  This property is a read-only property that returns all the levels that are currently part of the displayed table.
VerifyEnabled
  This property should be used with caution as it introduces performance issues. It primarily enables additional verification on the server of subset values. The selectors provided with the component attempt to validate this in advance so that potential server problems will not be encountered. If you are running in an environment that does not provide pre-validation, you might find it necessary to enable verification on the server in order to prevent invalid subset values from causing program halts in the underlying server model. This subset validation can cause a significant performance degradation, therefore, you should use this property with caution.

There are a number of other properties that can be used to support the serialization of subsets, computed values, ranges, bookmarks and sortOrders. For more information on these properties, see Interfaces.

## Core Classes

The MultidimensionalTableInterface includes a number of supporting classes including the Axis, Dimension, Hierarchy, Level, and Member classes. All these classes are subclasses of the ParentNode class, which is an implementation of the ExtendedNodeInterface.

Each of these classes implements its respective com.sas.mdtable interface (such as AxisInterface and DimensionInterface). To facilitate customized implementations, many methods are defined to return interfaces such as MemberInterface or LevelInterface rather than the com.sas.sasserver.mdtable class itself. For this particular implementation, the objects returned from these methods are actually com.sas.sasserver.mdtable instances of the corresponding class. As such, the values returned can be cast to their appropriate com.sas.sasserver.mdtable class when methods outside the interface are desired.

### Axis

The Axis class maintains the information and provides the functionality required by an axis (such as a Column or Row ) of the MultidimensionalTable model, including, but not limited to, providing the labels for the axis.

The Axis class includes the methods necessary to describe the elements that make up the axis. The axis is defined by the Dimensions that make up the axis (such as Geographic or Time). At any given point, a set of Levels (such as Country and Year) from each Dimension will be active on the axis. The active Hierarchy of the Dimension, as well as any action events that have taken place on the axis (such as Drilldown and Expand), dictates the Levels. Viewers use the labels of the active Levels as header labels for the axis labels themselves. The axis labels are derived from the Members of the active Levels. Each axis has ordinal identifiers or axis coordinates to identify a particular point or offset on the axis. The axis itself also has an ordinal to identify itself with respect to the other axes that make up the data model. The ordinal of the column axis is always 0 and the ordinal of the row axis is always 1. The labels for a particular point on the axis can be retrieved using getLabels(axisCoordinate).

See the MultidimensionalTableInterface Overview section for examples on determining the row and column coordinates.

### Dimension

The Dimension Class represents a dimension in a multidimensional cube or table.

A Dimension is the root of the multidimensional cube and is subordinate only to the Axis in a multidimensional table.

A Dimension is made up of a set of hierarchies. Each hierarchy represents different combinations of levels. Hierarchies are used to facilitate different drill orders. For example, with a Time Dimension that includes Levels for Year, Quarter, and Month, you may want to drill from Year to Month rather than Year, Quarter, Month. The Time Dimension then could have two hierarchies, H1 = Year/Quarter/Month and H2=Year/Month. The com.sas.sasserver.mdtable.MultidimensionalTableInterface implementation has a one-to-one correspondence between dimensions and hierarchies. The SAS mddb allows levels to be included in more than one dimension. There is little need to have multiple hierarchies nested in a dimension because each hierarchy is represented by a different dimension. Other multidimensional data providers require a level to appear in one and only one dimension. In this case, the hierarchy is used to facilitate different combinations of levels. The Hierarchy abstraction has been preserved for compliance with developing standards as well as future evolution.

In the case of multiple hierarchies in a dimension, one hierarchy is designated as the active hierarchy. When a dimension is queried for its levels, the levels are those of the active hierarchy.

The multidimensional cube is made up of all the dimensions defined by the multidimensional datasource. The multidimensional table includes only those dimensions currently represented on an axis.

### Hierarchy

The Hierarchy Class represents a hierarchy in a multidimensional cube or table.

A Hierarchy is a descendant of the Dimension and includes an ordered set of Levels. In the com.sas.sasserver.mdtable.MultidimensionalTableInterface implementation, there is a one-to-one correspondence between the dimension and the hierarchy. A dimension will have only one hierarchy as a child. It will have the same name as the dimension and is considered the active hierarchy.

Other implementations may have multiple hierarchies per dimension, in which case the active hierarchy property of the Dimension reflects the hierarchy currently in use by the table.

The order of the levels in the hierarchy dictates the navigation order when commands such as Drilldown or Expand are executed. If the hierarchy consists of the Levels Year, Quarter, and Month, a Drilldown command executed on Year would link to Month. See Dimension for more details.

### Level

The Level class maintains the information and provides the functionality required by a Level (classification variable) of the MultidimensionalTable model, including, but not limited to, customizations for totals, subsets and computed values.

The Level class includes the methods necessary to describe the data classifications that make up the MultidimensionalTable datasource. There is one Level for every data classification of the data source. These Levels are referred to as the Cube Levels. Additionally, there is one Level for every classification that is included on an Axis. These Levels are referred to as Table Levels. You can access a dictionary that includes instances of all the levels by calling the getAllLevels method. From a Table Level, you can get to the Cube Level through the getMetalLevel method on the LevelInterface. If this call returns null, then you have a Cube Level.

The children Members for a Cube Level include all possible values for a Level. The children Members for a Table Level include only those values that are valid given the current table layout and applied subsets.

In addition to the LevelInterface, the Level Class implements the CommandsInterface. The MultidimensionalTableView uses this interface to retrieve the commands that are used on the popup menu of a Level label such as Country.

The Level class also implements the SubsetInterface, TotalInterface, ComputedValueInterface and SortInterface. These interfaces allow the data to be customized for a given application.

The Level's children are the Members that constitute the valid values for the Level (for example, the Members for Month would be January, February, ... December). These members may be subset using the SubsetInterface. Additionally, creating computed values using the ComputedValueInterace can generate new Members. Totals may be toggled on and off with the TotalInterface. If totals are on, a new Member is created that represents the total of all Members for that Level. You can customize the label for this new total member using setTotalLabel. You may also highlight the total members using setTotalCellStyle and setTotalLabelStyle.

Due to the nature of remote data access, values set directly on the level (such as totals or subsets) are not immediately communicated to the server. An additional call to the MultidimensionalTableInterface is required to synchronize the Level's information with the server's. Refresh flags are used by the MultidimensionalTableInterface to indicate which, if any, modifications need to be synchronized with the server. In this fashion, all level's values can be modified and queued up for one remote method call initiated by a method call on the MultidimensionalTableInterface. The MultidimensionalTableInterface has methods such as setSubsets() and setTotals() that require no input parameters. These methods retrieve the information from the Level instances and send it to the server for synchronization.

The recommended way to use these interfaces is to use getAllLevels to retrieve the actual level instance. Then, use the appropriate interface on the level instance to set the desired values. Once you have set all the values on all the necessary levels, synchronize the server by calling the appropriate method on the MultidimensionalTableInterface.

    Dictionary levels = multidimensionalTableInterface1.getAllLevels();
    Level level;
    if ( levels.containsKey("COUNTRY") )
    {
    level = (Level)levels.get("COUNTRY");
    level.setTotalState(true);
    level.setTotalLabel("Totals for Country");
    }
    if ( levels.containsKey("MONTH") )
    {
    level = (Level)levels.get("MONTH");
    level.setTotalState(true);
    }
    multidimensionalTableInterface1.setTotals();

### Member

The Member class maintains the information and provides the functionality required by a Member (classification value) of the MultidimensionalTable model, including the implementation of the AxisLabelInterface.

The Member class includes the methods necessary to describe the values of the data that make up the Levels (classifications) of a MultidimensionalTable datasource. A Member is parented by a Level. It has an ordinal property that indicates its position in relation to its siblings.

The Member class implements the AxisLabelInterface, which presents the Member as an element of an axis point label. The axis point label may have peers if the axis contains more than one level. If a Member is used in the context of an AxisLabel, its parent is the label that precedes it on the axis and its child is the label that follows it on the axis. The metaMember in this case is the actual Table Member instance ( level.getMember(ordinal) ). When a member is used in the context of an AxisLabel, its ordinal is equal to its position within the labels on the row or column. The first label on a row would have an ordinal of 0, the second would have an ordinal of 1, and so on.

If the rootParent of a Member is an instance of Axis, the Member is presumed to be a child of a Table Level. Otherwise, the rootParent is an instance of Dimension and the Member is a child of a Cube Level. When the rootParent is an instance of Member, the member is being used in the context of an AxisLabel.

The Member class also implements the SortInterface. It has relevance only when the Member is used in the context of an AxisLabel. This allows a column of data to be uniquely identified, which enables the values in the column to be sorted.

The Member class also implements the CommandsInterface. This allows unique Members to surface valid commands that can be performed on the class. This interface is generally used in conjunction with the AxisLabelInterface to surface commands that can be performed on an axis label. The commands are used by the MultidimensionalTableView to generate the popup menu. Some commands supported include Drilldown, Expand, Up, Down, Left, Right, Sort, Totals, Subsets, and Computed Values.

A subclass of Member, TotalMember, facilitates highlighting the labels and cells associated with a total by implementing the StaticNodeStyleInterface

### ParentNode

ParentNode is an implementation of ExtendedNodeInterface, which extends the NodeInterface to include bi-directional heritage. That is, in addition to retrieving the children of a Node, you can also retrieve the node's parent. Axis, Dimension, Hierarchy, Level, and Member are all subclasses of ParentNode. ParentNode implements the common functionality of these classes. It includes such properties as Name, Label, Parent, Children, Delegate and Ordinal. It also includes a PropertyBag property called Properties to enable the addition of user-defined customized properties.

ParentNode uses the com.sas.models.StaticCacheInterface to manage the children. It implements an inner class that extends the AbstractChildrenReader adding implementation for the common functionality. The subclasses then implement the methods unique to the subclass via internal childrenReader classes. This includes the getFromCache and count methods. These implementations are part of the secondary cache used by the primary cache classes. When a value is requested from the primary cache and it is not found, the primary cache will query the secondary cache for the value. In the MultidimensionalTableInterface implementation, this typically involves a call to the server to retrieve the requested values.

Due to the nature of remote classes, this caching provides a mechanism to retrieve values from the server and store them on the client for future reference, eliminating the need to retrieve them from the server repeatedly. A cache size is set and when the number of objects retrieved exceeds the cache size, objects are rolled out and replaced with the latest requested objects. Additionally, the cache classes support reading ahead. When object 1 is requested, objects 1 through 100 are read in anticipation that object 2-n will subsequently be requested. This minimizes the number of wire calls and improves performance.

The subclasses typically include convenience methods that add specific terminology to general properties. For example, the parent of a Level class would be a Hierarchy; therefore, a getHierarchy method is implemented in the Level subclass. This method returns the same values as the getParent class of ParentNode. The Delegate of a table Level instance is the cube Level instance, and therefore a getMetaLevel method is provided. The children of a Level class are the Members. As such, the inner childrenReader class implements getFromCache and count to retrieve the member values of the Level as well as the member count. A getMemberCount method is provided that returns the same value as countNodeChildren of the ParentNode. The ordinal of a Level object is where it falls within the Levels of its parent -- the Hierarchy. Similarly, the ordinal of a Member object is where it falls among its siblings. Ordinals are 0-based.

Additionally, the ParentNode class includes a getRootParent method. This method traverses up the parentage hierarchy to find the root of the hierarchy. For example, a table Level object would have an Axis object for a root parent whereas a cube Level object would have a Dimension as its root parent. Members used in the context of an AxisLabel have a Member object as the root parent. For example, consider the AxisLabels for a row in the following table: Furnitiure, 1993, Quarter 1. The parent of the Quarter 1 AxisLabel is the 1993 AxisLabel and the parent of the 1993 Axis Label is the Furniture Axis Label. Each of these AxisLabel instances have a Delegate or MetaMember that is the actual Member object for the Level. Therefore, you can use the AxisLabel to get the table hierarchy in which it belongs.

## Interfaces

### SubsetInterface

The subset interface is implemented by the Level class. It enables the member values of a particular level to be filtered. The underlying model currently supports only equality filtering. That is, you must specify the specific values on which to filter (for example, Country = Canada, Germany). This implementation uses Objects of type String.

For simplicity, it is recommended that you use the Level class to set subsets as this class implements this interface. To set subsets, use getAllLevels to retrieve the actual level instances. Next, use the appropriate interface methods on the level instance to set the desired values. Once you have set all the values for all the desired levels, synchronize the server by calling setSubsets() on the MultidimensionalTableInterface.

    Dictionary levels = multidimensionalTableInterface1.getAllLevels();

    /* Error checking should be done to validate that Country is a valid Level and that */
    /* Canada and Germany are valid members for Country */

    if ( levels.containsKey("COUNTRY"))
    {
    Level level = (Level)levels.get("COUNTRY");

    /* Using invalid member values can result in exceptions */

    String subsets[] = {"CANADA", "GERMANY"};
    level.setSubset(subsets);
    }

    /* Update the server with any subset modifications that have been made to the Level objects */
    /* This will enumerate through all the Level objects. Any that indicate the subsets need */
    /* refreshing will be sent to the server for processing there. This allows you to queue up all */
    /* subset modifications for all levels and send them all to the server with one remote call */
    /* rather than many improving performance */

    multidimensionalTableInterface1.setSubsets();

For performance reasons, this enables you to queue up all subset modifications and subsequently process them with one call to the remote server. The sets on the level objects are not delivered directly to the server. Instead, a refresh flag is set and they are maintained on the client until a setSubsets call on the multidimensionalTableInterface model is called. This requires only one remote wire call.

There are two overloaded versions of setSubsets. One takes no parameters and retrieves the current Level objects of the model and passes them into the other method that takes a SubsetInterface[] array. However, any object that implements SubsetInterface can be used as input into the parameterized method. This could be your own instance of com.sas.sasserver.mdtable.Level or any other object that implements SubsetInterface.

Many of the interfaces supported by this implementation of the MultidimensionalTableInterface use this same design and are also implemented by the Level class (for example, TotalInterface, ComputedValuesInterface, and SortInterface). As such, the interface objects returned from corresponding methods on the MultidimensionalTableInterface (getSubsets, getTotals, getComputedValues, getSortOrders) are actually the Level instances and can be cast as such if desired.

This interface also maintains the information on drill subsets. Drill subsets are filters that are set by drilling down on a member value. For example, when you drill down on Canada, you effectively subset Country to Canada. Subset and drill subset values are often used in navigation displays (such as the webEIS database connected text) to indicate the filters that are currently active on the viewed data.

When the EIS coordinator is being used, a call to multidimensionalTableInterface.getSubsets() may be required to synchronize the client with any server updates. The server updates might have taken place due to updates from modifications on a coordinated instance. Currently, the remote connection implementation does not support server->client callbacks, making it difficult to automate this process. A call to getSubsets() forces a call to the server to refresh any subset changes that might have taken place on the server.

### TotalInterface

The TotalInterface enables you to turn totals off and on for a given Level by setting the total state. This effectively adds an additional Member to the Level. You can set the label for this total member using setTotalLabel("Totals for Country"). If no total label is set, the default label is the Level label (for example, Country). Some implementations may not support totals, in which case a call to the interface&rsquo;s isTotalEnabled should return false. This situation might occur if a user is restricted from seeing totals due to server access security controls.

To use this interface, use getAllLevels to retrieve the actual level instances. Next, use the appropriate interface methods on the level instance to set the desired values. Once you have set all the values on all the desired levels, synchronize the server by calling setTotals() on the MultidimensionalTableInterface.

    Dictionary levels = multidimensionalTableInterface1.getAllLevels();
    Level level;
    if ( levels.containsKey("COUNTRY") )
    {
    level = (Level)levels.get("COUNTRY");
    level.setTotalState(true);
    level.setTotalLabel("Totals for Country");
    }
    if ( levels.containsKey("MONTH"))
    {
    level = (Level)levels.get("MONTH");
    level.setTotalState(true);
    }

    /* Update the server with any total modifications that have been made to the Level objects */
    /* This will enumerate through all the Level objects. Any that indicate the totals need */
    /* refreshing will be sent to the server for processing there. This allows you to queue up */
    /* total modifications for all levels and send them all to the server with one remote call */
    /* rather than many improving performance */

    multidimensionalTableInterface1.setTotals();

See Level and SubsetInterface for more details on remote access performance considerations.

### ComputedValuesInterface

The ComputedValuesInterface facilitates adding additional Members to a Level. These new Members are generated based on an expression defined as part of the computed value. The expression can be as simple as a constant (0 &ndash; zero) or as complex as a formula involving other member values (Sum (Canada, Germany)). The Level object implements this interface. The interface stores the computed values as an AssociationList where the key is the label to be associated with the computed value and the value is an OrderedCollection containing the objects that make up the expression.

These objects include MemberInterface objects representing the member values involved in the expression (for example, "Canada", "Germany"), Strings for Operators (for example, "+", "-", "/", "*"), and Strings for function names (for example, "SUM"). If the computed value is added to the Measure (Level="_ANLSYS_"), then the result is a computed column. In this case, you could additionally provide a SAS Format to be applied to the cell values in that column.

The first example below illustrates using the ComputedValueInterface directly. The Level class also includes some convenience methods that create the AssociationList and OrderedCollection for you. The second example illustrates these convenience methods.

    /* Using the ComputedValueInterface methods */
    Dictionary levels = multidimensionalTableInterface1.getAllLevels();
    /* Use the case sensitive Level Name (not Label) */
    if ( levels.containsKey("YEAR"))
    {
    Level level = (Level)levels.get("YEAR");
    AssociationList compValues = new AssociationList();
    OrderedCollection oc = new OrderedCollection();
    String label = "Difference Between First Two Years";

    /* Use the SAS functions to handle missing values */
    oc.add("SUM");
    oc.add("(");

    /* Add the first Year Member to the expression */
    oc.add(level.getMember(0));
    oc.add(",");

    /* Add the difference operator to the expression */
    oc.add("-");

    /* Add the second Year Member to the expression */
    oc.add(level.getMember(1));
    oc.add(")");
    compValues.set(label, oc);

    /* If you like, add other computed values to the Association List */
    /* Set the computed values on the level */
    level.setComputedValues(compValues);
    }

    /* Using convenience methods supplied by the Level class */
    /* The sas sum function is not used here so any missing */
    /* values will generate a missing result */
    if ( levels.containsKey("_ANLSYS_") )
    {
    Level level = (Level)levels.get("_ANLSYS_");
    String label = "Unexpected Sales Lag";
    Object[] expression = new Object[4];
    int index = multidimensionalTableInterface1.findMember("_ANLSYS_", "Actual Sales", true);
    if (index >= 0 )
    {
    int i = 0;
    expression[i++] = level.getMember(index);
    expression[i++] = "-";
    index = multidimensionalTableInterface1.findMember("_ANLSYS_", "Predicted Sales", true);
    if ( index >= 0 )
    {
    expression[i++] = level.getMember(index);

    /* Set the desired SAS Format for the computed column */
    expression[i++] = "FORMAT=DOLLAR12.2";
    level.setComputedValue(label, expression);
    }
    }
    }
    /* Update the server with any computed values that have been added to the Level objects */
    /* This will enumerate through all the Level objects. Any that indicate the computed values */
    /* need refreshing will be sent to the server for processing there. This allows you to */
    /* queue up all computed value modifications for all levels and send them all to the server */
    /* with one remote call rather than many. */
    multidimensionalTableInterface1.setComputedValues();

In the above example, findMember is used to retrieve the member instance of the desired value. FindMember functions on the current table values of a Level. If the desired member is not currently included in the table (for example, it might be subset out or a measure not currently being viewed), you must retrieve the member from the cube Level.

Alternatively, you can create your own instance of Member and set its name and label accordingly. In this case, keep in mind that if a member cannot be found that has a label matching yours, an exception will occur. For levels with numerous members, handling such an exception may still be preferred over the performance issues involved with retrieving all the values and iterating over them.

    /* Retrieve the cube level instance from the table instance */
    Level cubeLevel = level.getMetaLevel();

    /* Retrieve all the members of the cube Level. */
    /* This will include all the possible values for this level. */
    /* -1 can be used for the count to return all the values. */

    Member cubeMember[] = cubeLevel.getMembers(0, -1);
    Member member = null;
    for ( int i = 0; i < cubeMember.length; ++i)
    {
    member = cubeMember[i];
    if ( member != null && member.getLabel().equals ("Sales Lag"));
    break;
    }

See Level and SubsetInterface for more details on remote access performance considerations.

### SortInterface

The MultidimensionalTableInterface implements the SortInterface in order to persist sort order information so that it can be applied at initialization. It works similarly to the other interfaces implemented by the Level class. However, the Member class also implements this interface in order to persist column sort order information. When a Member object is used as an AxisLabel, the sort order information can be stored with it to uniquely identify the column that is to be sorted, as well as its sort order. Typically, this information is stored with the AxisLabel member for the statistic label (or last label) of the column that is to be sorted. From the statistic label, the parentage hierarchy is traversed to uniquely identify the column.

The current implementation is most efficient when the objects used are the Level and Member objects provided in the com.sas.sasserver.mdtable package. SortInterface[] = multidimensionalTableInterface1.getSortOrders() will return the Level instances for any label sorts as well as the AxisLabel Member instance for a column sort. These in turn can be sent in as input to the setSortOrders method.

When setSortOrders(SortInterface[]) is used, the precedence of the sorts is implied by the order that they appear in the array. The last item in the array takes precedence over previous items. When setSortOrders() is used, the level sorts are applied first and the column or label is applied last.

The SortInterface defines the following static values for the different sort orders.

    public static final int DESCENDING = 1; /* Descending order */
    public static final int ASCENDING = 2;  /* Ascending order */
    public static final int RESET = 3;      /* Data order */
    public static final int NONE = 4;       /* When a column is sorted all other columns have a sort order of NONE */

To sort the members of a level:

    Dictionary levels = multidimensionalTableInterface1.getAllLevels();
    Level level;
    if ( levels.containsKey("COUNTRY") )
    {
    level = (Level)levels.get("COUNTRY");
    level.setSortOrder(SortInterface.DECENDING);
    System.out.println("Level "+ level.getName() + ":" + level.getSortOrder());
    for ( int k = 0; k < level.getMemberCount(); ++k )
    System.out.println(level.getMember(k).getLabel());
    }
    if ( levels.containsKey("MONTH" ))
    {
    level = (Level)levels.get("MONTH");
    level.setSortOrder(SortInterface.ASCENDING);
    System.out.println("Level " + level.getName() + ":" + level.getSortOrder());
    for ( int k = 0; k < level.getMemberCount(); ++k )
    System.out.println(level.getMember(k).getLabel());
    }

While the other interfaces provide a performance improvement by delaying the remote synchronization until all levels have been set, sorting does not work this way. A remote method call is required for each sort that is to be applied.

You can still choose to delay the actual sorting until all levels are set; however, there is little or not performance advantage. To do this, do not call level.sort() with each level. Instead, call multidimensionalTableInterface1.setSortOrders() after the sort orders for all levels are set. As with the other interfaces, this call queries each level to see if a sort is pending and then calls the server to sort the Level’s member values. Additionally, the call checks to see if a column sort is pending. When a column sort is applied, it takes precedence over any row level sorts.

To sort a column of cell values:

    /* First get the actual AxisLabel instances for the column you wish to sort */
    Axis column = (Axis)multidimensionalTableInterface1.getAxis(0);
    String labels[] = {"CANADA","Actual Sales", "Sum"};
    int columnIndex = column.getAxisCoordinate(labels);
    AxisLabelInterface columnLabels[] = null;
    if( columnIndex >= 0 )
    columnLabels = column.getLabels(columnIndex);

    /* Set the sort order on the last label of the column */
    if (columnLabels !=null && columnLabels.length > 0 )
    {
    Member sortLabel = (Member)columnLabels[columnLabels.length-1];
    sortLabel.setSortOrder(SortInterface.DESCENDING);
    }

    /* Retrieve the AxisLabel of the last sorted column */
    /* This will be the last label of the sorted column */
    /* Traverse up the parentage hierarchy to retrieve */
    /* all the labels in the column */
    Member sortLabel = (Member)column.getSortIdentifier();
    if ( sortLabel !=null )
    {
    int sortOrder = sortLabel.getSortOrder();

    /* As this is the last label in the column the ordinal*/
    /* will indicate how many labels precede this one */
    /* The ordinal is 0 -based */
    int count = sortLabel.getOrdinal();
    columnLabels = new Member[count+1];
    for (int i = count; i >= 0; --i)
    {
    columnLabels[i] = sortLabel;
    sortLabel = (Member)(sortLabel.getParent());
    }

    /* Print out the labels */
    for (int i = 0; i <= count; ++i )
    System.out.println(columnLabels[i].getLabel());

If you do not need the actual AxisLabel instances, you can retrieve string representations of the labels. To do so, use the HList returned from the tupleToHList static method of the Member class. In this situation, the HList name equals the Level name and the value equals the Member label.

    HList sortedColumn = Member.tupleToHList((Member)column.getSortIdentifier());
    System.out.println ("Sorted column:" + sortedColumn);

To determine which column was the last sorted column and retrieve the cell values for that column, use the following code:

    /* Determine the column coordinate */
    int coords[] = new int [2];
    coords[0] = column.getAxisCoordinate((Member[])columnLabels);
    coords[1] = 0;
    if ( coords[0] >= 0 )
    {

    /* Determine the cell ordinal for the first cell in the column */
    int startOrd = mi.getCellOrdinal(coords);

    /* Determine the cell ordinal for the last cell in the column */
    coords[1] = multidimensionalTableInterface1.getAxis(1).getMagnitude()-1;
    int endOrd = mi.getCellOrdinal(coords);

    /* Get the cells for the column */
    Object cells[] = mi.getCells(startOrd, endOrd);

    /* Print out the cells */
    for int d = 0; d < cells.length; ++d )
    System.out.println(cells[d]);
    }

See Level and SubsetInterface for more details on remote access performance considerations.

### RangeInterface

The RangeInterface facilitates setting ranges that can later be associated with certain visual attributes of a particular viewer. The ranges are stored with the model and the visuals are stored with the viewer. Potentially, you can apply different visuals to multiple viewers using the same model. For example, you might want a graph attached to the model to use one background color and font and a table attached to the same model to use a different color and/or font. This is accomplished by having the model maintain the range and the viewer maintain the visuals.

A range is made up of a collection of RangeSegments referred to as a RangeCollection. Each RangeSegment defines an upper and lower limit. See java.lang.Double for static final values that can be used to define the minimum and maximum values of the RangeSegment.

The range segment names are identified using styleId. Range segments support setting other properties such as a default foregroundColor and backgroundColor. The RangeSegment is an extension of the com.sas.collection.PropertyBag, which means that it can add properties through its set method. The viewer enables you to apply different visual properties to each named range segment. Many viewers use the foregroundColor and backgroundColor attributes set on the RangeSegment as their default values. A range is associated with a Measure and Statistic pair (such as ACTUAL/SUM).

For example, you may want to define a range for the Measure Grades and Statistic Average with three segments, EXCELLENT (100-85), AVERAGE (84-60), and POOR (59-0). The chart may choose to highlight EXCELLENT with GREEN whereas the table may choose to use BLUE for EXCELLENT. Each viewer simply associates the visual attributes it desires with the associated range segment styleId.

This package includes a RangeCollection and Range class to help implement this interface. Also, refer to com.sas.models for additional documentation of Range classes and interfaces.

The following is an example of adding a range to a multidimensionalTableV3Interface. This version of the interface includes an addRange convenience method that easily enables you to associate the range with a Measure and Statistic pair.

    RangeSegment segments [] = new RangeSegment[3];
    segments[0] = new RangeSegment(new Long(85), new Long(100));
    segments[0].set("foregroundColor", java.awt.Color.green);
    segments[0].set("styleId", "EXCELLENT");
    segments[1] = new RangeSegment(new Long(60), new Long(84));
    segments[1].set("foregroundColor", java.awt.Color.yellow);
    segments[1].set("styleId", "AVERAGE");
    segments[2] = new RangeSegment(new Long(0), new Long(59));
    segments[2].set("foregroundColor", java.awt.Color.red);
    segments[2].set("styleId", "POOR");
    RangeCollection actualRange = new RangeCollection(segments);
    actualRange.setContiguousSegmentsRequired(false);
    multidimensionalTableInterface1.addRange("GRADES", "AVG", actualRange);

### BookmarkableInterface

The MultidimensionalTableInterface implements the com.sas.util.BookmarkableInterface. This interface enables you to save the state of the object and return to that state at a later time. Available methods include getBookmark and gotoBookmark. The implementation returns an HList object that reflects the state of the underlying server technology. Currently, this is the same as a SAS/EIS Application list with some additional client side information appended to the list. See com.sas.util.BookmarkInterface, BookmarksInterface as well as com.sas.sasserver.BookmarkManagerInterface for additional interfaces available for implementing a complete bookmarking infrastructure.

## Commands

The com.sas.sasserver.mdtable package has a commands subpackage that contains the commands used by the MultidimensionalTableInterface to provide popmenu functionality. These commands are implementations of the com.sas.util.CommandInterface. All but the MDCommand have an associated dialog in com.sas.sasserver.Dialogs that is invoked through the command.

The MDCommand has no associated dialog. Instead, it defers to the server to execute the command. The commands performed by MDCommand include navigation commands, such as Drilldown, Down, Up, Expand, and Collapse. These commands are executed by the _performAction and _performMaskAction remote methods.

MDCommand is the parent class of many of these commands. It also statically defines the command masks associated with each command. The mask values can be used with the setCommandsMask method of the MultidimensionalTableInterface to enable and disable commands. By default, the commandsMask property is set to MDCommand.ALL_COMMANDS_MASK. To disable a command, AND out the unwanted command. For example, to disable the Show Detail Data&hellip; command, set the commandMask property as follows:

    multidimensionalTableInterface1.setCommandsMask(MDCommand.ALL_COMMANDS_MASK & ~MDCommand.SHOW_DETAIL_DATA_COMMAND);

The following table lists the supported commands, the dialog that each command invokes and the associated command mask. Unless otherwise noted:

- commands are in the com.sas.sasserver.mdtable.commands package
- dialogs are in the com.sas.sasserver.mdtable.dialogs package.

Command | Dialog | Mask
ComputedValuesCommand | ComputedValuesDialog | COMPUTED_VALUES_COMMAND
ExportToExcelCommand | ExportToExcelDialog | EXPORT_TO_EXCEL_COMMAND
FindCommand | FindDialog | FIND_LABEL_COMMAND
MDCommand |  | NAVIGATION_COMMANDS_MASK (COLLAPSE_COMMAND & DOWN_COMMAND & DRILLDOWN_COMMAND & EXPAND_COMMAND & LEFT_COMMAND & RIGHT_COMMAND & UP_COMMAND )
QueryCommand | QueryDialog | QUERY_COMMAND
RangeTypesCommand | RangeTypesDialog | EXCEPTION_HIGHLIGHTING_COMMAND
ShowDetailCommand | DetailDataDialog | SHOW_DETAIL_DATA_COMMAND
SubsetsCommand | SubsetsDialog | SUBSETS_COMMAND
TopNCommand | FilterDialog | RANK_COMMAND
TotalsCommand | TotalsDialog | TOTALS_COMMAND

For more information, see Command Tips.

## Dialogs

The com.sas.sasserver.mdtable also has a dialogs subpackage that contains the dialogs used by the MultidimensionalTableInterface. These dialogs are instantiated with the Commands associated with the popup menu. The panels used by the dialogs are in the com.sas.sasserver.mdtable package itself.

Currently, there is no infrastructure in place to enable you to replace the com.sas.sasserver.dialogs class with your own custom dialog class. When a dialog customization is desired, disable the command associated with the dialog and add a custom command to drive the custom dialog.

## Events

The MultidimensionalTableInterface sends PropertyChange events when the value of a property changes. Additionally, when a property value change affects the overall state of the model, a ContentsChangedEvent is fired.

AxisChangedEvent and AxisLabelChangedEvent, subclasses of the ContentsChangedEvent, are also fired by the interface. These events enable listeners to react to a more refined change. For example, a viewer listening for the AxisChangedEvent can refresh only the specific Axis that is effected by the change rather than refreshing everything.

The MultidimensionalTableInterface also sends the com.sas.mdtable.DrillEvent, which enables a listener to monitor the navigation actions that have been performed by the model.

The com.sas.util.EventGateInterface is implemented by the MultidimensionalTableInterface. This interface enables you to disable event notification during selected code execution to prevent event listeners from reacting immediately. When the last gate wait is cleared, the model sends a ContentsChangedEvent.

Event | Fired When
java.beans.PropertyChangeEvent | A property value changes. To and From values are included where possible. Noteworthy properties: application, cellTypes, columnAxis, commandsMask, computedvalues, coordinatorEnabled, database, dimensions, measures, metabase, ranges, rowAxis, selectedMeasures, selectedStatistics, statistics, subsets, totals, verifyEnabled
com.sas.collection.ContentsChangedEvent | Any of the following property values change: Application, metabase, database, subsets, totals, computedValues, statisticLabelHidden Or when the following methods are invoked: SetSource, initializeTable, modifyTable, gotoBookmark, close
com.sas.mdtable.AxisChangedEvent | A single computedValue on a displayed Level is added. The axis that the Level appears on is indicated. When a Level sort is applied. The axis that the Level appears on is indicated if the Level is currently displayed. When a column sort is applied the opposite (row) axis is indicated. A navigation command is performed (Drilldown, Up, Down, Left or Right).
com.sas.mdtable.AxisLabelChangedEvent | An Expand or Collapse is performed.
com.sas.mdtable.DrillEvent | A navigation command is performed (Drilldown, Down, Left or Right).