Technical Training “How to program a Widget in the new Content Server GUI” with the CSUI SDK

Hi folks

Beeing Content Server Trainer, I was asked to provide a training “How to program a Widget for the new Content Server GUI” to several customers. This is about the content of such a training and the reason why such a training is technically really advanced.

CSUI SDK introduces a shift in paradigma, the server is no longer providing the GUI, all is done by a Javascript Application at the client side.

Basic Training (newest version 1.005 from Feb/2018)

The basic training is very compact and lasts 5 days.

First, lets take a look on the components:

As a prerequisite, a firm knowledge of CSIDE, How to build a module, the Node Structure and whats to do with Content server Nodes is necessary.

At the end of the training, you’ll have a working knowledge of:

  1. Content Server Perspectives. What are they and how you can use them to provide a user specific interface.
  2. Content Server REST. This is the only possibility for the client to communicate with the server, so its mandantory to know the REST interface from the application point of vue. And, as REST can be very slow due to unneccesary data, its also mandantory to know, how to add REST services to the Content Server to get the data you want in the fastest way.
  3. Javascript. There are Javascript Patterns, which are used heavily in the SDK. If “Javascript Object Inheritance”, “Currying/Schoenfinkelizing”, “Decorators” etc are weird words for you, this chapter is the right chapter for you.
  4. Next is the infrastructural world of the sdk.
    CSUI Components
    The CSUI Components

    There are several components, which must be understood prior to build a Widget.

    1. node.js is the base Javascript system for our development
    2. grunt.js is a Javascript task runner, which we use to build, test and debug our widget
    3. yeoman is a scaffolding system, which will be used by a Opentext extension to initialize the development folder. It also creates the skeleton of a Content Server module, which is used as a “Carrier” for our Widget(s)
    4. backbone.js is the base framework to be used
    5. marionette.js is an extension to backbone.js, making Views easier.
    6. handlebars.js is the html templating framework used in the sdk
    7. require.js is the javascript module loader to be used
    8. bootstrap.js/binf.js is originally the public domain CSS/JS framework from Twitter for the appearance of the Widget. Binf is the Opentext variant, allowing to override CSS without negative effects
  5. Next is the SDK itself. Due to time restrictions, in the base training are only the base functions, the advanced training covers the other aspects. It contains
    1. Installing the SDK
    2. Building the Demo Widget
    3. CSS Style Overrides binf
    4. General Overview of the SDK
    5. Content of the SDK
    6. Routing (Preview of the Advanced Training)
    7. New Commands
    8. Custom Columns
    9. Metadata
    10. Define a new NodeType
    11. Create a Widget
    12. Base Widgets
    13. Controls
    14. Models
  6. Mockup and Test Data. How to setup mockup REST data. How to build test facilities in the SDK
  7. Anatomy of the Hello Widget. A walktrough through this widget
  8. Anatomy of the MyAssignment Widget
  9. Add another Widget to the Content Server. Change the Hello Widget and add additional fields
  10. Build a Custom Widget to be used as Client with a custom REST service. Here, a custom widget is build, which uses an extended version of the custom REST service from Part 2.
  11. If time allows: Some Tips and Tricks from a “Work-in-Progress” Widget

 

Advanced Training (Version 1.005 Feb/2018)

OK, thats the basic training. There are a lot of additional things inside the SDK, but to understand these, there must be a couple of weeks with practical experience in between. The advanced part has beed remodelled to include a couple of interesting things.  The advanced part contains:

  1. Chapter 1 Extended SDK Parts
    1. Additional Widgets
      1. NodesListReportView
      2. TilereportView
      3. FilteredCountChartView
      4. Carousel Widget View
      5. Userwidget eSocial
      6. ActivityFeedWidgetView
      7. ActivityFeedContent
      8. ChatWidget
    2. Datepicker
    3. switch
    4. Workflow (new in Content Server 16.2)
      1. Workflow Components
      2. Workflow in smartUI
      3. Starting Workflows
      4. URL Routes
      5. Workitem Actions
      6. Workitem Extension
      7. Writing Workflow Extensions
    5. New REST API Support (16.2) for Workflows
    6. Widgets not part of the SDK
      1. Mobile Scanning
      2. xECM: Header Widget with Business Object Infos
      3. xECM: Snapshot of current document Attachments
      4. xECM: Dossier View Widget
      5. Engineering Doc Management: Search
      6. xECM: Office365 Groups
  2. Chapter 2 Extended SDK Features
    1. Build Language Packs for Internationalization
    2. Commands
      1. Implementation and Inheritance from “CommandModel”
      2. Best Practices
      3. Using Commands
    3. Custom URL Router. Routing, adding custom Routers. Using Routers as Navigation.
    4. Behaviours. What are Behaviours?
      1. DefaultActionBehaviour
      2. BlockingBehaviour
      3. ExpandingBehaviour
      4. InfiniteScrollingBehaviour
      5. PerfectScrollingBehaviour
      6. PageLeavingBehaviour
    5. Mixins. What are Mixins? All available Mixins.
    6. Browsable Support for Collections. Using the “Browsable” support for Model-Collections.
  3. Chapter 3 Additional Things to consider
    1. Tips and Tricks (Work in Progress- List can change)
      1. Add a OTDS Ticket already in the browser to the connection object
      2. Re-using a OTDS Ticket as LLCookie
      3. Checking the paths in the test/index.html
      4. Using Helpers for supporting a select box with Handlebars
      5. Adding non CSUI supported JQuery functions in a view
    2. Handlebars advanced. A deeper look into Handlebars
    3. LESS advanced. A deepter look into LESS, the CSS language used in Bootstrap
    4. Accessibility in Bootstrap. What can be done to add support for screenreades etc to Bootstrap/Binf? Whats to avoid? Which tools are available
    5. Best Practices in Development

 

As you can see, there is a lot of stuff. The basic training last 5 days, the advanced training 2. But on the other side, you’ll get happy users with your new Widgets.

And that’s all what counts.

References

Javascript
  • “JavaScript Application Design “as a general introduction (covering Grunt) https://www.manning.com/books/javascript-application-design
  • Stoyanow’s JavaScript patterns http://shop.oreilly.com/product/9780596806767.do
  • Flanagan’s “JavaScript: The Definitive Guide” http://shop.oreilly.com/product/9780596805531.do
  • Crockfords “Javascript The good Parts” http://shop.oreilly.com/product/9780596517748.do
  • Flanagans “JavaScript Pocket Reference” http://shop.oreilly.com/product/0636920011460.do
Bootstrap
  • Spurlock: Bootstrap Responsive Web Development http://shop.oreilly.com/product/0636920027867.do
  • Syed Fazle Rahman: Jump Start Bootstrap: Get Up to Speed With Bootstrap in a Weekend https://www.amazon.com/gp/product/0992279437
  • Alan Forbes: The Joy of Bootstrap: A smarter way to learn the world’s most popular web framework https://www.amazon.com/gp/product/1522792813/
Require.JS
  • Greg Franko: Instant Dependency Management with RequireJS How-to https://www.amazon.com/Instant-Dependency-Management-RequireJS-How/dp/1782169067
  • David Sulc: Structuring Backbone Code with RequireJS and Marionette Modules
    https://leanpub.com/structuring-backbone-with-requirejs-and-marionette
Backbone
  • Developing Backbone.js Applications Adnan Osmani http://shop.oreilly.com/product/0636920025344.do
  • js Patterns and Best Practices Swarnendu De https://www.amazon.com/Backbone-js-Patterns-Best-Practices-Swarnendu/dp/1783283572
  • js Cookbook Vadim Mirgorod http://shop.oreilly.com/product/9781782162728.do
  • js Blueprints Andrew Burgess http://shop.oreilly.com/product/9781783286997.do

 

Backbone-Marionette
  • Getting Started with Backbone Marionette Raymundo Armendariz, Arturo Soto
    https://www.packtpub.com/web-development/getting-started-backbone-marionette
  • Marionette.js: A Gentle Introduction David Sulc https://leanpub.com/marionette-gentle-introduction
  • Marionette.js: A Serious Progression David Sulc https://leanpub.com/marionette-serious-progression
Mockjax and Jasmine
  • js Cookbook Vadim Mirgorod https://www.packtpub.com/mapt/book/web_development/9781782162728 Chapter 8
  • KnockoutJS Essentials Jorge Ferrando
    https://www.packtpub.com/application-development/knockoutjs-essentials
  • JavaScript Testing with Jasmine Eva Hahn https://www.safaribooksonline.com/library/view/javascript-testing-with/9781449356729/

 

Content Server Trainings
  • 4-0140 Content Server IDE (CSIDE) Fundamentals
  • 971-301 Livelink ECM Enterprise Server Workflow Customization Fundamentals
  • 03-0117/8 Webreports Design

 

 

Setting Node ACLs via Web Services API

One of the nicest things in the content server area is, you can set Access Control Lists not only for the owner or the default group of a node, and you can add or revoke rights to the node for virtually all users and groups defined in the content server.

Here, we want do discuss how to do this using Content Server Web services. First, let’s see how ACLs are organized. A simple object would display something like this:

The ACLs for this object

On the left hand side, you see the default access, there is always one Owner, one default group and a public group.

If you want to assign further access, you can select a user or a group by clicking on the bottom at the lower left hand side. This will select a user or a group. (Btw: It’s recommended to use groups instead of users)

On the right hand side, there are the ACLs for the selected user/group on the object. Here, we used the Administrator, therefore all rights are switched on.

Ok, how to use this on a c# client?

First, the ACLs for a given user are called NodePermissions. Use them like this. Let’s say, this is utils.setNodeRights(….)

/// <summary>
 /// Creates a Noderight Structure and returns it
 /// </summary>
 /// <param name="rights">"all" or see (see,seecontents)</param>
 /// <param name="id">the member ID for this node rights</param>
 /// <param name="type">Owner, Ownergroup, Public or ACL </param>
 /// <returns></returns>
 public DocumentManagement.NodeRight setNodeRights(string rights, long id, string type )
 {
 DocumentManagement.NodePermissions newPerm = new DocumentManagement.NodePermissions();

newPerm.SeeContentsPermission = true;
 newPerm.SeePermission = true;
 if (rights.Equals("all"))
 {
 newPerm.AddItemsPermission = true;
 newPerm.DeletePermission = true;
 newPerm.DeleteVersionsPermission = true;
 newPerm.EditAttributesPermission = true;
 newPerm.EditPermissionsPermission = true;
 newPerm.ModifyPermission = true;
 newPerm.ReservePermission = true;
 }
 DocumentManagement.NodeRight newRight = new DocumentManagement.NodeRight();
 newRight.Permissions = newPerm;
 newRight.RightID = id;
 newRight.Type =type;
 return newRight;

}

The Nodepermissions is simply a container with the single access rights set to true or false.

When the definition is finished, the NodePermissions must be encapsulated in a structure called NodeRight. This contains the ACLs defined and the user/group for the ACL. Use the id as long integer, this is the unique identifier of a user/group in the content server. You can use MemberServices to get this number, if you know the login-name.

Then, the system wants to know, which kind this user/group is. Use ACL, if these are additional users/groups, or use something like Owner, Ownergroup or Public, if the ACLs should belong to the predefined entities.

How to use this?

First, login to the Webservices.

Second. Get the node, onto which you want to set ACLs.

Thirth. Get the node rights

 NodeRightsUtilities utils = new NodeRightsUtilities();
 DocumentManagement.NodeRights nodeRights = docclient.GetNodeRights(ref otauth, pargs.nodenumber);
 Console.WriteLine("Got Noderights ");
 
 DocumentManagement.NodeRight[] nodesrights = nodeRights.ACLRights;

Forth. Set the ACLs for the standard entities (if needed). Define a NodeRight for every entity (user/group) you want to set. Define these 2 right groups.

 // setup standard permissions for base ACL group
 DocumentManagement.NodeRight newRightstandardGroup = utils.setNodeRights("see", standardGoupID, "ACL");
 DocumentManagement.NodeRight newRightsmanagerGroup = utils.setNodeRights("all", managergroupID, "ACL");

Define a special right group

 DocumentManagement.NodeRight newOwnerright = utils.setNodeRights("all", ownerID, "Owner");
 nodeRights.OwnerRight = newOwnerright;

 

The nodesrights on a node can contain something or be null. First, lets check, if the thing is null.

 if (nodesrights == null)
 {
 Console.WriteLine("Nodesrights not found- no external Users/groups assigned");
 Console.WriteLine("Setting a group with rights see/seecontent");
 
 DocumentManagement.NodeRight[] allrights = new DocumentManagement.NodeRight[2];
 allrights[0] = newRightstandardGroup;
 allrights[1] = newRightsmanagerGroup;
 nodeRights.ACLRights = allrights;
 docclient.SetNodeRights(ref otauth, pargs.nodenumber, nodeRights);
 authClient.Close();
 Console.WriteLine("All Rights and Groups set --- Finish");
 return;
 }

In this case we simply define a NodeRight array with two entries containing our new RightstandardGroup and our newRightmanagerGroup.

Let’s store them into our newly created array.

Let’s store this array in our nodeRights under ACLRights. This will change our copy of our structure which we downloaded before.

Next is simply a SetNodeRights with the nodenumber and the updated nodeRights array to write our changes back to the server.

If there are already entries, you should set them like this:

// Just display the first Entry of the first assigned Users/groups
 DocumentManagement.NodeRight right = nodesrights[0];
 DocumentManagement.NodePermissions nperms = right.Permissions;
 // check, if we do habe the groups already set

int newLen = nodesrights.Length;

 // copy rights into new longer Array
 DocumentManagement.NodeRight[] allrights = new DocumentManagement.NodeRight[newLen+2];
 for ( int i = 0; i < newLen;i++ )
 {
 allrights[i] = nodesrights[i];
 }
 allrights[newLen] = newRightsmanagerGroup;
 allrights[newLen + 1] = newRightstandardGroup;
 nodeRights.ACLRights = null;
 nodeRights.ACLRights = allrights;
 docclient.SetNodeRights(ref otauth, pargs.nodenumber, nodeRights);
 Console.WriteLine("All Rights and Groups set --- Finish");
 authClient.Close();

In this case, our nodesrights come from the nodeRights.ACLRights, because there are alredy entries.

Now, let’s do the same thing like we did on a new structure. But, we don’t want to delete existing entries, lets simply add our new RightstandardGroup and our newRightmanagerGroup to the ACLRights array.

One thing, if finished, first set the nodesRights.ACLRight to null and then fill it with the new extended ACL array.

Then do a SetNodeRights with the nodenumber and the updated nodeRights array to write our changes back to the server.

 

Finished. Quite easy, isn’t it?

 

BTW: There is no equivalent to do this on the REST api at the time of this post.

 

 

 

Installing and activating Content Server Web Services on a Content Server Installation

If you are thinking on using Content Server Webservices (CWS´) you may wonder how to activate this on a standard Content Server Installation. Normally, right out of the box, CWS is not active or installed, although you got the license to use it in your basic license.

As Administrator, you have to do a couple of things to activate CWS.

Decide which architecture you will use.

CWS can be used inside of the Microsoft Internet Information Server or inside a Java Application Server like Tomcat. Both ways to activate will be described here.

Locate the CWS Software

Look in your install directory of the content server. There, you will find a directory named “webservices”.

Webservices Basedirectory

Here, there are three entries:

  1. dotnet contains all CWS service definitions for use inside the Microsoft IIS
  2. java contains all webapps for use inside the Tomcat application server
  3. java6 contains the same thing as in 2. but for use with java 6.
Installing CWS inside the Microsoft Internet Information Server IIS

First, let’s examine how to install CWS inside IIS. Switch to the dotnet subdirectory, then to cws (do not use les-services, this is an old version supporting legacy clients)

All svc files for installation

Locate the .svc files you wish to install and use in CWS.

Open the IIS Manager. Create a new Application at the default web site

Create a new Application
App pointing to CWS

Enter the path to the dotnet\cws directory , enter for example CWS as alias.

Remark: IIS must be configured to

  • execute .NET 2.0 apps
  • allow “Read” and “Script” rights to the new app
  • execute WCF (can be ensured for example by “%SystemRoot%\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe -i”  (check, if something changed, if you are using newer versions)
  • allow “Read” and “Execute” rights for the ID of the Application pool on $OTHOME/webservices/dotnet

And (don’t forget), ensure that your webservides will use the same port as your content server uses.  If you use a nonstandard port (not 2099) you need to change the port in the file $OTHOME\webservices\dotnet\cws\web.conf

If you use the standard port, there is nothing for you to do.

 

Installing CWS in Tomcat

Alternatively, you can use Tomcat as a base for CWS.

In this case, go to the java6 base directory in the webservices dir.

The war files

In this dir, you’ll find the war (web application archive) files, which you need to deploy.

Deploy the cws.war file either to the TOMCAT\webapps directory or use the Tomcat service manager to deploy this file.

The tomcat webapps dir

Don’t forget, if you changed your port number of your content server from 2099 to something else, change also the value in the web.xml of the unpacked cws web app.

The port in the web.xml file

If you don’t use the same port numbers, the whole system will listen to different ports and will do nothing.

Test your installation

Your installation is correct, if a browser, pointing to

http://127.0.0.1:8080/cws/services/DocumentManagement  (Tomcat) or

http://127.0.0.1//cws/Documentmanagement.svc?wsdl (IIS) shows

DocumentManagement

Remark: The WSDL Urls reflect the infrastructure of course. You see “:8080” only with Tomcat.

 

And now, feel free to discover the new world of content web services.

A new SDK in Town: Content Server SmartUI SDK – First glimpse

Did you ever wonder, how to create new widgets or forms for the new SmartUI of the content server? The new SmartUI is responsive and can be used on mobile devices.

smartui

If you want to add new widgets or modify things, there is a new SDK on the knowledge center, which can be found here:

This are the components:

  • CSUI SDK Intro.pptx
    this is a powerpoint presentation on the SDK.
  • csui-doc-16.0.3.zip (1.7MB)
    this is the documentation as stand alone.
  • csui-sdk-16.0.3.zip (53.5 MB)
    this is the sdk itself. It contains a copy of the documentation.
  • csui-style-override-kit-16.0.3.zip (122 kb)
    this is a kit to provide branded css files, which will be used instead of the original ones.
  • generator-csui-extension-16.0.3.zip(51.9 MB)
    this is a generator, which will be used to setup an empty development project.

 

The basic requirement is the installation of node.js at your CSIDE machine.  You should be familiar with the REST API and Grunt to use the SDK.

The workflow is

  1. Generate an empty project on the harddisc
  2. Install all prerequisites in node.js (npm, grunt-cli)
  3. Modify the example according to your needs
  4. Import the module in out-module of your project into CSIDE
  5. Copy the widgets etc into your support folder
  6. Create the module in CSIDE and install it
The installed new Widget

Then you should see the example widget under the name of your module in the perspective manager.

Watch for upcoming posts on this exciting new Content Server technology.

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.

 

Upload a file into the content server from OScript

If you want to upload a file into the content server from your module, you can use something like this

function Assoc UploadFile(Record r, Integer FolderID, String fieldName)
    Dynamic retNodeID = -1 
    DAPINODE docNode 
    // Get the ATTACHMENT_FOLDER node and set some options.
    // ParentID = 2000 (Enterprise) 
    Object prgSession = .PrgSession()
    Object session = prgSession.DSession()
    DAPINODE nodeFolder=    DAPI.GetNodeByID(session.fSession,DAPI.BY_DATAID,FolderID,False )
    // Make the node name from the file and eventID. It has to be unique, so
   // check it, and append a number if necessary.
    String fname=$WebDoc.WebDocUtils.GetFileUploadName(r,fieldName)
    Assoc retUniq
    String sTmp=fname
    Integer cnt=0
    retUniq=$LLIApi.NodeUtil.IsNameUnique(sTmp,nodeFolder)
    while !(retUniq.OK)
       cnt+=1
       sTmp=fname+"_"+Str.ValueToString(cnt)
       retUniq=$LLIApi.NodeUtil.IsNameUnique(sTmp,nodeFolder)
    end
    fname=sTmp
    // Upload the document as a new node.
    Assoc result = $WebDoc.WebDocUtils.CreateFileUploadDocument(nodeFolder,r,fieldName,fname)
    return(result) 
  end

First, the folder ID is used to get the DAPINODE of the parent folder. The node name must be unique, so a check is necessary to ensure, that the intended nodename is not existing.

Then the file is uploaded.

Remark: This function does not consider mandatory categories, which must be added at upload time.

Building a Content Server Request Handler for AJAX Client Requests

In the standard request handler usage scenarios, there is always a complete URL, which is send to the server. Then the server processes that and returns the result page to the client.

With a little change, these standard request handlers in a module can be modified to be used from clients asynchronously by the usage of AJAX.

Client Part

The easiest approach is to use AJAX with JQUERY (which is delivered by the content server). If the base page was built from another request handler, it can include weblingo constructs. These are OSCRIPT parts, delimited by `-Delimiters. Like JSP, the final renderer evaluates everything between two delimiters and replaces that with the result of the evaluation.

So a AJAX JQUERY command with intermixed OSCRIPT parts can look like that:

This is a simple AJAX call, calling the request handler on the server as usual. But there is a parameter called func, which contains the actual request handler address and one parameter nodeid, which is the data parameter.

Request Handler
AJAX Part

Server Part

A sample request handler looks like this listing below. There is a prototype declared, this prototype must exist in the request.

Server Request Handler
Server Request Handler

The rest of the data is encapsulated in the JSON array. This array can be parsed into an assoc with the call to

$WebLL.JSONUtils.ParseJSON(request.configData)

and processed in the standard way.

Any responses should be handled in the standard way with an assoc.

String tst = $WebLL.JSONUtils.ToJSON(response)
  Web.Write(ctxOut, $WebLL.JSONUtils.GetJSONHeader(.fExtraHeaders))
  Web.Write(ctxOut, tst);
  .fHTMLFile = Undefined

This assoc is changed with a call to the ToJSON to a normal string.

Here, we do not have a output html file, so we have to use a Web.Write to write the output directly to the port. Do not forget to include the JSONHeaders.

The fHTMLFile stores normally the weblingo output file and has to be set to undefined in this case.

package DELIVERER::PUBLICATOR::AJAXHANDLER

public object UpdateAjax inherits DELIVERER::RequestHandlerOrphans::LLRequestHandler

override Boolean fEnabled = TRUE
  override String fFuncPrefix = 'deliverer'
  override Dynamic fHTMLFile
  override List fPrototype = { { 'configdata', -1, 'The config data changed at the client', FALSE } }
  /*
  * This is the Request handler to handle the Ajax Update Request(s)
  * Based on the original Request handlers, this handler receives an AJAX Request from the NodeAction Browse Page
  * and sends the Response back to the Browse Page (PUBLICATOR.html)
  *
  * @param {Dynamic} ctxIn
  * @param {Dynamic} ctxOut
  * @param {Record} request
  *
  * @return {Dynamic} Undefined
  */
  override function Dynamic Execute( \
  Dynamic ctxIn, \
  Dynamic ctxOut, \
  Record request )

Object prgCtx = .PrgSession()
  Assoc response = Assoc.CreateAssoc()
  Assoc cDat = $WebLL.JSONUtils.ParseJSON(request.configData)
  integer nodeid = cDat.id
  DAPINode node = DAPI.GetNodeByID(prgCtx.DapiSess(),nodeid)
  Assoc.Delete(cDat,"id") // remove the NodeID
  string remark = cDat.remark
  Assoc.Delete(cDat,"remark");
  node.pComment=remark
  // save configdata in pExtendedData
  // do not forget to include other assocs, if needed from the pExtendedData
  Assoc a = node.pExtendedData;
  //TODO Add Things
  a.configdata=cDat
  node.pExtendedData = a

DAPI.UpdateNode(node) // store it

// Send response
  response.ok="OK"
  response.msg="Fine"
  String tst = $WebLL.JSONUtils.ToJSON(response)
  Web.Write(ctxOut, $WebLL.JSONUtils.GetJSONHeader(.fExtraHeaders))
  Web.Write(ctxOut, tst);
  .fHTMLFile = Undefined

return Undefined
  end

end