Content Management Guide  

Chapter 3   Managing Tasks in the Content Management Subsystem

This chapter explains how tasks work in the Content Management subsystem and describes how to write and implement a custom task. It contains the following sections:

 
Top of page

About tasks

A Director task is a background job or process that you can configure to run at specified times. Typically, a task carries out a specific content management operation, such as publishing documents.

Each Director task is of either of two types: periodic and scheduled. Periodic tasks are configured to run at regular intervals, specified in milliseconds. Scheduled tasks are configured to run at specific dates and times.

A task must be enabled before it can be used in a deployed Director application. A list of enabled tasks appears in the Task section of the PMC. You can start and stop the tasks that appear in this list while the application is running.

Installed tasks   The following tasks are installed with the Content Management subsystem:

Task name

Description

publish

Publishes a specified set of documents

expire

Expires a specified set of documents

janitor

Removes a specified set of documents

synch

Synchronizes content management data with the Search subsystem engine, which by default is based on the Autonomy Dynamic Reasoning Engine (DRE); updates to content management data are propagated to the DRE.

NOTE   The synch task only appears in the Task section when the Content Management subsystem's Search synchronization mode is set to batch. In immediate synchronization mode, the Content Management subsystem automatically performs search synchronization operations.

default

Included for debugging and demonstration purposes. This task is not automatically implemented in a deployed application.

These installed tasks are highly configurable and can be adjusted to meet the specific needs of your application. For example, you might provide a task such as the publisher or the janitor with a query that defines the scope of its operation. Such a query would specify the set of documents on which the task was to operate.

    See How tasks are registered and configured for information on which files you need to edit to reconfigure an installed task and Customizing an installed task for an example.

New custom tasks   You may not be able to meet the needs of some applications just by reconfiguring the installed tasks. In these cases you can also create new, application-specific tasks.

When you create a new task, you register its type, name, description, and configuration information. You create Java classes to provide the task's functionality and you register these classes.

    See How tasks are registered and configured for information on the files you need to edit to register and configure a new task, and to register the Java classes you create for it.

 
Top of page

How tasks are registered and configured

Tasks—and the Java classes associated with them—are registered and configured in three XML files in your project's library/ContentMgmtService/ContentMgmtService.spf/ContentMgmtService-conf directory:

XML file

What it does

tasktypes.xml

Establishes the names and descriptions of tasks and identifies them as periodic or scheduled

Default_tasklist.xml

Configures tasks

services.xml

Associates tasks (and other Director functions) with their respective Java classes

 
Top of section

tasktypes.xml

The entries in tasktypes.xml establish the name and description of each task and identify each task as either periodic or scheduled. The structure of this file must conform to framework-task-type_3_0.dtd in your project's library/FrameworkService/FrameworkService.spf/DTD directory.

Here is an excerpt from tasktypes.xml, showing how the file is structured and how the default, synch, and publish tasks are defined:

  <framework-task-types>
  <!-- PERIODIC TASK TYPES -->
     <periodic>
        <task-type>
           <type-name>default</type-name>
           <type-descr>The Default Periodic Task</type-descr>
        </task-type>
        <task-type>
           <type-name>synch</type-name>
           <type-descr>Periodic CM/Search Engine Synchronization Task</type-descr>
        </task-type>
        <task-type>
           <type-name>publish</type-name>
           <type-descr>Periodic Document Publish Task</type-descr>
        </task-type>
        ...
     </periodic>
  <!-- SCHEDULED TASK TYPES -->
     <scheduled>
     ...
     </scheduled>
  </framework-task-types> 

 
Top of section

Default_tasklist.xml

The entries in Default_tasklist.xml configure each task in conformance with contentmgmt-task-list_3_0.dtd in your project's library/ContentMgmtService/ContentMgmtService.spf/DTD directory.

Here is an excerpt from Default_tasklist.xml showing how the file is structured and how the periodic-publish task is configured:

  <contentmgmt-task-list>
     ...
     <periodic-publish>
        <task-name>Default Repository Document Publish</task-name>
        <description>The Default Repository Document Publish Task</description>
        <since-last>false</since-last>
        <enabled>true</enabled>
        <interval>
           <millis>86400000</millis>
           <exact>false</exact>
        </interval>
        <do-all-not-yet-published>false</do-all-not-yet-published>
        <do-all-unpublished>false</do-all-unpublished>
        <do-all-ready>false</do-all-ready>
        <force-publish>false</force-publish>
     </periodic-publish>
     ...
  </contentmgmt-task-list>

Naming convention   Note that the tag name for the periodic-publish task is constructed from its type (periodic) and its name (publish) as defined in tasktypes.xml, connected by a hyphen. This is a required naming convention.

