OS-Package in OScript

Oscript OS Package

The famous OS package in OScript is very important for the internal functionalities of the Content Server Modules, but its hardly documented. This post is an overview of the methods in this package.

A closer look

All of these methods van be used by adressing tzhe methods with the package name (OS) in any oscript program. For example OS.Features(Object) lists all features of the object in the argument.

Disclaimer: This list is not complete and may change any time and any Content Server Version, as long as there is no official documentation of this package. And there is (of course) no guarantee on this list by me.

Methods

CreateOSpace(sPath)ObjectCreates a new OSpace
New(oParent)ObjectCreates a new OSpace object
Delete(Object)Deletes an OSpace object
NewTemp(MasterObject)Creates a new instance of an OSpace object
IsTemp(Object)IntegerWas the object created using newTemp. Result 0,1.
Root(MasterObject)ObjectReturns the module root object
Roots()ArrayReturns all loaded root objects
Orphans(Object)ArrayReturns all orphans in the objects ospace
Children(Object)ArrayGets children objects from the object
Patent(Object)ObjectGets the parent object
Name(Object), FileName(Object)StringReturns the name of the object/ospace of the object
ReadOnly(Object)BooleanIndicates, if the current ospace is locked
Features(Object)ListLists all features in the current object
IsFeature(Object, sFeatureName)BooleanReturns, if the Feature sFeatureName exists in Object
AddFeature(Object, sFeatureName, [optional] CopyableBoolean)Add a new feature to the object
DeleteFeature(Object, sFeatureName)Removes the feature sFeatureName from the object
ClearValue(Object, sName)Clears the content of the feature nName
IsObject(objRef)BooleanChecks, if the object still exists

Happy OSpace Changing

Config Settings send to a smartUI Module

There are several possibilities to set the configs from the base OScript Module. The most interesting is the method of setting the configs via Oscript. The complete list of the possibilities is_

  • When you visit a mode or get a children
    • Implement “fields” and/or “expand” in the appropriate CSNode.
    • Use the date from data.(fields.name)
  • When the Widget is created
    • Implement an REST service which will give you the data
    • Load the data on the first widget creation and cache it with a permanent model factory in the context
  • When the page is loaded
    • The CSUI::Extension::GetDynamicConfiguration method in OScript has normally this entry
function Assoc GetDynamicConfiguration( Object prgCtx, Record request )      
return Undefined
  end

Overwrite it with your desired configuration (here only the enableAppleSupport is displayed for clarity):

 function Assoc GetDynamicConfiguration(

        Object      prgCtx,

        Record      request )      
