Saturday, November 17, 2007

Tapestry 5 authorization

I figure if I design Buccoo modularly enough, I should be able to plug functionality from other modules in pretty easily with a little modification. One of these is the security authentication and authorization. From the Tapestry wiki there seem to be three types of approaches:
  1. In each page class check the security on the onRender method (I think that's the method name).
  2. Implement a dispatcher or a request filter that checks if the user has access.
  3. Integrate something like Acegi security.
Option one is simple, but not scalable. Also each page may need modification. Not a good option. Option two seems be covered on the Tapesty wiki and while a little more complicated seems good. It's scalable and could do everything option 1 could do. Option three looks like the best, scalable, provides for multiple authentication mechanisms. I have no clue how to work with Acegi as yet so until I get some more time, it looks like option two wins.

Option two is detailed in how to create a dispatcher and the follow up. For the purposes of Buccoo, I'll do a minimal implementation that I should be able to fill in later.

The first thing I'll do is create a UserPermissions class in the src/org/tobago/buccoo/services folder. When the current user is logged in, it should populate with what they have permission to see. There should be a function, I'll call it canAccess, which will return true if the user has permission to access the resource in the Request or not.


package org.tobago.buccoo.services;

import org.apache.tapestry.services.Request;

public class UserPermissions {

public boolean canAccess(Request request) {
boolean result = false;

return result;
}

}


The particulars of populating the permissions and actually implementing the access is something I would focus on later.

The next class I would need would be the AccessController class that I would also put in the src/org/tobago/buccoo/services folder. This is pretty much what Chris Lewis had in the Tapestry wiki except I have a couple more import statements.


package org.tobago.buccoo.services;

import java.io.IOException;

import org.apache.tapestry.services.ApplicationStateManager;
import org.apache.tapestry.services.Request;
import org.apache.tapestry.services.Response;
import org.apache.tapestry.services.Dispatcher;

import org.tobago.buccoo.services.UserPermissions;


public class AccessController implements Dispatcher {

/* Our state manager. */
private ApplicationStateManager asm;

/**
* Receive our state manager as a constructor argument. When we bind this
* service, T5 IoC will intelligently provide the state manager - batteries included!
*/
public AccessController(ApplicationStateManager asm) {
this.asm = asm;
}

public boolean dispatch(Request request, Response response) throws IOException {
boolean canAccess = true;

/*
* Per the application state documentation, we check for the existence of an
* ASO before attempting access. These prevents any unnecessary overhead
* (automatic session creation).
*/
if(asm.exists(UserPermissions.class)) {
UserPermissions perms = asm.get(UserPermissions.class);
/*
* The object referenced by 'perms' is an instance specific to
* the current request, which is what we need. Now check the
* permissions against the resource - how you do this of course
* depends on your resource restriction implementation. However
* you will most likely base this on the page (page name or
* class).
*/
canAccess = perms.canAccess(request);
}

/*
* Access control logic goes here. If the user is allowed to access the
* resource, canAccess should be set to true.
*/

if(!canAccess) {
/*
* This is an unauthorized request, so throw an exception. We'll need
* more grace than this, such as a customized exception page and/or
* redirection to a login page...
*/
throw new RuntimeException("Access violation!");
}

return false;
}
}


The very last thing to do is to register this AccessController class with Tapestry and configure it to be invoked before any request is fulfilled. We do this in the AppModule.java file. As we were not using it, I removed the timing filter example code that came with AppModule.java.


package org.tobago.buccoo.services;

import java.io.IOException;

import org.apache.tapestry.ioc.MappedConfiguration;
import org.apache.tapestry.ioc.OrderedConfiguration;
import org.apache.tapestry.ioc.ServiceBinder;
import org.apache.tapestry.ioc.annotations.InjectService;
import org.apache.tapestry.services.Request;
import org.apache.tapestry.services.RequestFilter;
import org.apache.tapestry.services.RequestHandler;
import org.apache.tapestry.services.Response;
import org.apache.tapestry.services.Dispatcher;
import org.slf4j.Logger;


/**
* This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
* configure and extend Tapestry, or to place your own service definitions.
*/
public class AppModule
{

public static void bind(ServiceBinder binder)
{
// binder.bind(MyServiceInterface.class, MyServiceImpl.class);

// Make bind() calls on the binder object to define most IoC services.
// Use service builder methods (example below) when the implementation
// is provided inline, or requires more initialization than simply
// invoking the constructor.

binder.bind(AccessController.class).withId("AccessController");

}


public static void contributeApplicationDefaults(
MappedConfiguration configuration)
{
// Contributions to ApplicationDefaults will override any contributions to
// FactoryDefaults (with the same key). Here we're restricting the supported
// locales to just "en" (English). As you add localised message catalogs and other assets,
// you can extend this list of locales (it's a comma seperated series of locale names;
// the first locale name is the default when there's no reasonable match).

configuration.add("tapestry.supported-locales", "en");
}


public void contributeMasterDispatcher(OrderedConfiguration configuration,
@InjectService("AccessController") Dispatcher accessController) {

configuration.add("AccessController", accessController, "before:PageRender");
}

}


Our additions were a two step process. Calling binder.bind sets up our class to be callable by the service name we give it - in this case AccessController. The method contributeMasterDispatcher has the AccessController passed to it dynamically (via an annotation InjectService) and then we modify the configuration to call the AccessController before the PageRender stage is reached.

Remember if the access controller returns false, the rendering continues as if there's no problem. If it returns true then it's handled the request itself and there's no need to go further. Currently our code doesn't do much, but it's a simple framework for getting some simple security into Buccoo.

Thursday, November 15, 2007

XML, XML where for art thou XML?

Having a cool new ANT build system in place (it's still missing the javadoc task and the package source task, but I'll live for the time being) it's time to get some actual work done. The current first application tutorial goes through much of what we need to know but let's put the necessary source files in the correct spot for Buccoo.

I decided on the package path for Buccoo classes to be org.tobago.buccoo so, following the convention, in the src folder we create an org folder and then a tobago sub-folder and then a buccoo sub-folder. It seems a generally good idea to seperate Tapestry classes into data, pages, and services, so we create those sub folders as well. The web folder has the non-java source files in it as well so we should double check that there's a WEB-INF sub folder. Our layout should be something like what's below.
Ok, our first file to tackle should really be the web.xml file which we should create in the web/WEB-INF folder. I've included the web.xml below.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Buccoo Tapestry 5 Application</display-name>
<context-param>
<!-- The only significant configuration for Tapestry 5, this informs Tapestry
of where to look for pages, components and mixins. -->
<param-name>tapestry.app-package</param-name>
<param-value>org.tobago.buccoo</param-value>
</context-param>
<filter>
<filter-name>app</filter-name>
<filter-class>org.apache.tapestry.TapestryFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>app</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>


This is almost exactly the same web.xml file that was described in the tutorial except I changed the display name (not so important) as well as the tapestry.app-package parameter to the package path for the Buccoo classes.

We have one more config file to tackle before we start coding up a storm. This would be the log4j.properties file that we're placing in the src folder. It's a text file that's a sibling to the org folder we have in src already. I've included the log4j.properties file below.


log4j.rootCategory=WARN, A1

# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%p] %c{1} %m%n

log4j.category.org.apache.tapestry.TapestryFilter=info
log4j.category.org.apache.tapestry=error
log4j.category.tapestry=error

log4j.category.org.tobago.buccoo=error

# Service category names are the name of the defining module class
# and then the service id.
log4j.category.org.tobago.buccoo.services.AppModule.TimingFilter=info

# Turning on debug mode for a page or component will show all of the code changes that occur when the
# class is loaded. Turning on debug mode for a page will enable detailed output about
# the contruction of the page, including the runtime code modifications that occur. Verbose
# mode is rarely used, as it output voluminous details about the rendering of the page.
# log4j.category.org.tobago.buccoo.pages.Start=debug


If you managed to have a gander at the log4j.properties file in the tutorial, this is exactly the same, except I changed all the namespaces to org.tobago.buccoo. The log4j lets us log messages with various levels of severity either to a file or the console. It's a lot easier than using a debugger in my opinion.

Time to start the java! Tapestry starts with a page named start and its associated class Start. From the tutorial, below is the code for the Start.java file which would be in the src/org/tobago/buccoo/pages folder.


package org.tobago.buccoo.pages;

import java.util.Date;

/**
* Start page of application.
*/
public class Start
{
public Date getCurrentTime()
{
return new Date();
}
}


The only thing different from the tutorial is the package location, for obvious reasons.

What we need now is the TML template file. I know Tapestry is designed to template out HTML but we'll diverge from the tutorial from here and create an XML template instead.

The TML template is a text file which we name Start.tml and place in the web folder. I've included the source below.


<response>
<status>1</status>
<results>
<message></message>
<responsetime>${currentTime}</responsetime>
</results>
</response>


I'm really not too sure what the AppModule.java file is or does, but it does seem rather important for extending Tapestry functionality, so I've included the source from the tutorial for reference. It should go in src/org/tobago/buccoo/services.


package org.tobago.buccoo.services;

import java.io.IOException;

import org.apache.tapestry.ioc.MappedConfiguration;
import org.apache.tapestry.ioc.OrderedConfiguration;
import org.apache.tapestry.ioc.ServiceBinder;
import org.apache.tapestry.ioc.annotations.InjectService;
import org.apache.tapestry.services.Request;
import org.apache.tapestry.services.RequestFilter;
import org.apache.tapestry.services.RequestHandler;
import org.apache.tapestry.services.Response;
import org.slf4j.Logger;

/**
* This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
* configure and extend Tapestry, or to place your own service definitions.
*/
public class AppModule
{
public static void bind(ServiceBinder binder)
{
// binder.bind(MyServiceInterface.class, MyServiceImpl.class);

// Make bind() calls on the binder object to define most IoC services.
// Use service builder methods (example below) when the implementation
// is provided inline, or requires more initialization than simply
// invoking the constructor.
}


public static void contributeApplicationDefaults(
MappedConfiguration<String, String> configuration)
{
// Contributions to ApplicationDefaults will override any contributions to
// FactoryDefaults (with the same key). Here we're restricting the supported
// locales to just "en" (English). As you add localised message catalogs and other assets,
// you can extend this list of locales (it's a comma seperated series of locale names;
// the first locale name is the default when there's no reasonable match).

configuration.add("tapestry.supported-locales", "en");
}


/**
* This is a service definition, the service will be named "TimingFilter". The interface,
* RequestFilter, is used within the RequestHandler service pipeline, which is built from the
* RequestHandler service configuration. Tapestry IoC is responsible for passing in an
* appropriate Log instance. Requests for static resources are handled at a higher level, so
* this filter will only be invoked for Tapestry related requests.
*
* <p>
* Service builder methods are useful when the implementation is inline as an inner class
* (as here) or require some other kind of special initialization. In most cases,
* use the static bind() method instead.
*
* <p>
* If this method was named "build", then the service id would be taken from the
* service interface and would be "RequestFilter". Since Tapestry already defines
* a service named "RequestFilter" we use an explicit service id that we can reference
* inside the contribution method.
*/
public RequestFilter buildTimingFilter(final Logger log)
{
return new RequestFilter()
{
public boolean service(Request request, Response response, RequestHandler handler)
throws IOException
{
long startTime = System.currentTimeMillis();

try
{
// The reponsibility of a filter is to invoke the corresponding method
// in the handler. When you chain multiple filters together, each filter
// received a handler that is a bridge to the next filter.

return handler.service(request, response);
}
finally
{
long elapsed = System.currentTimeMillis() - startTime;

log.info(String.format("Request time: %d ms", elapsed));
}
}
};
}

/**
* This is a contribution to the RequestHandler service configuration. This is how we extend
* Tapestry using the timing filter. A common use for this kind of filter is transaction
* management or security.
*/
public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
@InjectService("TimingFilter")
RequestFilter filter)
{
// Each contribution to an ordered configuration has a name, When necessary, you may
// set constraints to precisely control the invocation order of the contributed filter
// within the pipeline.

configuration.add("Timing", filter);
}
}


