Recent posts from Tom Davies

Tom Davies

Guicing Up Crucible

Tom Davies talks about Atlassian August 21, 2007 6:23 PM

Most Atlassian products use some inversion of control (IoC) container – Confluence uses Spring and JIRA uses Picocontainer.

Fisheye and Crucible don't use any IoC at present, and as part of the process of choosing one we decided to spike using Guice as Crucible's IoC container.

We wanted to learn about Guice because it has some interesting differences compared to Spring:

  • Configuration is in Java, not XML. This gives better compile time type checking, better refactoring support, and a more expressive configuration language.
  • Injection is done via annotations instead of named attributes (you can do this with Spring 2 as well). This has the disadvantage that your concrete classes depend on Guice, but the advantage that the IoC container knows exactly which injections are expected, so that not providing a binding becomes an error at start up rather than a later NullPointerException.

The first step was to bootstrap the container, which I did by installing a ServletContextListener which sets up Guice when the context was initialized:

public void contextInitialized(ServletContextEvent servletContextEvent) {
        CrucibleModule.init();
    }

The static init method does three things:

  1. Create an Injector from the CrucibleModule class.
  2. Make that Injector available as a static variable for those cases when we have to manually inject dependencies.
  3. Configure the frameworks used by Crucible to allow Guice to handle object creation.
public class CrucibleModule extends AbstractModule {
    private static Injector injector;
...
 public static void init() {
        injector = Guice.createInjector(new CrucibleModule());
        // set up xwork to use our special objectfactory
        ObjectFactory.setObjectFactory(injector.getInstance(ObjectFactory.class));        
    }
}

The basic challenge when you fit an IoC container into an application is to allow the container to inject dependencies into objects as they are created. This means taking over object creation for as many parts of your frameworks as you can.

For the purposes of the spike we want to remove the need for a servlet filter which makes the current HttpServletRequest and HttpServletResponse available as static fields.

In other words, instead of writing CrucibleFilter.getRequest() we want to say @Inject HttpServletRequest request; as a field in a class, or @Inject MyClassConstructor(..., HttpServletRequest request, ...) to provide the value as an argument to a constructor.

To do this either the object must be constructed by Guice, or for the first case we can cheat and use injector.injectMembers(existingObject) which of course doesn't work for constructor injection. In addition, the lifecycle of the object must match the lifecycle of the object being injected into it – injecting a request into an xwork Interceptor doesn't work, as the interceptor has a multi-request lifetime. Xwork Actions are fine, as they are created for each request.

We can address the scope mismatch by injecting a Provider instead of an actual value:

private @Inject
    Provider<HttpServletRequest> requestProvider;

and then calling requestProvider.get() when we need the object it provides. I don't understand how the type parameter is available to Guice at runtime, but it is, as this code works.

The cookbook recipe for what I needed to do is:

  1. Add the context listener to initialise Guice to web.xml
  2. Add the GuiceFilter to web.xml, to make request and response available.
  3. Make the CrucibleModule install Guice's ServletModule, which binds the request and response into the module.
  4. Create all actions (and other Xwork objects) via Guice:
    1. Bind a new ObjectFactory implementation in the CrucibleModule: bind(ObjectFactory.class).to(GuiceObjectFactory.class).in(Scopes.SINGLETON) this class uses Guice for object creation by overriding the buildBean method:
      @Inject private Injector injector;
      ...
      public Object buildBean(Class aClass) throws Exception {
              return injector.getInstance(aClass);
      }
    2. Set this ObjectFactory into Xwork's ObjectFactory singleton when we initialise Guice: ObjectFactory.setObjectFactory(injector.getInstance(ObjectFactory.class))
  5. Change BaseAction to take an HttpServletRequest as a constructor argument (we can't use field injection because BaseAction uses the request in its constructor)
  6. Change every subclass of BaseAction to have the appropriate constructor, with an @Inject annotation.
  7. As described above, change Interceptors which need the request to have a Provider injected.
Tom Davies

For this Fedex day I chose to implement HTML version diffs for Confluence.

Confluence shows the differences between versions in terms of the markup which produced each version of a page – this isn't always very clear. Often a paragraph is duplicated, with additions in one copy and deletions in the other:

paradiff.jpg

This looks better when the HTML is diffed. Or rather it would if the algorithm I chose hadn't made some unfortunate choices (I converted the python difflib to Java, because the only HTML diff I found via Google was based on it – I think I would have been better off using jrcs, the library we use now, even though it seems to be a bit of an orphan.):

htmlpara.jpg

The strategy I use is:

  1. Tokenize each version of the HTML into tags and words (so <img width=200 height=100 src="/xxx/foo.jpg"/> is a single token from the diff point of view, but <p>A Paragraph</p> is four).
  2. Run the diff algorithm and concatenate all the operations it produces – 'equal', 'changed' (which becomes an insert followed by a delete), 'added' and 'deleted'. Any text tokens get surrounded with a <span> with an appropriate class, and some other tags (like IMG) do too.
  3. Turn the HTML produced by the previous step into a DOM tree and traverse it, marking block level constructs (like <P> and <TR>) with a class to indicate that part of their contents have changed – that produces the blue lines in the margin.
  4. Replace the <a ... /> tags produced in the previous step by Neko with <a ...></a>, because the former breaks Safari and IE (at least).

There's still a lot to do:

  1. Try jrcs instead of my python difflib conversion.
  2. Apply the change anchors more sparingly, just once to each element which justifies a blue marker.
  3. Figure out what to do for lists which have just had indentation changes – these are not handled well at present.
  4. Look at more corner cases – for instance, what about a change which just changes the class of a DIV? How can we show that?