Assoc       basicAppData
Assoc       config 
(snip)

        Boolean     enableAppleSupport

    (snip)

        // changes in admin page should be reflected immediately by every thread

        settings = $WebAdmin.AdminUtils.GetSmartUISettings( prgCtx )
     (snip)
    if ( IsFeature( settings, "enableAppleSupport" ) && IsDefined( settings.enableAppleSupport ) )

        enableAppleSupport = settings.enableAppleSupport

    end

    

    config = Assoc{ 
    (snip)
     "csui/utils/commands/email.link": Assoc{ "enableAppleSupport": enableAppleSupport },
                  

    return config

end 

The returned assoc consists of a requirejs module name containing an assoc with the config values.

In your js module (here csui/utils/commands/email.link) add this line

var config = _.extend({
rewriteApplicationURL: false,
enableAppleSupport: false,
appleNodeLinkBase: 'x-otm-as-cs16://?launchUrl=nodes/'
}, module.config());

Then the config is avaliable in your js module and can be used like

var iOSEnabled = config.enableAppleSupport

Nice, isn’t it?

Practical smartUI: Reading complex categories on the Content Server Side

Normally, when building REST Services for smartUI, you’ll have to read complex attributes from categories to send them for display to a widget.

Lets use this example

A complex Category with 4 Sets and Table Key Lookups
A complex category with multible sets and table lookups (click to enlarge)

In your REST handler, you r task would be to get the values of the category attached to a node, read them and send them to the client using REST.

In this writing, we’ll focus on the “Read them” part. Our trick is

object  LLNODE = $LLIAPI.LLNodeSubsystem.GetItem(node.pSubtype)
result = LLNODE.NodeCategoriesGet(node)

Thos gives us all Categories connected to a node.

When we receive the REST call in ther server handler, we should do

  1. find the node id of the the object to examine.
  2. find the categories attached to this node (Red Arrow in the Screenshot)
  3. iterate over all categories and get the attributes we want. This is based on
    1. the attribute definitions (Green Arrow on the Screenshot)
    2. the attribute values (Yellow Arrow on the Screenshot)
  4. if you encounter a set (-18 as attribute type), you also will have to iterate over the set (Red Rectangle)
Get the Values of a complex category
The REST Handler to get the Values of a complex category

Lets take a look on the Values of a single Category. They are inside an assoc with one entry per attribute id.

i

The Value list in CSIDE
The value list in CSIDE

Now you are almost done. Put a switch construct to get all values of the attributes on which you are intersted and get them out of the fdata assoc. The Value is found in the fdata assoc of the n-th Category unter the content of the first Value entry. Then we have to pick the attribute id where we find the content of the attribute in the first Value entry.

The Str,ValueTostring typecasts the output to be a string. So when we finished the job, we’ll see in the entry event.eventcycle the value of the attribute with the name “EventCycle”.

Get the attributes of interest
The switch construct to get the attributes of interest

The only thing to do is put the values in assocs and send them to the smartUI widget under the main clause “events” in the JSON array

Sent the values back
Send the values back. Support 200 (ok) and 500 (error) as status code.

So you see, Categories are no Monsters. They are quite user friendly. Using the same mechanism, you can even update complex Categories with one REST call.

Happy smartUI programming.

New: We are offering custom widget development. Interested? Send an e-mail to merz at ebit-company.de stating the purpose of the widget and requesting a qoute

Quick List of all Categories and Attributes in a Content Server

Looking for a quick list of all Categories and Attributes configured in a Content Server? Tired of browsing the Categories Volume? Want to see all attrobutes and their IDs in a list?

Easy.

Simple use this line as your URL

<server><OT base><cgi>?func=attributes.dump

(p.ex. http://myserver/myContentServer/cs.exe?func=attributes.dump)

This will return this list:

The attribute.dump output
The attribute.dump output

Although this is a screenshot from a Contentserver 16.2.6, you can use this command at least back to Version 10.5.

 

Using the CSIDE Code Generation Wizard (1) – Create WebNode Action automatically

In the newer versions of CSIDE, you’ll find a code generation wizard, which writes code snipets for standard tasks automatically.

You can call this Code Generation wizard by clicking on the right mouse button.

Call of the Code Generation Wizard

Next, the Code Generation Wizard gives you a list of all templates, which can be used for the code generation. The amount of templates can vary from CSIDE version to CSIDE version.

List of all Templates to be used for the code generation

When you click on a template, you’ll get further info’s on the template. The amount of info’s can vary from template to template.

Infos on the template

Here in this post, we are using the “Create WebNodeAction” Command. Refer to future posts on examples of the other templates.

A WebNodeAction is a command, which is executed against a node by issuing a WebNodeCommand (in Contenxt/Node or other menus). For example, the “Open” command in a node menu is a WebNodeCommand, which issues the action for opening the node content.

After selecting “Create WebNodeAction”, we must provide two additional definitions.

Additional Infos to provide

First, we must select the package to put this command in. We simply select DELIVERER (the name of our module), but in practical situations, you should select a proper package.

And the name of the command has to be provided. After clicking “Finish”, a file with the name “MyTestCommand.os” will appear in the first level of our OSPACE.

The Command is generated as .os file

When you open the newly created file, you’ll see a complete command definition.

Command Definition Part 1

The fEnabled is set to true. The fWebScript has the default name of “mytestcommand.html” and there is an empty list of fPrototype.

Change the name of the fWebScript to the name you want. Add the Webingo parts of the commands output in this fWebScript.

If you use special parameters in the request, setup the proper fPrototype list.

The _Nodetypes() returns an undefined List, which should be changed immediately to reflect the node subtypes for which the command should be valid.

Command Definition Part 2

There is even a _SubclassExecute stub generated automatically. Add the action code to be executed in this _SubclassExecute stub.

Optional: Connect this WebNodeAction with a WebNodeCommand in a context or node menu or somewhere in the GUI to make it accessible for the end-user.

 

Calling OScript from Java Code (2)

In December 2016, we discussed how to call Java code from OScript . Now, we’ll discuss the other way round, how to call OScript from Java. This can be quite useful, if you want to use your business logic implemented in java against the content server.

The Java code must be put in the ojlib directory (see the previous post on this topic).

As always, if you want to deploy your java code within a module, do this

  • Build your code into jar files.
  • Add the jar files in OTHOME/ojlib/ or OTHOME/module/yourmodule_yourversions/ojlib/ directory (and their subdirectories) to be recognized by Content Serverk JVM’s application classloader.

The base thing is, you call OScript built-in functions through the OScriptObject.runScript method from a java coding.

For example, the java code

OScriptObject.runScript("echo","System.ThreadID()");

will display the unique integer for the current thread ID at the console (or in the logs, if you do not use CSIDE)

You can call all OScript functions and scripts. This example will list all nodes in the enterprise workspace

     // List nodes in the Enterprise workspace.
            result = (Map<String,Object>) OScriptObject.runScript( "$LLIAPI.NodeUtil.ListNodes",
				prgCtx,
				"(ParentID=:A1)",
				args );

prgCtx is the standard rogram Context, args is the ArrayList containing the arguments and A1 points to the first entry in the args array to be used as ParentID.

The next example can be used to get the current user from java coding, extract its userID and then derives the user name from ths user id. A standard logger is used to log the output, replace this with the logger of your preference.

    public static String getUserName( OScriptObject prgCtx )
    throws Exception
    {
        String retval = " user not found";
        
        try
        {        
            // get the user session object
            OScriptObject uSession = (OScriptObject) prgCtx.invokeScript( "USession" );
            // get the userID from the User Session
            Integer userID = (Integer) uSession.getFeature( "fUserId" );
            // display it
            logger.log( Level.INFO, "UserID is " + userID );
            
            retval = "The current login User: " + userID;
            
            // Get the Users name from the User ID
            Map<String,Object> status = (Map<String,Object>)
			OScriptObject.runScript( "$LLIAPI.UsersPkg.NameGetByID", uSession, 1000 );
			
            logger.log( Level.INFO, String.valueOf( status ) );
            logger.log( Level.INFO, (String) status.get( "Name" ) );
        }
        catch( Exception e )
        {
            logger.log( Level.SEVERE, "Caught Exception", e );
            throw e;
        }
        
        return retval;
    }

 

In the next posting on this topic, we’ll discuss the Mappings from JAVA to OScript and vice versa.

 

Execute a Livereport from OScript and get the results

From time to time, you may want to execute a livereport from an OScript function and get the results for further processing. This can be done quite easy.

First, you should have the node id of the report to execute.

In this scriptlet, the node id of this livereport to execute is nodeid

Second, you should setup a list with all input parameters. For each input parameter, create an assoc with inputType, label, prompt, textvalue and value of the parameter.

Add this assoc to the list of input parameters.

Store this list in the pExtendedData field of report node.

Object llnode = $LLiApi.LLNodeSubsystem.GetItem( $TypeReport )
Assoc data = llnode.ExecuteReport( prgCtx.fDBConnect, extdata )

Get the llnode of the report by calling the LLNodeSubSystem.GetItem. Execute the report by using llnode.ExecuteReport

The result of the livereport is in data.contents.

The whole call can look like this:

 node = DAPI.GetNodeById(prgCtx.DapiSess(),nodeid)
 if node.pSubType == $TypeReport // Is node a livereport?
     Assoc extdata = node.pExtendedData
     Assoc inp
    //Create input parameters assoc
     Assoc inp
     inp.inputType = "String"
     inp.label = "inputlabel1"
     inp.prompt = "DataID"
     inp.textValue = Str.String ( DataID )
     inp.value = Str.String ( DataID )
 
     //Attach inputs parameters list to extendeddata
     List inputs = {inp}
     extdata.inputs = inputs
     llnode = $LLiApi.LLNodeSubSystem.GetItem($TypeReport)
     Assoc data = llnode.ExecuteReport(prgCtx.fDBConnect, extdata)
     result.OK=true
     result.data = data.contents
     return result
end

 

Calling Java Code from OScript (1)

Sometimes it would be nice to use existing Java coding from a module instead of recoding this in OScript.

There is a facility in the content server which does exactly this bridging from OScript to Java, the so called JavaObject class. You’ll find the exact documentation in the “OScript API/Build-In Package Index”

In this first post of the series we’ll discuss the  basic calls from OScript.

First, you need some Java Code, compiled and in the form of a jar. Put this jar either in OTHOME/ojlib or (much better) in a ojlib directory in your module structure. After installing the module, the jar(s) are copied automatically to the OTHOME/ojlib. Then, the jvm classloader will find your jar(s).

From OScript it is possible to access static classes and instances.

Static
Dynamic InvokeStaticMethod(String classname, String methodName, List parameters)

Dynamic GetStaticField( String classname, String fieldName)

Dynamic SetStaticField( String className, String  fieldName, Dynamic value)

The return values are either Error or Dynamic if the call is successful.

Dynamic
JavaObject New (String className, List parameters)
Instance
Dynamic GetField(String fieldname)

Dynamic SetField(String fieldname, Dynamic value)

Dynamic InvokeMethod(String methodName, List parameter)

The return values are either Error or Dynamic if the call is successful.

An example
function void javaTest()

   JavaObject myObject

   myObject = JavaObject.new("my.own.package.class")
 
   Dynamic res = myObject.InvokeMethod("myMethod",{"aa","bb})

   if (isError(res))

     echo ("Init failed")

     return

   end

   Dynamic res1 = myObject.GetField("myResult")

   if (IsError(res)) 

     echo("Calculation failed")

     return

   end

   echo("The result is "+res1)

end

In the next post we’ll discuss how to get the JNI exceptions and the error stack from the jvm.

 

Checking for the membership in a Group (OScript)

From time to time you have to check, if your user is member of a defined group. The UAPI.RightsListByID returns a list of all groups, in which the specified user is a member (directly or indirectly through membership in another group).

You can use something like this snipped in your function.

object uSession = request.prgCtx.fUSession 

dynamic uGroups 

if isDefined(uSession) && !isError(uSession)

 uGroups = UAPI.RightsListByID(uSession.fSession, uSession.fUserId)

 if isError(uGroups) 

   dynamic check = error.ErrorToString(uGroups) 

  end 

 end 

The Dynamic check contains the RecArray of groups, in which the current user is a member.  This snipped is intended to be run from a request handler.

 

Making Configuration Values Cluster Safe

Normally, there is a file called “opentext.ini” in the config directory of your Content Server instance. This file contains a lot of configuration values.

A content server reads this file at boot time.

But, if you use a cluster of Content Servers, its a problem changing all “opentext.ini” files for a configuration value. And rebooting them will use a lot of down-time.

In a cluster, there is another option to store configuation values, the “KIni Table” in the database. This values are read immediately from the content server(s) and do not require a reboot.

From OScript, use the database storage like this

Retrieve a value (from the “MySoftware” Section)- Default Value 50, if this is not existing)

result = CAPI.IniGet(dbConnect.fLogin, ‘MySoftware’,‘StartJob’, 50)

Set a value ( sets StartJob value of MySoftware to 50)

result = CAPI.IniPut(dbConnect.fLogin, ‘NySoftware’,‘StartJob’, 50)

List all configuration values registered under MySoftware

result = CAPI.IniList(dbConnect.fLogin, ‘MySoftware’)

Delete a configuration value (deletes StartJob value from MySoftware)

result = CAPI.IniDelete(dbConnect.fLogin, ‘MySoftware’, ‘StartJob’)

 

The dbConnect.fLogin is the CAPICONNECT(the login object) to the database.