The only thing I changed was the package location.

After building and deploying, it seems that although the content is generating as it should, the content type is not being sent as text/xml. This is simple enough to solve it seems, by adding a Meta annotation to the Start class. Below is the updated source of the Start.java class.


package org.tobago.buccoo.pages;

import java.util.Date;
import org.apache.tapestry.annotations.Meta;

/**
* Start page of application.
*/
@Meta("tapestry.response-content-type=text/xml")
public class Start
{
public Date getCurrentTime()
{
return new Date();
}
}


I imported the org.apache.tapestry.annotations.Meta class and set the content-type using the Tapestry property.

I found this by searching the Tapestry user list (lots of helpful people there) and ended up finding a message replied to by someone named Fernando. If you're reading this, thanks Fernando!

One more build and deploy and our XML is served with the correct content type of text/xml. I played around a bit and Tapestry seems to strip out the XML header/encoding <?xml version="1.0" encoding="UTF-8" ?>. I'm sure there must be a way to include it if I really wanted to, but for now we have some basic XML output from a Tapestry 5 template. You can confirm after you type "ant deploy" at a command prompt while in the C:\work\buccoo directory and then visit http://localhost:8080/Buccoo/Start.

I've been reading through the Tapestry 5 wiki and I think the next thing I'd tackle is some simple Tapestry security.

