Monday, June 04, 2007

Tapestry Components in Scala

Recently, I became interested in Scala: Multiple inheritance, good support for functional programming, a nice syntax, to mention the greatest highlights.

As a first exercise, I tried to write some Tapestry-4 components in Scala to gain some real-world experience, and to see whether Tapestry's pretty extensive use of bytecode-generation would somehow break Scala's Java-compatibility. I was pretty pleased with the results:

The following is a trait to add authorisation support to arbitrary (form-)components. It controls whether to render the component into which it is mixed in and binds its disabled parameter.

package ch.marcus.components;

import org.apache.tapestry._

trait AccessControlled extends AbstractComponent {
object binding extends IBinding {
def getObject = Boolean.box(getIdPath.contains("readOnly")) // TODO: delegate to ac-service
def getObject(c: Class) = getObject
def getDescription = "AccessControl disabled-binding"
def setObject(o: Object) = {}
def isInvariant = false;
def getLocation = null;
}

override def finishLoad = {
setBinding("disabled", binding )
}

/**Derived components delegate to the desired renderComponent method here: */
def renderAccessControlled ( w: IMarkupWriter, c: IRequestCycle )

override def renderComponent( w: IMarkupWriter, c: IRequestCycle ) {
if ( ! getIdPath.contains("invisible") ) // TODO: delegate to ac-service
renderAccessControlled(w,c);
}
}


Here is how to derive an access-controlled version of the standard TextField component by inheriting from TextField and the trait we just defined:


package ch.marcus.components;

import org.apache.tapestry._
import org.apache.tapestry.form._

abstract class AuthorisedTextField extends TextField with AccessControlled {
//duplicate from scala.Object to make Tap's class-enhancer happy
@remote override def $tag(): Int = 0

override def renderAccessControlled( w:IMarkupWriter, c: IRequestCycle )
= super[TextField].renderComponent(w,c);

}

The only gotcha is the override of $tag which seems to be necessary due to a glitch in Tapestries class-enhancer. Oh, and for components with a specification(.jwc)-file this must be copied for the derived component. This is slightly annoying, but unnecessary for component that consequently use annotations (specless components).

This is how a Tapestry page looks like in Scala:

package ch.marcus.pages
import org.apache.tapestry.annotations._
import scala.reflect._

abstract class Home extends ScalaPage {
val text = "Hello, this is Scala."

@Persist
def getMbr : String
def setMbr( m:String )

def onSubmit = {
setMbr (getMbr + "x")
}
}

Note, how clean the code looks without all the syntactic noise you'd have to add in Java and how nice the Tapestry annotations (Persist) work with Scala.

Saturday, May 12, 2007

Clean and Flexible Authorisation for Tapestry Applications

Currently we'r working on an application that will be used by 4 different departments. And I can already anticipate people dropping in to casually remark someting like "Oh, by the way, we've hired this guy who needs Access to module X of the invoicing system, but of course he mustn't be allowed to press button Y, and he needs to be able to change the comment-field. And don't hurry, he'll not really need it until today afternoon."

Furthermore, the app is meant to be re-usable by subsidiaries in other countries. So putting conditionals in the view-templates (anyway bad) or cluttering component tags with disabled="ognl: admin || hasRole('blub')" kind of parameters was clearly no option.

The solution I liked best was to introduce an "@AuthorisedBlock" component which iterates over all form components it contains (in its tag-body), looks up the current users permissions (r, rw, neither) for them and, accordingly decides whether to render them enabled, disabled or not at all (see source-code at the end of this post).

The permission lookup is delegated to a simple rules engine. A rule is basically a triple (role, id-regular-expression, permission). The rules engine determines the rule with the longest pattern-match for the id-regexp - considering, of course, only rules which apply to the current user's roles.

So, in the template there's only the <span jwcid="@AuthorisedBlock"> tag enclosing the part of the template subject to authorisation checking. The actual authorisation rules can be put into a config file or into the database.

Of course, this pattern should be implementable using any web-framework with a notion of components. But Tapestry has a couple of nice features that make it particularly easy and straightforward to implement:

  • Tapestry's form-components implement a well-known, useful interface. Thus @AuthorisedBlock works with any of them without the need to build cumbersome wrappers around 3rd party library components just to support authorisation properly.

  • A component can easily control the rendering of its body, i.e. the components belonging to the child tags of its own.

  • A component takes part in the construction of a page's component trees in an oo-manner. This makes it easy to setup the binding for the disabled parameter.


