Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Friday, July 27, 2007

Tapestry 5 Preview at OSCON 2007

Yesterday was my session at OSCON. 45 minutes to covert Tapestry 5? Not a hope, especially with Rod Johnson running a little long.

I got the typical stunned reaction, followed by "is it done yet? Can I use it?" People crave T5 once they see it, and the presentation I do barely scratches the surface of what it can do today.

I've converted the presentation to PDF, which is great considering I had to flash through a lot of stuff about the grid very quickly.

Saturday, June 30, 2007

Upgrading Maven 2.0.5 to 2.0.7

I've been using Maven a bit for my "day job", converting a multi-module Ant build over to Maven. Since I just pushed out Tapestry 5.0.5 (awaiting a release vote), it seemed like a good time to look at upgrading to Maven 2.0.7.

The "day job" work has shown that having a traditionally stuctured aggregate project is viable even when using Eclipse. The Maven plugin for Eclipse is smart enough to read the parent POM as well as the child POM's in each module when building the overall classpath. I expect to restucture Tapestry 5 this way pretty soon, which will make it even easier to checkout and build the code than today. This structure, as a single mega-project, will make things easier when refactoring across module boundaries (a pain to do today, when using multiple Eclipse projects). However, the command line build will be very important, as it will catch some dependency issues that the Eclipse build will not.

Speaking of which ... Maven 2.0.7 (and 2.0.6 I believe) has tweaked dependencies. I had a couple of "runtime" scope dependencies inherited from tapestry-ioc to tapestry-core. These compiled fine in 2.0.5 but need to be changed in 2.0.7 to compile. In my case, since javassist was a "runtime" scope in tapestry-ioc, none of the javassist classes were visible when compiling tapestry-core.

A handy tool inside Maven for diagnosing this is the dependency plugin:

$ mvn dependency:resolve
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] org.apache.maven.plugins: checking for updates from tapestry
[INFO] org.apache.maven.plugins: checking for updates from howardlewisship.com
[INFO] org.apache.maven.plugins: checking for updates from codehaus.snapshots
[INFO] org.codehaus.mojo: checking for updates from tapestry
[INFO] org.codehaus.mojo: checking for updates from howardlewisship.com
[INFO] org.codehaus.mojo: checking for updates from codehaus.snapshots
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from tapestry
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from howardlewisship.com
[INFO] artifact org.apache.maven.plugins:maven-dependency-plugin: checking for updates from codehaus.snapshots
[INFO] ----------------------------------------------------------------------------
[INFO] Building Tapestry Core Library
[INFO]    task-segment: [dependency:resolve]
[INFO] ----------------------------------------------------------------------------
[INFO] [dependency:resolve]
[INFO] 
[INFO] The following files have been resolved: 
[INFO]    commons-codec:commons-codec:jar:1.3 (scope = compile)
[INFO]    commons-logging:commons-logging:jar:1.0.4 (scope = compile)
[INFO]    javax.servlet:servlet-api:jar:2.4 (scope = provided)
[INFO]    jboss:javassist:jar:3.4.ga (scope = runtime)
[INFO]    junit:junit:jar:3.8.1 (scope = provided)
[INFO]    log4j:log4j:jar:1.2.9 (scope = test)
[INFO]    org.apache.tapestry:tapestry-ioc:jar:5.0.5 (scope = compile)
[INFO]    org.apache.tapestry:tapestry-test:jar:5.0.5 (scope = provided)
[INFO]    org.easymock:easymock:jar:2.2 (scope = provided)
[INFO]    org.openqa.selenium.client-drivers:selenium-java-client-driver:jar:0.8.1 (scope = provided)
[INFO]    org.openqa.selenium.server:selenium-server:jar:0.8.1 (scope = provided)
[INFO]    org.testng:testng:jar:jdk15:5.1 (scope = provided)
[INFO] 
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Sat Jun 30 00:20:31 PDT 2007
[INFO] Final Memory: 4M/9M
[INFO] ------------------------------------------------------------------------
~/workspace/tapestry-core
$