Wednesday, November 14, 2007

Building a Tapestry application with ANT

Blasphemy! :). If you have a gander at the still-in-progress Tapestry tutorial you'll see the section on setting up your environment makes mention of using Maven to do the development of your brand-spanking-new Tapestry 5 based application. As nice and automatic the Maven way is (and I duly understand that Maven is like ANT on steroids) I have grown attached to my nice simple XML build files with ANT. I also have a bunch of other software I already know how to easily (and not so easily) integrate with the ANT build system. Thus, my first task, as I have chosen to accept it, is to construct a build.xml file that will let me build the Tapestry part of Buccoo with ANT.

Of course you can't make something "better" without seeing how the existing thing ticks - at least basically. So I followed the instructions, downloaded Maven and typed that handful of a command line. A short time later (ok, a while later - the 'net connection was slow) I had a neat sample application up and running. Jetty is pretty cool, but I want my stuff running on Tomcat, darnit.

So what did the quickstart show me? Without digging around, I saw I needed two java files, a properties file for the logging, a tml template file and a web.xml file. I had no clue where it was finding the jar files (I guess it's in a Maven xml config file somewhere). Short on patience I decided to download the binary build of Tapestry 5.0.6 which I figured MUST have the required jars in it. In the zip file there was a tapestry-tutorial1 folder. In the war archive (which is really a zip file with some meta data in another file) of the tapestry-tutorial1 folder I saw everything I needed in the WEB-INF/lib folder (well, except the tapestry-tutorial1.jar).

I copied the jar files to the C:\work\buccoo\lib folder. Back on track it was time to build my build.xml so I can quasi follow the tutorial but with ANT.

The build.xml resides in the C:\work\buccoo folder and starts off pretty simply.

<project name="Buccoo" default="build" basedir=".">
</project>

I named the project, set the default target to build (compile) and the base directory to the current directory. I need to define the build target though, otherwise ANT would complain.

<project name="Buccoo" default="build" basedir=".">
<target name="build" description="Builds the Web Application">
</target>

</project>

I need to make sure that certain folders exist (because the ANT build fails if the folders aren't there to copy stuff to). I will call this new task prepare! I think build should be dependent on it. All it should really do is create the final structure so all ANT has to do is zip it up and voila! A war file.

<project name="Buccoo" default="build" basedir=".">
<target name="prepare" description="Creates the Web Application's war directory">
</target>


<target name="build" description="Builds the Web Application" depends="prepare">
</target>
</project>

Time to fill in some gaps, let's fill in prepare. Time to create the war directory, the WEB-INF sub directory and its two sub-directories lib and conf. The lib directory is standard, conf is something I use for just-in-case configs that need to be deployed with the war. I'll also create another work folder (this time under res) which will have classes and lib. The classes folder should have the compiled classes of all the sources and the lib should have a jar of those classes. That makes it easy to copy to the WEB-INF/lib of the final war. I also added an ANT property tag so I can easily reference the application's name (used in naming jar and war files) easily.

<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />

</target>

<target name="build" description="Builds the Web Application" depends="prepare">
</target>
</project>

Why wouldn't we just want to use WEB-INF/classes like everyone else? Good question. Besides being neater (ok, personal opinion I admit), there seems to have been a bug with classes that use Tapestry which are not in jars not being able to find themselves and the few extra seconds to avoid that bug seems worth it to me.

Let's get the build target doing something, like compiling the source files. The javac compiler expects all those jars that are in the lib folder to be in the classpath, so we create a classpath path reference and use that when compiling.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>


<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>


</target>
</project>


The path tag allows us to reference all the files referenced to by the fileset, which happens to include all the jars in the lib folder. The javac tag compiles all the java files in the src folder and references the classpath to use when compiling via the classpath tag. For good measure I also added a file copy function that copies all files *.properties into the classes folder. This is for stuff that still relies on a properties file, like log4j.

Now that everything's compiled, time to put what we can in the war folder so we can just zip it up to package it. The first thing we do is package all the compiled classes into a jar file by itself. This is done by the jar task in the build target (that's the one that names it app.name-core.jar which resolves to Buccoo-core.jar). We also add another target - package. All this does is jar every single file we have in the war folder and name it Buccoo.war.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>


</project>


We're almost done. Web application archives are special in that they need a web.xml to tell the Servlet container (like Tomcat) configuration things about routing requests to the application. Without that file, it won't work. Luckily, we have a web folder that contains all the non-java source resources we need to create this war file.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


We copied the web.xml file across as well as some XML files (who knows, may need them for something), tml files (Tapestry template files) as well as swf files (the output of the OpenLaszlo UI is planned to be in flash). We could always add additional file extensions for images, javascript, css or whatever else we want to copy.

Funny thing, the war still wouldn't work! Arg, we forgot to copy the jars in the lib folders across to the WEB-INF/lib. Ok, now we know what to do, let's do it.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>


</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


We first copy all the jars in the lib folder across to WEB-INF/lib and then copy all the jars from the res/work/lib folder. That should be only one file Buccoo-core.jar. Sometimes things get so messed up with my working folders I just want to clean up. Although not necessary, below I've added a clean target which removes all the temporary folders (good thing they're created in the prepare target).



