7ContentScript and ScriptServer

ContentScript and ScriptServer sound similar, but they are two independent concepts that combined form a powerful tool. From the user's perspective the difference is hardly noticeable, so this collaboration is summarized under the term ContentScript. For programmers, however, the concepts must be understood and used separately.

ContentScript

ContentScript refers to a group of classes that can automatically make changes to documents in the CMS, just as an editor would do with their rights in cmsWorks editors desktop.

Basically the following classes are used:

app.cmsworks.cms.contentscript.ContentScript

contains all static access to ContentScript actions

app.cmsworks.cms.contentscript.ContentScriptManager

collects all Tasks in creation order to execute them with doTasks(). In case of an exception in one Task the previous task are called to rollback their actions in reverse order. This creates a transaction context over all steps of the overall task.

app.cmsworks.cms.contentscript.ContentScriptResource

represents a resource (a document) that was found or will be created when executing the script

app.cmsworks.cms.contentscript.ContentScriptFolder

represents a folder that was found or will be created when executing the script

app.cmsworks.cms.contentscript.ContentScriptResourceChange

represents a task to change the properties of a document

Let's assume there is a news project with articles and a category tree. A new article shall be created using a ContentScript. This article should be assigned to a category. In the category, there is a main entry that links to the main page of the category. In the folder where the main page is located, the article should be created in a year/month subfolder.

So if the document ID of the category is available in "categoryId" and the name of the article document is available in "articleDocumentName", the ContentScript looks like this:

// Create a DocumentModel from categoryId
DocumentModel dmCategory = new DocumentModel(categoryId, getScriptServer(), getScriptServer().getUrlCreator(), new Types());
// Get a CategoryModel from DocumentModel
CategoryModel cmCategory = new CategoryModel(dmCategory);
// Read the folder path of the main entry from the category
String folderPath = cmCategory.getMain().getPathOfFolder();
// Add the Year/Month subfolder from NOW
Calendar cal = Calendar.getInstance();
folderPath += "/" + cal.get(Calendar.YEAR) + "/" + cal.get(Calendar.MONTH);

// Create a new ContentScriptManager
ContentScriptManager csm = ContentScript.createManager(getCMS(), getUserId());
// Add the task to find or create the full folder path
ContentScriptFolder csrFolder = ContentScript.getOrCreateFolder(csm, folderPath);
// Add the task to create a document in the given folder with the given document name
// of the given document type RT_ARTICLE
ContentScriptResource csrArticle = ContentScript.createResource(csm, csrFolder, articleDocumentName, Types.RT_ARTICLE);
// Add the task to fill properties into the article
ContentScriptResourceChange propsArticle = ContentScript.changeResource(csm, csrArticle);
// Add a task to get the document for the categoryId
ContentScriptResource csrCategory = ContentScript.getResource(csm, categoryId);
// Fill the link to the category into the article property PT_CATEGORY
propsArticle.setLink(Types.PT_CATEGORY, csrCategory);

// execute the tasks and rollback automatically if a task could not be executed
csm.doTasksAuto();

The only ContentScript code for creating a new Article document

As described above, the category ID and document name must be provided in order to execute this ContentScript. This is done in the ScriptServer script (as shown in the full script example in the ScriptServer section of this chapter).

ContentScript tasks

ContentScript contains the following actions (tasks):

createManager(cms, userid)

creating a Manager to collect all tasks for a rollbackable run

getOrCreateFolder(csm, path)

creating the folder hierarchy of the given path if it's not existing yet

getFolder(csm, folder id)

direcly accessing an existing folder

getOrCreateSubFolder(csm, ContentScriptFolder, name)

creates a subfolder with the given name under a folder that has to be represented by a ContentScriptFolder. This way the ContentScriptFolder can be existing or there is the task to create it in the run just before this task is executed

getResource(csm, document id)

directly accessing an existing document

getOrCreateResource(csm, ContentScriptFolder, name, document type id)

either this creates the document in the given folder or it finds the resource there also checking that the resource type is correct.

createResource(csm, ContentScriptFolder, name, document type id)

explicitly creating a new document with the name of the document type in the folder.

changeResource(csm, ContentScriptResource)

the resource to be changed is represented gy a ContentScriptResource because it's either an already existing document or a document that is created prior to this task in the tasklist of the ContentScriptManager.

Document properties tasks

To handle properties of different types for a document in a ContentScriptResourceChange use

clearProperties()

empties the document. All property contents are deleted.

addProperty(propertyName, value)

setting a value to the property with the property name. Use the following java types

String

to fill a property of property type String

StringBuffer

to fill a property of property type Text

Integer

to fill a property of property type Integer

Date

to fill a property of property type Date

AbstractProperty