Wednesday, June 06, 2007

Talking in Boulder and Denver

I'm doing back-to-back JUG talks next week:

Jun 13: Denver JUG

Jun 14: Boulder JUG

I'll be doing the same talk, a compressed introduction to Tapestry 5.

Wednesday, May 16, 2007

Free and Excellent Code Coverage for Eclipse

The EMMA plugin for Eclipse is my latest addition to the "can't live without it" category. It allows you to run applications and test suites from within Eclipse and gather code coverage ... better yet, that code coverage data is shown visibly in your code, much as it is in an HTML Cobertura report.

The plugin is slick, fast, easy and non-intrusive.

The one thing you do need to do is split your output directories, so that production code goes into bin, and test code goes into test-bin; this allows you to turn off coverage information for your test classes, and just gather

Of course, IDEA also has code coverage, based on EMMA, built right in.

Tuesday, May 15, 2007

Tapestry at JavaOne 2007

JavaOne was a lot of fun this year; I didn't arrive in time for the JavaFX keynote, but I understood it to be underwhelming. Yep ... that's what Java needs ... to take on Adobe and Microsoft in Adobe's home territory.

On the other hand, this was a very social JavaOne; lots of good conversations and meeting with people I only know online.

I also attended the Java Server Faces 2.0 Expert Group kickoff. I can't really see where that's going to go, alas. It's not like everyone bowed down and said "JSF 2.0 shall be Tapestry" (not even Jacob Hookom, who does see Tapestry as a good model for much of JSF 2.0). But I did get a chance to chat with Gavin King ... about motorcycles. He's riding a Ducati now, and I'm thinking about picking up a new bike once I buy a house. Gavin --- I used to ride a Yamaha FZR1000.

The best part of the JSF meeting was when Jonathan Swartz stopped by. We shook hands and talked about how slow adoption of JCP standards.

Elsewhere ... the fun part about the RubyEnv parody ad ...

... is that Tapestry is jar #3. Interesting, though I guess it's hard to know what "JSF in a Jar" looks like, or "Struts in a Jar" for that matter. My intention is to reverse the meaning, make Tapestry 5 something that'll make the Ruby guys envious. We'll see.

Saturday, May 05, 2007

Tapestry at JavaOne

Not only am I doing my Tapestry 5 "BOF" session (Tapestry 5: Java Programming Language Power, Scripting Ease), but there's also TS-7354 (Fast Feedback Loop: Unit Testing Strategies for Tapestry) and BOF-9834 ( Grails, Sails, and Trails: Rails Through a Coffee Filter).

I'll be arriving Tuesday afternoon and staying through Saturday. JavaOne is always hectic, especially right around my session. However, I'd be glad to meet with people informally; drop me an e-mail at hlship AT gmail DOT com and we'll see.

Meanwhile, did anyone else suffer through Sun's Schedule Builder? Just a horrendous application, made all the worse that it is crying out for the Ajax treatment. Show me the sessions side-by-side, dynamically filter the page by day and time slot and session track, and let me click or drag the sessions I like. For god's sake ... don't make me schedule one sesson at a time and then scroll back to the top of the page!

Thursday, May 03, 2007

Launching IntelliJ for the first time...

First the damn cool crowd of NFJS speakers got me to buy a Mac, now they've suckered me into switching from Eclipse to IntelliJ. God help me, I don't even know where to start with this thing.

Tuesday, April 24, 2007

Duh! Annotation values as constants, not literals

One funny thing I discovered recently is that annotation values don't have to be literal values. You can reference constants. Thus I went from:

@Scope("perthread")
public class .... 

To:

@Scope(IOCConstants.PERTHREAD_SCOPE)
public class ...

I think this is a Good Thing, even though it's a bit more verbose (a static import helps there) because I know at compile time that there isn't a typo in my constant's variable name ... whereas @Scope("prthread") would not be caught until runtime.

Pleasing the Crowds, Improving IoC, Extending the Community