<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="clean" description="Deletes the Web Application's war directory and web archive file">
<echo message="Deleting ${app.name}'s war directory and web archive file ..."/>
<delete dir="${basedir}/war"/>
<delete file="${basedir}/${app.name}.war"/>
<delete dir="${basedir}/res/work"/>
</target>


<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>

</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

</project>


The very last thing to do is deploy the war file (provided we wrote some java source code that compiles in there). While this can be done manually, for development purposes I want to create an ANT target. This target would be named deploy. It would be responsible for stopping the Tomcat servlet container (in this case the one used by OpenLaszlo), removing any previous incarnation of our project (the war file and the exploded folder), starting back Tomcat and copying our war file so it can be re-exploded. Let's also add a property for the tomcat home folder so we can make some re-configuration easier if necessary.


<project name="Buccoo" default="build" basedir=".">
<property name="app.name" value="Buccoo" />
<property name="tomcat.home" value="C:/work/OpenLaszloServer4.0.6/Server/tomcat-5.0.24" />

<path id="classpath">
<fileset dir="${basedir}/lib">
<include name="*.jar"/>
</fileset>
</path>

<target name="clean" description="Deletes the Web Application's war directory and web archive file">
<echo message="Deleting ${app.name}'s war directory and web archive file ..."/>
<delete dir="${basedir}/war"/>
<delete file="${basedir}/${app.name}.war"/>
<delete dir="${basedir}/res/work"/>
</target>