created with a mimetype to fill a property of property type Blob

removeProperty(String propertyName)

remove the content of a single property

addLink(propertyName, ContentScriptResource, optional pos)

adding a link to a linklist property. The linked resource is represented by the ContentScriptResource

setLink(propertyName, ContentScriptResource, optional pos)

adding a link to a linklist property only if the targeted resource is not linked already

removeLink(propertyName, pos)

remove a link from a linklist property by position

removeLink(propertyName, ContentScriptResource)

remove a link from a linklist by the link target

removeAllLinks(propertyName)

removes all links from the linklist property

approve()

After all properties are changed the document can be approved with this call.

Failure scenarios

Assuming the ContentScript shall create 3 documents and a first document was created but the second document cannot be created because there already is a document with the name existing the rollback will delete the first created document.

Assuming the ContentScript shall create documents but the user calling the ContentScript has not the right to create new documents from the user rights managemet the script will abort and rollback any actions that may have been done before.

ScriptServer

ScriptServer is a JSP container service similar to a Generator service. In addition to executing JSPs in an htdocs folder, this service also manages scripts that are called from the cmsWorks editors desktop. Such a script can display input fields in the ContentScript window in the editors desktop, for example, an input field for selecting a category and an input field for the article name, and wait for user input. When the user clicks "Continue," the script reads the responses from the input fields and execute the actual ContentScript.

A script performs a rather specific task in the context of a document structure as well as in the context of document types in a project. That's why scripts are placed in a folder /htdocs/scriptserver/[projectname] within the cmsWorks installation.

The projectname entry is read from the cms.properties configuration from entry /app/cmsworks/service/cms/CMSCore/Customer.

The base of a script handled by the ScriptServer is the following code:

<%@page import="com.topasworks.kernel.server.ServerManager,
                com.topasworks.kernel.service.ServiceNotAvailableException,

                app.cmsworks.service.contentscript.ScriptServer,
                app.cmsworks.service.contentscript.AbstractJspScript,

                app.cmsworks.util.lang.DataMessage
               " 
       session="false"
       contentType="text/plain; charset=UTF-8"
%><%!
/** This script will be created and registered into the ScriptServer
 */
public class MyScript extends AbstractJspScript {
  
  /** Constructor giving the scriptServer and the request to read the userId 
   *  and any available script inti parameters from
   *  The script was called with the id of the user that is logged in into the cmsWorks editors desktop
   */
  public MyScript(ScriptServer scriptServer, ServletRequest request) throws ServiceNotAvailableException {
    super(scriptServer, request);
  }
  
  /** This name shows in the entries of the threadpool of the ScriptServer service
   */
  public DataMessage getName() {
    return new DataMessage("The basic script");
  }

  /** Here is defined what the script acutally does.
   */
  public void execute() throws Exception {
    // if the desktop user clicks on abort a cancel flag is set into the Script.
    // if this method is called and the cancel flag is set a ScriptCanceledExcpetion will 
    // be thrown to leave any further execution of the script.
    // so place this check at any point where no resources are harmed or any transaction is completed
    // at least after every waitForAnswers() call.
    checkCancelState();
  }
}

%><%
  try {
    // Fetch the name of the ScriptServer service from the Attribute
    String myServiceName = request.getAttribute(app.cmsworks.util.RequestAttrConstants.CW_REQ_ATTR_SERVICE_NAME).toString();
    // Get the ScriptServer service from the ServerManager
    ScriptServer scriptServer = (ScriptServer) ServerManager.getBaseManager().getProxy(myServiceName);
    // Create the Script
    MyScript script = new MyScript(scriptServer, request);
    // Give it to the ScriptServerService to be executed
    int scriptId = scriptServer.runScript(script);
    // return the Script-ID for the editors desktop so it can manage to fetch state updates and transfer user inputs
    out.print(scriptId);
  }
  catch(Throwable t) {
    // any exception appearing while initiating the script is shown in the editors desktop to give the developer a hint.
    t.printStackTrace(new java.io.PrintWriter(out));
  }
%>

Basic script in a ScriptServer for instance in a scriptbase.jsp

A ScriptServer script is working in the context. The following contexts are available:

  • A document of a specific document type
  • A document from a list of specific types
  • Any document
  • A folder
  • No context meaning "global"

To register a script to a context in the cmsWorks editors desktop edit the desktop configuration. Any change in this configuration is active from the next login. Script JSPs always are named script[name].jsp. But in the when configuring the editors desktop refer to this script only by [name].

Full script example

Filling the method execute with

  • adding the input fields for a category selection and the article document name
  • waiting for the user input
  • reading the field inputs
  • performing the ContentScript
  • opening the created Article document
  • closing the ContentScript window

