Implementing execution context

Context with adapters

Building context with flexible adapters significantly reduces coding time. The goal is to do the mappings only and retain lazy value fetching.

Following excerpts are taken from Hyperon Motor-Insurance Demo App.

Context Implementation


public class MotorContext extends HyperonContext {

    private Quote quote;
    private Coverage coverage;
    private Driver driver;
    private Option option;

    ...

    /**
     * gets value from [path],
     * where [path] is valid path in canonical model,
     * for example:
     * - quote.driver.age
     * - quote.driver.address.zipcode
     * - coverage.option.quote.planCode
     * - coverage.code
     */
     
    @Override
    public Object get(String path) {
        String name = getFirstToken(path);
        String subpath = skipFirstToken(path);

        Adapter adapter = null;
        if (name.equals("quote")) {
            adapter = new QuoteAdapter(getQuote());
        }

        if (name.equals("coverage")) {
            adapter = new CoverageAdapter(getCoverage());
        }

        if (name.equals("option")) {
            adapter = new OptionAdapter(getOption());
        }

        if (adapter != null) {
            return adapter.get(subpath);
        }

        return null;
    }

    private String getFirstToken(String path) {
        return StringUtils.substringBefore(path, ".");
    }

    private String skipFirstToken(String path) {
        return StringUtils.substringAfter(path, ".");
    }
}

Path argument of get method is divided into tokens ('.' delimited). Based on first token's value the proper adapter is chosen and remaining tokens (subpath) are passed to it.

Sample adapters

These are sample adapters:

public class QuoteAdapter extends Adapter {

    private Quote quote;

    public QuoteAdapter(Quote quote) {
        this.quote = quote;
    }

    @Override
    protected Mapping getMapping() {
        return new Mapping()
            .add("planCode", quote != null ? quote.getPlan() : null)
            .add("driver", new DriverAdapter(quote.getDriver()))
            .add("vehicle", new VehicleAdapter(quote.getVehicle()))
            .add("options", new CollectionAdapter(quote.getOptions(),
                option -> new OptionAdapter((Option) option)
            ));

    }

}

public class CoverageAdapter extends Adapter {

    private Coverage coverage;

    public CoverageAdapter(Coverage coverage) {
        this.coverage = coverage;
    }

    @Override
    public Mapping getMapping() {
        return new Mapping()

            // simple types (model leaves)
            .add("code", coverage.getCode())
            .add("limit1", coverage.getLimit1())
            .add("limit2", coverage.getLimit2())
            .add("premium", coverage.getPremium())

            // parent option (there is exactly one parent option)
            .add("option", new OptionAdapter(coverage.getOption()))

            // parent quote (there is exactly one parent quote)
            .add("quote", new QuoteAdapter(coverage.getQuote()));
    }

}

The role of the getMapping() is to match single token extracted from path to final value e.g.

code -> coverage.getCode()

or next adapter in chain e.g.

option -> new OptionAdapter(coverage.getOption())

The subsequent adapter that is called will do exactly the same. This way the entire path is readily covered.

Having context implemented in such way, we can query for all valid paths:


Driver driver = new Driver()
    .setFirstName("John")
    .setLastName("Potter")
    .setGender("M")
    .setDateOfBirth(Date.from(LocalDate.now().minusYears(40) ...)
    .setAddress(new Address("Lake Jackson", "Allwood St", "77566"));

Quote quote = new Quote("FULL", driver);    // quote / insurance policy proposal

Option option1 = new Option("OPT1", 1);     // 2 tariff options
Option option2 = new Option("OPT2", 2);
quote.addOption(option1);                   // added to this quote
quote.addOption(option2);

Coverage cov = new Coverage("BI", 310);     // sample coverage added to option1
option1.addCoverage(cov);


// we create MotorContext with ONLY coverage provided
HyperonContext ctx = new MotorContext(cov);

// now, our context will resolve all valid paths inferable from this coverage
assertEquals(ctx.getString("quote.driver.address.zipcode"), "77566");
assertEquals(ctx.getInteger("quote.driver.age").intValue(), 40);
assertEquals(ctx.getString("coverage.option.quote.driver.address.zipcode"), "77566");
assertEquals(ctx.getDecimal("coverage.premium").intValue(), 310);
...

More tutorials

Implementing execution context