In short, Tapestry makes it easy to write the rather frameworky parts of an application, because it has nicely coherent inner workings and it's very liberal in exposing these to the application programmer and letting him change them. At least, this is true for versions 3 and 4 of Tapestry. Version 5 may become a bit more secretive. I'm not sure, whether I'll actually like that. Of course, I see the intended win: A Tap 3/4 app's GUI code will typically depend quite heavily on the framework, even on parts that were perhaps never intended to be publicly (ab-)used. So, a framework update to a new major version will be hard. However, I don't think this is really harmful. After all: Your frontend code doesn't contain any business logic, right? So, when you really need to go for a major frontend framework update, what could be the reason for this need? You'll probably want to give your application a fairly complete overhaul of the whole GUI anyway and you want to do it in a new way using the new features of your new framework (-version). What's the big point in backwards compatibility then?


public abstract class AuthorisedBlock extends AbstractComponent {
@InjectObject("service:iis.web.AuthorisationSvc")
public abstract AuthorisationSvc getAuthSvc();

@Override
protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) {
renderBody(writer, cycle);
}

@Override
public void renderBody(IMarkupWriter writer, IRequestCycle cycle) {
for (int i = 0; i < _bodyCount; i++) {
IRender r = _body[i];
if ( r instanceof IFormComponent ) {
IFormComponent formComp = (IFormComponent) r;
AccessPermission permission
= getAuthSvc().permissionFor( formComp.getExtendedId() );
if ( permission == invisible )
continue;

}
cycle.getResponseBuilder().render(writer, r, cycle);
}
}

@Override
public void addBody(IRender r) {
super.addBody(r);
if ( r instanceof IFormComponent ) {
IFormComponent formComp = (IFormComponent) r;
formComp.setBinding("disabled",
new DisabledBinding(formComp.getExtendedId()) );
}
}

private class DisabledBinding implements IBinding {

private String extendedId;

public DisabledBinding(String extendedId) {
this.extendedId = extendedId;
}

public Location getLocation() {
return null;
}

public String getDescription() {
return toString();
}

public Object getObject() {
return getAuthSvc().permissionFor(extendedId) == readOnly;
}

public Object getObject(Class type) {
return getObject();
}

public boolean isInvariant() {
return false;
}

public void setObject(Object value) {
throw new UnsupportedOperationException(this+" is a read-only binding");
}

@Override
public String toString() {
return "sythetic disabled binding for " + extendedId+" to AuthorisedBlock";
}

}

}

Wednesday, April 25, 2007

Subversive beats Subclipse as SVN Plugin for Eclipse

I've switched from subclipse to subversive recently. The former used to annoy me by

  • its inability to handle directory moves/copies. I had to go and delete .svn dirs manually

  • The annoying need to do manual updates after each commit


Subversive mostly works in any of these cases. And when it doesn't want to keep track of me moving some directory around, it politely asks me to commit before continuing.

Thursday, March 08, 2007

Maven2: creating JavaDoc links behind a firewall

I just spent the better part of the morning trying to make javadoc links work. In short: configuring the proxy did not work - We have to cope with offline package-lists, not a big problem really. Of course, to configure the maven-javadoc-plugin to do this, reading the source was the quickest way to go. So put something like this in your pom.xml:

<reporting>
<plugins>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<linksource>true</linksource>
<offlineLinks>
<offlineLink>
<url>http://java.sun.com/j2se/1.5.0/docs/api/</url>
<location>T:/maven/apidoc-packagelists/jdk-1.5.0</location>
</offlineLink>
</offlineLinks>
<docfilessubdirs>true</docfilessubdirs>
<excludedocfilessubdir>CVS</excludedocfilessubdir>
</configuration>
</plugin>

The locations must be valid Java file paths, pointing to a locally accessible directory which contains the package-list file for the given url.

Thursday, February 15, 2007

XFire newbie gotcha: use the correct ServiceFactory

