Less Configuration - Enhanced Tapestry Component Resolution.
I like to be able to organize files into groups
with folders and I also don't like to do too much configuration file
maintenance. Tapestry will find components in the /WEB-INF directory in my WAR
without configuration but with many pages and components in an application it
becomes unwieldy. Tapestry allows me to add these components to my
specification, specifying the path, relative to the /WEB-INF folder, and t-deli.com
provides an ant-task to automate the maintenance of these component
declarations. What if I could structure my pages and components into any
directory structure I want and not have worry about maintaining the application
specification file, even with an automated tool? I could add components while
testing without a restart. Since servlet containers can explode a deployed WAR
file into a directory structure this method would even work in a deployed
Tapestry application.Fortunately,
Tapestry has a number of extension points which can be used to extend various
framework behaviors. One of these is the
ISpecificationResolverDelegate
interface. I can implement this interface and
add an extension into my application
configuration:
<extension name="org.apache.tapestry.specification-resolver-delegate"
class="com.mjhenderson.users.tapestry.CustomSpecificationResolver"
immediate="yes">
The interface consists of only 2
methods:
public IComponentSpecification findPageSpecification(
IRequestCycle cycle,
INamespace namespace,
String simplePageName);
public IComponentSpecification findComponentSpecification(
IRequestCycle cycle,
INamespace namespace,
String type);
The delegate is Tapestry's last resort for
resolving a component specification which is not found by the built-in
resolution methods. It allows Tapestry component specifications to be derived by
any means, constructed dynamically, retrieved from a RDBMS, whatever you can
think of. The delegate is also responsible for caching component specifications.
Tapestry will not cache component specifications resolved by the
delegate.All I want to do is to be
able to add components to my application, in any directory structure I find
convenient under /WEB-INF and not have to maintain the application
specification. This has one immediate benefit, I keep forgetting to edit the
file and run into problems when I run the app, I have to stop, edit the
specification and restart the app. The delegate implementation prevents this
error.However the major advantage,
from my point of view is less
configuration, I get to maintain components in
a logical directory structure and let the framework do the leg-work of finding
them at runtime.My resolver delegate
implementation is fairly simple, each of the methods is only a few lines
long:
public IComponentSpecification findPageSpecification(IRequestCycle cycle,
INamespace namespace, String name) {
if (!_initialized) {
_init(cycle);
}
if (!_applicationIsExplodedWAR) {
return null;
}
IResourceLocation location = _locator.getSpecificationLocation(name+".page");
if (location != null) {
return _specificationSource.getPageSpecification(location);
}
return null;
}
The delegate must be initialized and it
cannot be done in the no-argument constructor, it requires access to the
IRequestCycle:
private void _init(IRequestCycle cycle) {
IResourceLocation rootLocation = Tapestry.getApplicationRootLocation(cycle).getRelativeLocation("/WEB-INF/");
_applicationIsExplodedWAR = rootLocation.getResourceURL().toString().startsWith("file:");
if (_applicationIsExplodedWAR) {
_locator = new RecursiveFileLocator(rootLocation);
_specificationSource = cycle.getEngine().getSpecificationSource();
}
_initialized = true;
}
The RecursiveFileLocator is responsible
for resolving components in a single directory. It maintains a Map of child
locators for each of it's sub-directories. It locates a specification by first
looking in the folder it represents and, if the file is not found, forwarding
the request on to each of it's child locators until the file with the expected
name is found:
public IResourceLocation getSpecificationLocation(String expectedName) {
IResourceLocation location = _resolveThisFolderSpecificationLocation(expectedName);
if (location == null) {
location = _resolveChildFolderSpecificationLocation(expectedName);
}
return location;
}
private IResourceLocation _resolveThisFolderSpecificationLocation(String expectedName) {
IResourceLocation location = (IResourceLocation)_locations.get(expectedName);
if (location == null) {
location = _location.getRelativeLocation(_location.getPath()+"/"+expectedName);
if (location.getResourceURL() == null) {
return null;
}
_locations.put(expectedName, location);
}
return location;
}
private IResourceLocation _resolveChildFolderSpecificationLocation(String expectedName) {
List children = _getChildFolderLocators();
Iterator iterator = children.iterator();
while (iterator.hasNext()) {
RecursiveFileLocator child = (RecursiveFileLocator)iterator.next();
IResourceLocation location = child.getSpecificationLocation(expectedName);
if (location != null) {
return location;
}
}
return null;
}
This solution works well for me in
development with Eclipse and JettyLauncher and on Tomcat where a deployed WAR
file is exploded. If the servlet container you use does not explode the WAR file
this method will not work.There are a
couple of shortcomings, the locator caches locations of components and the
delegate caches specifications so I can't move a component from one folder to
another without restarting my application and I can't create a new sub-folder
in my file hierarchy without a restart. These could be worked on until
specification location can be manipulated in the IDE while running and
testing.I looked over the Tapestry
component resolution code to see how and when the delegate is invoked and I
should be able to patch the built-in component resolution to use the
RecursiveFileLocator and dispense with the delegate altogether.
Posted: Fri - August 13, 2004 at 08:17 AM
|