The Chain Builder

This document explains how to build chains programmatically by using the chain builder. Before proceeding, we should be familiar with the fundamental chain concepts. Building a chain programmatically means that we do not use services.xml to configure the chain and its components. Instead, we build a chain directly from Java code. This is useful for testing.

Building a Chain

Let us try to build a simple chain of components. For this example, we call our components filters. What they do is not important - what matters is that all filter classes have a common baseclass. First, we define the baseclass and two implementations, FilterA and FilterB:

    static class Filter {}
    static class FilterA extends Filter {}
    static class FilterB extends Filter {}

    Filter a = new FilterA();
    Filter b = new FilterB();

To make the following examples clearer, we will use this helper function to obtain a new chain builder for a chain that will be named "myChain":

    private ChainBuilder<Filter> newChain() {
        return new ChainBuilder<>("myChain");
    }
Now, let's build a chain where filter a is placed before filter b:
    Chain<Filter> chain = newChain().
            add(a, before(b)).
            add(b).
            build();

Or, alternatively:

    Chain<Filter> chain = newChain().
            add(a).
            add(b, after(a)).
            build();
The statements before(b) and after(a) are calls to static methods in a class called Dependencies, so we need to import those. Here is a fully working example:
import static com.yahoo.yolean.chain.Dependencies.after;
import static com.yahoo.yolean.chain.Dependencies.before;

class MyChainTest {
    static class Filter {}
    static class FilterA extends Filter {}
    static class FilterB extends Filter {}

    Chain<Filter> getMyChain()
        Filter a = new FilterA();
        Filter b = new FilterB();

        return new ChainBuilder<>("myChain").
                add(a, before(b)).
                add(b).
                build();
    }
}

Adding Dependencies

Above, we added components to the chain using the add method. Let's take a closer look at how that method is defined:

  public final ChainBuilder<T> add(T component, Dependencies<? extends T>... dependencies)

As we can see, the builder itself is returned, allowing us to chain the add calls. But the most interesting part is its parameters:

  1. the component itself
  2. one or more Dependencies

Each 'Dependencies' object represents an ordering constraint that applies to the component. Remember, there are three dependency types, and the Dependencies class provides methods for each of them:

provides
Takes a varargs of string labels. Example:
  provides("removeExplicitLanguage", "familyFilter");
before
Three separate methods allow adding 'before' constraints. Each method takes a varargs of one of the following: component instances, component classes, or string labels (something that another component provides). Examples:
  before(finalFilter, penultimateFilter, …);
  before(FinalFilter.class, PenultimateFilter.class, …);
  before("final", "penultimate", …);
after
Just like 'before', there are separate methods that take either a varargs of component instances, component classes, or string labels (something that another component provides). Examples:
  after(firstFilter, secondFilter, …);
  after(FirstFilter.class, SecondFilter.class, …);
  after("first", "second", …);

Hence, a more complex example of building a chain can look like this:

    Chain<Filter> chain = newChain().
            add(firstFilter, provides("first"), before(secondFilter, finalFilter)).
            add(secondFilter, provides("second"), after("first")).
            add(penultimateFilter, after("first", "second")).
            add(finalFilter, after(SecondFilter.class, PenultimateFilter.class)).
            build();

Note that most of these constraints are only added for sake of the example. We could, of course, have obtained the same ordering by using only a few of them.

Subclasses

If a component has a before/after dependency on a class, it also has the same dependency on all its subclasses. Consider the following example, where component a is said to be before all instances of class FilterB. This also means that component a will be placed before all instances of the subclass ExtendsFilterB.

    static class FilterA extends Filter {}
    static class FilterB extends Filter {}
    static class ExtendsFilterB extends FilterB {}

    Filter a = new FilterA();
    Filter b = new ExtendsFilterB();

    Chain<Filter> chain = newChain().
                add(a, before(FilterB.class)).
                add(b).
                build();

Annotation dependencies

In addition to the dependencies given programmatically, the chain builder also honors any dependency annotations that are given in the implementation classes. Any conflicts across these dependencies will also be detected by the builder.