I've been quite busy of late: a nearby Tapestry 4 project to pay the bills, a bunch of chances to talk to crowds about Tapestry 5 (in Philadelphia, Minneapolis and at home in Portland, Oregon).

I've significantly changed my Tapestry 4 presentation; it now highlights the BeanForm component, then the Table component and lastly Trails. I'm showing the high end of what's possible in Tapestry, rather than showing people the gory little details up front. That usually gets their attention.

I then show what's going on in Tapestry 5 and that really get's peoples jaws dropping. People crave this. They want to use it today. That is the desired effect. Case-insensitive, pretty URLs. Live class reloading. No XML. Incredible performance. Best of breed exception reporting. And we've barely gotten started yet.

I've been busy: Guice has set a high bar for IoC containers, but it still doesn't have certain features that Tapestry is dependent upon. That hasn't slowed me from taking the best ideas from it. Tapestry 5 now has a similar approach to autobuilding, and in most cases, a Tapestry service no longer needs a service builder method. Instead, a module can define a static bind() method and use the ServiceBinder to tell Tapestry about service interfaces and service implementations: Here's an example from the Tapestry module itself:

    public static void bind(ServiceBinder binder)
    {
        binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class);
        binder.bind(PersistentLocale.class, PersistentLocaleImpl.class);
        binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class);
        binder.bind(
                ApplicationStatePersistenceStrategySource.class,
                ApplicationStatePersistenceStrategySourceImpl.class);
        binder.bind(BindingSource.class, BindingSourceImpl.class);
        binder.bind(TranslatorSource.class, TranslatorSourceImpl.class);
        binder.bind(PersistentFieldManager.class, PersistentFieldManagerImpl.class);
        binder.bind(FieldValidatorSource.class, FieldValidatorSourceImpl.class);
        binder.bind(ApplicationGlobals.class, ApplicationGlobalsImpl.class);
        binder.bind(AssetSource.class, AssetSourceImpl.class);
        binder.bind(Cookies.class, CookiesImpl.class);
        binder.bind(Environment.class, EnvironmentImpl.class);
        binder.bind(FieldValidatorDefaultSource.class, FieldValidatorDefaultSourceImpl.class);
        binder.bind(RequestGlobals.class, RequestGlobalsImpl.class);
        binder.bind(ResourceDigestGenerator.class, ResourceDigestGeneratorImpl.class);
        binder.bind(ValidationConstraintGenerator.class, ValidationConstraintGeneratorImpl.class);
        binder.bind(EnvironmentalShadowBuilder.class, EnvironmentalShadowBuilderImpl.class);
        binder.bind(ComponentSource.class, ComponentSourceImpl.class);
        binder.bind(BeanModelSource.class, BeanModelSourceImpl.class);
    }

The ServiceBinder interface is fluent, we could follow on with .withScope(), .withId() or .eagerLoad() if we wanted. In most cases, defaults from there are sensible or come from annotations on the implementation class (and therefore, rarely need to be overridden).

The primary mechanism for injection is via constructor parameters. In general, annotations are not necessary on those parameters any more, and Tapestry will find the correct object or service automatically (primarily by matching on parameter type).

Service builder methods are still useful for when a service involves more than just instantiating a class, such as registering it for some kind of notification from another service:

    public ComponentClassResolver buildComponentClassResolver(ServiceResources resources)
    {
        ComponentClassResolverImpl service = resources.autobuild(ComponentClassResolverImpl.class);

        // Allow the resolver to clean its cache when the source is invalidated

        _componentInstantiatorSource.addInvalidationListener(service);

        return service;
    }

The autobuild() method will construct an instance, performing necessary injections. We can then perform any additional realization before returning it.

The end result has been to simplify and minimize the amount of code in the module builder classes.

Tapestry IoC now supports services that do not have a service interface; the actual type is used as the service interface and the service is not proxied: it is created on first reference and can't have interceptors. Normal services are proxied on first reference, and only realized (converted into a full service with a core service implementation and interceptors) on first use (the first method call).

In a step away from the code, we are running a vote to add Dan Gredler as a Tapestry committer. I expect that to run successfully, and I also expect to add a few more people to the roles soon.