<target name="prepare" description="Creates the Web Application's war directory">
<echo message="Creating ${app.name}'s war directory ..." />
<mkdir dir="${basedir}/war" />
<mkdir dir="${basedir}/war/WEB-INF" />
<mkdir dir="${basedir}/war/WEB-INF/lib" />
<mkdir dir="${basedir}/war/WEB-INF/conf" />
<mkdir dir="${basedir}/res/work" />
<mkdir dir="${basedir}/res/work/classes" />
<mkdir dir="${basedir}/res/work/lib" />
</target>

<target name="build" description="Builds the Web Application" depends="prepare">
<echo message="Building ${app.name} ..." />
<javac srcdir="${basedir}/src" destdir="${basedir}/res/work/classes">
<include name="**/*.java"/>
<classpath refid="classpath"/>
</javac>
<copy todir="${basedir}/res/work/classes">
<fileset dir="${basedir}/src">
<include name="**/*.properties"/>
</fileset>
</copy>

<echo message="Packaging ${app.name}'s core archive file ..."/>
<delete file="${basedir}/res/work/lib/${app.name}-core.jar"/>
<jar jarfile="${basedir}/res/work/lib/${app.name}-core.jar">
<fileset dir="${basedir}/res/work/classes" includes="**"/>
</jar>

<copy todir="${basedir}/war/WEB-INF">
<fileset dir="${basedir}/web/WEB-INF">
<include name="web.xml"/>
</fileset>
</copy>

