Part 23 - If You Build It, They Will Come: Creating Dimensions And Cubes
As I said earlier it's questionable whether you would include code to build dimensions and cubes into what is predominantly a data loader application in a production environment. However it's been done here to show how more than anything else. The call from
main() is:
Code: Select all
if(bContinue){
try {
TM1StockCubeCreator.createStockDataCube(oTM1Server, goLogWriter,iDebug);
} catch (Exception e) {
bContinue = false;
goLogWriter.outputToLog(SC_PROCEDURE_NAME + ": " + e.getMessage());
goLogWriter.outputToLog(SC_PROCEDURE_NAME + ": " + "Error occurred when creating cube; aborting.");
}
}
That is, we call the static method
.createStockDataCube from the
TM1StockCubeCreator class, passing in the TM1 server object that we're connected to (
oTM1Server), the
goLogWriter object, ad the
iDebug value.
The createStockDataCube Method
In the TM1StockCubeCreator class, two constants are declared that we'll use in the method:
Code: Select all
public static final String SC_NAME_STOCKCUBE = "StockPriceCube";
public static final String SC_NAME_UPLOADTOTALELEMENT = "Total Loads";
The following two variables are declared in the procedure:
Code: Select all
String sErrMsg = "";
String SC_PROCEDURE_NAME = "TM1StockCubeCreator";
We begin inside a try block with:
Code: Select all
TM1Cube oTM1Cube = oTM1Server.getCube(SC_NAME_STOCKCUBE);
if(oTM1Cube.isError()){
// Etc
So we use the server object's
.getCube method to try to get the cube that we're writing to. As I mentioned previously there are two overloaded forms of this method name, one accepting an integer (which we used to loop through the cubes previously) and one accepting a
String (representing the name of the cube).
This is followed by an
if() test, with the condition checking whether the cube's
.isError method returns
true. If it doesn't, meaning that there is no error because the cube
is there and we now have a reference to it, then there is no code to execute; the method just exits returning
true. Obviously there's no point in creating the cube if it's already there.
If there
is an error, though, we need to know what kind of error it is. There is a method for TM1 objects named
.getErrorCode which returns one of the values defined in the
TM1ErrorCode class. We need to determine whether it's the
ObjectPropertyNotList one which indicates that the object that we tried to get doesn't exist; that it's "not in the list":
Code: Select all
// This error is OK; we just need to create it.
if(oTM1Cube.getErrorCode()==TM1ErrorCode.ObjectPropertyNotList){
//Etc
The
else condition for that
if() is to throw a new
Exception object back; the cube has
an error but we can't anticipate what type it is. Granted there are other types that we
could check for like security access. If you type
TM1ErrorCode followed by a period, a humongously long list pops up:

- 000580_TM1ErrorCodes.jpg (119.77 KiB) Viewed 27065 times
However I cannot think why security would be an issue because non-Admins should not be running code to create cubes in the first place. And I can't think of any others that are worth specifically processing, so the Not In List one is the only one that I'm looking for. Any others just get logged and bounced back to
main().
If the not in list error occurs then we move into a new
try/catch block and call an internal process to try to create the dimensions of the cube. That process is passed the same arguments as the
createStockDataCube method itself is:
Code: Select all
createStockDataDimensions(oTM1Server, goLogWriter, iDebugMode);
Private Method createStockDataDimensions
This is the complete code for that method:
Code: Select all
/**
*
* @param oTM1Server
* @return The number of dimensions created.
* @throws Exception
*/
private static int createStockDataDimensions(TM1Server oTM1Server,
LogWriter goLogWriter, int debugMode) throws Exception{
String sDimName = "";
String sEltDeft = "";
String sErrMsg = "";
int i = 0;
int iReturn = 0;
String SC_PROCEDURE_NAME = "createStockDataDimensions";
// You may think that you set the value of a Real using setReal(double val).
// But if you do that and pass in a constant, the application doesn't merely
// error, it heads for the Bermuda Triangle and vanishes.
// Consequently we'll force a double (the Java equivalent of Real) and pass
// that to the constructor method TM1Val(java.lang.Double val)
double dblWeight = Double.valueOf(1).doubleValue();
//If this cannot be completed, we need to throw an exception back
// to the calling process to stop it from trying to create the cube.
// For consistency's sake I'll make this 0 to 5 given that
// the array of these elements needs to be zero based for the
// ValArray used in creating the cube.
for (i = 0; i <= 5; i++) {
switch (i) {
case 0:
sDimName = "SP_Exchange";
sEltDeft = "No Exhange";
break;
case 1:
sDimName = "SP_StockCode";
sEltDeft = "No StockCode";
break;
case 2:
sDimName = "SP_DateCode";
sEltDeft = "2000-01-01";
break;
case 3:
sDimName = "SP_TimeCode";
sEltDeft = "EOD";
break;
case 4:
sDimName = "SP_UploadTime";
sEltDeft = "Latest";
break;
case 5:
sDimName = "SP_Measure";
sEltDeft = "Volume";
break;
} // End switch block
if(debugMode!=0){System.out.println("Initiating loop " + i + ", Dim " + sDimName + ", Element " + sEltDeft);}
try {
TM1Dimension oTM1Dimension = oTM1Server.getDimension(sDimName);
if(debugMode!=0){System.out.println("Getting Dim object " + sDimName);}
if(oTM1Dimension.isError()){
// This is OK, we need to create it.
if(oTM1Dimension.getErrorCode()==TM1ErrorCode.ObjectPropertyNotList){
// Do the creation and check the result.
if(debugMode!=0){System.out.println("Creating Dim object " + sDimName);}
oTM1Dimension = oTM1Server.createDimension();
if (oTM1Dimension.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Failed to create " + sDimName + "; Error " + oTM1Dimension.getErrorMessage();
// Critical error.
throw new Exception(sErrMsg);
}
// Create the default element.
if(debugMode!=0){System.out.println("Creating Element " + sEltDeft + " in Dim object " + sDimName);}
TM1Element oTM1Element;
oTM1Element = oTM1Dimension.insertElement(TM1Element.NullElement, sEltDeft, TM1ObjectType.ElementSimple);
if (oTM1Element.isError()) {
sErrMsg = SC_PROCEDURE_NAME + ": Failed to insert element " + sEltDeft + " into dimension " + sDimName;
// Also a critical error. We'll try to kill the dimension so that it doesn't clutter up memory.
try {
//You "destroy" an unregistered object, but you "delete" a registered one.
//Unless of course it's a TI function like CubeDestroy which deletes registered cubes.
oTM1Dimension.destroy();
} catch (Exception e) {
// Hey, we tried... I find that with the TM1 API e.getMessage is often NULL
// so I prefer to go for .toString
sErrMsg = sErrMsg + ". Also failed to destroy temporary dimension object: " + e.toString();
}
goLogWriter.outputToLog(sErrMsg);
throw new Exception(sErrMsg);
} // End of if() checking insertion of element
if(i==4){
try {
if(debugMode!=0){System.out.println("Creating consolidation element " + SC_NAME_UPLOADTOTALELEMENT + " in Dim object " + sDimName);}
TM1Element oTM1EltConsol = oTM1Dimension.insertElement(TM1Element.NullElement, SC_NAME_UPLOADTOTALELEMENT, TM1ObjectType.ElementConsolidated);
// Errors relating to the consolidation aren't critical, just annoying
if( oTM1EltConsol.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Error creating the consolidation element " + SC_NAME_UPLOADTOTALELEMENT
+ " in dimension " + sDimName + ": " + oTM1EltConsol.getErrorMessage();
goLogWriter.outputToLog(sErrMsg);
}else{
TM1Val oValBoolResult = new TM1Val();
if(debugMode!=0){System.out.println("Adding element to the consolidation in Dim object " + sDimName);}
oValBoolResult = oTM1EltConsol.addComponent(
oTM1Element, new TM1Val(dblWeight));
if(debugMode!=0){System.out.println("Testing whether element was added successfully to the consolidation in Dim object " + sDimName);}
if(!oValBoolResult.getBoolean()){
sErrMsg = SC_PROCEDURE_NAME + ": Error inserting element " + sEltDeft
+ " into consolidation element "
+ SC_NAME_UPLOADTOTALELEMENT + " in dimension " + sDimName;
goLogWriter.outputToLog(sErrMsg);
}else{
if(debugMode!=0){System.out.println("No error adding to the consolidation in Dim object " + sDimName);}
}
}
//TODO: Set the sort order property for this dimension.
//Optional, since the MDX subsets take care of this.
} catch (Exception e) {
if(debugMode!=0){System.out.println("In error block for adding element to the consolidation in Dim object " + sDimName);}
sErrMsg="Error inserting the consol element for " + sDimName
+ " dimension; " + oTM1Dimension.getErrorMessage();
goLogWriter.outputToLog(sErrMsg);
}
} // End if i=4, the creation of the consolidation for SP_UploadTime
if(i==5){
oTM1Dimension.insertElement(TM1Element.NullElement, "Day Low", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Day High", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Day Close", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Day Open", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Last Bid", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Last Offer", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Last Price", TM1ObjectType.ElementSimple);
oTM1Dimension.insertElement(TM1Element.NullElement, "Last Volume", TM1ObjectType.ElementSimple);
} // End i=5, extra element insertion onto SP_Measure
// When I didn't do the assignment on the .register method line (that is,
// when I called the method without assigning the return value to anything)
// in the early days of development, the last dim wasn't created. It's possible
// that the reassignment is needed to prevent the unregistered dim being held onto.
// (Aside from which I needed to assign the result to a TM1Dimension variable
// to do the isError check anyway.)
if(debugMode!=0){System.out.println("Checking consistency of Dim object " + sDimName);}
if ( oTM1Dimension.check().getBoolean() ){
oTM1Dimension = oTM1Dimension.register(oTM1Server, sDimName);
if(oTM1Dimension.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Error registering the " + sDimName + " dimension; " + oTM1Dimension.getErrorMessage();
throw new Exception(sErrMsg);
} else{
iReturn++;
if(debugMode!=0){System.out.println("Created the " + sDimName + " dimension loop " + i);}
}
}else{
sErrMsg = SC_PROCEDURE_NAME + ": Dimension " + sDimName + " failed its consistency check; "
+ oTM1Dimension.getErrorMessage();
goLogWriter.outputToLog(sErrMsg);
throw new Exception(sErrMsg);
} // End of nre dimension consistency .check block.
}else{
sErrMsg = SC_PROCEDURE_NAME + ": Error accessing dimension " + sDimName + oTM1Dimension.getErrorMessage();
goLogWriter.outputToLog(sErrMsg);
throw new Exception(sErrMsg);
} //End of check whether the error was a Not In List type.
}else{
if(debugMode!=0){System.out.println("Dim object " + sDimName + " was successfully retrieved.");}
} // End of if/elseif checking whether the object had an error.
} catch (Exception e) {
sErrMsg = SC_PROCEDURE_NAME + ": Error accessing dimension " + sDimName + ". " + e.toString();
goLogWriter.outputToLog(sErrMsg);
throw new Exception(sErrMsg);
}
} // End of for loop.
return iReturn;
}
So what is it doing?
First, we need a value which allows us to specify the default element weighting of 1 for any consolidations. That weight should be a TM1 "Real" value which is of course the type of floating point value that is used to store N element values in a TM1 cube. (As I said in the inline comments, beware of the setReal method, whose presence may lead you down the wrong path here.) However Java knows nothing of TM1 or its Real values, so we have to use the Java equivalent; specifically, the primitive type
double. The problem is that 1 by itself is not something that Java would think of as a double (it would typically treat a constant of 1 as an
int), so we need to force the issue.
We declare the variable
dblWeight as a variable with a data type of
double. Then we call upon the double data type's helper class; you may recall that I said that each primitive data type has a corresponding complex object (its "helper class") which contains a bunch of methods to manipulate values. Accordingly I call the
Double class, tell it that I want a
.valueOf 1 using the method of the same name, and that I emphatically want it as a
.doubleValue() by calling that method:
Code: Select all
double dblWeight = Double.valueOf(1).doubleValue();
Most of the new dimensions will have a single element (to begin with, at least), and there will be 6 of them. There's therefore no point repeating the same code 6 times; instead we loop through it, feeding in different variable values each time. To honour Java's "zero based" inclinations I run the counter from 0 to 5:
Code: Select all
for (i = 0; i <= 5; i++) {
switch (i) {
case 0:
sDimName = "SP_Exchange";
sEltDeft = "No Exhange";
break;
case 1:
sDimName = "SP_StockCode";
sEltDeft = "No StockCode";
break;
//Etc
The
for() loop we've seen a few times already but I think this is the first time we've met the
switch block in Java aside from one "as an aside" mention of a change to it that came in with Java 7. It's the approximate equivalent to the
Select Case block in VBA but with a substantial difference. In this one I'm populating two variables in each loop depending on the counter number; the name of the dimension and the name of the element (the default element) that I'll be inserting. After each
case block's code, though, I have to add a
break; statement. If you fail to do that then if case 0 is true, the code will keep flowing down and executing case 1's block then case 2's block then case 3's block (etc). What would end up happening is that on each of the 6 loops I'd be trying to create the dimension name and element specified in case 5. It's a really stupid design that Java inherited from the C language. Thankfully the code template in Eclipse comes with the
break; statements built in.
A couple of other things to be aware of with
switch:
- Naturally the code for a switch block needs to be enclosed in braces after the switch(i) test, where the control value (i in this case) is contained inside round brackets. (As you can see, Java is reasonably consistent about this type of structure in its various control statements.) With a number of the code blocks I've used the technique that I referred to earlier of using an in-line comment after the closing brace of each substantial block of code, so that we have some idea of where we are.
- Prior to version 7 you could only use numerics for the case tests. In version 7 onwards you can use Strings. You shouldn't be targeting Java version 6 any longer but I give you fair warning just in case.
- There is a default: case that can be used if none of the other options apply, though I won't be using it here.
The next bit should be familiar enough. Inside a
try block, we use the
.getDimension method of the
TM1Server class object to try to get a reference to the dimension, just as we used a similar syntax to try to get a reference to the data cube. We're trying to get a reference to the dimension for the same reason; that is, to see whether it exists:
Code: Select all
try {
TM1Dimension oTM1Dimension = oTM1Server.getDimension(sDimName);
//Etc
We then check the
.isError method of the resulting object, and again check whether it's a Not In List error.
Code: Select all
if(oTM1Dimension.isError()){
// This is OK, we need to create it.
if(oTM1Dimension.getErrorCode()==TM1ErrorCode.ObjectPropertyNotList){
//Etc
If it's any other type of error then we log it, and throw an
Exception back... you get the idea by now. And if
oTM1Dimension.isError() is
false, meaning that there's no error at all (and therefore meaning that the dimension already exists), we just skip on to the next one. The only
else code that's executed in such a case is a debug statement. But that's of no value to us as a learning tool, so let's continue with the assumption that the dimension was
not in the list. What do we do then?
We call the
TM1Server object's
.createDimension method, and assign the result to a variable that we've declared as a
TM1Dimension type.
Code: Select all
oTM1Dimension = oTM1Server.createDimension();
Note that no name is specified here. This dimension is what's known as an unregistered dimension. There's space made for it in memory, but it has no name yet and only you and the server know of its existence. The server will not make it publicly known until you successfully register it, at which time it will be given a name.
Obviously we check the new dimension for any errors:
Code: Select all
if (oTM1Dimension.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Failed to create " + sDimName + "; Error " + oTM1Dimension.getErrorMessage();
// Critical error.
throw new Exception(sErrMsg);
// Etc
There are no fixable errors that I can think of here, so we abort the whole process and spit an
Exception back up the chain.
Otherwise, our new dimension needs some elements. We begin by declaring a variable which will hold a reference to the element that we create. I could of course have done this earlier in the code, but this variable doesn't have to live for very long and, more importantly, re-declaring the variable here ensures that we don't have anything left over from a previous loop. I'm not initialising it with any value; there's no point:
We then call the
.insertElement method of our new unregistered dimension to insert a new element.
Code: Select all
oTM1Element = oTM1Dimension.insertElement(TM1Element.NullElement, sEltDeft, TM1ObjectType.ElementSimple);
We need to supply three arguments:
- The element that we want to insert the new element ahead of. Not just the name of it; this needs to be a variable holding another TM1Element object. In this case we don't have one, so we use the TM1Element class' .NullElement field. That allows us to say that we don't want to specify an element, as a result of which the new one will go to the first position in the dimension.
- The name of the new element as a String. This is a variable that was populated in the switch block earlier.
- The type of the element. This needs to be specified as one of the constant values supplied by the TM1ObjectType class, in this case .ElementSimple. (Which, of course, is one of the many aliases for "an N element".)
Then we test whether the element was inserted correctly. In this case if it didn't we want to at least try to get rid of the temporary dimension as well. (There's no point in keeping the dimension if it has no elements.) Don't confuse the
.destroy method, which is used on unregistered objects, with
.delete, which is used on registered ones. (As I mentioned in the comments, this distinction isn't followed consistently in TM1 given that "destroy" is used in the TI functions to get rid of an existing, registered object on the server.) You can check this in the documentation if in doubt. Both the
.destroy and
.delete methods are inherited from the TM1Object class.
Code: Select all
if (oTM1Element.isError()) {
sErrMsg = SC_PROCEDURE_NAME + ": Failed to insert element " + sEltDeft + " into dimension " + sDimName;
// Also a critical error. We'll try to kill the dimension so that it doesn't clutter up memory.
try {
//You "destroy" an unregistered object, but you "delete" a registered one.
//Unless of course it's a TI function like CubeDestroy which deletes registered cubes.
oTM1Dimension.destroy();
} catch (Exception e) {
// Hey, we tried... I find that with the TM1 API e.getMessage is often NULL
// so I prefer to go for .toString
sErrMsg = sErrMsg + ". Also failed to destroy temporary dimension object: " + e.toString();
}
goLogWriter.outputToLog(sErrMsg);
throw new Exception(sErrMsg);
} // End of if() checking insertion of element
A couple of the dimensions need some additional elements; one needs a consolidation and the other a few extra N elements. We use an
if() block to identify the number of the dimension; we could have also checked against the dimension name of course.
The same
.insertElement method is used. The only difference (aside from using a "constant" for the name) is that in this case we use the
.ElementConsolidated value from the
TM1ObjectType class.
Code: Select all
if(i==4){
try {
TM1Element oTM1EltConsol = oTM1Dimension.insertElement(TM1Element.NullElement, SC_NAME_UPLOADTOTALELEMENT, TM1ObjectType.ElementConsolidated);
If the consolidation element can't be created... eh, who cares? We'll log it but it's not something to worry about.
Code: Select all
if( oTM1EltConsol.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Error creating the consolidation element " + SC_NAME_UPLOADTOTALELEMENT
+ " in dimension " + sDimName + ": " + oTM1EltConsol.getErrorMessage();
goLogWriter.outputToLog(sErrMsg);
If there is no error in creating the consolidation element, we need to add the N element that we created earlier to that consolidation. This is done through the
.addComponent method of the
TM1Element class; specifically that method of our newly minted consolidation element. We need to pass in:
- A TM1Element object representing the element that we are going to add to the dimension; and
- A TM1Val object that has the weighting. We'll create the TM1Val object on the fly using the new keyword. And we'll pass the value that we want the resulting TM1Val object to have to the class' constructor method. (If you look at the documentation for the TM1Val class, you'll see that it has constructor methods with a huge range of arguments.) As mentioned previously, we need to pass it a "real" value, which means a double.
The method will return another
TM1Val value capsule object, so we declare a variable first (
oValBoolResult) to hold that result.
To check the result we call the
TM1Val class'
.getBoolean method. See how we're using the bang (
!) operator in the
if() block? That means that we execute that block if the
oValBoolResult value is
not true. (That is, if the result of the operation is
false.) Again we don't care that much and just log the error.
Code: Select all
}else{
TM1Val oValBoolResult = new TM1Val();
oValBoolResult = oTM1EltConsol.addComponent(
oTM1Element, new TM1Val(dblWeight));
if(!oValBoolResult.getBoolean()){
sErrMsg = SC_PROCEDURE_NAME + ": Error inserting element " + sEltDeft
+ " into consolidation element "
+ SC_NAME_UPLOADTOTALELEMENT + " in dimension " + sDimName;
goLogWriter.outputToLog(sErrMsg);
}else{
if(debugMode!=0){System.out.println("No error adding to the consolidation in Dim object " + sDimName);}
}
}
For dimension 5 (the measures dimension) we just add 8 extra N elements. This is really just a repeat of the code that we used to add the first one. I don't check the result of the insertions, but would if this was a production system. I elected not to here because it would just clutter the code rather than provide any further enlightenment given that you've already seen how to do this.
Before you try to register a server you should always run the
TM1Dimension class'
.check method to confirm that it passes a consistency check. (Such as checking that it has elements, no duplicate elements, etc, etc.) That method returns a
TM1Val object which contains a Boolean so we can check the value by calling the TM1Val's
.getBoolean method. If that is
false, obviously we throw an Exception.
But if the check is passed, we proceed to register the dimension by calling the
TM1Dimension object's
.register method, passing:
- A reference to the server that the dimension will be registered on (the oTM1Server variable in our case); and
- A String representing the name of the object.
The
.register method, if successful, will return a reference to the newly registered dimension.
This is important; that newly registered dimension is effectively a different object from the unregistered one that you created. So the
oTM1Dimension variable on the left of the expression below is not the same as the one on the right; if the registration succeeds, the one that was on the right effectively ceases to exist. I'm using the same variable for both to make sure that the code no longer tries to retain any reference to the old one. (As noted in the comments I did have a problem with the last dimension in the loop registering properly before I did that.)
The last thing to do is check the
.isError property of the
oTM1Dimension variable which will, of course, be holding the new registered dimension at this point. Then it's the usual; if it's an error then throw an
Exception back up the stack (I don't bother trying to
.destroy it as I have, in theory, lost the reference to the unregistered dimension anyway and if it fails to register after a check there may be something very, very wrong), but if it works then I simply increment the return value counter by 1. (
iReturn++; )
Code: Select all
if ( oTM1Dimension.check().getBoolean() ){
oTM1Dimension = oTM1Dimension.register(oTM1Server, sDimName);
if(oTM1Dimension.isError()){
sErrMsg = SC_PROCEDURE_NAME + ": Error registering the " + sDimName + " dimension; " + oTM1Dimension.getErrorMessage();
throw new Exception(sErrMsg);
} else{
iReturn++;
//etc
At the end of this process we return the
iReturn value indicating the number of new dimensions created though I don't actually do anything with that. (Though obviously I could log it or report it some way if I wanted to.)
Let's Recap
Let's briefly recap the methods that we used here:
- Dimensions live on servers, so to get a dimension we must obviously use a method of a server object. In this case we use the TM1Server class' .getDimension method.
- Most TM1 objects, including dimensions, have an .isError field, which we can check to see whether the last action was completed successfully; such as getting a reference to a TM1Dimension.
- They also have .getErrorCode methods, which we can check for specific error codes that correspond to values in the TM1ErrorCode object. In the case that was illustrated here we are checking for an .ObjectPropertyNotList error to see whether the object that we tried to get actually exists.
- To create a new dimension we use the TM1Server object's .createDimension method. There are of course other .createXxx methods for other objects. The objects created by such methods are unregistered and have no name until you register them. You should again check the .isError field to confirm that this has worked.
- Elements live in dimensions, so we need a variable containing a TM1Dimension object first. We then use the .insertElement method to create the element, specifying its name and calling one of the values from the TM1ObjectType class to specify what type of element it is. Again this new element's .getError field should be checked. (Notwithstanding that I didn't in some cases, but for a trainer I can get away with it.)
- You create consolidation elements in the same way but need to specify a different TM1ObjectType. To add components you need to use the consolidation element's .addComponent method, passing a TM1Element instance containing the element that you want to add to it, and a TM1Val object containing a double value stating the new element's weight. This function returns a TM1Val object containing a Boolean value. You use the .getBoolean method of TM1Val to see whether the operation completed successfully.
- Once the elements have been added you call the new TM1Dimension object's .check method to validate the dimension.
- Finally you register the object by calling the new object's .register method, passing in a reference to the TM1Server object that you're registering it on, and the name that you want to use for it.