Enabling a task   Also note that the task is enabled by setting the content of the <enabled> tag to true. To disable a task, you would set this value to false.

 
Top of section

services.xml

The services.xml file includes entries that associate tasks (and other Director functions) with their respective Java classes. This structure of this file must conform to framework-services_3_0.dtd in your project's library/FrameworkService/FrameworkService.spf/DTD directory.

Here is an excerpt from services.xml showing how the periodic-publish task is handled:

  <service>
  <interface>com.sssw.cm.periodic-publish</interface>
  <impl-class>com.sssw.cm.task.impl.EboDocPeriodicPublishTask</impl-class>
  <description>Periodic CM Document Publish Task</description>
  <max-instances>0</max-instances>
  <startup>M</startup>
  <namespaced>false</namespaced>
  </service>

Graphical view   Director also provides a graphical view of this file where you can add new entries.

New tasks only   You will need to add new entries to services.xml only if you create new tasks.

 
Top of page

Customizing an installed task

You customize an installed task by editing its configuration in the Default_tasklist.xml file.

In the following example, a document query has been added to the definition of the periodic-publish task. The query is specified in the <content-search> element.

The new code configures the periodic-publish task to publish all documents whose STATUS has been set to Reviewed.

  <periodic-publish>
     <task-name>Default Repository Document Publish</task-name>
     <description>The Default Repository Document Publish Task</description>
     <since-last>false</since-last>
     <enabled>true</enabled>
     <interval>
        <millis>86400000</millis>
        <exact>false</exact>
     </interval>
     <do-all-not-yet-published>false</do-all-not-yet-published>
     <do-all-unpublished>false</do-all-unpublished>
     <do-all-ready>false</do-all-ready>
     <force-publish>false</force-publish>
     <content-search>
        <where-clause>
           <eq>
              <var>STATUS</var>
              <val>Reviewed</val>
           </eq>
        </where-clause>
     </content-search>
  </periodic-publish>

    For a complete description of the elements and values you can use to construct a document query within a task's definition, see the definition of the <content-search> element in contentmgmt-task-list_3_0.dtd.

Need to redeploy   You must redeploy your application EAR before any task configuration changes will take effect.

 
Top of page

Creating and implementing a new task

The following procedure is based on the example of creating a new task named new-doc-notifier that checks for new documents and notifies a list of recipients about the new documents by e-mail.

To create and implement a new task:

  1. Register your task type

    To register the type of the new task, modify the tasktypes.xml file. You can register the task as scheduled, periodic, or both scheduled and periodic. In this example, the new task is periodic:

      <periodic> 
         ............. 
         <task-type> 
            <type-name>new-doc-notifier</type-name> 
            <type-descr>Periodic CM task for notifying of any new documents.</type-descr> 
         </task-type> 
    
  2. Register your task in the tasklist

    To register the task in the tasklist, add a new element to the Default_tasklist.xml file:

      <periodic-new-doc-notifier>
         <task-name>New Document Notifier</task-name>
         <description> Periodic CM task for notifying of any new documents.</description> 
         <since-last>false</since-last> 
         <enabled>true</enabled> 
         <interval> 
            <millis>86400000</millis> 
               <exact>false</exact> 
         </interval> 
         <!-- any other XML that is specific to the custom task goes here... -->
         <!-- for instance, there may be a node here defining the list of email recipients. -->
         <recipients>
            <recipient>user@myco.com</recipient>
            <recipient>user2@myco.com</recipient>
            <recipient>user3@myco.com</recipient>
         </recipients>
            <mail-smtp-host>smtp_host@myco.com</mail-smtp-host>
            <subject>New documents have been added</subject>
            <text>The following new documents have been added:</text>
      </periodic-new-doc-notifier>
    

    Note that the name of the XML tag surrounding the task definition (<periodic-new-doc-notifier>) must be constructed from the task's type (periodic or scheduled) and the task's name in Default_tasklist.xml. This naming pattern is required.

  3. Write Java classes for the new task

    The generic Director task management API is provided in the com.sssw.fw.task.api package. This package contains very general interfaces for tasks, task types, and task management:

    The Content Management subsystem subclasses those interfaces in its own task management package, com.sssw.cm.task.api. It provides its own EbiTask and EbiTaskManager along with EbiTaskMgmtDelegate, all three of which should be used for managing tasks. This package also contains generic interfaces for document publishing, expiration, removal, and synchronization between the Content Management subsystem and the Search subsystem engine.

    When writing your own custom task, you should implement one of the following interfaces:

    In the code for the new-doc-notifier example, the NewDocumentNotifier class extends com.sssw.cm.task.impl.EboTask and encapsulates the details of the task's duties and how they are carried out. The PeriodicNewDocumentNotifier class is the periodic subclass of the NewDocumentNotifier class.

        For a complete listing of the Java code for the new-doc-notifier example, see Custom task sample code.

  4. Register the new task's class

    To register the Java class for your new task, you add an entry to the services.xml file under <!-- Task management related objects -->:

      <!-- Periodic tasks --> 
      ........................ 
         <service> 
            <interface>com.myco.cmtask.api.periodic-new-doc-notifier</interface> 
            <impl-class>com.myco.cmtask.impl.PeriodicNewDocumentNotifier</impl-class> 
            <description>The periodic new document notifier class.</description> 
            <max-instances>0</max-instances> 
            <startup>M</startup> 
         </service> 
    

    Note that in order for the object to be factoried and instantiated correctly, the interface naming should correspond to the task kind and type. For example, periodic and new-doc-notifier map to periodic-new-doc-notifier in the <interface> node value.

  5. For your custom task to be loaded and instantiated correctly, you must:

    1. Place your custom task class or classes into a separate JAR.

    2. Add the JAR to your Director EAR.

    3. In the PMC WAR of your application, add the custom class JAR to the Class-Path section of the META-INF/MANIFEST.MF file.

      This ensures that class loading works correctly, and that the user of the PMC can manage the custom tasks in the Task section of the PMC.

  6. Build and deploy your application EAR

        To review the steps involved in building and deploying your application EAR, see the Quick Start.

  7. Start the task

    1. In a browser window, launch the PMC and log in.

    2. Click the Tasks button.

    3. In the Tasks Pane, click to select your task, then click the Start button.

 
