Archive for May, 2009

when creating a new workflow, I use the following as a checklist in order to achieve the best quality:

  1. create the new workflow.
  2. modify the name of the workflow.
  3. modify the brief description of the workflow.
  4. place the start event on the canvas
  5. place the end event on the canvas
  6. modify the start event name to “Start”
  7. modify the end event name to “End”
  8. ask myself what is the minimum required information needed to complete the workflow. Each will become a variable.
  9. ask myself which of those variables will be free text (configurable variables) or drop-down list selections (datasource variables).
  10. create the datasources if necessary.
  11. create the datasource global variables.
  12. create the configurable global variables
  13. add the tasks in the workflow. For each one:
    1. modify the name of the task
    2. modify the brief description of the task
    3. modify all the relevant properties to the task. If this is a URLExternal task, develop the underlying class.
    4. link the task. For each link
      1. modify the name of the link.
        Hint: never use “go to …” as it can scare users and can also change. Use “… done”.

This is a 4 step process.

  1. We need to define a custom operation that points to the workflow, 
  2. We use the operation in the menu so that users can do the invoking.
  3. We make the toolkit so that teamsite accepts our new operation
  4. We grant people the right to use our operation

 Defining a custom operation

In the teamsite/local/config/lib/content_center/customer_src/etc/conf/customer folder, the file userops_custom.xml is the one we want to modify.

<custom-operations>
<operation description="Calls the workflow to create a new intranet site" display-name="New Site (Intranet)" id="acme.newsite.intranet" />
</custom-operations>

The id must be unique throughout the system. The operation itself does not do anything, it is used later to find who’s allowed to use the action before the action is checked from the link.

Modifying the menu

In order to modify a menu, you need to find where to place it. For that, navigate to the application folder teamsite/httpd/webapps/content_center/WEB-INF/conf/ccpro and view the file ui_common.xml

In this example, we want to create a link below the File > New Site menu option. Looking at the link when we hover over it gives us the “iw.livesite.ccpro.create_site” command. Now we know that, we can find the file this menu has been created in with this little script:

command="iw.livesite.ccpro.create_site"
for file in `find teamsite/local/config/lib/content_center/reference/etc/conf/ -name ui*.xml 2>/dev/null`
 do
  echo ${file}
  cat ${file} | grep ${command}
 done

Open the file given as containing the command (and therefore the link using it) and find the command again.
We need to customise our customisation file called ui_custom.xml with the containing element (and all its parent hierarchy to the action list).

<iwov-ui>
    <action-list id="iw.ccpro.filesys.menubar">
        <menu id="iw.ccpro.file.menu">
        </menu>
    </action>
</iwov-ui>

We need to insert a menu item, below the link to create a new site.

<iwov-ui>
    <action-list id="iw.ccpro.filesys.menubar">
        <menu id="iw.ccpro.file.menu">
            <iwov-insert-after id="iw.livesite.ccpro.file_menu.create_site.link">
                 <link id="acme.file.newsite.intranet"
                        label="New Site (Intranet)"
                        description="Creates a new intranet site"
                        url="/iw-cc/command/iw.ccpro.new_job?iw_template_file=intranet_new_site&amp;iw_template_name=intranet_new_site&amp;workflowType.flag=true"
                        target="_blank"
                        operationID="acme.newsite.intranet"/>
            </iwov-insert-after>
        </menu>
    </action>
</iwov-ui>

Making the toolkit

Navigate to teamsite/local/config/lib/content_center/customer_src and issue the following command as root

./make_toolkit.sh

Granting rights

 Now we’ve done all the hard work, All we need to do is give the operation to one of the roles. For example, admin. On the administration tab, select the manage roles and expand the custom operations part for the admin role.

In order to find the class behind a command, get the command id (e.g. iw.teamsite.create_group), initialise a variable named command with this value:

command="iw.teamsite.create_group"

And run the following code on it:

 for file in `find /apps/interwoven/teamsite/httpd/webapps/content_center/WEB-INF/conf -name commands.xml 2>/dev/null`
 do
  echo ${file}
  cat ${file} | grep ${command}
 done

Totally undocumented anywhere, you can also navigate to the following url and search for the text of the command: http://teamsite.acme.com/iw-cc/base/support/show_command_desc.jsp