I'm getting very excited. Things are coming together nicely (but never fast enough).

Saturday, April 07, 2007

Ouch! VMWare Fusion is not compatible with VMWare Player

Turns out that VMWare Fusion, the VMWare for Mac OS X, creates an image that isn't compatible with the VMWare player on Windows. This is why I'm happy I do test runs, but it does mean I have to spend another few hours recreating my workshop environment on Windows, using VMWare Workstation. So much for catching a movie tonight, or leaving the house tomorrow.

Hopefully this is something that'll be corrected in a later release.

Tapestry 5 at JavaOne and OSCON

My session at JavaOne has been scheduled:

BOF-6563: Tapestry 5: Java Programming Language Power, Scripting Ease
Esplanade 307-310
May 9 20-07, 8:55pm

In addition to JavaOne and NFJS, I've also been accepted to speak again at OSCON this year. I'm looking forward to spreading the Tapestry 5 message and meeting people using Tapestry, as always!

Wednesday, April 04, 2007

More Servlet Spec Wish-List Ideas

I'm thinking back to an earlier post about Servlet Spec 2.5 and what's missing.

Here's a few more ideas, based on implementing Tapestry 5 and other things besides. My previous ideas still stand.

web.xml introspection: It would be great to know what's going on inside web.xml. An API to query the web.xml meta-data and learn the names and mappings of the other servlets and filters would be great. Tapestry 5 acts as a filter and it would be handy if it could know what other servlets were in the WAR to help it decide whether to process a URL as inside Tapestry, or let another servlet handle it.

Event registration: Many of the listeners possible with the servlet API only apply to objects created by the servlet container's brain-dead IoC container. If my code needs to recerive notifications as a say, HttpSessionAttributeListener, I currently have to put together a Rube Goldberg machine in order to receive those notifications. There should be an API, perhaps attached to the ServletContext object, for registering for such notifications.

Quality of Service: Currently we have single server or clustered. Single server is not scalable beyond a certain point. Clustered causes a lot of problems managing data and introduces lots of overhead in terms of copying session attributes around the cluster.

I would like at least one additional level of service: non-replicated cluster. It assumes sticky sessions such that all requests for a given session are handled by a single box within the cluster. If that box fails, then future requests will be sent to a different box within the cluster, and the application will receive a notification that data has been lost.

Really, most non-financial applications can survive a rare loss of data midway through a session, and having this intermediate service level would allow the vast majority of applications to scale much, much higher than is possible today. I've previously implemented something like this by storing an object in the HttpSession that contained a Map in a transient variable.

Concurrency documentation: Many objects in the Servlet API are shared between threads (ServletContext, HttpSession) and it would be good form, and very useful, to know the proper semantics there. Futher, there needs to be better documentation for application and framework developers about the implications thereof.

In light of what I've learned about the Java Memory Model from Brian Goetz (and his book, Java Concurrency in Practice) any non-synchronized access to a shared object, say a mutable object stored into the HttpSession, is suspect: a potential source of scary, non-reproduceable, intermittent concurrency bugs.

Should all methods of such objects be synchronized? Can the servlet container acquire a lock when the HttpSession is accessed and hold it for the application for the remainder of the request (that would be handy!). Could there be an API for allowing the framework/application to manage access to HttpSession variables?

It's clear that the HttpSession API was designed to store small, immutable objects such as Strings and Integers. It is also clear that most applications don't use it this way (though Tapestry is a little better because it mostly stores individual page fields in the session, which are often small immutables, whereas most frameworks and applications store more complex state holding mutable objects).

... well, I've run out of steam, but I'll be racking my brains for more ideas like these.

Tuesday, April 03, 2007

Tapestry 5 IoC from a Guice Perspective

So Guice has a few good ideas, and with Tapestry 5 still in a fluid state, I've absorbed a few of them into Tapestry 5 IoC. At the core, is the key ObjectProvider interface:

public interface ObjectProvider
{
    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator);
}