<copy todir="${basedir}/war" overwrite="true">
<fileset dir="${basedir}/web">
<include name="**/*.xml"/>
<include name="**/*.tml"/>
<include name="**/*.swf"/>
</fileset>
</copy>

<copy todir="${basedir}/war/WEB-INF/lib">
<fileset dir="${basedir}/lib">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${basedir}/res/work/lib">
<include name="**/*.jar"/>
</fileset>
</copy>

</target>

<target name="package" description="Packages the Web Application's web archive file" depends="build">
<echo message="Packaging ${app.name}'s web archive file ..."/>
<delete file="${basedir}/${app.name}.war"/>
<jar jarfile="${basedir}/${app.name}.war">
<fileset dir="${basedir}/war" includes="**"/>
</jar>
</target>

<target name="deploy" description="Deploys the package into a container" depends="clean,package">
<exec dir="." executable="cmd">
<arg line='/c C:\work\OpenLaszloServer4.0.6\Server\lps-4.0.6\lps\utils\stopTomcat.bat'/>
</exec>

<sleep seconds="2"/>

<delete file="${tomcat.home}/webapps/${app.name}.war"/>

<delete dir="${tomcat.home}/webapps/${app.name}" />

<exec dir="." executable="cmd" spawn="true">
<arg line='/c C:\work\OpenLaszloServer4.0.6\Server\lps-4.0.6\lps\utils\startTomcat.bat'/>
</exec>

<sleep seconds="2"/>

<copy todir="${tomcat.home}/webapps/">
<fileset dir="${basedir}">
<include name="${app.name}.war"/>
</fileset>
</copy>
</target>

</project>


OpenLaszlo uses two batch files on Windows to do its starting and stopping. The stopTomcat.bat file stops the OpenLaszlo service. The way that the startTomcat.bat starts the service though, when we call that it needs to be spawned so it doesn't keep the last task from executing (which is copying the war file - rather important).

There you have it a build.xml file that lets us build Tapestry 5 based applications. In my next post I'll see if I can get some rudimentary XML output from it.

Getting started with the development environment.

Well the first step for this project is setting up the development environment. I did this once before for this project, but I had to re-do it a bit because of a bug in Tapestry 5.0.6 (the latest version as of writing this). Tapestry can't find resources if the work folder for the servlet container (I'm using Tomcat - I don't know if it's Tomcat specific) has spaces in the path. It throws up an ugly error message. Given that I want Tapestry to work with the Tomcat shipped with OpenLaszlo I had to take that into consideration.

Ok, so the first thing I did was install the latest JDK. In installed by default to C:\Program Files\Java\jdk1.6.0_03 and as I would like to use ANT, I set the JAVA_HOME environment variable to that path as seen in the screenshot below.

You can get to the environment variable system properties by pressing the Windows key and the Pause button (or alternatively right click on my computer and select properties). From there navigate to the advanced tab and then environment variables. Click New to create a new System variable or double click on an existing one to edit its value.



With that done I downloaded the latest version of Apache Ant (1.7.0 as of this writing) I could find and extracted that to C:\Program Files\Apache Software Foundation\apache-ant-1.7.0 and added the bin folder to the System path. You can see a screenshot of the path modification below.

The path was placed in quotes and appended after a semi-colon. So the path would have ended in ;"C:\Program Files\Apache Software Foundation\apache-ant-1.7.0\bin". A test of typing ant from the command prompt should result in a message "Buildfile: build.xml does not exist!". This shows ANT is working, but doesn't know what to do.

The next step is to install OpenLaszlo. By default it installs into the program files folder. Do not accept the default path! Install into C:\work\OpenLaszloServer4.0.6 (notice no spaces) as seen in the screenshot below.

I'm not setting up any database stuff as yet as my primary concern is getting OpenLaszlo and Tapestry talking to each other. That and I haven't quite decided if I should be using something like hibernate or directly talking to the database in the configuration.

The final step is to setup the folders for the development process. I've included a screenshot below of the layout I have.

