Aug 062014
 

Introduction

Many programming languages provide a way to override methods, and from the override call the original method that was overridden. This is a fundamental part of object-oriented programming (OOP), and is often made possible with a keyword like super. It may be hard to believe, but this is sort of supported in OScript.

OpenText Content Server OScript supports some OOP concepts (read about it in Part I of this blog series), but is rarely used. Instead, most implementations are written with static methods on classes that are not instantiated as objects, or are just instantiated once for the life of the thread (i.e., a singleton). This works, perhaps is arguably not optimal, but still permits overrides on its methods.

In OScript you do a lot of subclassing (also known as creating a “child” or “orphan”). You inherit all the functions and features from the parent and can selectively override functions and features to suit your requirements. However, sometimes you just need to make a minor modification to a function without wanting to have a full copy (plus the minor change) in your subclass. This is when a “super” call can be handy.

In this blog post I’ll outline a few ways a “super” call can be made in OpenText Content Server. I’ll discuss how this works in OScript, RHCore, and WebLingo files. By no means is this an exhaustive list. Rather, this post will discuss some OScript concepts and introduce workarounds to the limitations.

A note about terminology: When discussing the object hierarchy in OScript I often interchange the terms “parent” with “superclass” and “child” with “subclass”. They’re the same thing. I also prefer to refer to “OScript objects” as “classes” since in this context they behave more like classes than objects.

Calling “super” in OScript

The super keyword can be used in any script override to call the original script on the parent class. The syntax is simple and could look like the following for an override of a function named funcName():

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    Dynamic rtnVal = super.funcName(arg1, arg2, ..., argN)

    // do other stuff

    return rtnVal
end

The super keyword traverses up the inheritance stack and returns the first ancestor where the function on it differs from the function that called it. The function can then be executed on the returned class.

There is one problem. Calling a function in this manner doesn’t run the script in the context of the subclass. That is, the value of this will be that of the ancestor and not of the class that called it (keep in mind this is implied with any dot-reference e.g., .fFeature and this.fFeature are the same thing). This means if funcName() references a feature or script (e.g., .fCache or .someOtherFunction()) then it will access it on the ancestor and not on the class where the override was made. This is a big problem, and is why I wrote in the introduction that OScript only sort of supports this.

Fortunately, there is a workaround. The this context of a function can be changed by assigning the script to a local variable before executing it. OScript doesn’t support closures and by assigning the script to a local variable we change the value of this when it’s executed. That is:

function Dynamic funcName(Dynamic arg1, Dynamic arg2, ..., Dynamic argN)

    // Retrieve the script from our superclass
    Script superClassFunc = super.funcName

    // Execute it here with "this" being our subclass
    Dynamic rtnVal = superClassFunc(arg1)

    // do other stuff ...

    return rtnVal
end

This is somewhat analogous to the call method in JavaScript, which is used to change the value of this on a function call (albeit with a very different syntax).

Let me demonstrate with an example. Say you create a new Content Server subtype based on the Folder subtype. You orphan the Folder WebNode and LLNode into your module, give it a unique subtype and name, and are almost done. You like the default implementation of Action-Audit() (which displays audit information), but want to add a condition that only administrators can run it. This can be done by overriding the Action-Audit() script and wrapping the super call in a conditional block:

function Void Audit( Dynamic parm )
    Object prgCtx = parm.prgCtx     

    if prgCtx.USession().HasByPassPerm()
        // retrieve and execute the original script using super
        Script superClassAudit = super.('Action-Audit')
        superClassAudit(parm)
    else
        // error
        parm.response.error.message = "You must be an administrator to view this page."
    end     
end

This is much cleaner than overriding the entire Action-Audit() script to make the change, which is something that’s done too often. The advantage to the approach should be obvious: If the original code for Action-Audit() changes then nothing needs to be modified in our override to accommodate the change.

It surprises me how seldom the super keyword is used. Instead, the convention is to create an empty “subclass” function (e.g., SubclassExecute(), llnode.NodeAddVersionSubclassPost(), etc.) that subclasses can override and implement. There isn’t really anything wrong with this, but does limit the depth to which a class can be overridden in this manner.

Calling “super” with RHCore

The OScript super keyword does not work with the Frame datatype. There is good reason for this, but it’s unfortunate since the entire RHCore OOP concept is based on the Frame type (read more about this in Part I of this blog series). To get around this I added a super() function to RHCore, which is similar in idea to the super keyword and works with my Frame type. This syntax is:

$RHCore.Utils.super(this, 'funcName')(arg1, arg2, ..., argN)

A key difference between the super keyword and $RHCore.Utils.super is the return value. The super keyword returns the ancestor object, while $RHCore.Utils.super returns the script from the ancestor. This means we don’t have do anything special to execute the script with the correct context. That is, the value of this will be that of the object.

This means our previous audit example could be rewritten as:

function Void Audit( Dynamic parm )
    Object prgCtx = parm.prgCtx

    if prgCtx.USession().HasByPassPerm()
        // call the original script
        $RHCore.Utils.super(this, 'Action-Audit')(parm)
    else
        // error
        parm.response.error.message = "You must be an administrator to view this page."
    end
end

Finally, a shortcut is available on the base RHObject to make super calls. This is simple and compact:

.super('funcName')(arg1, arg2, ..., argN)

Addendum: Changing the scope of this

After writing this blog post I found an alternative way to control the value of this in a super call (or any call, actually). Have a look at Revisiting the making of “super” calls in my subsequent blog post.

Calling “super” with WebLingo files

It’s a common requirement to change a WebLingo page in Content Server. A novice developer might edit the HTML file directly in place, which is bad practice since it introduces support and upgrade issues. A more experienced developer may use a WebLingo override tool (there are a few floating around) to remap the file to a location in a custom module. These work, but require the entire content of the HTML file to copied over and changed. This forms an intrusive duplication of code and will fall out of sync if a subsequent version of a module changes the original file.

RHCore introduces a new WebLingo override approach that’s easy to use (i.e., requires no OScript to create the override), and provides a means to call the original Weblingo file (i.e., the “super” WebLingo) from the override. This isn’t always useful, but works well if you just need to:

  • change the preconditions of a Weblingo file; or
  • prepend or append content to a Weblingo file.

It’s surprising how often this is all that’s needed in an override. To do this I introduced a function called $RHCore.HTMLUtils.CallSuper, which can be executed from a WebLingo file (including a WebLingo override). It does more or less the same as the WebLingo ;;call directive, but allows the original WebLingo (i.e., the “super”) to be called. The syntax is a bit strange, but this is just a quirk of how OScript compiles WebLingo.

Let’s consider an example where this is useful. In RHCore I wanted to add a mechanism to programatically insert HTML into the header of any page. I approached this by creating a subsystem, which any module can use to register and generate content. The content can be HTML, JavaScript, an iFrame, or anything else that’s valid. To do this without changing core code I overrode the webnode/html/llmastheadcomponent.html file (which is the header of most pages), inserted the execution of the callbacks, and then called the original llmastheadcomponent.html. That looks like this:

;;webscript LLMastHeadComponent( Assoc guiComponents )
    <!-- File: rhcore/webnode/llmastheadcomponent.html -->

    ;Object cb
    ;for cb in $RHCore.CommonheaderSubystem.GetItems()
        `%Lcb.Execute(.fPrgCtx, .fArgs)`
    ;end

    ;// Call the original llmastheadcomponent.html
    `$RHCore.HTMLUtils.CallSuper( .ModHTMLPrefix('webnode') + 'llmastheadcomponent.html', _f, this, {guiComponents})`

    <!-- End File: rhcore/webnode/llmastheadcomponent.html -->
;;end

I’m using this setup to generate an “RHCore Developer Toolbar” (with quick links and fast user switching) and a friendly warning message if any database schema is out of sync. Here’s a screenshot:

I can’t think of a less intrusive way of doing this, and it’s not likely to cause problems if the original llmastheadcomponent.html ever gets changed.

Wrapping Up

My goal with any OScript project is to make my overrides as nonintrusive and as forward compatible as possible. While other methods exist to what I outlined here, I find myself going back to these tools more and more often.

Questions or comments about what you read here? Please leave a comment below.

Need help developing for Content Server or interested in using RHCore? Contact me at cmeyer@rhouse.ch.

  12 Responses to “Part IX: Making “super” calls in OpenText Content Server”

  1. I always learn something when reading your posts, so definitely worth the read. I confess I was not aware of the “super” keyword as we have been using an alternative method (thank you Chris Webster!) which promotes the same idea. Another huge benefit to this methodology is that more than 1 module can “override” the same script without stepping on each other.

    One question, do patches have any affect on this? I ask because I have seen patches trump overrides before.

    Thanks for taking the time to educate us and keep it coming!

    • Hi John, thanks for your comment. I’m aware and a fan of Chris Webster’s “override parent” script. I use something similar in some of my work. It’s useful when you want to override a script in place and change its default behaviour. I guess the super keyword is more meant for subclasses.

      Patches won’t cause problems. The reason you’ve seen conflicts is because patching occurs after all __Init() scripts are called (where you likely have the override parent call). Since the super call doesn’t do anything with __Init there won’t be this problem.

      Could some of this be useful in your work?

      • I kind of took the article to mean that super is just another way of calling OS.Parent but now I am not so sure. Is it only to be used in a subclass “environment”?

        And unfortunately (for me) I am a “content server administrator” these days so not much development going on. I still like to keep up with it though.

        • Hi John, I guess it can be used elsewhere as long as its understood how it works. The super keyword does more than call OS.Parent. Let me illustrate: Say you have objects A, B, and C. A is the parent of B and B is the parent of C. On A you have a function A.funcName(). On B you have an override of this function, which uses the super keyword. On C you have no override of the function and it’s inherited from B. Now, if you call C.funcName() the super keyword must return A and not B since that is the class with the parent function. Weird, huh? The super keyword is aware of what function it’s in and uses this to traverse up the structure to return the appropriate ancestor. Does this help?

  2. Very nice series.Please keep it coming

    • Thanks Appu. I’m trying to decide what to write about next. Tell me, from what you’ve read so far: Do you get a bigger picture sense of what I’m trying to do with the various components and see how it all fits together? Thanks.

  3. Although super is a keyword in earlier versions of Content Server, it doesn’t appear to be implemented until 10.5. Is this what you found as well?

    • Hi Evan, I’m not sure I understand. Are you saying the super keyword was reserved but didn’t do anything? I haven’t found that to be the case: I’m working with CS10 and super works as expected. What are you seeing?

      • Hi Chris,

        Yes, I had noticed the super keyword a long time ago but never really investigated. I was interested by your post so I tried it out. I created a child object under some code I was currently using, overrode a script feature that was overridden at a couple levels, and super is undefined. I have since tried a couple different scenarios – I tried on a child of a Ospace Root (no orphans), and I tried orphans. I tested the same code on CS10.5 and it performs as you describe it. On which version of Content Server have you tested this? I was using CS10 SP 2 update 13. I ran the same tests on LL9.5, LL9.7.0, LL9.7.1, which all reported IsUndefined(super) == true.

        • Hi Evan, I can confirm this exists and works in LL971. Keep in mind the super keyword is only defined when the running script has a this context. For example, calling IsUndefined(super) in a Builder script window will return true (since this is not defined here).

          Something else: In Builder you can run an open script with CTRL-R. However, doing so doesn’t provide a this context and will therefore fail. It’s only when you right-click on the feature in the right pane and choose ‘Run Script’ that this is defined (and hence super is as well). Perhaps you ran into this?

          If you’re curious, I’ve posted my custom super function on GitHub as a Gist. This has the advantage of supporting frames (assuming a single “parent”) and works as such:

          $YourModule.Utils.super(this, 'functionName')(arg1, ..., argN)
          

          It doesn’t work with multiple super calls, but it’s something I hope to support some day.

          • Interesting — you’re right! — it did work when I ran it (Ctrl+R) from the OSpace window, but not because this wasn’t defined. this was definitely defined (as I could echo it out). I knew this would be undefined if I had created a new script that wasn’t a feature of an object, but I was using an object’s feature.

            The real reason is because of the way the function is called when you run Ctrl+R from the script editor. Look at the Cmd-Run script in Builder.ScriptEditor — it calls the function this way:

            function nodebug Run( ... )
            dynamic x = .Compile( true )

            if Type( x ) == ScriptType
            .fObject.( x )()
            end

            end

            In this case, x contains the actual script. Contrast this with how the script is run through the OSpace browser:

            function nodebug Run( ... )
            dynamic val
            string feature

            for feature in .tagFeatures.pSelection
            val = .fObject.( feature )

            if IsInvokable( val )
            echo( .fObject.( feature )() )
            end
            end

            end

            Here, the feature variable contains the name of the feature. To sort all this out, I created a simple test:

            Create a new ospace called “Test”
            Create a child object under the root called ChildObj and add it to the globals for $Test.
            Define a function on the root called TestFunc and have it Echo(super) and Echo(this)
            Optionally override TestFunc on the child.

            <

            p>Next, run this script:

            Function Test()
            Script fn = $Test.ChildObj.TestFunc

            // method 1
            $Test.ChildObj.( "TestFunc" )()

            // method 2
            $Test.ChildObj.TestFunc()

            // method 3
            $Test.ChildObj.( fn )()

            // method 4
            fn()

            End

            I get these results:

            this: #9b000105
            super: #0

            this: #9b000105
            super: #0

            this: #9b000105
            super: ?

            this: ?
            super: ?

            It seems pretty clear that when running object.( nameOfFeature )(), this is set to object, and super is correctly set. However, when running object.( scriptValue )(), this is set to object but super is not identified.

            All four calls use the same OScript VM instruction CALL_METHOD. The first two calls are identical: They load ChildObj and “Test” onto the stack, then execute CALL_METHOD. The third call method loads ChildObj, then loads the Script onto the stack, then executes CALL_METHOD. The fourth method loads the current value of this onto the stack (which might be undefined), then loads the script value, then executes CALL_METHOD.

            It’s interesting — if you create another script feature on ChildObj containing our code from TestFunc, the last two call methods behave identically — both have this defined, but neither can resolve super.

            Super is implemented using the LOAD_SUPER instruction. Something in the CALL_METHOD instruction must set the name of the feature being called, which allows LOAD_SUPER to later resolve the value of super. Actually, come to think of it, the name of the feature could be identified since it remains in a known position on the stack (it’s the last value on the stack before the current frame).

            Anyway, this is all quite interesting. Thanks for your posts — they’re always a good read.

          • You’re right! The this keyword is defined when running CTRL-R from a script window (I don’t know why I thought that). But I guess it does make sense that super is undefined when this is something other than the script owner (i.e., the object on which the script is defined). In that case what would super really mean? Thanks for the comment: It clarifies the behaviour to me. Now to see how CS10.5 does this… 🙂

 Leave a Reply

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)