CSNODE Quick Start Guide

OTAdmin
OTAdmin Administrator, E Community Administrator
edited July 19, 2021 in Articles #1

originally published May 2019 | 🕐️ 14 minute read

Introduction

This document will explain how to use and extend csNode.

In a nutshell, csNode is a wrapper for LLNode, to provide a more consistent interface that can be used in a programmatic way for various applications (the REST API is the first consumer). Although still in its infancy, csNode is an API layer that can be used in lieu of certain LLNode calls.

This document assumes the reader has knowledge of Builder (10.0.x) and/or the Content Server IDE (10.5.x).

As a quick start guide, an exhaustive list of all possible actions and related input parameters will not be provided. To learn more, run the Info method on a node, role or action.

The examples in this document are closer to pseudo-code than production ready code; often lacking error handling in order to illustrate new concepts.


CSNode OSpace Overview

The Root object has three main classes: ObjectsWithPrototypes, Prototypes and Subsystems.

ObjectsWithPrototypes

Values by themselves (without metadata to describe them) are not terribly useful. This is where prototypes come into play, which are divided into Objects and Actions. Each “object” class has its own dedicated “action” class. The Objects class contains Members, Nodes and Roles. Nodes is the wrapper for specific LLNodes while Members is the wrapper for user/group functionality. Roles encompass shared functionality across multiple subtypes (categories, classifications, etc.).

Unlike LLNode with its top-heavy inheritance model, a given csNode can take various reusable components (actions and roles) and construct itself during run-time.

Prototypes

You probably won't need to work with this class at all but this is where the supported data types for the prototypes are defined. You will notice that these are all simple data types, you will not find Dynamic and Assoc here. These “complex” data types are often misused and result in code that is more difficult to use and maintain.

Subsystems

You shouldn't need to modify anything in here, this is where subsystems are defined to support objects, actions and prototypes. There are a few public methods in here which are useful (NewNode, GetNode, GetRootNode).

UnitTestStubs

This is the only orphan in the csNode ospace, which contains some test stubs that need just a bit of modification to work. Steps to create a test:

  1. Orphan the given test into your <ospace>_test module.
  2. Modify and run the 0 Setup method.
  3. Modify the TestSubclassSetupValues method (if needed). Depending on the test, other methods may need to be overwritten.
  4. Run the test by calling the 0 Test method.

Of course, you can orphan from UNITEST:UnitTest to create additional tests. These are just pre-canned ones to reduce maintenance and avoid duplication.?

Using the csNode API

All actions (node and role) are called in the exact same manner; set zero or more parameters and then call the Go method.

NOTE: Since the actions are attached at run-time, you may need to check their existence before referencing them to avoid possible script crashes. For example:

if ( csNode.HasAction( "browse" ) )
    checkVal = csNode.Browse().Go()
end

NOTE: Objects in OScript are not deleted when they go out of scope. Since a csNode is an object, it should be wrapped in a Frame for automatic garbage collection. Failure to do so will result in memory leaks. For example:

Frame       f
f = $KERNEL.FrameWrapper.New( csNode )

If you are looping though a series of csNodes, you can add each frame wrapped csNode to a List. For example:

List        garbageCollection
garbageCollection = List.SetAdd( garbageCollection, $KERNEL.FrameWrapper.New( csNode ) )

NOTE: Referencing local features/script is often done using the dot operator (.) which implies the current object (this.). Since csNode makes use of the super function, it needs to pass in the object to be acted upon (as a convention, the variable is named self). If you see self in the function declaration, make sure you are calling self. and not this. or ., otherwise you will be working with the wrong object.

Get Node (existing)

To get a csNode, all you need is a program context and a node ID. For example:

Dynamic checkVal
ObjRef      csNode
Frame       f

checkVal = $CSNODE.NodeSubsystem.GetNode( prgCtx, nodeID )
if ( checkVal.ok == TRUE)
    csNode = checkVal.csNode
    f = $KERNEL.FrameWrapper.New( csNode )
else
    ...
end

A csNode contains both the data and the class, unlike DAPINODE (data) and LLNode (class).

Get Node Properties

Once you have a csNode, you can access its properties via the available getters. For example:

name = csNode.GetName()

A csNode does not have any setters by design. To update node properties, run the Update action.

Update Node Properties

Set one or more properties on the Update action and then call the Go method. For example:

csNode.Update().SetName("New Name")
csNode.Update().SetDescription("New Description")
checkVal = csNode.Update().Go()
if ( checkVal.ok != TRUE )
    ...
end

If the update was successful, the current csNode in memory will be refreshed with the new values.

Get Node (new)

To get a new csNode, all you need is a program context and a subtype. For example:

Dynamic checkVal
ObjRef      csNode
Frame       f

checkVal = $CSNODE.NodeSubsystem.NewNode( prgCtx, $TypeFolder )

if ( checkVal.ok == TRUE)
    csNode = checkVal.csNode
    f = $KERNEL.FrameWrapper.New( csNode )
else
    ...
end

You can't do too much with a new csNode other than view its class properties and call the Create action.

Create Node

After getting a new csNode (for the desired subtype), set any properties (name and parent ID are required) on the Create action then call the Go method. For example:

csNode.Create().SetParentID(2000)
csNode.Create().SetName("New Folder")
checkVal = csNode.Create().Go()
if ( checkVal.ok == TRUE )
    nodeID = checkVal.result.id
else
    ...
end

If the create was successful, the current csNode in memory will be refreshed with the new values.

Delete Node

After getting an existing csNode, call the Go method on the Delete action. For example:

checkVal = csNode.Delete().Go()
if ( checkVal.ok != TRUE )
    ...
end

The csNode will still be in memory after the delete until it goes out of scope.

Browse Node

After getting an existing csNode, set zero or more parameters on the Browse action and then call the Go method. For example:

csNode.Browse().SetPageSize(100)
csNode.Browse().SetPageNumber(2)

checkVal = csNode.Browse().Go()
if ( checkVal.ok == TRUE )
    data = checkVal.result.data
else
    ...
end

By default, the browse action will default to page 1 and 25 items per page.

Info

The Info method can be called on a node, role or action to get more detailed information about the specified object.

For a given node, data will contain the property values (name, description, etc.) while definitions will describe each property (which could be used in UI rendering). The definitions_order key will return a suggested order of the definitions. The definitions_map key (if specified) will return any relationships between the definitions. The applicable actions and roles are listed in available_actions and available_roles respectively. If the node is a container, the valid child subtypes are listed in addable_types. The subtype is returned in type, the display name is returned in type_name and additional class properties are returned in type_info. For example:

info = csNode.Info()

For a given node action, data will contain the values of input parameters while definitions will describe each input parameter (which could be used in validation and/or UI rendering). The subtype is returned in type and its display name in type_name. The action is returned in action and its display name in action_name. For example:

csNode.Create().Info()