Top of page

Custom task sample code

This section provides a listing of the Java code for the NewDocumentNotifier class discussed in Write Java classes for the new task.

It also includes the code for the PeriodicNewDocumentNotifier class, which is the periodic subclass of the NewDocumentNotifier class.

 
Top of section

NewDocumentNotifier

  package com.myco.cmtask.impl;
   
  // Java imports
  import java.io.*;
  import java.sql.Timestamp;
  import java.util.*;
  import javax.mail.*;
  import javax.mail.internet.*;
  import javax.activation.*;
   
  // FW imports
  import com.sssw.fw.api.*;
  import com.sssw.fw.exception.*;
  import com.sssw.fw.log.*;
  import com.sssw.fw.task.exception.*;
  import com.sssw.fw.util.*;
   
  // CM imports
  import com.sssw.cm.api.*;
  import com.sssw.cm.factory.*;
  import com.sssw.cm.task.api.*;
  import com.sssw.cm.task.impl.EboTask;
   
  // Other imports
  import org.w3c.dom.*;
   
  abstract public class NewDocumentNotifier extends EboTask
  {
      //
      // Constants
      //
   
      protected static final String RECIPIENTS = "recipients";
      protected static final String RECIPIENT = "recipient";
      protected static final String SMTP_HOST = "mail-smtp-host";
      protected static final String SUBJECT = "subject";
      protected static final String TEXT = "text";
      protected static final String SENDER = "sender";
      protected static final String NEWLINE = "\n";
      protected static final String MAIL_SMTP_HOST = "mail.smtp.host";
      protected static final String LINE_SEPARATOR = "line.separator";
   
      // These actually belong in a resource bundle...
      protected static final String ERROR = "An error occurred while executing the New Document Notifier task.";
      protected static final String DEFAULT_SUBJECT = "New documents have been added";
      protected static final String DEFAULT_TEXT = "The following documents have been added:";
      protected static final String DEFAULT_SENDER = "notifier@myco.com";
      protected static final String LOCATION = "Location: ";
      protected static final String TITLE = "Title: ";
      protected static final String AUTHOR = "Author: ";
   
      //
      // Member variables
      //
   
      protected EbiLog m_log;                  // our log
      protected ArrayList m_recipients;        // notification recipients
      protected String m_smtpHost;             // SMTP host
      protected String m_subject;              // message subject
      protected String m_text;                 // message text
      protected String m_sender;               // sender
      protected String m_lineSep;              // line separator
   
      // Constructor
      public NewDocumentNotifier()
      {
          // Use the CM log
          m_log = EboLogFactory.getLog(EboLogFactory.CM);
   
          m_recipients = new ArrayList();
   
          m_subject = DEFAULT_SUBJECT;
   
          m_text = DEFAULT_TEXT;
   
          m_sender = DEFAULT_SENDER;
      }
   
      // Initialization from XML
      public void fromXML(Node node)
      {
          // Rely on the superclass to get all the general task settings
          super.fromXML(node);
   
          try
          {
              NodeList nodes = node.getChildNodes();
              if (nodes != null)
              {
                  // Process the nodes
                  for (int i = 0; i < nodes.getLength(); i++)
                  {
                      Node child = nodes.item(i);
                      String nodeName = child.getNodeName();
   
                      if (child.getNodeType() == Node.ELEMENT_NODE)
                      {
                          // Recipient list
                          if (RECIPIENTS.equals(nodeName))
                              processRecipientList(child);
   
                          // SMTP host
                          else if (SMTP_HOST.equals(nodeName))
                              m_smtpHost = getElementValue(child);
   
                          // Message subject
                          else if (SUBJECT.equals(nodeName))
                              m_subject = getElementValue(child);
   
                          // Base message text
                          else if (TEXT.equals(nodeName))
                              m_text = getElementValue(child);
   
                          // Sender
                          else if (SENDER.equals(nodeName))
                              m_sender = getElementValue(child);
                      }
   
                  } // end for each node
              }
          }
          catch (Exception ex)
          {
              EboExceptionHelper.handleException(
                  ex,         // the exception
                  m_log,      // our log to write exception into
                  false,      // no, do not print the stack trace to console
                  false);     // no, do not rethrow as a runtime exception
          }
      }
   
      // Process the list of recipients provided in the XML task definition
      protected void processRecipientList(Node node)
      {
          NodeList nodes = node.getChildNodes();
          if (nodes != null)
          {
              // Process the nodes
              for (int i = 0; i < nodes.getLength(); i++)
              {
                  Node child = nodes.item(i);
   
                  if (child.getNodeType() == Node.ELEMENT_NODE)
                  {
                      String nodeName = child.getNodeName();
                      if (RECIPIENT.equals(nodeName))
                      {
                          String recipient = getElementValue(child);
                          if (!EboStringMisc.isEmpty(recipient))
                              m_recipients.add(recipient);
                      }
                  }
              }
          }
      }
   
      // Extract a node value from a Node
      public static String getElementValue(Node node)
      {
          // Entities are often considered separate text nodes, for example
          // Jim&apos;s wagon is represented by three text nodes "Jim", "&apos;",
          // and "s wagon". Thus all children need to be concatenated in order to
          // retrieve the proper text node value.
   
          String nodeValue;
          if (node.hasChildNodes())
          {
              Node curNode = node.getFirstChild();
              nodeValue = EboStringMisc.m_emptyStr;
              while (curNode != null)
              {
                  nodeValue = nodeValue + curNode.getNodeValue();
                  curNode = curNode.getNextSibling();
              }
          }
          else
              nodeValue = EboStringMisc.m_emptyStr;
          return nodeValue;
      }
   
      // Carry out the task
      public void doTask() throws EboTaskException
      {
          try
          {
              super.doTask();
   
              EbiContentManager cmgr = EboFactory.getDefaultContentManager();
              EbiDocQuery query = (EbiDocQuery)cmgr.createQuery(EbiDocQuery.DOC_QUERY);
   
              // If we're to only get the data that's changed since the time
              // that the task was last run
              if (getSinceLast())
              {
                  // Figure out the start of the interval
                  Timestamp fromTime = getFromTime();
   
                  // Figure out the end of the interval
                  Timestamp toTime = new Timestamp((new Date()).getTime());
   
                  EbiQueryExpression expr = null;
                  EbiQueryExpression expr2 = null;
   
                  // Augment the where clause with the time interval
                  if (fromTime != null)
                      expr = query.whereCreateDate(fromTime, EbiDocQuery.ROP_GREATER, false);
                  if (toTime != null)
                      expr2 = query.whereCreateDate(toTime, EbiDocQuery.ROP_LEQ, false);
   
                  // Set the augmented where clause into the query
                  if (expr != null && expr2 != null)
                  {
                      expr.andExpression(expr2);
                      query.setWhere(expr);
                  }
              }
              // otherwise we'll process all the documents
   
              // Get the list of documents
              Collection documents = cmgr.findElementsFiltered(m_context, query);
   
              // Send the email notifications
              sendNotifications(documents);
          }
          catch (Exception ex)
          {
              throw new com.sssw.fw.task.exception.EboTaskException(ex, ERROR);
          }
      }
   
      // Send the email notifications to our recipients
      protected void sendNotifications(Collection documents)
          throws EboUnrecoverableSystemException, EboSecurityException,
              MessagingException
      {
          if (!documents.isEmpty())
          {
              String msgText = getEmailMessageBody(documents);
   
              // For each recipient
              for (int i = 0; i < m_recipients.size(); i++)
              {
                  String recipient = (String)m_recipients.get(i);
                  send(
                      m_sender,    // from
                      recipient,   // to
                      m_smtpHost,  // host
                      m_subject,   // subject
                      msgText);    // text
              }
          }
      }
   
      // Generate an email
      // "The following documents have been added:
      //
      // <doc 1>
      // <doc 2>
      // .......
      // <doc N>"
      protected String getEmailMessageBody(Collection documents)
          throws EboUnrecoverableSystemException, EboSecurityException
      {
          String lineSeparator = getLineSeparator();
          StringBuffer buf = new StringBuffer(m_text);
          buf.append(lineSeparator);
          buf.append(lineSeparator);
   
          Iterator iter = documents.iterator();
          while (iter.hasNext())
          {
              EbiDocument doc = (EbiDocument)iter.next();
              buf.append(getDocumentDescriptor(doc));
              buf.append(lineSeparator);
              buf.append(lineSeparator);
          }
   
          return buf.toString();
      }
   
      // Send an email
      protected static void send(
          String from,
          String to,
          String host,
          String subject,
          String msgText)
          throws MessagingException
      {
          Properties props = System.getProperties();
          props.put(MAIL_SMTP_HOST, host);
          Session session = Session.getDefaultInstance(props, null);
   
          // Create a message
          Message msg = new MimeMessage(session);
          msg.setFrom(new InternetAddress(from));
          InternetAddress[] address = { new InternetAddress(to) };
          msg.setRecipients(Message.RecipientType.TO, address);
          msg.setSubject(subject);
          msg.setSentDate(new Date());
          msg.setText(msgText);
          Transport.send(msg);
      }
   
      // Generate a document descriptor
      // Location: <...>
      // Title: <...>
      // Author: <...>
      protected String getDocumentDescriptor(EbiDocument doc)
          throws EboUnrecoverableSystemException, EboSecurityException
      {
          String lineSeparator = getLineSeparator();
          StringBuffer buf = new StringBuffer(LOCATION);
          buf.append(doc.getURL(false));
          buf.append(lineSeparator);
          buf.append(TITLE);
          buf.append(doc.getTitle());
          buf.append(lineSeparator);
          buf.append(AUTHOR);
          buf.append(doc.getAuthor());
          return buf.toString();
      }
   
      // Figure out the line separator to use
      protected String getLineSeparator()
      {
          if (m_lineSep == null)
              m_lineSep = System.getProperty(LINE_SEPARATOR, NEWLINE);
          return m_lineSep;
      }
   
      abstract protected Timestamp getFromTime();
  }

 
