How to write a module

This document will describe how to write an module for the Coefficient framework.

Assumptions

Through out this tutorial, the following assumptions will be made:

  • The notation $COEFFICIENT will refer to the primary Coefficient directory. For example, if you are using UNIX and have downloaded the Coefficient sources to your home directory, then the notation $COEFFICIENT would probably refer to the directory which has the name of:
      /home/yourname/coefficient
    
    If you are using Windows, then the notation $COEFFICIENT would probably refer to the directory
      C:\coefficient
    

Prerequisites

In order to work through this tutorial, you must have downloaded the entire CVS tree for Coefficient. The individual modules should be downloaded into a directory called:

  $COEFFICIENT/modules

Sample module

This tutorial will cover a sample module which can be downloaded here. This jar file should be expanded inside the modules directory mentioned above. If you do this correctly, then the directory

  $COEFFICIENT/modules/sample

should contain the following files:

  application.xml
  build.properties.samples
  build.xml
  src

This document will assume that you have the sources to the "sample" module and can refer to them while reading this document.

Directory Structure

For easy deployment, modules for Coefficient should all live under the directory indicated in the ${modules.dir} variable of the build.properties file found in the $COEFFICIENT directory. That entry might look like:

  modules.dir=/home/yourname/coefficient/modules

If you have just downloaded all of the sources for Coefficient, then that directory should have subdirectories such as:

  • mailForum
  • issueTracker
  • news
  • discussion
  • vote
  • task
  • sample

If you wish to make a new module for Coefficient, you should make a new subdirectory within this modules subdirectory with its name being the name of the module. For example, if you were writing a module which would provide chat facilities, that subdirectory might be called "chat".

application.xml

The first file you must copy into your new subdirectory is the "application.xml" file from "sample". That file currently looks like:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN' 'http://java.sun.com/j2ee/dtds/application_1_2.dtd'>

<application>
<display-name>Sample</display-name>
<description>This module show how to write a simple project module</description>
   <module>
   <ejb>sample-ejb.jar</ejb>
   </module>
</application>

It is absolutely critical that you change the three items listed in bold print above to reflect your new module. For example:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN' 'http://java.sun.com/j2ee/dtds/application_1_2.dtd'>

<application>
<display-name>chat</display-name>
<description>This module provides chat facilities to project members</description>
   <module>
   <ejb>chat-ejb.jar</ejb>
   </module>
</application>

build.xml

The build.xml file can be copied from the "sample" directory into your new directory with no changes. You must however make sure that the path to the modules-common.ent file is correct, at the moment it is ../../modules-common.ent

build.properties

The build.properties.sample file can be copied from the "sample" directory into your new directory and renamed build.properties. One change needs to be made. The line which looks like:

  project.name=sample

must be changed to reflect the new module name:

  project.name=chat

The build.properties files for the individual modules also have an entry which must refer back to the primary Coefficient directory:

  coefficient.path=/home/yourname/coefficient

src directory

The source directory for the module must be created. In the "sample" module, the source directory is:

  src/za/org/coefficient/modules/sample

For your new, "chat" module, the source directory should be:

  src/za/org/coefficient/modules/chat

Velocity

The initial release of Coefficient uses Velocity to generate the web pages. This is not a requirement. If you prefer to use some other utility or write your own rendering software, you are free to do so. However, if you wish to use Velocity and the utilities which Coefficient provides, then you must also make a "templates" directory within the "chat" directory:

  src/za/org/coefficient/modules/chat/templates

Module Class

You can use the Sample.java as a basis for the class for your new module. Here is the source of Sample replace all instances of the string Sample with your new module name:

package za.org.coefficient.modules.sample;

import java.util.HashMap;

import za.org.coefficient.authentication.CoefficientUser;
import za.org.coefficient.authentication.Role;
import za.org.coefficient.core.Project;
import za.org.coefficient.interfaces.CoefficientContext;
import za.org.coefficient.modules.BaseProjectModule;
import za.org.coefficient.util.ejb.SecurityUtil;
import za.org.coefficient.util.ejb.VelocityScreenUtil;