will complete the example.

<%@page import="com.topasworks.kernel.server.ServerManager,
                com.topasworks.kernel.service.ServiceNotAvailableException,

                app.cmsworks.service.contentscript.ScriptServer,
                app.cmsworks.service.contentscript.AbstractJspScript,
                app.cmsworks.service.contentscript.ScriptQuestion,

                app.cmsworks.cms.contentscript.ContentScript,
                app.cmsworks.cms.contentscript.ContentScriptFolder,
                app.cmsworks.cms.contentscript.ContentScriptResource,
                app.cmsworks.cms.contentscript.ContentScriptResourceChange,
                app.cmsworks.cms.contentscript.ContentScriptManager,

                app.cmsworks.cms.document.CategoryModel,
                app.cmsworks.cms.document.DocumentModel,

                app.cmsworks.util.lang.DataMessage,

                java.util.Calendar
               " 
       session="false"
       contentType="text/plain; charset=UTF-8"
%><%@include file="includes/documentmodel.jsf" 
%><%!
public class MyScript extends AbstractJspScript {

  public MyScript(ScriptServer scriptServer, ServletRequest request) throws ServiceNotAvailableException {
    super(scriptServer, request);
  }

  public DataMessage getName() {
    return new DataMessage("Create Article");
  }

  public void execute() throws Exception {
    checkCancelState();

    // just an explaining text for the editor
    createLabel(new DataMessage("In order to create a new article " + 
        "please select a category and insert a document name.", DataMessage.NO_TRANSLATION));

    // create the category selection input element
    ScriptQuestion qCategoryId = createQuestion(QT_CATEGORY, new DataMessage("Category", DataMessage.NO_TRANSLATION));

    // create the document name input field for the article document to create
    ScriptQuestion qArticleDocumentName = createQuestion(QT_RESOURCENAME,  
        new DataMessage("Article document name", DataMessage.NO_TRANSLATION));
    
    // Wait for the editors input (this automatically checks the cancel state so no extra call is needed)
    waitForAnswers();
    
    // Read the editors input
    int categoryId = Integer.parseInt(qCategoryId.getAnswer());
    String articleDocumentName = qArticleDocumentName.getAnswer();

    // Create a DocumentModel from categoryId
    DocumentModel dmCategory = new DocumentModel(categoryId, getScriptServer(), 
        getScriptServer().getUrlCreator(), new Types());
    // Get a CategoryModel from DocumentModel
    CategoryModel cmCategory = new CategoryModel(dmCategory);

    // Read the folder path of the main entry from the category
    String folderPath = cmCategory.getMain().getPathOfFolder();
    // Add the Year/Month subfolder from NOW
    Calendar cal = Calendar.getInstance();
    folderPath += "/" + cal.get(Calendar.YEAR) + "/" + cal.get(Calendar.MONTH);
    
    // Create a new ContentScriptManager
    ContentScriptManager csm = ContentScript.createManager(getCMS(), getUserId());
    // Add the task to find or create the full folder path
    ContentScriptFolder csrFolder = ContentScript.getOrCreateFolder(csm, folderPath);
    // Add the task to create a document in the given folder with the given document name
    // of the given document type RT_ARTICLE
    ContentScriptResource csrArticle = ContentScript.createResource(csm, csrFolder, articleDocumentName, Types.RT_ARTICLE);
    // Add the task to fill properties into the article
    ContentScriptResourceChange propsArticle = ContentScript.changeResource(csm, csrArticle);
    // Add a task to get the document for the categoryId
    ContentScriptResource csrCategory = ContentScript.getResource(csm, categoryId);
    // Fill the link to the category into the article property PT_CATEGORY
    propsArticle.setLink(Types.PT_CATEGORY, csrCategory);

    // execute the ContentScript
    csm.doTasksAuto();

    // finalize the script opening the created article document
    addResultCommandOpenResource(csrArticle.getResource().getId());
    // close the ContentScript window
    addResultCommandCloseScript();
  }
}

%><%
  try {
    String myServiceName = request.getAttribute(app.cmsworks.util.RequestAttrConstants.CW_REQ_ATTR_SERVICE_NAME).toString();
    ScriptServer scriptServer = (ScriptServer) ServerManager.getBaseManager().getProxy(myServiceName);
    MyScript script = new MyScript(scriptServer, request);
    int scriptId = scriptServer.runScript(script);
    out.print(scriptId);
  }
  catch(Throwable t) {
    t.printStackTrace(new java.io.PrintWriter(out));
  }
%>

The full script for the ContentScript "Create article" in scriptcreatearticle.jsp

The document model