If you want to create a new URLExternalTask class, here’s a skeleton that can be re-used. It allows you to run an test the class before having to rebuild the toolkits, therefore reducing the interferences (and also the build times). It also lets you control the workflow when errors have been intercepted and redirect the flow to an error handling task.

/*
 * Author: Laurent Picquet
 */
package com.acme.workflow;

import com.interwoven.cssdk.access.CSAuthenticationException;
import com.interwoven.cssdk.common.CSClient;
import com.interwoven.cssdk.common.CSException;
import com.interwoven.cssdk.common.CSRemoteException;
import com.interwoven.cssdk.factory.CSLocalFactory;
import com.interwoven.cssdk.filesys.CSFile;
import com.interwoven.cssdk.filesys.CSVPath;
import com.interwoven.cssdk.workflow.CSExternalTask;
import com.interwoven.cssdk.workflow.CSURLExternalTask;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Laurent Picquet
 */
public class SkeletonTask implements CSURLExternalTask {

 private CSClient client = null;
 private CSExternalTask task = null;
 private CSFile[] attachedCSFiles=new CSFile[0];
 private StringBuilder executionLog = null;
 private String transitionLinkName = "";
 private static String KEY_ONSUCCESS_TASK_VARIABLE = "onSucess";
 private static String KEY_ONFAILURE_TASK_VARIABLE = "onFailure";

 public void execute(CSClient client, CSExternalTask task, Hashtable params) throws CSException {
 this.client = client;
 this.task=task;
 attachedCSFiles=task.getArea().getFiles(task.getFiles());
 try {
 execute();
 transitionLinkName = task.getVariable(KEY_ONSUCCESS_TASK_VARIABLE);
 } catch (Exception e) {
 transitionLinkName = task.getVariable(KEY_ONFAILURE_TASK_VARIABLE);
 e.printStackTrace();
 } finally {
 if (transitionLinkName == null) {
 transitionLinkName = "";
 }
 task.chooseTransition(transitionLinkName, executionLog.toString());
 }
 }

 private void execute() {
 executionLog = new StringBuilder();
 executionLog.append("SkeletonTask begin execute\r\n");
 /* do the work here. */
 executionLog.append("SkeletonTask end execute\r\n");
 }

 public static void main(String args[]) throws CSException {
 String configFilePath = "/apps/interwoven/teamsite/cssdk/cssdk.cfg";
 Properties localProperties = new Properties();
 localProperties.setProperty("cssdk.cfg.path", configFilePath);
 CSLocalFactory csLocalFactory = (CSLocalFactory) CSLocalFactory.getFactory(localProperties);
 SkeletonTask task = new SkeletonTask();
 try {
 task.client = csLocalFactory.getClientForCurrentUser(Locale.ENGLISH, "SkeletonTask", "localhost");
 } catch (CSAuthenticationException ex) {
 Logger.getLogger(SkeletonTask.class.getName()).log(Level.SEVERE, null, ex);
 } catch (CSRemoteException ex) {
 Logger.getLogger(SkeletonTask.class.getName()).log(Level.SEVERE, null, ex);
 } catch (CSException ex) {
 Logger.getLogger(SkeletonTask.class.getName()).log(Level.SEVERE, null, ex);
 }

 if (task.client != null) {
 System.out.println("connected to TeamSite.");
 task.attachedCSFiles=new CSFile[1];
 task.attachedCSFiles[0]=task.client.getFile(new CSVPath("/default/main/acme/WORKAREA/acme/sites/acme/default.site"));
 task.execute();
 }
 }
}

More often than not, you have email addresses to validate. Here’s a Perl regular expression that should do it nice and easy for you.

^(\w|\-|\_|\.|\'|\#|\$|\*|\/|\^|\!|\%|\=|\||\~|\{|\})+\@((\w|\-|\_)+\.)+[a-zA-Z]{2,}$

This can be used in a data capture template to validate the email address

<item name="email" pathid="email">
    <label>Email address</label>
    <description>Please enter a valid email address (e.g. john.doe@acme.com)</description>
    <text validation-regex="^(\w|\-|\_|\.|\'|\#|\$|\*|\/|\^|\!|\%|\=|\||\~|\{|\})+\@((\w|\-|\_)+\.)+[a-zA-Z]{2,}$">
        <default>john.doe@acme.com</default>
    </text>
</item>