/**
 * This is a sample module that is meant for instructional purposes only.
 * NOTE: Methods that modify the CoefficientContext in any way, setModuleContent
 * counts, should return the CoeffcientContext. This is so that in a remote
 * environment we do not loose the data.
 *
 * @pojo2ejb.class 
 *   name="Sample"
 *   jndi-prefix="za/org/coefficient/project/"
 *   interface-extends="za.org.coefficient.interfaces.Module"
 *   interface-local-extends="za.org.coefficient.interfaces.ModuleLocal"
 *
 * @web.resource-env-ref
 *   name="za/org/coefficient/project/Sample"
 *   type="za.org.coefficient.modules.sample.Sample"
 * @web.resource-env-ref
 *   name="Sample"
 *   type="za.org.coefficient.modules.sample.Sample"
 */
public class Sample extends BaseProjectModule {
    
    public String getModuleDisplayName () {
        return "Sample";
    }

    public String getModuleDescription () {
        return "Sample Module";
    }

    public String getMainMethod () {
        // NOTE: this can be any method of this class that makes sense
        return "doInitialWork";
    }

    // roles which can execute the method
    String roles[] = {
        SecurityUtil.SITE_ADMIN_ROLE_DESC,
        SecurityUtil.PROJECT_CHAMPION_ROLE_DESC,
        SecurityUtil.PROJECT_MEMBER_ROLE_DESC
    };

    public String canExecuteForRole (CoefficientContext ctx, 
                                     String methodName, Role usersHighestRole){
        String role = usersHighestRole.getDescription();
        
        if ( methodName.equals("doInitialWork") ||
             methodName.equals("displayUsersData") ||
             methodName.equals("getSummaryForProject") ||
             methodName.equals("showSummaryForProject")) {
            for(int i = 0; i < roles.length; i++) {
                if (role.equals(roles[i])) {
                    return null;
                }
            }
            return "not authorised";
        }

        return "not authorised";
    }
        
    public CoefficientContext deleteAllForProject (CoefficientContext ctx) {
        return ctx;
    }

    public String displayUsersData(CoefficientUser user) {
        // If you would like to display data on the "My Data" module page
        // this is where you should render the content for the provided
        // user. In this example we only return a simple message
        // NOTE: there is a default implementation that returns the
        // empty string, only implement this method if you have information
        // to display.
        return "Users data for the Sample Module: Joe Bloggs, xxx-yyyy";
    }

    public String getSummaryForProject(CoefficientContext ctx) {
        // This is where you put summary information that will show up
        // in the 'title' tag of the link to your module from the project
        // page. This should show information like what the status of the
        // data is for your module.
        // NOTE: there is a default implementation that returns the
        // empty string, only implement this method if you have information
        // to display.
        return "There are a total of 1 elements in the Sample module";
    }

    public CoefficientContext showSummaryForProject(CoefficientContext ctx) {
        // This is similar to getSummaryForProject except that you set the
        // content into the context. This information will be shown as an
        // in-line summary on the project page if you have configured the
        // projects to show inline summaries.
        // NOTE: there is a default implementation that returns the
        // empty string, only implement this method if you have information
        // to display.
        String html = "<table width='100%'><tr><td>" + getSummaryForProject(ctx)
            + "</td></tr></table>";
        ctx.setModuleContent(html, getModuleDisplayName());
        return ctx;
    }

    public CoefficientContext doInitialWork (CoefficientContext ctx) {
        Project project = ctx.getProject();
        HashMap map = new HashMap();
        map.put("name", "Joe Bloggs");
        map.put("telephone", "xxx-yyyy");
        StringBuffer sb = new StringBuffer("Problem here");
        sb = VelocityScreenUtil.getProcessedScreen("index.vm", map);

        // Set the html into the context
        ctx.setModuleContent(sb.toString(), getModuleDisplayName());
        return ctx;
    }

}

Firstly, all occurrences of the word "Sample" or "sample" must be changed to "Chat" or "chat" respectively.

xDoclet

The Java comments which start with the string @pojo2ejb are important meta tags for the utility XDoclet. These tags provide information needed to generate a Session Bean wrapper if you are deploying coefficient as a J2EE application. It is critical that these tags are modified as indicated above to indicate the proper name for the new module.