The OpenLaszlo4.0.6 folder is basically populated by the OpenLaszlo installer so I'll focus on the buccoo folder.
  • The lib subfolder contains all the jars that are dependent for Buccoo to compile properly.
  • The res subfolder has a temporary work area (to make some automated tasks simpler) as well as being an easy spot to integrate anything else that may require more than just copying a jar to the lib folder.
  • The src folder contains all the sources for the Tapestry side of Buccoo organized in the org.tobago.buccoo package.
  • The war folder should be created by the ANT script. It contains the contents as it should be for the final Buccoo.war file. It would copy files from the lib, compiled classes as well as any WAR specific files from the web folder.
  • The web folder contains any non-java resources (like Tapestry tml files) and meta-data (like the web.xml in the META-INF) that we want to include in the war.

That's it! Now we're ready to start writing the ant build.xml file to automate the compilation process.

Tuesday, November 13, 2007

Inspiration

I had started Buccoo after looking at the OpenLaszlo blueprint application LZProject and its implementation. I felt the LZProject overcomplicated certain things and wasn't as clean as I would have liked it.

With the LZProject, the back end development wasn't as transparent as I envisioned it should be and could easily lead to difficulties in development and testing for larger scale projects. I didn't see it being an easy task to test the front end and back end separately.

On the plus side, the LZProject had a nice design pattern for the form processing and I have tried to incorporate the pattern into the Buccoo project.

Tapestry 5 itself has some Rich Internet Application functionality but the extent of the capabilities is hard to match when compared to OpenLaszlo. The Tapestry 5 DHTML doesn't seem to provide elements such as windows, menus, dragging and dropping etc.

Below is the idea behind the model for the Buccoo project.

The diagram is a scribbling on a notepad that my girlfriend did while I was explaining it to her. The blob that's the user (no offence heh) says "give me page". Using the OpenLaszlo user interface a http/https request is sent to the web server which has Tapestry running as an application. It's Buccoo with Tapestry as its base, but you get the idea. The Tapestry based application code queries the database if necessary and merges the results it gets with the proper template. The result is then returned to the OpenLaszo user interface where the user can use the results.

Project Buccoo - NS FAQ

Not so frequently asked questions.

Q1: Can I customize the look and feel?
A1: Yes. You can use any style available in OpenLaszlo to customize Buccoo's user interface. A general style which will make it easier to do this type of thing is currently a limitation, but is scheduled to be worked upon

Q2: Where can I get Buccoo's source code?
A2: When I think Buccoo in its entirety is ready to be released I'd provide a link to a project (on sourceforge probably) that contains the code. Until then I'd be providing code as I work on it and document it through the blog.

Project Buccoo - About

This is a blog chronicling the daily trials and tribulations involved in developing a Rich Internet Application template that I have named project Buccoo.

Project Buccoo: A web application template using OpenLaszlo and Tapestry for scalable Rich Internet Application based websites.

Project goal: To develop a set of scalable, secure templates to provide a readily usable application leveraging XML and Java for portability and localization.

Requirements for development:
  • OpenLaszlo - The OpenLaszlo presentation server 4.0.6 - Uses Tomcat 5.024 as its webserver
  • Apache Ant 1.7 - To easily build and deploy the files related to Buccoo
  • Apache Tapestry 5.0.6 - Provides the middleware and the presentation to the user interface
  • Firefox 2.0.0.9 - For viewing XHTML and XML files (with Flash 9 plugin installed for OpenLaszlo flash interface)
  • Java compatible database (MySQL or HSQLDB should work)
  • Text editor.
While the development is detailing the steps on a Windows based environment, the steps should be easily portable to any development environment supporting Java and Tomcat.

Target audience: Those desiring to implement a Rich Internet Application with OpenLaszlo and have an existing understanding of Web technology, XML and Java.

Licenses: Buccoo uses components that are either Artistic License or Apache 2 so it is currently licensed under the Apache 2 license.

How do I envision this project being used?
  1. As a base for anyone who wants to quickly develop a secure and responsive Rich Internet Applications
  2. As a reference for anyone who wants to integrate OpenLaszlo with a REST (Representational State Transfer - like a SOAP webservice, but not as complicated).
  3. As a reference for anyone who wants to use / produce XML content using Tapestry 5.
  4. Anything you can think of using it for!

Why the name Buccoo?

I live in Trinidad and Tobago - a twin island republic in the West Indies. Buccoo is a name of an area close to where I vacationed as I came up with this project. The Buccoo reef is a popular snorkeling spot that highlights quality of the generally unseen beauty available on the islands. My hope is that this project will also highlight the two projects it's based upon - OpenLaszlo and Tapestry.