Developing Wiki Plugins for CodeBeamer: Using the Service Layer (Part 3)

Author: aron.gombas@intland.com


Overview

So far in the first two articles in this series, we have reviewed the principles of CodeBeamer wiki plugin development through two simplified examples.
In this last part, we will develop a more real-world taste plugin that uses internals of CodeBeamer.

Service Layer in CodeBeamer

What is the Service Layer?

The Service Layer (also known as Business Layer or Business Tier) is composed by a set of Java classes that capture the core business logic of CodeBeamer. This acts like a client-side business abstraction and hides the underlying implementation details, too. The initiator of a Service Layer operation can be an action invoked from the Web Layer (Presentation Layer), a remote method call invoked from the CodeBeamer remote API or even a wiki plugin.

All the Service Layer objects (the managers) are:

  • in the com.intland.codebeamer.manager package
  • specialized for one entity, and follow the EntityManager naming convention like UserManager or TrackerManager
  • singletons, i.e. the only existing instance can be obtained by calling their getInstance() method

The managers support the following base groups of methods:

  1. CRUD operation methods: methods to Create, Return, Update and Delete the entities managed ((e.g. create())
  2. various finder methods: query methods to return the entities managed, by various criteria (e.g. findTrackerItemsByTracker())
  3. other entity-specific operations (e.g. lock for artifacts)

Note that the Service Layer is our actual "internal" API. Practically, it means that if you program against this interface, your code will not be vulnerable for future changes.
However, technically it is possible to interact with other lower-level CodeBeamer components (like calling the DAOs or working directly with the database), it is strongly discouraged, as it will very likely hurt data integrity and security rules.

Using the Service Layer

Using the service methods is fairly trivial, as most of the operations are implemented by a single method call. For more details about the parameters, please study the javadocs of the classes in the com.intland.codebeamer.manager package, but here are a couple of examples to give you an idea how simple it is.
Note that entities are represented by Data Transfer Objects in CodeBeamer, and these classes follow the EntityDto naming convention.

Creating a new tracker:

TrackerDto tracker = new TrackerDto();
tracker.setName("My requirements");
// set other properties here by calling various setters
TrackerManager.getInstance().create(user, tracker, null);

System.out.println("New tracker ID:" + tracker.getId());

Searching for a tracker item when its identifier is known:

Integer id = new Integer(97);
TrackerItemDto trackerItem = (TrackerItemDto)TrackerItemManager.getInstance().findById(user, id);

System.out.println("Tracker item found:" + trackerItem.getSummary());

Note that most of the business methods (including even the finders) require passing the UserDto instance that represents the initiator (it is the currently signed in user in most of the cases), because the Service Layer also takes care of the security: for example, if the user has no access rights to the given tracker item, it won't be returned by the finders.

Wiki plugins and the Service Layer

As wiki plugins run in the same JVM like the CodeBeamer core, they can access the Service Layer in the same exact way as any other internal class of CodeBeamer.

The anatomy of a typical wiki plugin will look like this:

  1. parse the plugin parameters
  2. interact with the Service Layer, for example, run queries
  3. set up a Velocity context with the information returned
  4. render a Velocity template with the context
  5. return the String evaluated

In the appendix you will find the full source code of a wiki plugin that lists all the tracker items submitted by or assigned to the currently signed user in the current project. The code is very simple, but it illustrates all the major techniques you need to be familiar with when developing plugins that interact with CodeBeamer's internals.

Testing

Create a testpage and enter:
[{ServiceLayerDemoPlugin}]

You should see something like this:

bond's Tracker Items
 [#BUG-1628] Window can't be closed by clicking the 'X' 	Bug by bond
 [#CRQ-1629] Background color should be darker 			Change Request by bond
 [#BUG-1630] Hour-glass cursor is blinking 		 	Bug by bond
 [#TSK-1631] Make EventMulticaster thread-safe 		 	Task by bond
(4 items)

Appendix

ServiceLayerDemoPlugin.java

package com.intland.codebeamer.wiki.plugins;

import java.util.List;
import java.util.Map;

import org.apache.velocity.VelocityContext;

import com.ecyrd.jspwiki.WikiContext;

import com.intland.codebeamer.manager.TrackerItemManager;
import com.intland.codebeamer.manager.TrackerManager;
import com.intland.codebeamer.persistence.dao.TrackerItemDao;
import com.intland.codebeamer.persistence.dto.UserDto;
import com.intland.codebeamer.persistence.dto.WikiPageDto;

public class ServiceLayerDemoPlugin extends AbstractCodeBeamerWikiPlugin {
	public String execute(WikiContext context, Map params) {
		// get beans from wiki context
		UserDto user = getUserFromContext(context);
		WikiPageDto page = getPageFromContext(context);

		// parse params (they default to false)
		boolean includeAssignedTo = Boolean.parseBoolean((String)params.get("assigedTo"));
		boolean includeSubmittedBy = Boolean.parseBoolean((String)params.get("submittedBy"));
		if(!includeAssignedTo && !includeSubmittedBy) {
			// set at least includeAssignedTo to true
			includeAssignedTo = true;
		}

		// find all trackers in the current project
		List trackers = TrackerManager.getInstance().findByProject(user, page.getProject());

		// find all tracker items in those trackers that are assigned-to or submitted-by by the current user
		List trackerItems = TrackerItemManager.getInstance().findByOwner(user, trackers,
                                        TrackerItemDao.TF_NOFILTER, includeAssignedTo, includeSubmittedBy);
		// set up Velocity context
		VelocityContext velocityContext = getDefaultVelocityContextFromContext(context);
		velocityContext.put("trackerItems", trackerItems);

		// render template
		return renderPluginTemplate("servicelayerdemo-plugin.vm", velocityContext);
	}
}

servicelayerdemo-plugin.vm

<h3>${user.name}'s Tracker Items</h3>
#tableOpen()
	#foreach($trackerItem in $trackerItems)
		#trOpen()
			#tdText("#linkTrackerItem(${trackerItem})")
			#tdSeparator()
			#tdText("${trackerItem.tracker.name} by ${trackerItem.submitter.name}")
		#trClose()
	#end
#tableClose()

<span style="font-size: smaller; color: #888888;">
	#if(${trackerItems.size()} == 1)
		(${trackerItems.size()} item)
	#else
		(${trackerItems.size()} items)
	#end
</span>