For dealing with document types and properties for the website generation it is advised to create a documentmodel.jsf. The document types are content of the database and are the same for Generator services as well as for the ScriptServer service (Find a filled example in the Navigation documentation). So simply copy the documentmodel.jsf into the scripts folder to include it into the scripts like it's contained in the example above. Make sure that when actual document types are changed or added to make the adjustments in the documentmodel.jsf in all Generators htdocs folder and in the ScriptServers htdocs folder.

Strategy

The Script in a ScriptServer has to be programmed like a wizard. Put input elements on the first page. After a click on "Continue" read the inputs and decide if further inputs are necessary. Eventually a second page with input fields are necessary.

As an example the could be a checkbox asking if for the article to be created also a picture should be created. Only if checked on a second page a mandatory input element for the document name of the medium document is presented.

Interactions between input elements within one page is not supported.

Script features

The Script of a ScriptServer has several features extending the normal work with documents and folders in the cmsWorks editors desktop.

Context resources

If the script works in the context of documents or folders use

int[] ids = toIdList(getScriptInitParameter("contextIds"));

to get the list of ids of documents or folders the script was started with.

Sometimes it is necessary to force the script execution to work in the context of only one document. The following code is placed if that is the case.

int[] ids = toIdList(getScriptInitParameter("contextIds"));
if (ids.length > 1) {
addResultMessage("Just start this script in the context of one chapter only.");
return;
}

Input elements

An input element for the Script of the ScriptServer is created with

createQuestion(question type, label, optional default value);

This creates a mandatory input element. If the field is empty and the editor clicks on "Continue" The validator will mark this field as invalid. On the programmers side this means the field value cannot be empty.

createOptionalQuestion(question type, label, optional default value);

This creates an optional input element. On the programmers side this means the field value may be empty.

createLabel(text)

Explain the effects of the script or what to fill into the input fields. It's possible to send HTML code with this to enhance the presentation in the ContentScript Window.

createSeparator();

just for grouping input fields

The following question types (input element types) are available:

QT_STRING

for a string input field

QT_TEXT

for a richtext input field

QT_CATEGORY

for a category selection input field

QT_INT

for a integer value input field

QT_DATE

for a date input field

QT_RESOURCENAME

for a string input field checking for resource naming rules

QT_CHECKBOX

for a checkbox input field

QT_RADIOBUTTON

for a radiobutton input field

QT_RESOURCES

for a linklist input field

QT_COMBOBOX

for a selectbox/combobox input field

Some special input fields can be created with

createCheckbox(label, boolean)

creating a checkbox element beeing initially checked or not

createCombobox(label, optional default value)

creating a dropdown defining the option to be selected initially

addComboboxOption(combobox question, value, view value)

adding an option to a select box / combo box defining the shown entry assigned to the real value to be returned

createComboboxEditable(label, optional default value)

creating a combo box as an input field with suggestions but freely editable

Results

addResultMessage(message)

add a single result message at the end of the script or maybe while looping through multiple tasks collect result messages on the fly.

addResultResource(resource or id)

if documents shall be listed as the result of the script simply add them while handling them

addResultCommandOpenResource(document id)

directly open a document from script. It could be called multiple times with different documents.

addResultCommandCloseScript()

closes the ContentScript Window. Any other results would not be shown.

setStateMessage(message text)

when performing a lot of tasks which takes a noticeably amount of time provide a state information about the progress with this call.

DataMessage

When placing a label or explanatory text into the script DataMessage provides the possibility to translate the texts for the language configured to the editors desktop user. DataMessage separates the phrases to be translated from the data. To provide translated texts use the Translation tool in the ADM-Tools creating new entries for the current project only.

Example showing progress of the ContentScript:

int current = 3;
int all = 20;
String msg = "Handling " + DataMessage.DATA + " of " + DataMessage.DATA + ".";
DataMessage dm = new DataMessage(msg, Integer.valueOf(current), Integer.valueOf(all));
setStateMessage(dm);

If translations are not relevant use DataMessage with DataMessage.NO_TRANSLATION like in the example above.

Summary

ContentScripts are helping to establish workflows for content creation. For example when a component should be created for a Homepage the task contains to create one Container of the specific type to define the layout, one or more Reference documents containing title, text, and links, one Medium document with a picture. These steps can be combined in a ContentScript presenting all necessary input fields in one view but creating multiple documents of different types filled with the correct inputs which are also stored in a specified folder structure.

ContentScript is the primary tool for importing information from others sources into cmsWorks documents. Let's say there is a feed from an external source whose data is to be used in the CMS spreading the content in document structures. ContentScript was created to integrate these data into your own website to allow you to use your own layout and work processes for editing, enrichment, or linking between these contents. Writing a ContentScript is the solution for automating repetitive tasks while reducing input errors.