Skip to content

Commit cb338c5

Browse files
authored
Merge pull request #268 from prashanthjos/master
Collector Context changes to handle simple Objects
2 parents 68fbdbc + 133f8e4 commit cb338c5

File tree

7 files changed

+595
-289
lines changed

7 files changed

+595
-289
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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.networknt.schema;
2+
3+
public abstract class AbstractCollector<E> implements Collector<E>{
4+
5+
@Override
6+
public void combine(Object object) {
7+
// Do nothing. This is the default Implementation.
8+
}
9+
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
*/
99
public interface Collector<E> {
1010

11-
public E collect();
11+
12+
/**
13+
* This method should be called by the intermediate touch points that want to
14+
* combine the data being collected by this collector. This is an optional
15+
* method and could be used when the same collector is used for collecting data
16+
* at multiple touch points or accumulating data at same touch point.
17+
*/
18+
public void combine(Object object);
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+
*/
25+
public E collect();
26+
1227

1328
}

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

Lines changed: 101 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,113 @@
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}
910
* implementations.
1011
*/
1112
public class CollectorContext {
1213

13-
static final String COLLECTOR_CONTEXT_THREAD_LOCAL_KEY = "COLLECTOR_CONTEXT_THREAD_LOCAL_KEY";
14-
15-
// Get an instance from thread info (which uses ThreadLocal).
16-
public static CollectorContext getInstance() {
17-
return (CollectorContext) ThreadInfo.get(COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
18-
}
19-
20-
/**
21-
* Map for holding the collector type and {@link Collector}
22-
*/
23-
private Map<String, Collector<?>> collectorMap = new HashMap<String, Collector<?>>();
24-
25-
/**
26-
* Map for holding the collector type and {@link Collector} class collect method
27-
* output.
28-
*/
29-
private Map<String, Object> collectorLoadMap = new HashMap<String, Object>();
30-
31-
public <E> void add(String collectorType, Collector<E> collector) {
32-
collectorMap.put(collectorType, collector);
33-
}
34-
35-
public Object get(String collectorType) {
36-
if (collectorLoadMap.get(collectorType) == null && collectorMap.get(collectorType) != null) {
37-
collectorLoadMap.put(collectorType, collectorMap.get(collectorType).collect());
38-
}
39-
return collectorLoadMap.get(collectorType);
40-
}
41-
42-
/**
43-
* Load all the collectors associated with the context.
44-
*/
45-
void load() {
46-
for (Entry<String, Collector<?>> collectorEntrySet : collectorMap.entrySet()) {
47-
collectorLoadMap.put(collectorEntrySet.getKey(), collectorEntrySet.getValue().collect());
48-
}
49-
}
50-
51-
/**
52-
* Reset the context
53-
*/
54-
void reset() {
55-
this.collectorMap = new HashMap<String, Collector<?>>();
56-
this.collectorLoadMap = new HashMap<String, Object>();
57-
}
14+
// Using a namespace string as key in ThreadLocal so that it is unique in
15+
// ThreadLocal.
16+
static final String COLLECTOR_CONTEXT_THREAD_LOCAL_KEY = "com.networknt.schema.CollectorKey";
17+
18+
// Get an instance from thread info (which uses ThreadLocal).
19+
public static CollectorContext getInstance() {
20+
return (CollectorContext) ThreadInfo.get(COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
21+
}
22+
23+
/**
24+
* Map for holding the name and {@link Collector} or a simple Object.
25+
*/
26+
private Map<String, Object> collectorMap = new HashMap<String, Object>();
27+
28+
/**
29+
* Map for holding the name and {@link Collector} class collect method output.
30+
*/
31+
private Map<String, Object> collectorLoadMap = new HashMap<String, Object>();
32+
33+
/**
34+
*
35+
* Adds a collector with give name. Preserving this method for backward
36+
* compatibility.
37+
*
38+
* @param <E>
39+
* @param name
40+
* @param collector
41+
*/
42+
public <E> void add(String name, Collector<E> collector) {
43+
collectorMap.put(name, collector);
44+
}
45+
46+
/**
47+
*
48+
* Adds a collector or a simple object with give name.
49+
*
50+
* @param <E>
51+
* @param name
52+
* @param collector
53+
*/
54+
public <E> void add(String name, Object object) {
55+
collectorMap.put(name, object);
56+
}
57+
58+
/**
59+
*
60+
* Gets the data associated with a given name. Please note if you are using a
61+
* Collector you should wait till the validation is complete to gather all data.
62+
*
63+
* For a Collector, this method will return the collector as long as load method
64+
* is not called. Once the load method is called this method will return the
65+
* actual data collected by collector.
66+
*
67+
* @param name
68+
* @return
69+
*/
70+
public Object get(String name) {
71+
Object object = collectorMap.get(name);
72+
if (object instanceof Collector<?> && (collectorLoadMap.get(name) != null)) {
73+
return collectorLoadMap.get(name);
74+
}
75+
return collectorMap.get(name);
76+
}
77+
78+
/**
79+
*
80+
* Combines data with Collector identified by the given name.
81+
*
82+
* @param name
83+
* @param data
84+
*/
85+
public void combineWithCollector(String name, Object data) {
86+
Object object = collectorMap.get(name);
87+
if (object instanceof Collector<?>) {
88+
Collector<?> collector = (Collector<?>) object;
89+
collector.combine(data);
90+
}
91+
}
92+
93+
/**
94+
* Reset the context
95+
*/
96+
void reset() {
97+
this.collectorMap = new HashMap<String, Object>();
98+
this.collectorLoadMap = new HashMap<String, Object>();
99+
}
100+
101+
/**
102+
* Loads data from all collectors.
103+
*/
104+
void loadCollectors() {
105+
Set<Entry<String, Object>> entrySet = collectorMap.entrySet();
106+
for (Entry<String, Object> entry : entrySet) {
107+
if (entry.getValue() instanceof Collector<?>) {
108+
Collector<?> collector = (Collector<?>) entry.getValue();
109+
collectorLoadMap.put(entry.getKey(), collector.collect());
110+
}
111+
}
112+
113+
}
58114

59115
}

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

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -216,31 +216,31 @@ public ValidationResult validateAndCollect(JsonNode node) {
216216
return validateAndCollect(node, node, AT_ROOT);
217217
}
218218

219-
220-
/**
221-
* This method both validates and collects the data in a CollectionContext.
222-
*
223-
* @param jsonNode JsonNode
224-
* @param rootNode JsonNode
225-
* @param at String of path
226-
* @return ValidationResult
227-
*/
228-
protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) {
229-
try {
230-
// Create the collector context object.
231-
CollectorContext collectorContext = new CollectorContext();
232-
// Set the collector context in thread info, this is unique for every thread.
233-
ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext);
234-
Set<ValidationMessage> errors = validate(jsonNode, rootNode, at);
235-
// Load all the data from collectors into the context.
236-
collectorContext.load();
237-
// Collect errors and collector context into validation result.
238-
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
239-
return validationResult;
240-
} finally {
241-
ThreadInfo.remove(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
242-
}
243-
}
219+
220+
/**
221+
*
222+
* This method both validates and collects the data in a CollectionContext.
223+
* @param jsonNode
224+
* @param rootNode
225+
* @param at
226+
* @return
227+
*/
228+
protected ValidationResult validateAndCollect(JsonNode jsonNode, JsonNode rootNode, String at) {
229+
try {
230+
// Create the collector context object.
231+
CollectorContext collectorContext = new CollectorContext();
232+
// Set the collector context in thread info, this is unique for every thread.
233+
ThreadInfo.set(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY, collectorContext);
234+
Set<ValidationMessage> errors = validate(jsonNode, rootNode, at);
235+
// Load all the data from collectors into the context.
236+
collectorContext.loadCollectors();
237+
// Collect errors and collector context into validation result.
238+
ValidationResult validationResult = new ValidationResult(errors, collectorContext);
239+
return validationResult;
240+
} finally {
241+
ThreadInfo.remove(CollectorContext.COLLECTOR_CONTEXT_THREAD_LOCAL_KEY);
242+
}
243+
}
244244

245245
@Override
246246
public String toString() {
@@ -254,5 +254,9 @@ public boolean hasRequiredValidator() {
254254
public JsonValidator getRequiredValidator() {
255255
return requiredValidator;
256256
}
257+
258+
public Map<String, JsonValidator> getValidators() {
259+
return validators;
260+
}
257261

258262
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public static Builder builder(final JsonSchemaFactory blueprint) {
237237
return builder;
238238
}
239239

240-
private JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) {
240+
protected JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) {
241241
final ValidationContext validationContext = createValidationContext(schemaNode);
242242
validationContext.setConfig(config);
243243
JsonSchema jsonSchema = new JsonSchema(validationContext, schemaUri, schemaNode);

0 commit comments

Comments
 (0)