For a given role, the applicable actions are listed in available_actions. The subtype is returned in type and its display name in `type_name. The role is returned in role and its display name in role_name. For example:

csNode.Classifications().Info()

For a given role action, data will contain the values of input parameters while definitions will describe each input parameter (which could be used in validation and/or UI rendering). The subtype is returned in type and its display name in type_name. The action is returned in action and its display name in action_name. The role is returned in role and its display name in role_name. For example:

csNode.Classifications().Add().Info()


Extending the csNode API

This section assumes you already have an existing and working LLNode that lives outside of LLIAPI (if your node lives in LLIAPI, you can add content to the CSNODE ospace).

The instructions provided assume that Builder is the IDE (CS 10.0.x). Some steps may not be applicable in the Content Server IDE (10.5.x).

CSNode contains a series of convenience methods to create methods and populate features. Currently, the Content Server IDE implementation is a multi-step process (see Appendix B).

Common Properties

  1. Orphan CSNODE:Root:ObjectsWithPrototypes:Objects:Nodes or an appropriate child into your ospace and give it a name.
  2. Overwrite the Setup function. The value for enabled should be TRUE. The value for type should be the subtype of your LLNode. The value for actions is a list of actions that your csNode will support. The value for roles is a list of roles that your csNode will support (roles applicable to all subtypes, such as categories, do not need to be listed here). For now, leave actions and roles alone. When done, save Setup and run it.
  3. Run the BuildFeatures function. This will copy certain features from LLNode/WebNode into your csNode as a convenience. You should only need to run this once, unless of course something were to change in LLNode or WebNode.
  4. Build your ospace by running <ospace>.Globals.BuildOSpace and then restart Builder (may not be required in the Content Server IDE).

You should now be able to retrieve common properties (name, description, create date, etc.) on your node (see Using the csNode API - Get Node Properties).

Troubleshooting

Check the node subsystem registry to make sure your csNode was registered properly:

CSNODE:Subsystems:ObjectSubsystems:NodeSubsystem:<temp>.registry

Try running Setup and BuildOspace again and restart Builder.

Custom Properties

You can skip this section if your LLNode does not have subtype specific properties. You will get the common properties for free via inheritance (name, description, create date, etc.). For a custom properties example see:

CSNODE:Root:ObjectsWithPrototypes:Objects:Nodes:URL
  1. Overwrite the SetupPrototypes function. For each property, you will need to declare a prototype. Prototypes have a default set of values, so you only need overwrite where necessary (“name” is required). If the property is an ID that can be expanded, set the persona (see Appendix A). Save when finished (you can modify this at any time during design).
  2. Run BuildPrototypes to create a feature and getter for each prototype defined in SetupPrototypes. The default getters will just return the value in the feature (this behavior can be overwritten if desired). BuildPrototypes will overwrite feature values but it will not overwrite the getters. If you remove a prototype from SetupPrototypes, you will need to remove the feature and getter manually.
  3. Overwrite the Load function to assign values to the custom features during run-time.
  4. You should now be able to retrieve common and custom properties on your node (see “Using the csNode API - Get Node Properties”).

Troubleshooting

Make sure you called super.SetupPrototypes (with followAncestors condition) when you overwrote SetupPrototype.

Make sure you called super.Load when you overwrote Load.

Set a breakpoint in Load to debug.

Common Actions

Depending on your LLNode, there may existing actions that you can add to your csNode (Browse, Delete, Copy, Move, etc.). If your LLNode does not have any custom properties, you can use the Create and Update actions (if applicable). If your LLNode has an associated volume, you may be able to use BrowseGeneric (to avoid having to create a custom browse action).

  1. Overwrite the Setup function and specify any common actions. When done, save Setup and run it. Restart Builder as the template for a given csNode is cached after first use.
  2. You should now be able to perform these actions on a csNode.

Troubleshooting

Check the actions feature for the particular action.

Check that the action has been registered properly:

CSNODE:Subsystems:ActionSubsystems:NodeActionSubsystem:<temp>.registry

Modifying Actions

If your LLNode does not have any custom metadata, you can skip this section. This section assumes we are modifying an existing create action (other actions would require similar steps). For a modified action example see:

CSNODE:Root:ObjectsWithPrototypes:Actions:NodeActions:Create:CreateURL
  1. Orphan CSNODE:Root:ObjectsWithPrototypes:Actions:NodeActions:Create into your ospace and name it Create (eg. for URL it would be CreateURL).
  2. Overwrite the Setup function. The value for enabled should be TRUE. If your action is read-only, set read_only to TRUE (otherwise FALSE). The value for slot_name should be left as “Create” and the xlate for type_name should be left alone. The value for type should uniquely identify the action (eg. CreateURL) but you do need to be careful of naming collisions (the end user will not see this name so there is some leeway). When done, save Setup and run it.
  3. Overwrite SetupPrototypes to define any custom input parameters for your action. Save when finished.
  4. Run BuildPrototypes to create a feature and setter for each prototype defined in SetupPrototypes. The default setters will do some standard validation and, if successful, will set the value in the feature (this behavior can be overwritten if desired). BuildPrototypes will overwrite feature values but it will not overwrite the setters. If you remove a prototype from SetupPrototypes, you will need to remove the feature and setter manually.
  5. Overwrite the GoPre function to add any custom input parameters to the request.
  6. Overwrite <csNode>.Setup to include any modified actions and save (use the value in <action>.type).
  7. Build your ospace and restart Builder.
  8. You should now be able to perform this action on a csNode.

Troubleshooting

Check the actions feature for the particular action.

Check that the action has been registered properly:

CSNODE:Subsystems:ActionSubsystems:NodeActionSubsystem:<temp>.registry

Custom Actions

You can skip this section if your LLNode does not have subtype specific functionality.

  1. Orphan CSNODE:Root:ObjectsWithPrototypes:Actions:NodeActions into your ospace and give it a name.
  2. Overwrite the Setup function and fill in the features. The value for type will be used to register the action, so it should be fairly unique to avoid possible naming collisions. The value for slot_name is how the developer will call the action, so it should be meaningful but could be prone to naming collisions. When done, save Setup and run it.
  3. Overwrite SetupPrototypes to define any custom input parameters for your action. Save when finished.
  4. Run BuildPrototypes to create a feature and setter for each prototype defined in SetupPrototypes. The default setters will do some standard validation and, if successful, will set the value in the feature (this behavior can be overwritten if desired). BuildPrototypes will overwrite feature values but it will not overwrite the setters. If you remove a prototype from SetupPrototypes, you will need to remove the feature and setter manually.
  5. Overwrite the GoPre function to add any custom input parameters to the request.
  6. Overwrite the GoSubclass function to call a similar function LLNode.
  7. Overwrite <csNode>.Setup to include your custom action and save (use the value in <action>.type).
  8. Build your ospace and restart Builder.

You should now be able to perform this action on a csNode.

Troubleshooting

Check the actions feature for the particular action.

Check that the action has been registered properly:

CSNODE:Subsystems:ActionSubsystems:NodeActionSubsystem:<temp>.registry 
RESTAPI:Subsystems:RestAPISubsystem:<temp>.fMappings

Roles

You can skip this section if your LLNode does not have any shared functionality across multiple subtypes. A role is basically collection of actions, with its own namespace (to reduce naming collisions). For a role example see:

CSNODE:Root:ObjectsWithPrototypes:Objects:Roles:Versions
  1. Orphan CSNODE:Root:ObjectsWithPrototypes:Objects:Roles into your ospace and give it a name.
  2. Overwrite the Setup function and fill in the features. The value for actions should be actions registered with the RoleActionSubsystem (not the NodeActionSubsystem). When done, save Setup and run it.
  3. Overwrite the IsSupported function to determine if the specified subtype can have this role (default behavior). A given csNode can always subscribe to a role by overwriting its own Setup function. If the role is common across all subtypes just return TRUE.
  4. 4a) Orphan CSNODE:Root:ObjectsWithPrototypes:Actions:RoleActions into your ospace and give it a name.
  5. 4b) Create actions for your role (these are identical to node actions, they just live in a different spot). Although role actions have their own subsystem, there can still be naming collisions. One naming convention is to use the name of the action followed by the role name (eg. AddVersion, DeleteVersion).
  6. Build your ospace and restart Builder.
  7. You should now be able to perform these actions on a csNode.

Troubleshooting

Check that the role was registered properly:

CSNODE:Root:Subsystems:ObjectSubsystems:RoleSubsystem:<temp>.registry

Check that the actions were registered properly:

CSNODE:Subsystems:ActionSubsystems:RoleActionSubsystem:<temp>.registry
RESTAPI:Subsystems:RestAPISubsystem:<temp>.fMappings

Role Callback

Sometimes a role may want to have some control over a given action. These are just methods that live in a role with a certain naming convention.

Callback<action>Set - used to set any values before an action is execute (and before Pre is called)

Callback<action>Pre - used to perform any processing before an action is executed

Callback<action>Post - used to perform any processing after an action is executed

Callback<action>Data - used to append to the data structure during an Info call

Callback<action>Definitions - used to append to the definitions structure during an Info call

For examples see:

CSNODE:Root:ObjectWithPrototypes:Objects:Roles:Categories


Appendix A: Personas

A persona is a way to identify an ID as a particular type, so that it can be processed accordingly.

node: the property is a node ID

member: the property can be a user or group ID

user: the property is a user ID

group: the property is a group ID

storage_provider: the property is a storage provider

For example:

prototype = self.AddPrototype( "id", IntegerType )
prototype.name = [CSNODE_LABEL.ID]
prototype.persona = "node"


Appendix B: CS IDE “setup” Scripts

  1. Make a copy of the os file that will be modified.
  2. From the Module Explorer tab, double-click the desired “setup” script.
  3. Modify the script and save (memory only).
  4. Right-click on the same “setup” script in the Module Explorer tab and select Run Script.
  5. From the OScript Explorer tab, select the related os file.
  6. From the menu, select Source | Overwrite Object Source, click OK when prompted. Unfortunately, the process changes everything in the os file (including tabs).
  7. Make a copy of the new os file and merge with the old file using a diff tool.
  8. Copy the resolved code back into the Content Server IDE.

In some cases, it may be easier to edit some of these features by hand rather than running the “setup” scripts.

Tagged: