Skip to Content

SAP Knowledge Management as a web publishing platform lacks, in my opinion, something that is available on most web servers: Server Side Scripting.  Imagine you could easily add a hit counter display image to a KM managed page.  Or the name of the last modifier of a document image.  Or the last modified date.  Or, in a versioned document, a link list of all previous versions of that document. image Or you could include content from other resources at certain places, to have a common, single location for the header and footer parts of your documents.  Or create an overview document that lists all documents from the parent collection of that overview document.  In a more detailed implementation,  that page could generate an rss feed image.   Or you could easily insert a link that leads to the resource comments page. And so on and so on.  The possibilities are limited only by your imagination (and some technical restrictions ;)).

   To achieve a kind of server side scripting in KM,  I wrote a Content Filter that parses out special tags in the document content.   These tags point to groovy script files that are situated in an additional KM repository as text files.  When the filter encounters one of the tags,  it looks it up in the script repository   and executes it right at the place where the tag was placed in the document. And of course, the script has the chance to write something at that place in the document.  Implementing block style scripts or passing variables between scripts, like in Java Server Pages Taglets, is also possible.  However, to allow for a little security administration, scripting inside the documents is not possible,  ie. only scripts that are contained that are contained in the script repository can be used.  This way, the administrator can restrict the use of scripts by adjusting the Access Control Lists of that script resource.       For interpreting the scripts, the Groovy Scripting engine is used in the filter.  Groovy is a Java Scripting Language which has the potential to become a standard in the Java world.  It is also filed as Java Specification Request 241

 

Details

The syntax the filter parses for finding the script is the following:

Simple script calls:

$SCRIPTNAME$

  Will try to execute a script /scripts/SCRIPTNAME.txt from the repository 

Parametrized script calls:

$SCRIPTNAME(sometext,sometext)$

Will try to execute a script /scripts/SCRIPTNAME.txt from the repository.  The script has an implicit variable named param with the value sometext,sometext available. 

Block script calls:

$SCRIPTNAME(sometext,sometext){$
something else here, even other script calls.
$}$

Will first try to execute a script /scripts/SCRIPTNAME_BEGIN.txt from the repository.  If the /scripts/SCRIPTNAME_BEGIN.txt script returns 0, the contents inside the block will be executed/written to the output stream.  If the return value is 1, the content of the block is not executed/written.  After the content is executed/written, the filter will try to execute a script called /scripts/SCRIPTNAME_END.txt from the repository.   If the script /scripts/SCRIPTNAME_END.txt returns the value 1, the content after $SCRIPTNAME(sometext,sometext){$ will be executed/written again, and the /scripts/SCRIPTNAME_END.txt script is executed again.   If the return value is different, the block is considered to be finished. Both the /scripts/SCRIPTNAME_BEGIN.txt and /scripts/SCRIPTNAME_END.txt end scripts  have an implicit variable named param with the value sometext,sometext available. 

Implicit Variables

In the script coding, four variables are available without prior definition:

  • resource
    Every script has an implicit variable resource available, which is the IResource object in which the script is executed.
  • param:  An implicit variable param is available which holds the string content of the optional parameter parentheses  (eg. $SCRIPTNAME(sometext,sometext){$ will cause the param variable to evaluate to sometext,sometext).  If multiple parameters are required, parsing the commas etc. must be done inside the script. If there are no parentheses contained in the script call,  param will evaluate to null. 
  • vars: An implicit variable vars is available which evaluates to a java object of type java.util.Map.  The map is the same for every script executed in the resource call and can be used for passing values between scripts. Eg. consider a for each loop script that implements looping over the elements of a collection.  The script calls inside such a for each block need to have a reference to the  current iteration item. This is done using the vars map. 
  • out:
    A java.io.PrintWriter for writing the document content at that place.  Note that the Groovy print and println statements also write to  that PrintWriter object.  

 

Script Examples

In the following section, I’ll list some example scripts I wrote for testing, showcasing and playing around.  If you are not yet comfortable with the Groovy scripting language,  see the Getting Started Guide of Groovy.

Hit Counter

This script prints out a number indicating how often the resource has been displayed.  Of course we could rewrite the script so that it shows a cool image instead.

HITCOUNT

import com.sapportals.wcm.repository.*;

int retVal = 1;
IPropertyName name = new PropertyName("com.sap.sdn.armin.hitcounter", "hitcount");
IMutableProperty property = (IMutableProperty) resource.getProperty(name);
if (property == null)
  property = new MutableProperty(name, new Integer(1));
else {
  retVal = property.getIntValue() + 1;
  property.setIntValue(retVal);
}
resource.setProperty(property);
print retVal

If you put this script as /scripts/HITCOUNT.txt into your KM, you’ll be able to show the hitcount from any resource the GroovyFilter applies to by just adding $HITCOUNT$ to the resource’s content. The script will store the hit count of the resource in a special resource property called com.sap.sdn.armin.hitcounter.hitcount.

Last Modifier

This script prints out who has last modified the current resource. Or if you pass in a parameter, tries to retrieve  a resource from the variable space and shows who has last modified the denoted resource.

AUTHOR

def res = vars.get(param)
if(res==null || !(res instanceof com.sapportals.wcm.repository.IResource)) res = resource
print res.lastModifiedBy

Here you see how the vars and the param variables are used.  First a variable named res is defined which holds the value stored in  the variable space by the name that has been passed in to the script call. Eg., if the script was invoked with $AUTHOR(dingdong)$, the script would try to retrieve a variable called dingdong. If no resource is retrieved or the object retrieved is not an IResource object, the current resource is taken for the output. And the last line just prints out the toString() result of a call to res.getLastModifiedBy() 

For Each block

This is a block script, therefore we need two scripts, a _BEGIN script and an _END script.  In addition to that, the script block is able to loop not only over a java.util.Collection,  but also over a java.util.Iterator and a com.sapportals.wcm.repository.ICollection. It expects two parameters, separated by comma: 

      

  1. The name of the internal loop item,        ie. the name of the variable that holds the element of the collection        in each iteration.
  2.   

  3. The name of the variable that holds the collection object.

 

FOR_EACH_BEGIN

Note the if block at the end of the script: If the iterator does not have a single element,  the _BEGIN script will return 2 which means that the block content is skipped, ie. the block content is not executed/written at all.

import com.sapportals.wcm.repository.ICollection
def params = param.split(",")
def varname = params[0]
def col = vars.get(params[1])
def iterator = null
if(col instanceof Collection){
  iterator = col.iterator
} else if(col instanceof Iterator){
  iterator = col
} else if(col instanceof ICollection) {
  iterator = col.children.listIterator()
}

if(iterator.hasNext()){
  vars.put(varname, iterator.next())
  vars.put(varname+"_it", iterator)
  return 0
} else {
  return 2
}

FOR_EACH_END

And this is the _END script of the for each block.  It will try to retrieve the next element of the iterator and to repeat  the block if there is such an element.
 

def params = param.split(",")
varname = params[0]
iterator = vars.get(varname+"_it")
if(iterator.hasNext()){
  vars.put(varname, iterator.next())
  return 1
} else {
  return 0
}


Name of a resource

Prints out the name of a resource or the name of the current resource.

NAME

def res = vars.get(param)
if(res==null){
  res = resource
}
println res.name

The Parent Collection of the current resource as variable

This is more or less a helper script for other scripts which exposes the current collection as a variable.  It is rather short:

COL_AS_VAR

vars.put(param, resource.parentCollection)

Resource Content Example Using Scripts

   Using these scripts from above, you can script your resource in order to create a table of contents of the current collection:

$COL_AS_VAR(col)$

$FOR_EACH(currentResource,col){$
 
$}$
$NAME(currentResource)$$AUTHOR(currentResource)

This would create an HTML table, which lists the name and the last author of the collection of the current resource.  Hope you get the idea.

 

Download and Installation Manual

 

  1. Download the .par containing source and binaries from here.  Groovy is subject to license terms which can be found inside the .par file, and there inside the lib/groovy-all-1.0-JSR-06.jar file. 
  2. Unzip the .par file from the download and deploy the .par file containing the filter implementation to your portal, using the Portal Administration Console (available at http://yourhost:yourport/irj/servlet/prt/portal)
  3. Create the script repository: In the System Administration > System Configuration > Knowledge Management > Content Management > Repository Managers area,  create a new instance of the CM repository manager with the property prefix set to /scripts,  the Hide in Root Folder property unchecked and the Active property checked (the last two properties can be found under ‘Advanced Options’).
  4. Configure the repository filter: In the System Administration > System Configuration > Knowledge Management > Content Management > Repository Filters area,  configure the com.sap.sdn.armin.KMScripting filter: set the priority to some legal value (1, for example),  and select the documents repositry as the repository this filter applies to, or any other repository that seems convenient to you.
  5. Create your first script: Go to Content Administration > KM Content > scripts choose Folder > New > Text file… . In the dialog set HITCOUNT as the name and  paste the text from the Hit Counter example above into the text box (not into the description box ;-). Save it.
  6. Use the script you created: In the documents repository, edit a resource, and, at a proper place, add $HITCOUNT$ into the document. I mention the documents repository,  because I configured the filter for this repository, see the step above.   

 

License & Legal stuff

Just for the records, I feel the urge to mention that the .par provided for download here contains the groovy binary distribution groovy-all-1.0-JSR-06.jar.  Groovy is licensed under its own terms, however, the .jar contains a host of other software which is subject to license. For further investigation in that matter,  please download the file groovy-1.0-jsr-06.zip from the Groovy site distribution page.

License for my own coding. The code comes without any license.  It is neither guaranteed to work nor to be harmless to any part of your computer or software or network.  And in fact, it’s not ‘well implemented’ when it comes to function or performance. It’s just for getting a taste of what scripting in KM could do for you.  

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Markus Kohler
    Hi,
    Great post !
    I always wanted to give Groovy on the SAP WebAS a try, but feared that I would have to solve the deployment problem first.

    Now that you showed that it works, I will have to take a look at it really soon.

    Regards,
    Markus

    (0) 
  2. Anonymous
    Hey, this is a masterpiece which was really missing for a long time to make pages within SAP’s Enterprise Portal more dynamic! And it is so easy to extend the capabilities just by putting a new text script somewhere! Everyody should give it a try and submit new scripts to the community! I love it!

    Klaus 

    (0) 
  3. Sergey Zavarzin
    Hi,
    It really sounds like a great solution!
    But I tried to reproduce your example for my server but unfortunately, it doesn’t work on NW04s (filter doesn’t start). On NW04 I deployed and configured as you described but then I try to change some file to add $HITCOUNT$ string or just to upload some file, I get this filter error:  com.sap.sdn.armin.kmscripting returns null. Could you please suggest me idea why it happens so?

    Thanks+Regards
    Sergey

    (0) 

Leave a Reply