1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind.io.json;
7
8 import com.fasterxml.jackson.core.JsonGenerator;
9
10 import gov.nist.secauto.metaschema.core.model.IBoundObject;
11 import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior;
12 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
13 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
14 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
15 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
16 import gov.nist.secauto.metaschema.databind.model.IBoundInstance;
17 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
18 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
19 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
20 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
21 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
22 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
23 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
24 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
25 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
26 import gov.nist.secauto.metaschema.databind.model.IBoundProperty;
27 import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceWriteHandler;
28 import gov.nist.secauto.metaschema.databind.model.info.IFeatureComplexItemValueHandler;
29 import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler;
30 import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;
31 import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
32
33 import java.io.IOException;
34 import java.util.List;
35
36 import edu.umd.cs.findbugs.annotations.NonNull;
37
38 @SuppressWarnings("PMD.CouplingBetweenObjects")
39 public class MetaschemaJsonWriter implements IJsonWritingContext, IItemWriteHandler {
40 @NonNull
41 private final JsonGenerator generator;
42
43
44
45
46
47
48
49
50 public MetaschemaJsonWriter(@NonNull JsonGenerator generator) {
51 this.generator = generator;
52 }
53
54 @Override
55 public JsonGenerator getWriter() {
56 return generator;
57 }
58
59
60
61
62
63 @Override
64 public void write(
65 @NonNull IBoundDefinitionModelComplex definition,
66 @NonNull IBoundObject item) throws IOException {
67 definition.writeItem(item, this);
68 }
69
70
71
72
73
74 private <T> void writeInstance(
75 @NonNull IBoundProperty<T> instance,
76 @NonNull IBoundObject parentItem) throws IOException {
77 @SuppressWarnings("unchecked")
78 T value = (T) instance.getValue(parentItem);
79 if (value != null && !value.equals(instance.getResolvedDefaultValue())) {
80 generator.writeFieldName(instance.getJsonName());
81 instance.writeItem(value, this);
82 }
83 }
84
85 private <T> void writeModelInstance(
86 @NonNull IBoundInstanceModel<T> instance,
87 @NonNull Object parentItem) throws IOException {
88 Object value = instance.getValue(parentItem);
89 if (value != null) {
90
91
92
93 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
94 if (!collectionInfo.isEmpty(value)) {
95 generator.writeFieldName(instance.getJsonName());
96 collectionInfo.writeItems(new ModelInstanceWriteHandler<>(instance), value);
97 }
98 }
99 }
100
101 @SuppressWarnings("PMD.NullAssignment")
102 private void writeFieldValue(@NonNull IBoundFieldValue fieldValue, @NonNull Object parentItem) throws IOException {
103 Object item = fieldValue.getValue(parentItem);
104
105
106 IBoundInstanceFlag jsonValueKey = fieldValue.getParentFieldDefinition().getJsonValueKeyFlagInstance();
107 if (item == null) {
108 if (jsonValueKey != null) {
109 item = fieldValue.getDefaultValue();
110 }
111 } else if (item.equals(fieldValue.getResolvedDefaultValue())) {
112
113 item = null;
114 }
115
116 if (item != null) {
117
118
119
120
121 String valueKeyName;
122 if (jsonValueKey != null) {
123 Object keyValue = jsonValueKey.getValue(parentItem);
124 if (keyValue == null) {
125 throw new IOException(String.format("Null value for json-value-key for definition '%s'",
126 jsonValueKey.getContainingDefinition().toCoordinates()));
127 }
128 try {
129
130 valueKeyName = jsonValueKey.getJavaTypeAdapter().asString(keyValue);
131 } catch (IllegalArgumentException ex) {
132 throw new IOException(
133 String.format("Invalid value '%s' for json-value-key for definition '%s'",
134 keyValue,
135 jsonValueKey.getContainingDefinition().toCoordinates()),
136 ex);
137 }
138 } else {
139 valueKeyName = fieldValue.getParentFieldDefinition().getEffectiveJsonValueKeyName();
140 }
141 generator.writeFieldName(valueKeyName);
142
143
144 writeItemFieldValue(item, fieldValue);
145 }
146 }
147
148 @Override
149 public void writeItemFlag(Object item, IBoundInstanceFlag instance) throws IOException {
150 writeScalarItem(item, instance);
151 }
152
153 @Override
154 public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException {
155 writeScalarItem(item, instance);
156 }
157
158 @Override
159 public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException {
160 writeModelObject(
161 instance,
162 item,
163 this::writeObjectProperties);
164 }
165
166 @Override
167 public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException {
168 writeGroupedModelObject(
169 instance,
170 item,
171 (parent, handler) -> {
172 writeDiscriminatorProperty(handler);
173 writeObjectProperties(parent, handler);
174 });
175 }
176
177 @Override
178 public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException {
179 writeDefinitionObject(
180 definition,
181 item,
182 (ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeObjectProperties);
183 }
184
185 @Override
186 public void writeItemFieldValue(Object item, IBoundFieldValue fieldValue) throws IOException {
187 fieldValue.getJavaTypeAdapter().writeJsonValue(item, generator);
188 }
189
190 @Override
191 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException {
192 writeModelObject(instance, item, this::writeObjectProperties);
193 }
194
195 @Override
196 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException {
197 writeGroupedModelObject(
198 instance,
199 item,
200 (parent, handler) -> {
201 writeDiscriminatorProperty(handler);
202 writeObjectProperties(parent, handler);
203 });
204 }
205
206 @Override
207 public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException {
208 writeDefinitionObject(definition, item, this::writeObjectProperties);
209 }
210
211 @Override
212 public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException {
213 IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item);
214 assert actualInstance != null;
215 actualInstance.writeItem(item, this);
216 }
217
218
219
220
221
222
223
224
225
226
227
228 private void writeScalarItem(@NonNull Object item, @NonNull IFeatureScalarItemValueHandler handler)
229 throws IOException {
230 handler.getJavaTypeAdapter().writeJsonValue(item, generator);
231 }
232
233 private <T extends IBoundInstanceModelGroupedNamed> void writeDiscriminatorProperty(
234 @NonNull T instance) throws IOException {
235
236 IBoundInstanceModelChoiceGroup choiceGroup = instance.getParentContainer();
237
238
239 String discriminatorProperty = choiceGroup.getJsonDiscriminatorProperty();
240 String discriminatorValue = instance.getEffectiveDisciminatorValue();
241
242 generator.writeStringField(discriminatorProperty, discriminatorValue);
243 }
244
245 private <T extends IFeatureComplexItemValueHandler> void writeObjectProperties(
246 @NonNull IBoundObject parent,
247 @NonNull T handler) throws IOException {
248 for (IBoundProperty<?> property : handler.getJsonProperties().values()) {
249 assert property != null;
250
251 if (property instanceof IBoundInstanceModel) {
252 writeModelInstance((IBoundInstanceModel<?>) property, parent);
253 } else if (property instanceof IBoundInstance) {
254 writeInstance(property, parent);
255 } else {
256 writeFieldValue((IBoundFieldValue) property, parent);
257 }
258 }
259 }
260
261 private <T extends IFeatureComplexItemValueHandler> void writeDefinitionObject(
262 @NonNull T handler,
263 @NonNull IBoundObject parent,
264 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
265 generator.writeStartObject();
266
267 propertyWriter.accept(parent, handler);
268 generator.writeEndObject();
269 }
270
271 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModel<IBoundObject>>
272 void writeModelObject(
273 @NonNull T handler,
274 @NonNull IBoundObject parent,
275 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
276 generator.writeStartObject();
277
278 IBoundInstanceFlag jsonKey = handler.getItemJsonKey(parent);
279 if (jsonKey != null) {
280 Object keyValue = jsonKey.getValue(parent);
281 if (keyValue == null) {
282 throw new IOException(
283 String.format("Null value for json-key for definition '%s'",
284 jsonKey.getContainingDefinition().toCoordinates()));
285 }
286
287
288 String key;
289 try {
290 key = jsonKey.getJavaTypeAdapter().asString(keyValue);
291 } catch (IllegalArgumentException ex) {
292 throw new IOException(
293 String.format("Illegal value '%s' for json-key for definition '%s'",
294 keyValue,
295 jsonKey.getContainingDefinition().toCoordinates()),
296 ex);
297 }
298 generator.writeFieldName(key);
299
300
301 generator.writeStartObject();
302 }
303
304 propertyWriter.accept(parent, handler);
305
306 if (jsonKey != null) {
307
308 generator.writeEndObject();
309 }
310 generator.writeEndObject();
311 }
312
313 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelGroupedNamed> void writeGroupedModelObject(
314 @NonNull T handler,
315 @NonNull IBoundObject parent,
316 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
317 generator.writeStartObject();
318
319 IBoundInstanceModelChoiceGroup choiceGroup = handler.getParentContainer();
320 IBoundInstanceFlag jsonKey = choiceGroup.getItemJsonKey(parent);
321 if (jsonKey != null) {
322 Object keyValue = jsonKey.getValue(parent);
323 if (keyValue == null) {
324 throw new IOException(String.format("Null value for json-key for definition '%s'",
325 jsonKey.getContainingDefinition().toCoordinates()));
326 }
327
328
329 String key;
330 try {
331 key = jsonKey.getJavaTypeAdapter().asString(keyValue);
332 } catch (IllegalArgumentException ex) {
333 throw new IOException(
334 String.format("Invalid value '%s' for json-key for definition '%s'",
335 keyValue,
336 jsonKey.getContainingDefinition().toCoordinates()),
337 ex);
338 }
339 generator.writeFieldName(key);
340
341
342 generator.writeStartObject();
343 }
344
345 propertyWriter.accept(parent, handler);
346
347 if (jsonKey != null) {
348
349 generator.writeEndObject();
350 }
351 generator.writeEndObject();
352 }
353
354
355
356
357
358
359
360 private class ModelInstanceWriteHandler<ITEM>
361 extends AbstractModelInstanceWriteHandler<ITEM> {
362 public ModelInstanceWriteHandler(
363 @NonNull IBoundInstanceModel<ITEM> instance) {
364 super(instance);
365 }
366
367 @Override
368 public void writeList(List<ITEM> items) throws IOException {
369 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance();
370
371 boolean writeArray = false;
372 if (JsonGroupAsBehavior.LIST.equals(instance.getJsonGroupAsBehavior())
373 || JsonGroupAsBehavior.SINGLETON_OR_LIST.equals(instance.getJsonGroupAsBehavior())
374 && items.size() > 1) {
375
376 writeArray = true;
377 generator.writeStartArray();
378 }
379
380 super.writeList(items);
381
382 if (writeArray) {
383
384 generator.writeEndArray();
385 }
386 }
387
388 @Override
389 public void writeItem(ITEM item) throws IOException {
390 IBoundInstanceModel<ITEM> instance = getInstance();
391 instance.writeItem(item, MetaschemaJsonWriter.this);
392 }
393 }
394 }