|
Content Management Guide |
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:
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:
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.
Tasksand the Java classes associated with themare registered and configured in three XML files in your project's library/ContentMgmtService/ContentMgmtService.spf/ContentMgmtService-conf directory:
|
XML file |
What it does |
|---|---|
Establishes the names and descriptions of tasks and identifies them as periodic or scheduled | |
Associates tasks (and other Director functions) with their respective Java classes |
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>
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.
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.
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.
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:
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>
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.
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.
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.
For your custom task to be loaded and instantiated correctly, you must:
Place your custom task class or classes into a separate JAR.
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.
Build and deploy your application EAR
To review the steps involved in building and deploying your application EAR, see the Quick Start.
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.
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's wagon is represented by three text nodes "Jim", "'",
// 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();
}
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.