-
Notifications
You must be signed in to change notification settings - Fork 57
Architecture
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.
This is, with logging, the core component of this library.
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
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
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
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();
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
.
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 ProcessingMessage
s, 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.
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.