xref: /aosp_15_r20/external/json-schema-validator/doc/collector-context.md (revision 78c4dd6aa35290980cdcd1623a7e337e8d021c7c)
1### CollectorContext
2
3There could be use cases where we want collect the information while we are validating the data. A simple example could be fetching some value from a database or from a microservice based on the data (which could be a text or a JSON object. It should be noted that this should be a simple operation or validation might take more time to complete.) in a given JSON node and the schema keyword we are using.
4
5The fetched data can be stored somewhere so that it can be used later after the validation is done. Since the current validation logic already parses the data and schema, both validation and collecting the required information can be done in one go.
6
7The `CollectorContext` and `Collector` classes are designed to work with this use case.
8
9#### How to use CollectorContext
10
11The `CollectorContext` is stored as a variable on the `ExecutionContext` that is used during the validation. This allows users to add objects to context at many points in the framework like Formats and Validators where the `ExecutionContext` is available as a parameter.
12
13Collectors are added to `CollectorContext`. Collectors allow to collect the objects. A `Collector` is added to `CollectorContext` with a name and corresponding `Collector` instance.
14
15```java
16CollectorContext collectorContext = executionContext.getCollectorContext();
17collectorContext.add(SAMPLE_COLLECTOR_NAME, new Collector<List<String>>() {
18    @Override
19    public List<String> collect() {
20        List<String> references = new ArrayList<String>();
21        references.add(getDatasourceMap().get(node.textValue()));
22        return references;
23    }
24});
25```
26
27However there might be use cases where we want to add a simple Object like String, Integer, etc, into the Context. This can be done the same way a collector is added to the context.
28
29```java
30CollectorContext collectorContext = executionContext.getCollectorContext();
31collectorContext.add(SAMPLE_COLLECTOR, "sample-string")
32```
33
34To use the `CollectorContext` while validating, the `validateAndCollect` method has to be invoked on the `JsonSchema` class.
35This method returns a `ValidationResult` that contains the errors encountered during validation and a `ExecutionContext` instance that contains the `CollectorContext`.
36Objects constructed by collectors or directly added to `CollectorContext` can be retrieved from `CollectorContext` by using the name they were added with.
37
38To collect across multiple validation runs, the `CollectorContext` needs to be explicitly reused by passing the `ExecutionContext` as a parameter to the validation.
39
40```java
41ValidationResult validationResult = jsonSchema.validateAndCollect(jsonNode);
42ExecutionContext executionContext = validationResult.getExecutionContext();
43CollectorContext collectorContext = executionContext.getCollectorContext();
44List<String> contextValue = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
45
46// Do something with contextValue
47...
48
49// To collect more information for subsequent runs reuse the context
50validationResult = jsonSchema.validateAndCollect(executionContext, jsonNode);
51```
52
53There might be use cases where a collector needs to collect the data at multiple touch points. For example one use case might be collecting data in a validator and a formatter. If you are using a `Collector` rather than a `Object`, the combine method of the `Collector` allows to define how we want to combine the data into existing `Collector`. `CollectorContext` `combineWithCollector` method calls the combine method on the `Collector`. User just needs to call the `CollectorContext` `combineWithCollector` method every time some data needs to merged into existing `Collector`. The `collect` method on the `Collector` is called by the framework at the end of validation to return the data that was collected.
54
55```java
56class CustomCollector implements Collector<List<String>> {
57
58    List<String> returnList = new ArrayList<>();
59
60    private Map<String, String> referenceMap = null;
61
62    public CustomCollector() {
63        referenceMap = getDatasourceMap();
64    }
65
66    @Override
67    public List<String> collect() {
68        return returnList;
69    }
70
71    @Override
72    public void combine(Object object) {
73        returnList.add(referenceMap.get((String) object));
74    }
75}
76
77CollectorContext collectorContext = executionContext.getCollectorContext();
78if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
79    collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector());
80}
81collectorContext.combineWithCollector(SAMPLE_COLLECTOR, node.textValue());
82
83```
84
85One important thing to note when using Collectors is if we call get method on `CollectorContext` before the validation is complete, we would get back a `Collector` instance that was added to `CollectorContext`.
86
87```java
88// Returns Collector before validation is done.
89Collector<List<String>> collector = collectorContext.get(SAMPLE_COLLECTOR);
90
91// Returns data collected by Collector after the validation is done.
92List<String> data = collectorContext.get(SAMPLE_COLLECTOR);
93
94```
95
96If you are using simple objects and if the data needs to be collected from multiple touch points, logic is straightforward as shown.
97
98```java
99CollectorContext collectorContext = executionContext.getCollectorContext();
100// If collector name is not added to context add one.
101if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
102    collectorContext.add(SAMPLE_COLLECTOR, new ArrayList<String>());
103}
104// In this case we are adding a list to CollectorContext.
105List<String> returnList = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
106
107```