Implementations of this interface plug into a chain-of-command within Tapestry and, via the AnnotationProvider, can query for the presence of annotations to help decide exactly what is to be injected. A lot of Guice's examples, things like the @Blue service, are easily replicated in Tapestry 5 IoC, there's just not a lot of need for it. I supposed it would look something like:

public class BlueObjectProvider {
  private final Map<Class,Object> _blueObjects;

  public BlueObjectProvider(...) { ... }

    <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ServiceLocator locator) {
      if (annotationProvider.getAnnotation(Blue.class) == null) return null;

      return objectType.cast(_blueObjects.get(objectType));
  }
}

Implicit here is a service configuration of implementations, keyed on object type, used when the @Blue annotation is present. Here's what the module would look like:

public class BlueModule {

  public ObjectProvider buildBlueProvider(Map<Class, Object> configuration)
  {
    return new BlueObjectProvider(configuration);
  }

  public void contributeMasterObjectProvider(
    @InjectService("BlueProvider") ObjectProvider blueProvider,
    OrderedConfiguration<ObjectProvider> configuration) {
    
    configuration.add("Blue", blueProvider);
  }

  public void contributeBlueProvider(MappedConfiguration<Class, Object> configuration,
    @InjectService("MyBlueService") Service myBlueService) {
    configuration.add(Service.class, myBlueService);
  }
}

public class OtherModule {

  public OtherService build(@Inject @Blue Service service) {
    return new OtherServiceImpl(service);
  }
}

On the one hand, this is more verbose, since its somewhat more procedurally based than Guice's approach, which is pattern based. On the other hand, it demonstrates a couple of key features of Tapestry 5 IoC:

  • Service construction occurs inside service builder methods; there's no layer of abstraction around service instantiation, configuration and initialization ... you just do it in Java code. There simply isn't a better language for descibing instantiating Java objects and invoking Java methods than Java itself.
  • Dependency injection is a concern of the module not the service implementation. The special annotations go in the module class, and the service implementation class is blissfully unaware of where those dependencies come from or are identified and obtained.
  • Dependencies are passed to the service builder methods, which do whatever is appropriate with those dependencies; in many cases, the dependencies are not passed to the instantiated class, but are used for other concerns, such as registering the new service for event notifications from some other service.
  • The contributeBlueProvider() method can appear in many different modules, and the results are cumulative. This allows one module to "lay the groundwork" and for other modules to "fill in the details". And, again, if this style of injection ("flavored" with the annotations) really became popular, it would be easy to integrate it into the central Tapestry 5 IoC module so it could be leveraged everywhere else.

In fact, in the OtherModule class, you can see how the injection lays out once the groundwork is prepared: Just an @Inject qualified with @Blue and you're done. Again, I can't emphasize enough how well moving construction concerns into the service builder methods works; it gives you complete control, it's unit testable in a couple of different ways, it keeps the service implementations themselves clean (of dependencies on the IoC container -- even in the form of annotations). It also keeps explicit what order operations occur in. HiveMind chased this with ever more complex XML markup to describe how to instantiate, configure, and initialize services.

That last feature, the interaction of multiple modules discovered at runtime, is the key distinguishing feature, and the one that's hardest to grasp. For me, it is the point where an IoC container transitions from an application container to a framework container. In an application container, you explicitly know what modules are present and how they will interact. It's your cake, starting from raw flour, sugar and eggs. Nothings in the mix that you didn't add yourself.

With HiveMind and Tapestry 5 IoC, the goal is to build off of an existing framework, and its interwoven set of services. The framework, or layers of framework, provide the cake, and you are trying to add just the icing. Configurations are the way to inject that icing without disturbing the whole cake, or even knowing the cake's recipe.

So Guice is a good kick in the pants, and Tapestry 5 IoC has evolved; the old @Inject annotation, which used to include a string attribute to name the thing to inject, has been simplified. The @Inject annotation is still there, but the string part is gone. Tapestry primarily works from the object type to match to the lone service implementing that interface. When that is insufficient, there's the MasterObjectProvider and Alias service configurations, which provide plenty of room to disambiguate (possibly by adding additional annotations to the field being injected).

