Chain of Filters
AbstractChainOfFilters is the small engine that runs ordered pipelines inside the index. It accepts an immutable context object plus a mutable result carrier and iterates over a list of Filter<Context, Result> steps. Each step returns true to continue or false to short-circuit, which is how we cheaply exit when work is already finished (cache hits) or impossible (invalid plan).
Mechanics
- The base class keeps the ordered
List<Filter<Context, Result>>and exposes a singlefilter(context, result)method that drives the loop. - Steps see the immutable context and may update the mutable result; they must return
falseonce they have produced a terminal outcome so the chain stops immediately. - Subclasses wrap the call to
filterso they can prepare state/result objects and handle cleanup. - Filters are regular classes that implement
Filter<Context, Result>and can be reused or composed in different orders when building a pipeline.
Typical Usage
1) Define small, focused filters:
final class ValidateInput implements Filter<RequestCtx, ProcessingState> {
@Override
public boolean filter(final RequestCtx ctx, final ProcessingState state) {
if (!ctx.isValid()) {
state.fail("missing fields");
return false; // stop the chain early
}
return true;
}
}
2) Compose them into a pipeline subclass that owns setup/teardown:
final class ProcessingPipeline
extends AbstractChainOfFilters<RequestCtx, ProcessingState> {
ProcessingPipeline(
final List<Filter<RequestCtx, ProcessingState>> steps) {
super(List.copyOf(steps));
}
ProcessingState run(final RequestCtx ctx) {
final ProcessingState state = new ProcessingState();
filter(ctx, state); // short-circuit rules apply
return state;
}
}
Where It Is Used
Segment Searcher Pipeline (read path)
SegmentSearcherPipeline (src/main/java/org/hestiastore/index/segment/SegmentSearcherPipeline.java) runs a three-step lookup when serving SegmentSearcher#get:
final SegmentSearcherPipeline<K, V> pipeline = new SegmentSearcherPipeline<>(
List.of(
new SegmentSearcherStepDeltaCache<>(valueTypeDescriptor), // stop if cached hit or tombstone
new SegmentSearcherStepBloomFilter<>(), // stop if bloom says “definitely not”
new SegmentSearcherStepIndexFile<>())); // stop after sparse-index + on-disk read
pipeline.run(ctx, result);
return result.getValue();
- Short-circuits: cache hit/tombstone, Bloom filter negative, or sparse-index probe missing/false-positive correction.
- Context/result types:
SegmentSearcherContextcarries the key plus caches/searcher;SegmentSearcherResultholds the found value (ornullwhen absent).
Guidelines for Adding New Pipelines
- Keep steps single-purpose and side-effect aware; ensure they set a final result before returning
false. - Copy the incoming steps list (
List.copyOf(...)) to avoid accidental reordering at runtime. - When resources need cleanup, wrap the
filterinvocation in atry/finallyinside your subclass. - Pair unit tests with short-circuit cases; see
src/test/java/org/hestiastore/index/AbstractChainOfFiltersTest.javafor a minimal example.