1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.databind.io.json;
28
29 import com.fasterxml.jackson.core.JsonParser;
30 import com.fasterxml.jackson.core.JsonToken;
31
32 import gov.nist.secauto.metaschema.core.model.util.JsonUtil;
33 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
34 import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
35 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance;
36 import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance;
37 import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance;
38 import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance;
39 import gov.nist.secauto.metaschema.databind.model.IClassBinding;
40 import gov.nist.secauto.metaschema.databind.model.IFieldClassBinding;
41 import gov.nist.secauto.metaschema.databind.model.info.IDataTypeHandler;
42 import gov.nist.secauto.metaschema.databind.model.info.IModelPropertyInfo;
43 import gov.nist.secauto.metaschema.databind.model.info.IPropertyCollector;
44
45 import org.apache.logging.log4j.LogManager;
46 import org.apache.logging.log4j.Logger;
47
48 import java.io.IOException;
49 import java.util.HashMap;
50 import java.util.Map;
51
52 import edu.umd.cs.findbugs.annotations.NonNull;
53 import edu.umd.cs.findbugs.annotations.Nullable;
54
55 public class MetaschemaJsonReader
56 implements IJsonParsingContext {
57 private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonReader.class);
58
59 @NonNull
60 private final JsonParser parser;
61 @NonNull
62 private final IJsonProblemHandler problemHandler;
63
64
65
66
67
68
69
70
71 public MetaschemaJsonReader(
72 @NonNull JsonParser parser) {
73 this(parser, new DefaultJsonProblemHandler());
74 }
75
76
77
78
79
80
81
82
83
84 public MetaschemaJsonReader(
85 @NonNull JsonParser parser,
86 @NonNull IJsonProblemHandler problemHandler) {
87 this.parser = parser;
88 this.problemHandler = problemHandler;
89 }
90
91 @Override
92 public JsonParser getReader() {
93 return parser;
94 }
95
96 @Override
97 public IJsonProblemHandler getProblemHandler() {
98 return problemHandler;
99 }
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140 @SuppressWarnings({
141 "PMD.CyclomaticComplexity", "PMD.NPathComplexity"
142 })
143 @Nullable
144 public <T> T read(@NonNull IAssemblyClassBinding targetDefinition) throws IOException {
145 if (!targetDefinition.isRoot()) {
146 throw new UnsupportedOperationException(
147 String.format("The assembly '%s' is not a root assembly.", targetDefinition.getBoundClass().getName()));
148 }
149
150 boolean objectWrapper = false;
151 if (parser.currentToken() == null) {
152 parser.nextToken();
153 }
154
155 if (JsonToken.START_OBJECT.equals(parser.currentToken())) {
156
157 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
158 objectWrapper = true;
159 }
160
161 String rootFieldName = targetDefinition.getRootJsonName();
162 JsonToken token;
163 Object instance = null;
164 while (!(JsonToken.END_OBJECT.equals(token = parser.currentToken()) || token == null)) {
165 if (!JsonToken.FIELD_NAME.equals(token)) {
166 throw new IOException(String.format("Expected FIELD_NAME token, found '%s'", token.toString()));
167 }
168
169 String fieldName = parser.currentName();
170 if (fieldName.equals(rootFieldName)) {
171
172 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
173
174
175 IDataTypeHandler dataTypeHandler = IDataTypeHandler.newDataTypeHandler(targetDefinition);
176
177
178 instance = dataTypeHandler.readItem(null, this);
179
180
181 break;
182 }
183
184 if (!getProblemHandler().handleUnknownProperty(targetDefinition, instance, fieldName, this)) {
185 LOGGER.warn("Skipping unhandled top-level JSON field '{}'.", fieldName);
186 JsonUtil.skipNextValue(parser);
187 }
188 }
189
190 if (instance == null) {
191 throw new IOException(String.format("Failed to find root field '%s'.", rootFieldName));
192 }
193
194 if (objectWrapper) {
195
196 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
197 }
198
199 return ObjectUtils.asType(instance);
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 protected boolean readInstance(
220 @NonNull IBoundNamedInstance targetInstance,
221 @NonNull Object parentObject) throws IOException {
222
223 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME);
224
225 String propertyName = parser.currentName();
226 if (LOGGER.isTraceEnabled()) {
227 LOGGER.trace("reading property {}", propertyName);
228 }
229
230 boolean handled = targetInstance.getJsonName().equals(propertyName);
231 if (handled) {
232
233 parser.nextToken();
234
235 Object value = readInstanceValue(targetInstance, parentObject);
236
237 if (value != null) {
238 targetInstance.setValue(parentObject, value);
239 }
240 }
241
242
243
244 JsonUtil.assertCurrent(parser, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
245 return handled;
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259 protected Object readInstanceValue(
260 @NonNull IBoundNamedInstance instance,
261 @NonNull Object parentObject) throws IOException {
262 Object value;
263 if (instance instanceof IBoundNamedModelInstance) {
264
265
266 IModelPropertyInfo info = ((IBoundNamedModelInstance) instance).getPropertyInfo();
267 IPropertyCollector collector = info.newPropertyCollector();
268
269
270 info.readValues(collector, parentObject, this);
271
272
273 value = collector.getValue();
274 } else if (instance instanceof IBoundFlagInstance) {
275
276 value = ((IBoundFlagInstance) instance).getDefinition().getJavaTypeAdapter().parse(parser);
277 } else if (instance instanceof IBoundFieldValueInstance) {
278
279 value = ((IBoundFieldValueInstance) instance).getJavaTypeAdapter().parse(parser);
280 } else {
281 throw new UnsupportedOperationException(
282 String.format("Unsupported instance type: %s", instance.getClass().getName()));
283 }
284 return value;
285 }
286
287
288
289 @SuppressWarnings({
290 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity"
291 })
292 @Override
293 public void readDefinitionValue(
294 IClassBinding targetDefinition,
295 Object targetObject,
296 Map<String, ? extends IBoundNamedInstance> instances) throws IOException {
297 IBoundFlagInstance valueKeyFlag = null;
298 if (targetDefinition instanceof IFieldClassBinding) {
299 IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition;
300 valueKeyFlag = targetFieldDefinition.getJsonValueKeyFlagInstance();
301 }
302
303
304 Map<String, ? extends IBoundNamedInstance> remainingInstances = new HashMap<>(instances);
305
306
307 while (!JsonToken.END_OBJECT.equals(parser.currentToken())) {
308 boolean handled = false;
309 String propertyName = parser.getCurrentName();
310 assert propertyName != null;
311
312 if (JsonToken.FIELD_NAME.equals(parser.currentToken())) {
313
314 IBoundNamedInstance property = remainingInstances.get(propertyName);
315 if (property != null) {
316 handled = readInstance(property, targetObject);
317 remainingInstances.remove(propertyName);
318 }
319 } else {
320 throw new IOException(
321 String.format("Unexpected token: " + JsonUtil.toString(parser)));
322 }
323
324 if (!handled) {
325 if (valueKeyFlag != null) {
326
327 IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition;
328 valueKeyFlag.setValue(targetObject,
329 valueKeyFlag.getDefinition().getJavaTypeAdapter().parse(propertyName));
330
331
332 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
333
334 IBoundFieldValueInstance fieldValue = targetFieldDefinition.getFieldValueInstance();
335 fieldValue.setValue(
336 targetObject,
337 fieldValue.getJavaTypeAdapter().parse(parser));
338 valueKeyFlag = null;
339 } else if (!getProblemHandler().handleUnknownProperty(
340 targetDefinition,
341 targetObject,
342 propertyName,
343 this)) {
344
345 if (LOGGER.isWarnEnabled()) {
346 LOGGER.warn("Unrecognized property named '{}' at '{}'", propertyName,
347 JsonUtil.toString(ObjectUtils.notNull(parser.getCurrentLocation())));
348 }
349 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
350 JsonUtil.skipNextValue(parser);
351 }
352 }
353 }
354
355 if (!remainingInstances.isEmpty()) {
356 getProblemHandler().handleMissingInstances(
357 targetDefinition,
358 targetObject,
359 ObjectUtils.notNull(remainingInstances.values()));
360 }
361 }
362 }