The big advantage is type safety; refactor the name of a service interface and you'll see little or no disruption of the service network, because dependencies are almost always expressed in terms of just the (refactored) service interface, rather than any additional layer of "logical name".

I think there's a growing issue with Google-worship. It was interesting and disturbing to see so many people announce "Spring is dead! Long live Guice!" on various blogs (and I'll even admit to a little envy ... how come Tapestry 5 IoC doesn't generate this kind of interest?). Guice itself is pretty useless by itself, just as Spring's IoC container is, by itself, useless. The value of Spring is in what's built on top of the container: Wrappers for JDBC, Hibernate, JPA, JMS and every other acronym you can name. Transaction support and interceptors. And, more recently, the really solid AspectJ layer. If Guice wants to be a Spring-killer, or even a real player in the IoC space, it needs to embrace and extend Spring: make it easier to use those Spring integrations than it would be using Spring natively.

Thursday, March 15, 2007

Ruby script for creating new Tapestry 5 projects

One thing with building lots of demos for upcoming presentations, labs, & etc. is that I'm having to use mvn archetype:create a lot and that command line is just hideous.

So I took a deep breath, stepped back, and wrote a Ruby script, newproj to simplify the process:

#!/usr/bin/ruby

require 'getoptlong'

opts = GetoptLong.new(
  [ "--group", "-g", GetoptLong::REQUIRED_ARGUMENT ],
  [ "--artifact", "-a", GetoptLong::REQUIRED_ARGUMENT ],
  [ "--package", "-p", GetoptLong::OPTIONAL_ARGUMENT ],
  [ "--version", "-v", GetoptLong::OPTIONAL_ARGUMENT ]
)

group = nil
artifact = nil
package = nil
version = "1.0.0-SNAPSHOT"
error = false

begin
  opts.each do | opt, arg |
    case opt
      when "--group" 
        group = arg
      when "--artifact" 
        artifact = arg
      when "--package" 
        package = arg
      when "--version" 
        version = arg
    end
end
rescue GetoptLong::Error
  error = true
end

if error || ARGV.length != 0 || group == nil || artifact == nil
  puts "newproj: --group groupId --artifact arifactId [--package package] [--version version]"
  exit 0
end

package = package || "#{group}.#{artifact}"

command = "mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart -DarchetypeVersion=5.0.3"
command << " -DgroupId=#{group} -DartifactId=#{artifact} -DpackageName=#{package} -Dversion=#{version}"

puts command

Kernel::exec(command)

I'll eventually add to this; it needs the option to control the mvn -o (offline) flag, and further in the future, the ability to choose the correct archetype (once we add more than just quickstart). But this sure beats copying and pasting out of the documentation, like I've been doing.

Monday, March 05, 2007

Q: What's your toughest coding challenge? A:...

I've seen this question before on resumes and other people's blogs. I've never had a specific example that worked well. Previously, I've had vague stories of struggling with, say, Javassist ... but the best story here would involve my own code.

A few weeks ago, I noticed that some of my tests, the integration tests, didn't run very well on the Tapestry Bamboo server (our continuous integration server). Occasionally, even when developing on my Mac, I'd see these anomalous errors.

When things work in unit tests and fail in integration tests, one of the first places to look is for concurrency issues. Tapestry has a lot of code related to concurrency: synchronizing, caching, clearing of caches, on-demand instantiation, the works.

I started seeing things that made me question Java reality. For instance, this method is only invoked from a dynamic proxy, from a synchronized method, and the proxy only invokes the method once, yet I was seeing multiple calls:

public class OneShotServiceCreator implements ObjectCreator
{
    private final ServiceDef _serviceDef;

    private final ObjectCreator _delegate;

    private boolean _locked;

    public OneShotServiceCreator(ServiceDef serviceDef, ObjectCreator delegate)
    {
        _serviceDef = serviceDef;
        _delegate = delegate;
    }

