This document will describe how to write an module for the Coefficient framework.
Through out this tutorial, the following assumptions will be made:
/home/yourname/coefficientIf you are using Windows, then the notation $COEFFICIENT would probably refer to the directory
C:\coefficient
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
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.
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:
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".
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>
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
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
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
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
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.
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.
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.
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.
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.
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.
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.
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
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.
This method will be automatically called when a user wishes to delete a project. Currently this is not implemented.
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.
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.
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.