Skip to content
fge edited this page Feb 19, 2013 · 15 revisions

Introduction

This is an overview of the components of this library.

Note that it hasn't reached 1.0 state, and the API may still change while it reaches a stable state.

Processors, processor chains and selectors

This is, with logging, the core component of this library.

Processor

Processors are the main unit of work. The main interface is Processor<IN, OUT>. It has three helper classes: ProcessorChain, ProcessorMap and ProcessorSelector.

The Processor interface itself is quite simple:

public interface Processor<IN extends MessageProvider, OUT extends MessageProvider>
{
    OUT process(final ProcessingReport report, final IN input)
        throws ProcessingException;
}

ProcessorChain

ProcessorChain allows to chain processors together: if the output of a processor p1 is compatible with the output of processor p2, then you can create a new processor p which is the result of p2 applied to the output of p1 like so:

Processor<X, Y> p1;
Processor<Y, Z> p2;
// Chain them:
final Processor<X, Z> p = ProcessorChain.startWith(p1).chainWith(p2).getProcessor();

This class also provides a .failOnError() method which will have the effect to abort processing with an exception if the previous processor in the chain returned an error (that is, the associated report declares failure, see below).

ProcessorMap

ProcessorMap allows to create a Processor<IN, OUT> based on a key K extracted from an input IN. It is an abstract class which, when you extend it, asks that you implement this method:

    protected abstract Function<IN, K> f();

This Function<IN, K> is what will compute the key out of the input. Of course, you should ensure that the key extracted is actually usable as a Map key (that is, it obeys the equals()/hashCode() contract. When implemented, you can do this:

final ProcessorMap<K, IN, OUT> myMap = new MyMapper()
    .addEntry(k1, p1).addEntry(k2, p2).addEntry(k3, p3)
    .setDefaultProcessor(defaultProcessor);

final Processor<IN, OUT> processor = myMap.getProcessor();

Note that if you do not set a default processor and no suitable key is found in the map, processing fails with an exception.

ProcessorSelector

ProcessorSelector can be viewed as ProcessorMap on steroids. It allows you to select the processor to execute not based on a single key, but on an arbitrary predicate based on the input. Like ProcessorMap, it may have a default processor when no predicate matches, and like ProcessorMap, it will throw an exception if there is no default processor and all predicates failed.

Guava's aptly-named Predicate is used. Sample usage:

final Processor<IN, OUT> processor = new ProcessorSelector<IN, OUT>()
    .when(predicate1).then(p1)
    .when(predicate2).then(p2)
    .otherwise(defaultProcessor)
    .getProcessor();

Putting all this together

Given that all these utility classes output processors, you can create arbitrarily complex chains. For instance, json-schema-validator uses a combination of the following:

  • it uses ProcessorChain to chain together ref resolving, syntax validation, schema digesting, keyword building;
  • it uses ProcessorMap to act according to the schema's declared $schema.

Logging

CURRENTLY IN REWORK

The base interface for this is ProcessingReport. It contains logging methods with familiar names like .debug(), .info(), .warn() and .error(). As it is an interface, the way to handle messages passed as arguments (which are ProcessingMessages, see below) is entirely up to implementations.

The base abstract class implementing this interface allows you to set the logging threshold and exception threshold as well, which is how you can tell the validation process to stop at the first error.

Logging messages and exceptions

The ProcessingMessage class is what processors use to report messages to a ProcessingReport, as already mentioned. One feature of processing messages is that they have a builtin exception mechanism, via the .asException() method. The base abstract implementation of a report, AbstractProcessingReport, uses that (throw message.asException();) to throw exceptions when the log level of a message is greater than, or equal to, the exception threshold.

By default, the exception thrown is a plain ProcessingException. You can change that by using .setExceptionProvider() and provide this method with an implementation of ExceptionProvider. This allows to differentiate exceptions according to which processor threw the exception.

Note however that all exceptions must be subclasses of ProcessingException.

There is also an unchecked exception class called ProcessingConfigurationError. As its name says, this class, and its subclasses, are used when configuration errors are encountered. As you cannot alter a validator once it is built (at least, not with this library), those exceptions are guaranteed never to be thrown while processing.

Clone this wiki locally