    /**
     * We could make this method synchronized, but in the context of creating a service for a proxy,
     * it will already be synchronized (inside the proxy).
     */
    public Object createObject()
    {
        if (_locked)
            throw new IllegalStateException(IOCMessages.recursiveServiceBuild(_serviceDef));

        _locked = true;

        return _delegate.createObject();
    }

}

The code that calls this looks something like:

private synchronized SomeService _delegate()
{
  if (_delegate == null) {
    _delegate = (SomeService) _creator.createObject();
    _creator = null;
  }

  return _delegate;
}

You can see how this would tend to drive you a bit crazy. Eventually, I realized what was going on ... sometimes a runtime exception (an OutOfMemoryError) would be thrown, so the proxy would never complete _delegate(), and would (later, possibly in a different thread), re-invoke this the createObject() method ... and fail, because the lock was set. Solution:

     */
    public Object createObject()
    {
        if (_locked)
            throw new IllegalStateException(IOCMessages.recursiveServiceBuild(_serviceDef));

        // Set the lock, to ensure that recursive service construction fails.

        _locked = true;

        try
        {
            return _delegate.createObject();
        }
        catch (RuntimeException ex)
        {
            _log.error(IOCMessages.serviceConstructionFailed(_serviceDef, ex), ex);

            // Release the lock on failure; the service is now in an unknown state, but we may
            // be able to continue from here.

            _locked = false;

            throw ex;
        }

    }

I also started seeing bizarre error messages, like: Unable to resolve page 'MyPage' to a component class name. Available page names: Start, MyPage, YourPage.. What the hell was going on there ... was something modifying the underlying CaseInsensitiveMap? But the access methods are synchronized.