Top of section

PeriodicNewDocumentNotifier

  package com.myco.cmtask.impl;
   
  // Java imports
  import java.sql.Timestamp;
   
  // Framework imports
  import com.sssw.fw.task.api.*;
  import com.sssw.fw.task.impl.*;
   
  // CM imports
  import com.sssw.cm.api.*;
  import com.sssw.cm.task.api.*;
   
  // Other imports
  import org.w3c.dom.*;
   
  public class PeriodicNewDocumentNotifier
      extends NewDocumentNotifier
      implements EbiPeriodicTask
  {
      //
      // Protected data
      //
   
      protected long      m_interval; // interval, if any
      protected boolean   m_exact;    // run asap or x millis after current time
   
      //
      // Constructor
      //
   
      public PeriodicNewDocumentNotifier()
      {
      }
   
      public boolean isExact()
      {
          return m_exact;
      }
   
      public long getInterval()
      {
          return m_interval;
      }
   
      public void setExact(boolean exact)
      {
          m_exact = exact;
      }
   
      public void setInterval(long millis)
      {
          m_interval = millis;
      }
   
      public void fromXML(Node node)
      {
          super.fromXML(node);
          EboTaskHelper.getPeriodicDataFromXML(this, node);
      }
   
      public String toString()
      {
          return super.toString() +
              ", Interval (millis)=" + m_interval +
              ", Exact=" + m_exact;
      }
   
      protected Timestamp getFromTime()
      {
          // For an interval-based task, the 'from' time is 'none' if the task
          // has not run once yet; otherwise it's
          // task_first_scheduled_time + interval*times_task_ran
          return (m_timesRan < 1) ? null :
                  new Timestamp(
                      m_launchTime.getTime() + m_interval * (m_timesRan - 1));
      }
  }


   

Content Management Guide  

Copyright © 2002, SilverStream Software, Inc. All rights reserved.