getMainMethod

The string returned by the method getMainMethod is merely the name of the first method which will be executed by this module. In the example above, the method name is "doInitialWork". Note that there exists a method called "doInitialWork" which requires a CoefficientContext as a parameter.

canExecuteForRole

The Coefficient framework will execute the method "canExecuteForRole" before it executes any other method on the module. This gives the module writer an opportunity to do some security checking to ensure that it is legal for this module to execute. If "canExecuteForRole" returns null, then the request method will be executed on the module. If "canExecuteForRole" returns a string, then that string is deemed to be an error message and is displayed to the user.

In the example given above, site administrators, project champions, and project members are allowed to execute all the methods in the module and all other users are denied access.

displayUsersData

If you would like to display data on the "My Data" module page this is where you should render the content for the provided user. In this example we only return a simple message. NOTE: there is a default implementation that returns the empty string, only implement this method if you have information to display.

getSummaryForProject

This is where you put summary information that will show up in the 'title' tag of the link to your module from the project page. This should show information like what the status of the data is for your module.NOTE: there is a default implementation that returns the empty string, only implement this method if you have information to display.

showSummaryForProject

This is similar to getSummaryForProject except that you set the content into the context. This information will be shown as an in-line summary on the project page if you have configured the projects to show inline summaries.NOTE: there is a default implementation that returns the empty string, only implement this method if you have information to display.

index.vm and Velocity

The file "index.vm" contains the following:

  <P>This is a sample project module
  <P>$name
  <P>$telephone

This is an example of a Velocity template. The HashMap which is sent to the method "VelocityScreenUtil.getProcessedScreen" contains values to be subsituted for the strings $name and $telephone

jndi-name

Coefficient handles modules slightly differently depending on the jndi-prefix.

In the example above, the jndi-prefix XDoclet metatag looks like:

  jndi-prefix="za/org/coefficient/project/"

If the jndi-name begins with

  za/org/coefficient/project/
the module will be considered to be a project module and can be added to projects. Examples of this would be the modules Vote and Task which are released with Coefficient.

If the jndi-prefix begins with

  za/org/coefficient/navigation/
the module name will appear in the top navigation bar of Coefficient. Examples of this would be the Projects (search), About, Help, Mail Forum, and My Data modules.

If the jndi-prefix begins with

  za/org/coefficient/admin/
the module will only be available to the site administrator. Examples of this would be User Administration, Confirm Projects, Category Administration and Workflow Administration.

If the jndi-prefix begins with

  za/org/coefficient/nav-project/

the module will be both a project module and one which appears in the navigation bar. An example of this would be the Mail Forum module which appears in the top navigation bar and can be assigned to projects.

If the jndi-prefix begins with

  za/org/coefficient/permanent

the module will be permanently available. Examples of this type of module are Login and News.

deleteAllForProject

This method will be automatically called when a user wishes to delete a project. Currently this is not implemented.

CoefficientContext

All methods which are invoked by the Coefficient framework will receive a CoefficientContext as the parameter. This encapsulates all the data which the user is currently accessing or submitting.

The currently selected project can be obtained using the method getProject as seen in the example above.

Methods that modify the CoefficientContext in any way, setModuleContent counts, should return the CoeffcientContext. This is so that in a remote environment we do not loose any data since the object is passed by value and not by reference.

ant

At a module level, the build.xml script provided should give you all the functionality you require in order to compile and hot deploy a new module:

  ant deploy

should recompile, jar up, and redeploy the required ear file. It should not be necessary to restart JBOSS.

At the Coefficient level, however, there is a current timing constraint. If you redeploy the main Coefficient module, then all the submodules need to be redeployed. The build.xml script for the primary Coefficient module provides an additional target

  ant deploy-all-modules

which should do exactly that.

deploying as a web-application in tomcat

When deploying coefficient as a web-application all modules/themes found in the modules directory will be merged together with the core and each other to build one large .war. This is because tomcat has no notion of hot-deployable modules. In this case you will do an

  ant clean clean-all-modules deploy-war

from the $COEFFICIENT directory. In this instance you do not need to have JBoss on your system or pointed to in the build.properties. Instead you must specify the tomcat.dir property.