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.
