Showing posts with label velocity. Show all posts
Showing posts with label velocity. Show all posts

Thursday, January 10, 2008

Rhizome and the PAC Pattern

Rhizome is designed basically according to the PAC (Presentation-Abstraction-Control) design pattern. PAC is similar to MVC, but probably better for web development, and definitely better for Rhizome's goals.

Presentation:
The presentation layer bundled with Rhizome, which makes use of Velocity, is not terribly impressive. It does what a presentation layer ought to do, and no more. It is responsible for taking data and formatting it for return to the user.

Rhizome is pretty flexible on the presentation layer. Velocity is included because I like it enough to use it, and don't hate it enough to look for something else. Also, its introspection abilities are excellent, and make template writing much easier. No special interfaces, etc.

Of course, all that it takes to use a different presentation layer is a few lines of code extending from com.technosophos.rhizome.command.AbstractCommand. And a couple of lines of XML in commands.xml.

But one thing should be noted about the presentation layer as it is: No (non-GUI related) processing is done in the presentation layer. No callbacks to the system are used, and no new resources are retrieved from the system once template rendering is initiated. In short, presentation is passive. It takes what it's given, and it presents it. That's all.

Abstraction:
This is similar to the Model layer in MVC, but in PAC it is the layer responsible for managing the data.

Rhizome has a robust abstraction layer, packaged in com.technosophos.rhizome.repository. This abstraction provides unified methods of accessing data. The current implementation uses a disk-based repository and indexes.

The abstraction engine is broken into three parts: A repository (or database) for storing entire documents, a searcher for finding resources in the repository (usually by search data and metadata), and an indexer. The indexer takes documents and preprocesses them before storing them in the document repository.

Basically, the abstraction layer of Rhizome is a document database (DocumentDB). It is responsible for storage and retrieval of documents.

In some ways, the Rhizome document DB is itself a PAC: The RepositoryManager manages the repository like a controller. The search, indexing, and repository access tools deal directly with data abstraction. And the document representations (in com.technosophos.rhizome.document) providing a common format for the app's client -- in this case, Rhizome's controller. Thus, the RepositoryManager retrieves content, makes sure it is in the correct format, and then returns to the RhizomeController. (Is this a valid instance of PAC? Probably. PAC controllers can be chainable.)

Control:
This layer is responsible for exerting control over the rest of Rhizome. All requests pass through the controller, and the controller is responsible for passing requests to both the repository (abstraction) and whatever the presentation layer is (Velocity, for example).

The com.technosophos.rhizome.controller tree -- and the RhizomeController object specifically -- handle control of the program. RhizomeController is a Front Controller (Which seems to be a compatible pattern with PAC) that can be chained to another controller. Usually, the external controller is a Servlet.

The controller maps requests (from the external controller or user) to a chain of internal commands that must be run in sequence to fulfill the request.

Velocity Variables Persisting Across Templates

Yesterday, I noticed that the Velocity template engine used in Rhizome was behaving strangely. Variables set using the velocity #set directive were persisting over multiple calls to VelocityEngine.mergeTemplate().

So if I set a variable on pageA (using templateA), and then requested pageB (using a different template), the variable would still be available. This has some nasty consequences -- especially considering that Velocity (by default) will not assign nulls to variables.

Example:
Template A:

#set($title= 'My Title');
$title

Template B:
#set($title = $myObj.getTitle())
$title


What happens if getTitle() returns null? This bit is less than intuitive: Velocity ignores the assignment altogether.

So the combination of the variable context bleeding and the null assignment behavior is that if template A is rendered on a first request, and then template B is used on the next request, the title for template B will be
'My Title'.

Surely, this is not good behavior. So what is going on?

First, the Null behavior can be configured. Second, the problem with the variable bleeding is a result of some interesting Velocity internals dealing with VelocityContext objects. Let's look at some Java pseudocode:

VelocityEngine ve = new VelocityEngine();
VelocityContext cxt = newVelocityContext(myHashMap);
ve.mergeTemplate(myTemplateFile,"UTF-8", cxt, myWriter);


In the three lines above, we create a velocity engine, create a context, and then merge a single template.

Line 2 is the important one for us: When Velocity merges a template, it uses the context for storing some variable information. If it finds a Map inside of the context, it uses that Map for storing variables, assuming the Map will be discarded after the template merge.

What if the Map is static? You guessed it: All variables will be stored in the same Map. Bad.

The solution, as it turns out, is fairly simple -- though a bit odd: Remove the Map from the immediate context. This can be done with the Context Chaining feature of Velocity's contexts:

VelocityContext cxt = new VelocityContext(new VelocityContext(myHashMap));

(Alternately, you could just clone the Map into a non-static instance. The above should be less resource intensive, caveat emptor).

Finally, it is also easy (in Velocity 1.5 and later, at least) to get rid of the null assignment behavior. Simply set the SET_NULL_ALLOWED property to true:
ve.setProperty(VelocityEngine.SET_NULL_ALLOWED, true);
These changes are now checked into Rhizome's repository.