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.model.info;
28
29 import com.fasterxml.jackson.core.JsonGenerator;
30 import com.fasterxml.jackson.core.JsonParser;
31 import com.fasterxml.jackson.core.JsonToken;
32
33 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
34 import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
35 import gov.nist.secauto.metaschema.core.model.util.JsonUtil;
36 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
37 import gov.nist.secauto.metaschema.databind.io.BindingException;
38 import gov.nist.secauto.metaschema.databind.io.json.IJsonParsingContext;
39 import gov.nist.secauto.metaschema.databind.io.json.IJsonWritingContext;
40 import gov.nist.secauto.metaschema.databind.io.xml.IXmlParsingContext;
41 import gov.nist.secauto.metaschema.databind.io.xml.IXmlWritingContext;
42 import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
43 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance;
44 import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance;
45 import gov.nist.secauto.metaschema.databind.model.IBoundNamedInstance;
46 import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance;
47 import gov.nist.secauto.metaschema.databind.model.IClassBinding;
48 import gov.nist.secauto.metaschema.databind.model.IFieldClassBinding;
49
50 import java.io.IOException;
51 import java.util.Collection;
52 import java.util.Map;
53 import java.util.function.Function;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56
57 import javax.xml.namespace.QName;
58 import javax.xml.stream.XMLStreamException;
59 import javax.xml.stream.events.StartElement;
60
61 import edu.umd.cs.findbugs.annotations.NonNull;
62 import edu.umd.cs.findbugs.annotations.Nullable;
63 import nl.talsmasoftware.lazy4j.Lazy;
64
65 class ClassDataTypeHandler implements IDataTypeHandler {
66 private final boolean jsonKeyRequired;
67 @NonNull
68 private final IClassBinding classBinding;
69
70 @NonNull
71 private final Lazy<Map<String, ? extends IBoundNamedInstance>> propertyMap;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 @NonNull
92 private static Map<String, ? extends IBoundNamedInstance> getInstancesToParse(
93 @NonNull IClassBinding targetDefinition,
94 boolean requiresJsonKey) {
95 Collection<? extends IBoundFlagInstance> flags = targetDefinition.getFlagInstances();
96 int flagCount = flags.size() - (requiresJsonKey ? 1 : 0);
97
98 @SuppressWarnings("resource") Stream<? extends IBoundNamedInstance> instanceStream;
99 if (targetDefinition instanceof IAssemblyClassBinding) {
100
101 instanceStream = ((IAssemblyClassBinding) targetDefinition).getModelInstances().stream();
102 } else if (targetDefinition instanceof IFieldClassBinding) {
103 IFieldClassBinding targetFieldDefinition = (IFieldClassBinding) targetDefinition;
104
105 IBoundFlagInstance jsonValueKeyFlag = targetFieldDefinition.getJsonValueKeyFlagInstance();
106 if (jsonValueKeyFlag == null && flagCount > 0) {
107
108 IBoundFieldValueInstance fieldValue = targetFieldDefinition.getFieldValueInstance();
109 instanceStream = Stream.of(fieldValue);
110 } else {
111
112 instanceStream = Stream.empty();
113 }
114 } else {
115 throw new UnsupportedOperationException(
116 String.format("Unsupported class binding type: %s", targetDefinition.getClass().getName()));
117 }
118
119 if (requiresJsonKey) {
120 IBoundFlagInstance jsonKey = targetDefinition.getJsonKeyFlagInstance();
121 assert jsonKey != null;
122 instanceStream = Stream.concat(
123 flags.stream().filter((flag) -> !jsonKey.equals(flag)),
124 instanceStream);
125 } else {
126 instanceStream = Stream.concat(
127 flags.stream(),
128 instanceStream);
129 }
130 return ObjectUtils.notNull(instanceStream.collect(
131 Collectors.toUnmodifiableMap(
132 IBoundNamedInstance::getJsonName,
133 Function.identity())));
134 }
135
136 public ClassDataTypeHandler(
137 @Nullable IBoundNamedModelInstance targetInstance,
138 @NonNull IClassBinding classBinding) {
139 this.classBinding = classBinding;
140
141 this.jsonKeyRequired = targetInstance != null && targetInstance.getPropertyInfo().isJsonKeyRequired();
142 this.propertyMap = ObjectUtils.notNull(Lazy.lazy(() -> getInstancesToParse(
143 classBinding,
144 this.jsonKeyRequired)));
145 }
146
147 @Override
148 public IDataTypeAdapter<?> getJavaTypeAdapter() {
149
150 return null;
151 }
152
153 @Override
154 @NonNull
155 public IClassBinding getClassBinding() {
156 return classBinding;
157 }
158
159 @Override
160 public boolean isUnwrappedValueAllowedInXml() {
161
162 return false;
163 }
164
165 @Override
166 public boolean isJsonKeyRequired() {
167 return jsonKeyRequired;
168 }
169
170 @NonNull
171 protected Map<String, ? extends IBoundNamedInstance> getJsonInstanceMap() {
172 return ObjectUtils.notNull(propertyMap.get());
173 }
174
175 @SuppressWarnings("resource")
176 private boolean readItemJsonKey(
177 @NonNull Object targetObject,
178 IJsonParsingContext context) throws IOException {
179 JsonParser parser = context.getReader();
180 IClassBinding definition = getClassBinding();
181
182 IBoundFlagInstance jsonKey = definition.getJsonKeyFlagInstance();
183 if (jsonKey == null) {
184 throw new IOException(String.format("JSON key not defined for object '%s'%s",
185 definition.toCoordinates(), JsonUtil.generateLocationMessage(parser)));
186 }
187
188
189 String key = ObjectUtils.notNull(parser.getCurrentName());
190
191 Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key);
192 jsonKey.setValue(targetObject, value.toString());
193
194
195 JsonUtil.assertAndAdvance(parser, JsonToken.FIELD_NAME);
196
197 boolean keyObjectWrapper = JsonToken.START_OBJECT.equals(parser.currentToken());
198 if (keyObjectWrapper) {
199 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
200 }
201
202 return keyObjectWrapper;
203 }
204
205 @SuppressWarnings({
206 "resource",
207 "PMD.NPathComplexity", "PMD.CyclomaticComplexity"
208 })
209 @Override
210 public <T> T readItem(Object parentObject, IJsonParsingContext context)
211 throws IOException {
212 JsonParser parser = context.getReader();
213 boolean objectWrapper = JsonToken.START_OBJECT.equals(parser.currentToken());
214 if (objectWrapper) {
215 JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT);
216 }
217
218 Object targetObject;
219 try {
220 targetObject = classBinding.newInstance();
221 classBinding.callBeforeDeserialize(targetObject, parentObject);
222 } catch (BindingException ex) {
223 throw new IOException(ex);
224 }
225
226 IClassBinding definition = getClassBinding();
227 boolean readJsonKey = isJsonKeyRequired();
228 boolean keyObjectWrapper = false;
229 if (readJsonKey) {
230 keyObjectWrapper = readItemJsonKey(targetObject, context);
231 }
232
233 if (keyObjectWrapper || JsonToken.FIELD_NAME.equals(parser.currentToken())) {
234 context.readDefinitionValue(definition, targetObject, getJsonInstanceMap());
235 } else if (parser.currentToken().isScalarValue()) {
236
237 IFieldClassBinding fieldDefinition = (IFieldClassBinding) definition;
238 Object fieldValue = fieldDefinition.getJavaTypeAdapter().parse(parser);
239 fieldDefinition.getFieldValueInstance().setValue(targetObject, fieldValue);
240 }
241
242 try {
243 classBinding.callAfterDeserialize(targetObject, parentObject);
244 } catch (BindingException ex) {
245 throw new IOException(ex);
246 }
247
248 if (keyObjectWrapper) {
249
250 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
251 }
252
253 if (objectWrapper) {
254 JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT);
255 }
256 return ObjectUtils.asType(targetObject);
257 }
258
259 @Override
260 public Object readItem(Object parentInstance, StartElement start, IXmlParsingContext context)
261 throws IOException, XMLStreamException {
262 return context.readDefinitionValue(getClassBinding(), parentInstance, start);
263 }
264
265 @Override
266 public void writeItem(Object item, QName currentParentName, IXmlWritingContext context)
267 throws IOException, XMLStreamException {
268 context.writeDefinitionValue(classBinding, item, currentParentName);
269 }
270
271 @SuppressWarnings("resource")
272 @Override
273 public void writeItem(Object targetObject, IJsonWritingContext context) throws IOException {
274 JsonGenerator writer = context.getWriter();
275
276 writer.writeStartObject();
277
278 IClassBinding definition = getClassBinding();
279 boolean writeJsonKey = isJsonKeyRequired();
280 if (writeJsonKey) {
281 IBoundFlagInstance jsonKey = definition.getJsonKeyFlagInstance();
282 assert jsonKey != null;
283
284
285 Object flagValue = jsonKey.getValue(targetObject);
286 String key = jsonKey.getValueAsString(flagValue);
287 if (key == null) {
288 throw new IOException(new NullPointerException("Null key value"));
289 }
290 writer.writeFieldName(key);
291
292
293 writer.writeStartObject();
294 }
295
296 context.writeDefinitionValue(
297 classBinding,
298 targetObject,
299 getJsonInstanceMap());
300
301 if (writeJsonKey) {
302 writer.writeEndObject();
303 }
304
305 writer.writeEndObject();
306 }
307
308 @Override
309 public Object copyItem(@NonNull Object fromItem, Object parentInstance) throws BindingException {
310 return classBinding.copyBoundObject(fromItem, parentInstance);
311 }
312
313 }