Automated Coder

Exploring the Code of CruiseControl.Net

Requests, Actions and Templates

Posted by Craig Sutherland on 22 September, 2008

Previously

I’ve finally bitten the bullet and started pulling apart the dashboard in order to understand it. In my last post on the dashboard (here) I started looking at how it does its job. I looked at the HttpHandler, the RequestController, IActionFactory and the IResponses. While this was a general overview of the processing, there are several areas uncovered! E.g. how does the dashboard know which server/project is being used? What happens to the data in the ASP.Net request? How is NVelocity used? What are templates? And so on …

In this post I’ll look at what happens at the ASP.Net request and how the URL gets translated into project/server information. Then I’ll pull part a couple of actions and their associated templates. By this point I should know enough of the inner workings of dashboard to start modifying it!

Requests

Rather than using the standard Request class in ASP.Net, the web dashboard has its own version. To start with it has an interface (IRequest) and the implementation (NameValueCollectionRequest). I assume this has been done for testing (see the ASP.Net MVC blogs and how they have gotten around this same issue), but it also allows them to do some custom processing.

As part of SetupObjectSourceForRequest() in CruiseObjectSourceInitializer, the initial ASP.Net Request is stored in the ObjectSource, plus an instance of NameValueCollectionRequest with the relevant data from the Request. This includes the QueryString/Form values (stored together), the headers, path and URL. Looking through the code of this method, I also saw it sets another ‘request’ instance – this time ICruiseRequest/RequestWrappingCruiseRequest.

While IRequest is a more general ‘request’ interface, ICruiseRequest is specifically for dealing with CruiseControl.Net requests. This is a wrapper around IRequest and it has three properties of interest: ServerName, ProjectName and BuildName. This is the class that is responsible for breaking apart a URL to return the server, project and build information.

At this point I should explain the format of the URLs that the dashboard generate. They use a form of REST for the URLs, including the server, project and build names (hence why they had to implement an HttpHandler). For example, to view all the projects on a server the following URL is used:

http://ccnetlive.thoughtworks.com/ccnet/server/local/ViewServerReport.aspx

To view the builds for a project:

http://ccnetlive.thoughtworks.com/ccnet/server/local/project/CCNet/ViewProjectReport.aspx

To view a project build:

http://ccnetlive.thoughtworks.com/ccnet/server/local/project/CCNet/build/log20080919051543Lbuild.1.4.0.3690.xml/ViewBuildReport.aspx

The first part (http://ccnetlive.thoughtworks.com/ccnet/) is the server and applicaton folder (yes, I’ve used CCNetLive :) ). Next (server/local) is the server that is being viewed, followed by the project (project/CCNet), the build (build/log20080919051543Lbuild.1.4.0.3690.xml) and finally the page name. The page name (minus the extension) is the name of the action to perform. All names (server, project and build) are optional, but it is up to the action to handle any missing values.

In the URLs the name of an item is prefixed with the type of the item. This opens up possibilities later on for adding additional values to the URL (e.g. a session token). RequestWrappingCruiseRequest is the class that knows about these item types and how to handle them.

Generating URLs is handled by ILinkFactory/DefaultLinkFactory and a series of type-specific classes that implement IAbsoluteLink. These classes are:

  • BuildLink
  • ProjectLink
  • ServerLink
  • FarmLink

Internally these classes rely on ICruiseUrlBuilder/DefaultCruiseUrlBuilder to generate the actual URLs (yes, layer upon layer!) This simply goes through and appends server/project/build. If any of the previous items are missing (e.g. server or build) then the subsequent items are skipped.

That hopefully covers everything on requests and URLs.

Actions

For this post I’m going to dissect three different actions: ViewFarmReport, ViewBuildLog and ForceBuild. These three actions use different ways of achieving their goals.

ForceBuild

I’m going to start with ForceBuild, since this is the simplest of the three. The class the handles this action is ForceBuildXmlAction - it is one of the default actions that is registered in CruiseObjectSourceInitializer.SetupObjectSourceForRequest().

In its Execute() method it calls the ForceBuild() method on an IFarmService instance. This instance is passed in via the constructor and holds a link (via .Net remoting) to the actual CruiseServer. Once this has been done it generates an XML fragment containing the results – which is always success (there is no checking of whether the request was actually sent or not).

ViewBuildLog

Next is ViewBuildLog, which is a little more complex. This action is handled by HtmlBuildLogAction. This is a configurable, plug-in action (i.e. not hard-coded in CruiseObjectSourceInitializer.SetupObjectSourceForRequest().) Adding this action to the ObjectStore is the job of BuildLogBuildPlugin. This is an Exortech.NetReflector decorated class that simply registers HtmlBuildLogAction.

The Execute() method generates HTML from an NVelocity template. The templates are in the templates folder, and the dashboard has a number of classes for working with them. But to keep things simple, basically all the valves for the template are stored in a Hashtable. This hashtable and the name of the template are passed into an IVelocityViewGenerator instance (again from the constructor). This instance is then responsible for converting the template into HTML.

The actual template is a simple fragment of HTML with some NVelocity bookmarks:

<p>Click <a href="$logUrl">here</a> to open log in its own page</p>
<pre class="log">$log</pre>

The items that start with a dollar sign ($) are the NVelocity bookmarks. These are the values that are set in HtmlBuildLogAction. The other point is this is only a fragment of the full document – this is the job of IVelocityViewGenerator and its supporting classes to turn it into a full HTML document (I’ll cover these some other time).

ViewFarmReport

Now I’ve left ViewFarmReport to last as it’s the most complex of the three actions I’m covering. The other two both performed their actions and directly returned responses. In contrast, this action uses IProjectGridAction to do its actual processing. But first, the class FarmReportFarmPlugin is both the ICruiseAction and also the plugin for this action, and it is loaded via the configuration file.

Currently the only implementation of IProjectGridAction is VelocityProjectGridAction. This generates a grid containing all the projects for either a server or the whole server farm. Internally it uses ProjectGrid and ProjectGridRow to achieve this. Again, it uses an NVelocity template to generate the actual HTML – although this time the template is more complex as it needs to iterate through an array. The values for the hashtable are set in VelocityProjectGridAction, including an array of ProjectGridRow entities.

Otherwise, this action is no more complex than the others :)

Until Next Time

Most actions are similar to these three, with varying levels of complexity.This covers (almost) the complete process from requesting an action to the final result. I now know how to get to an action, how it is loaded into the application and where NVelocity is used.

Now while understanding the basic workings of actions does tend to simplify things, I’ve left out how IVelocityViewGenerator works and how the other page content is generated. This includes the breadcrumbs across the top, the list of actions down the side and the builds list. But I’ve covered enough for now, I’ll leave these final details to my next post ;)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>