Skip to content

Commit fa40960

Browse files
Collector Context changes to handle simple Objects
1 parent 7194f77 commit fa40960

File tree

5 files changed

+344
-61
lines changed

5 files changed

+344
-61
lines changed

doc/collector_context.md

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,106 @@
11
### CollectorContext
22

33

4-
There could be usecases 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) in a given JSON node and the schema keyword we are using.
4+
There could be usecases 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.
55

66
The fetched data can be stored some where 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.
77

8-
CollectorContext and Collector classes are designed to satisfy this usage.
8+
CollectorContext and Collector classes are designed to work with this usecase.
99

1010
#### How to use CollectorContext
1111

12-
Objects of CollectorContext live on ThreadLocal which is unique for every thread. This allows users to add objects to context at many points in the framework like Formats,Keywords,Validators etc.
12+
Objects of CollectorContext live on ThreadLocal which is unique for every thread. This allows users to add objects to context at many points in the framework like Formats,Validators (Effectively CollectorContext can be used at any touch point in the validateAndCollect method thread call).
1313

1414
CollectorContext instance can be obtained by calling the getInstance static method on CollectorContext.This method gives an instance from the ThreadLocal for the current thread.
1515

1616
Collectors are added to CollectorContext. Collectors allows to collect the objects. A Collector is added to CollectorContext with a name and corresponding Collector instance.
1717

1818
```
1919
CollectorContext collectorContext = CollectorContext.getInstance();
20-
collectorContext.add(SAMPLE_COLLECTOR_TYPE, new Collector<List<String>>() {
21-
@Override
22-
public List<String> collect() {
23-
List<String> references = new ArrayList<String>();
24-
references.add(getDatasourceMap().get(node.textValue()));
25-
return references;
26-
}
27-
});
20+
collectorContext.add(SAMPLE_COLLECTOR_NAME, new Collector<List<String>>() {
21+
@Override
22+
public List<String> collect() {
23+
List<String> references = new ArrayList<String>();
24+
references.add(getDatasourceMap().get(node.textValue()));
25+
return references;
26+
}
27+
});
2828
```
2929

30-
To validate the schema with the ability to use CollectorContext, validateAndCollect method has to be invoked on the JsonSchema class. This class returns a ValidationResult that contains the errors encountered during validation and a CollectorContext instance. Objects constructed by Collectors can be retrieved from CollectorContext by using the name they were added with.
30+
However there might be usecases 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.
31+
32+
```
33+
CollectorContext collectorContext = CollectorContext.getInstance();
34+
collectorContext.add(SAMPLE_COLLECTOR,"sample-string")
35+
36+
```
37+
38+
To validate the schema with the ability to use CollectorContext, validateAndCollect method has to be invoked on the JsonSchema class. This class returns a ValidationResult that contains the errors encountered during validation and a CollectorContext instance. Objects constructed by Collectors or directly added to CollectorContext can be retrieved from CollectorContext by using the name they were added with.
3139

3240

3341
```
3442
ValidationResult validationResult = jsonSchema.validateAndCollect(jsonNode);
3543
CollectorContext context = validationResult.getCollectorContext();
36-
List<String> contextValue = (List<String>)context.get(SAMPLE_COLLECTOR_TYPE);
44+
List<String> contextValue = (List<String>)context.get(SAMPLE_COLLECTOR);
3745
3846
```
3947

40-
Note that CollectorContext will be removed from ThreadLocal once validateAndCollect method returns. Also the data is loaded into CollectorContext only after all the validations are done.
48+
Note that CollectorContext will be removed from ThreadLocal once validateAndCollect method returns. Also the data collected by Collectors is loaded into CollectorContext only after all the validations are done.
49+
50+
There might be usecases where a collector needs to collect the data at multiple touch points. For example one usecase 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.
51+
52+
```
53+
class CustomCollector implements Collector<List<String>> {
54+
55+
List<String> returnList = new ArrayList<String>();
56+
57+
private Map<String, String> referenceMap = null;
58+
59+
public CustomCollector() {
60+
referenceMap = getDatasourceMap();
61+
}
62+
63+
@Override
64+
public List<String> collect() {
65+
return returnList;
66+
}
67+
68+
@Override
69+
public void combine(Object object) {
70+
returnList.add(referenceMap.get((String) object));
71+
}
72+
}
73+
74+
CollectorContext collectorContext = CollectorContext.getInstance();
75+
if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
76+
collectorContext.add(SAMPLE_COLLECTOR, new CustomCollector());
77+
}
78+
collectorContext.combineWithCollector(SAMPLE_COLLECTOR, node.textValue());
79+
80+
```
81+
82+
One 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.
83+
84+
```
85+
// Returns Collector before validation is done.
86+
Collector<List<String>> collector = collectorContext.get(SAMPLE_COLLECTOR);
87+
88+
// Returns data collected by Collector after the validation is done.
89+
List<String> data = collectorContext.get(SAMPLE_COLLECTOR);
90+
91+
```
92+
93+
If you are using simple objects and if the data needs to be collected from multiple touch points, logic is straightforward as shown.
94+
95+
```
96+
97+
CollectorContext collectorContext = CollectorContext.getInstance();
98+
// If collector name is not added to context add one.
99+
if (collectorContext.get(SAMPLE_COLLECTOR) == null) {
100+
collectorContext.add(SAMPLE_COLLECTOR, new ArrayList<String>());
101+
}
102+
// In this case we are adding a list to CollectorContext.
103+
List<String> returnList = (List<String>) collectorContext.get(SAMPLE_COLLECTOR);
104+
105+
```
41106

