Content Management Guide

CHAPTER 5

Managing Tasks

This chapter describes how tasks work in the Content Management (CM) subsystem and explains how to reconfigure installed tasks and write and implement custom tasks. It contains the following sections:

For more information    You also can use the CMS Administration Console to manage tasks. For more information, see Administering Automated Tasks.

 
Top of page

About tasks

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

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

Types of tasks   There are two types of exteNd Director tasks: 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 can be scheduled, periodic, or both.

 
Top of section

Installed tasks

The following tasks are installed with the CM 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 CM data with the Search subsystem engine, which by default is based on the Autonomy Dynamic Reasoning Engine (DRE); updates to CM data are propagated to the DRE.

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

default

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

Configurability   These installed tasks are highly configurable (in a set of three XML files) 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.

For more information    For information on which files you need to edit to reconfigure an installed task, see About how tasks are registered and configured. For an example, see Customizing an installed task.

 
Top of section

Custom tasks

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

When you create a task, you:

For more information     For information on the files you need to edit to register and configure a new task—and register the Java classes you create for it, see About how tasks are registered and configured next.

 
Top of page

About 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 exteNd 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 (or both). 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 installed tasks are initially 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 for the Default_tasklist.xml file.

Enabling or disabling a task   Note that to enable a task, you set the content of the <enabled> tag to true. To disable a task, you set this value to false.

 
Top of section

services.xml

The services.xml file includes entries that associate tasks (and other exteNd Director functions) with their respective Java classes. The 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   exteNd 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 added code (shown in bold) 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 more information    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_4_0.dtd.

Need to redeploy   You must redeploy your application EAR for any task configuration changes to 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.

Procedure To create and implement a new task:

  1. Register your task type.

    To do so, 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 do so, 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>
    

    Naming convention    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 convention is required.

  3. Write Java classes for the new task.

    The generic exteNd 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 CM 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 CM 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 more information    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 Java class.

    To do so, 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> 
    

    Naming convention    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. Prepare for your custom task to be loaded and instantiated correctly:

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

    2. Add the JAR to your exteNd 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 users can manage the custom tasks in the Task section of the CMS Administration Console.

  6. Build and deploy your application.

  7. Start the task:

    1. In a browser window, launch the CMS Administration Console and log in.

    2. Click the Tasks button to enter Tasks mode.

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

    TIP:   To stop a task, click the Stop button.

 
Top of page

Custom task sample code

This section provides a listing of the Java code for the NewDocumentNotifier class discussed in Step 3 above.

This section 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,      // Don't print stack trace to console
                  false);     // Don't 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 e-mail notifications
              sendNotifications(documents);
          }
          catch (Exception ex)
          {
              throw new com.sssw.fw.task.exception.EboTaskException(ex, ERROR);
          }
      }
   
      // Send the e-mail 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);    // Yext
              }
          }
      }
   
      // Generate an e-mail
      // "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 e-mail
      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));
      }
  }

 
Top of page

Working with task events

Task events are an extension of the exteNd Director event model framework, consisting of state change events, event producers, and event listeners (including vetoable listeners). This section includes these topics:

For more information    This section assumes familiarity with exteNd Director event model and event handling. For more information, see the section on working with events in Developing exteNd Director Applications.

 
Top of section

Task event types

The API defines a set of state change events related to task management operations. Event IDs are exposed on the individual event classes as well as on the com.sssw.fw.task.event.api.EbiConstants interface:

Task operation

Event ID constant

Task added

EVENT_ID_TASK_ADDED

Task completed

EVENT_ID_TASK_COMPLETED

Task disabled

EVENT_ID_TASK_DISABLED

Task enabled

EVENT_ID_TASK_ENABLED

Task failed

EVENT_ID_TASK_FAILED

Task started

EVENT_ID_TASK_STARTED

Task stopped

EVENT_ID_TASK_STOPPED

Tasks listed

EVENT_ID_TASKS_LISTED

Generic state change events   In addition, there are generic state change constants representing types of changes defined in com.sssw.fw.event.api.EboStateChangeEvent.

 
Top of section

Registering for a task event

Procedure To register a task event listener:

You can register for a specified type or types of events using this version of addStateChangeListener():

  public boolean addStateChangeListener(
     BitSet events, EbiStateChangeListener listener)

where events is a bit set of event IDs.

Use the event IDs specified in com.sssw.fw.event.api.EbiConstants. For example, this code registers for the task started, stopped, and completed operations:

  EbiTaskMgmtDelegate tmgr = new EbiTaskMgmtDelegate();
  EbiStateChangeListener listener = new EbiStateChangeListener();
  // Instantiate a Java BitSet and populate it
  BitSet events = new BitSet();
  events.set(EbiConstants.EVENT_ID_TASK_STARTED);
  events.set(EbiConstants.EVENT_ID_TASK_STOPPED);
  events.set(EbiConstants.EVENT_ID_TASK_COMPLETED);
  // add listener
  tmgr.addStateChangeListener(events, listener);

 
Top of section

Enabling or disabling a task event

Procedure To enable or disable task events:

  1. Open the config.xml for the Framework subsystem in your exteNd Director project.

  2. Find this property:

      com.sssw.fw.task.events.enable
    
  3. Set the value to true for enable or false for disable.

  4. Redeploy your project.



Copyright © 2004 Novell, Inc. All rights reserved. Copyright © 1997, 1998, 1999, 2000, 2001, 2002, 2003 SilverStream Software, LLC. All rights reserved.  more ...