If you use the jaxb binding (the default with the Maven-plugin) to generate you client proxies, to actually use your service, you have to instantiate a org.codehaus.xfire.jaxb2.JaxbServiceFactory instead of an org.codehaus.xfire.service.binding.ObjectServiceFactory. Otherwise the parameter names (wsdl:part@name) will be replaced with "in0"-elements by XFire - and the server will complain about an unexpected element "in0".

Quality of Maven integration becomes decisive for OSS adoption - good bye Axis

Today I realised, how important it became for any open-source project to play nicely with the Maven repository and to get its dependencies right in its pom's .
When I had to whip up a web-service client yesterday, I started out trying Axis-2 since I knew the server impl was bases on Axis. Quickly I had to decide to either start debugging their maven-plugins dependencies (obviously wsdl4j was lost somhow), or to try something different (XFire). What I definitely not wanted was to clutter my build with Ant or, worse, CLI calls. I quickly tried XFire, its Maven plugin worked out of the box - even the library itself was a pleasant surprise (easy to use, nice Jaxb-bindings, clean generated code).

Sunday, February 11, 2007

New Version 1.0.2 of hivedoc-plugin for HiveMind /Maven2

Finally, I got around to enhancing the hivedoc-plugin with:

  • Support for sub-modules

  • Removal of empty columns in the module-summary table.

The new version 1.0.2 should be on the central repo as soon as MAVENUPLOAD-1377 is resolved.

Monday, January 08, 2007

Half a Year of Maven 2: An experience of the awkward kind

We're using Maven 2 now for all new projects. At the highest level I can summarize our experience as:
  1. The concepts behind Maven are sound and amazingly useful.
  2. The implementation is just about usable - no show-stoppers anymore, but lots and lots of little, annoying bugs and surprising gotchas
  3. The documentation is sloppy.
The really funny thing is, how point 1. still by far outweighs the shortcomings 2. and 3 when it comes to the question "Was it worth it, after all?".

So what made it useful, after all?
  • First of all, it gives your builds a uniformity and completeness (JavaDocs, HiveDocs, PMD, CPD, ...) that would be difficult to achieve otherwise without having a dedicated, organization-wide build-manager (as a real person:).
  • Secondly, it facilitates re-use by making doing the right things easier than the wrong ones. Having cleanly specified dependencies referring to specific binary versions of jars makes a bigger difference than you might think without trying. And being able to inherit a big part of your build-setup from an org-wide POM (the ant project descriptor) is a real killer feature. Together with archetypes this makes the overhead for starting a new library-module really negligable.
Enough praise. The downside is, there is still way enough room for maven-improvement wishes for the new year. Too many things just make - sorry - too sloppy an impression for a project which is purportedly backed by a professional org (Mergere). Some of the issues I remember are:
  • Docs: One of the benefits of Maven should be good consistent docs. Ever searched for reference docs on the site-descriptor site.xml? Google the mailing-lists. Wondering how to generate standard plugin-documentation? Don't bother with the docs. Just have a look at the POM's of existing modules. Looking for core API-docs? You'll find something like this
  • The SCM / release plugin claims to support DOS'ish CVS-pathes (D:\...) - in the report it works, but the release:prepare fails.
  • Special characters in the generated docs don't work. E.g. german locale.
  • Want to create a repo-bundle for a multi-module project? Doesn't work.
  • In a Mojo you can inject things into private fields - If a superclass happens to have a private field of the same name the injection fails silently.
So, after all, a great tool with a great amount of small issues to work on. Please, Maven guys, put some effort in finishing off the little bugs - and thanks for a useful tool.

Friday, January 05, 2007

New Version of hivedoc-plugin for HiveMind

Since some people seem to use it, I've updated my hivedoc-plugin to version 1.0.1 . Notable changes are:
  • Support for custom resource root directories
  • Backport to Java 1.4
  • proper plugin-docs
The new version should show up on the central Maven 2 repo soon. If you need it before that, here's the repo-bundle.

Tuesday, January 02, 2007

hivedoc-plugin generates Maven 2 docs for HiveMind descriptors

If you're using HiveMind and Maven 2, generating docs for your HiveMind descriptors is now really quick and simple.
Find the docs under http://marcus-schulte.ch/hivedoc-plugin/.