Once you start seeing bizarre concurrent behavior, and after listening to a few Brian Goetz talks about concurrency (not to mention his great book) ... well, you can start getting paranoid. Maybe its a bug in the JVM (trust me, it's never going to be a bug in the JVM). Maybe I'm not understanding access to effectively immutable objects outside of synchronized blocks (that's a bit more reasonable).

Then I saw something even more bizarre:

    public  T getService(String serviceId, Class serviceInterface, Module module)
    {
        notBlank(serviceId, "serviceId");
        notNull(serviceInterface, "serviceInterface");
        // module may be null.

        ServiceDef def = _moduleDef.getServiceDef(serviceId);

        if (def == null) throw new IllegalArgumentException(IOCMessages.missingService(serviceId));

        if (notVisible(def, module))
            throw new RuntimeException(IOCMessages.serviceIsPrivate(serviceId));

        Object service = findOrCreate(def);

        try
        {
            return serviceInterface.cast(service);
        }
        catch (ClassCastException ex)
        {
            // This may be overkill: I don't know how this could happen
            // given that the return type of the method determines
            // the service interface.

            throw new RuntimeException(IOCMessages.serviceWrongInterface(serviceId, def
                    .getServiceInterface(), serviceInterface));
        }
    }

See the comment about how that code is not reachable? It was reached! It took a while, but I found out that sometimes I was getting back the wrong ServiceDef object out of the ModuleDef. Rarely, but sometimes.

Then it struck me ... all of this strange behavior was traced to my CaseInsensitiveMap implementation. Sure it has a 100% code coverage test suite ... but when I double checked the code I found some sloppiness: it uses a couple of instance variables as a scratch pad while searching. This means that, under the right timing, even reads of the effectively immutable Map by different threads would interfere with each other, with Thread A getting the result from Thread B's query. Here's a diff of the solution, which basically moved those two scratch variables into their own inner class.

Things are working perfectly (for now), which is a great relief. This bug hunt was a distraction from other things, but better to tackle it now than later. This is also a good example of the need for a continuous integration server ... the fact that the server is on a different OS and JDK ensured that the tests would fail pretty consistently on the CI server even though they mostly worked on my Mac. Brian states this as well is his book ... test on all sorts of hardware with all sorts of memory configurations, because you don't know which one aligns with your bugs and brings them out into the open.

Saturday, March 03, 2007

T5 Spring Integration -- Hibernate next?

I've put together a first pass at Tapestry 5/Spring integration as another new module: tapestry-spring-integration.

It's small and to the point, leveraging the normal Spring configuration for web applications, just making beans available for injection into Tapestry components and services. Also, it makes accessing the Spring beans (from the Tapestry side) case insensitive.

Next up will be some form of Tapestry 5 / Hibernate integration ... however, due to the conflicting licenses, I may take a pass at tapestry-ejb3.

That licensing is driving me crazy; I've checked repeatedly with the Lords of Apache Licensing, and they maintain that the ASL is not compatible with the LGPL, that by linking to LGPL code (importing LGPL classes, in Java terms) the LGPL "infects" the ASL code, adding an unwanted restriction not present in ASL.

From the sidelines, it's funny and disturbing: The ASL folks talk about "fauxpen source licenses" as if openness was purely black and white, and the least restriction was a total betrayal. Meanwhile, the FSF camp keeps saying the licenses are compatible. Go figure.

I've had discussions with people who really got heated over ASL vs. LGPL. Andrew Oliver, for one, really tried to sell me on the idea that the ASL was a boon for corporations over individuals. From my position, the theoretical taking of "Tapestry" over by, say, IBM and rebranding it as "IBM Web Presentation Objects" (or something) would be laughable ... and even if it did happen, I think it would still be good for Tapestry, which is good for me, and good for the Tapestry community. True open source forks are really rare and look more like a straw man argument than a real consideration.

Wednesday, February 28, 2007

Screencast #5: Grid Component

The latest Tapestry 5 Screencast is now ready; this one clocks in at almost 11 minutes, and shows me forgetting parameter names and such as I set up a grid with my good ole' fallback: iTunes music data. I actually made a couple of mistakes on purpose to show off the exception reporting (that's only gotten better since 5.0.2, the base line for this screencast).

I think this shows off how quickly and easily you can pull data out of a service and up on the screen. To borrow a phrase: "I don't have time for drag and drop!"

One minor mistake was that I didn't map the context correctly (I had cut-n-pasted an existing launch configuration); thus all the URLs are prefixed with "hilo/" rather than, say, "musiclib/". Oh well. Oops, and I forgot to hide the dock!

Tuesday, February 27, 2007

Monday, February 26, 2007

Tapestry 5.0.2 released

A new preview release, Tapestry 5.0.2 is now available. It fixes a number of bugs and adds a number of features (including line precise exception reporting, and sortable Grid columns). Along the way, I found out some important deployment notes about Tomcat (and JBoss).

The new release is available via the central Maven repository, or via direct download.

Sunday, February 25, 2007

Tapestry 5 and Groovy

I was curious just how well T5 and Groovy would work together, so I took an existing application, enabled Groovy for it (using the Eclipse plugin) and added a Groove page to my existing application:

package org.example.hilo.pages;

class Holder
{
  String firstName;
  String lastName;
  int age;
}

class Groove {

 def message()
 {
   "Get your Groove on, Baby!"
 }
 
 def onAction()
 {
  println "onAction invoked"
 }
 
 def items()
 {
   [ new Holder(firstName: "Howard", lastName:"Lewis Ship", age:40), new Holder(firstName: "Scott", lastName: "Simon", age:42) ]
 }
}

Guess what? It just worked, no problems. I generated a Grid with the two Holder items, my onAction() method is invocable, and I can retrieve the message. It is just bytecode under the covers.

Alas, without some kind of annotation support, there's no way to mark a field as persistent, so there's tremendous limits on what can be accomplished right now. I've heard rumors that some kind of JDK annotation support is forthcoming; failing that, I've thought about an additional file, read at runtime, that would provide class, method and field annotations. But I'd rather someone else does it.

In addition, Tapestry is reading the compiled .class file; I haven't even thought about trying to get things to work from uncompiled .groovy files and I can imagine there may be some class loader headaches there.

Still, for five minutes effort, it was nice. And I really think I could come to really like the streamlined, sensible syntax ... and for god's sake, first class closures!