src/main/java/com/networknt/schema/Collector.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@
88
*/
99
public interface Collector<E> {
1010

11+
/**
12+
* This method should be called by the intermediate touch points that want to
13+
* combine the data being collected by this collector. This is an optional
14+
* method and could be used when the same collector is used for collecting data
15+
* at multiple touch points or accumulating data at same touch point.
16+
*/
17+
public default void combine(Object object) {
18+
};
19+
20+
/**
21+
* Final method called by the framework that returns the actual collected data.
22+
* If the collector is not accumulating data or being used to collect data at
23+
* multiple touch points, only this method can be implemented.
24+
*/
1125
public E collect();
1226

1327
}

src/main/java/com/networknt/schema/CollectorContext.java

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.HashMap;
44
import java.util.Map;
55
import java.util.Map.Entry;
6+
import java.util.Set;
67

78
/**
89
* Context for holding the output returned by the {@link Collector}
@@ -11,50 +12,105 @@
1112
*/
1213
public class CollectorContext {
1314

14-
static final String COLLECTOR_CONTEXT_THREAD_LOCAL_KEY = "COLLECTOR_CONTEXT_THREAD_LOCAL_KEY";
15+
// Using a namespace string as key in ThreadLocal so that it is unique in
16+
// ThreadLocal.
17+
static final String COLLECTOR_CONTEXT_THREAD_LOCAL_KEY = "com.networknt.schema.CollectorKey";
1518

1619
// Get an instance from thread info (which uses ThreadLocal).
1720
public static CollectorContext getInstance() {
1821
return (CollectorContext) ThreadInfo.get(COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
1922
}
2023

2124
/**
22-
* Map for holding the collector type and {@link Collector}
25+
* Map for holding the name and {@link Collector} or a simple Object.
2326
*/
24-
private Map<String, Collector<?>> collectorMap = new HashMap<String, Collector<?>>();
27+
private Map<String, Object> collectorMap = new HashMap<String, Object>();
2528

2629
/**
27-
* Map for holding the collector type and {@link Collector} class collect method
28-
* output.
30+
* Map for holding the name and {@link Collector} class collect method output.
2931
*/
3032
private Map<String, Object> collectorLoadMap = new HashMap<String, Object>();
3133

32-
public <E> void add(String collectorType, Collector<E> collector) {
33-
collectorMap.put(collectorType, collector);
34+
/**
35+
*
36+
* Adds a collector with give name. Preserving this method for backward
37+
* compatibility . *
38+
*
39+
* @param <E>
40+
* @param name
41+
* @param collector
42+
*/
43+
public <E> void add(String name, Collector<E> collector) {
44+
collectorMap.put(name, collector);
3445
}
3546

36-
public Object get(String collectorType) {
37-
if (collectorLoadMap.get(collectorType) == null && collectorMap.get(collectorType) != null) {
38-
collectorLoadMap.put(collectorType, collectorMap.get(collectorType).collect());
47+
/**
48+
*
49+
* Adds a collector or a simple object with give name.
50+
*
51+
* @param <E>
52+
* @param name
53+
* @param collector
54+
*/
55+
public <E> void add(String name, Object object) {
56+
collectorMap.put(name, object);
57+
}
58+
59+
/**
60+
*
61+
* Gets the data associated with a given name. Please note if you are using a
62+
* Collector you should wait till the validation is complete to gather all data.
63+
*
64+
* For a Collector, this method will return the collector as long as load method
65+
* is not called. Once the load method is called this method will return the
66+
* actual data collected by collector.
67+
*
68+
* @param name
69+
* @return
70+
*/
71+
public Object get(String name) {
72+
Object object = collectorMap.get(name);
73+
if (object instanceof Collector<?> && (collectorLoadMap.get(name) != null)) {
74+
return collectorLoadMap.get(name);
3975
}
40-
return collectorLoadMap.get(collectorType);
76+
return collectorMap.get(name);
4177
}
4278

4379
/**
44-
* Load all the collectors associated with the context.
80+
*
81+
* Combines data with Collector identified by the given name.
82+
*
83+
* @param name
84+
* @param data
4585
*/
46-
void load() {
47-
for (Entry<String, Collector<?>> collectorEntrySet : collectorMap.entrySet()) {
48-
collectorLoadMap.put(collectorEntrySet.getKey(), collectorEntrySet.getValue().collect());
86+
public void combineWithCollector(String name, Object data) {
87+
Object object = collectorMap.get(name);
88+
if (object instanceof Collector<?>) {
89+
Collector<?> collector = (Collector<?>) object;
90+
collector.combine(data);
4991
}
5092
}
5193

5294
/**
5395
* Reset the context
5496
*/
5597
void reset() {
56-
this.collectorMap = new HashMap<String, Collector<?>>();
98+
this.collectorMap = new HashMap<String, Object>();
5799
this.collectorLoadMap = new HashMap<String, Object>();
58100
}
59101

102+
/**
103+
* Loads data from all collectors.
104+
*/
105+
void loadCollectors() {
106+
Set<Entry<String, Object>> entrySet = collectorMap.entrySet();
107+
for (Entry<String, Object> entry : entrySet) {
108+
if (entry.getValue() instanceof Collector<?>) {
109+
Collector<?> collector = (Collector<?>) entry.getValue();
110+
collectorLoadMap.put(entry.getKey(), collector.collect());
111+
}
112+
}
113+
114+
}
115+
60116
}

src/main/java/com/networknt/schema/JsonSchema.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNo
198198
ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext);
199199
Set<ValidationMessage> errors = validate(jsonNode, rootNode, at);
200200
// Load all the data from collectors into the context.
201-
collectorContext.load();
201+
collectorContext.loadCollectors();
202202
// Collect errors and collector context into validation result.
203203
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
204204
return validationResult;

0 commit comments

Comments
 (0)