Datei: NdoJsonFormatter/NdoJsonFormatter/NdoJsonFormatter.cs
Last Commit (1814d4b)
1 | // |
2 | // Copyright (c) 2002-2024 Mirko Matytschak |
3 | // (www.netdataobjects.de) |
4 | // |
5 | // Author: Mirko Matytschak |
6 | // |
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
8 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation |
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
10 | // Software, and to permit persons to whom the Software is furnished to do so, subject to the following |
11 | // conditions: |
12 | |
13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions |
14 | // of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
19 | // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
20 | // DEALINGS IN THE SOFTWARE. |
21 | |
22 | using System; |
23 | using System.Collections; |
24 | using System.Collections.Generic; |
25 | using System.IO; |
26 | using System.Linq; |
27 | using System.Reflection; |
28 | using System.Text; |
29 | using NDO.ShortId; |
30 | using NDO.Mapping; |
31 | using Newtonsoft.Json; |
32 | using Newtonsoft.Json.Linq; |
33 | using NDOInterfaces; |
34 | |
35 | namespace NDO.JsonFormatter |
36 | { |
37 | ····/// <summary> |
38 | ····/// Formatter implementation which serializes NDO ObjectContainers and ChangeSetContainers into Json. |
39 | ····/// </summary> |
40 | ····public class NdoJsonFormatter : INdoFormatter |
41 | ····{ |
42 | ········PersistenceManager pm; |
43 | |
44 | ········/// <summary> |
45 | ········/// Constructs an NdoJsonFormatter object. This constructor is sufficient for serialization. |
46 | ········/// </summary> |
47 | ········public NdoJsonFormatter() |
48 | ········{ |
49 | ············this.pm = null; |
50 | ········} |
51 | |
52 | ········/// <summary> |
53 | ········/// Constructs an NdoJsonFormatter object. This constructor should be used for deserialization. |
54 | ········/// </summary> |
55 | ········/// <param name="pm"></param> |
56 | ········public NdoJsonFormatter( PersistenceManager pm ) |
57 | ········{ |
58 | ············this.pm = pm; |
59 | ········} |
60 | |
61 | ········IPersistenceCapable DeserializeObject( JToken jobj ) |
62 | ········{ |
63 | ············var shortId = (string)jobj["_oid"]; |
64 | |
65 | ············if (shortId != null) |
66 | ············{ |
67 | ················var pc = this.pm.FindObject(shortId); |
68 | ················pc.NDOStateManager = null;··// Detach object |
69 | ················pc.FromJToken( jobj ); |
70 | ················return pc; |
71 | ············} |
72 | |
73 | ············return null; |
74 | ········} |
75 | |
76 | ········IPersistenceCapable DeserializeHollowObject(JToken jobj) |
77 | ········{ |
78 | ············var shortId = (string)jobj["_oid"]; |
79 | |
80 | ············if (shortId != null) |
81 | ············{ |
82 | ················var pc = this.pm.FindObject(shortId); |
83 | ················pc.NDOStateManager = null;··// Detach object |
84 | ················return pc; |
85 | ············} |
86 | |
87 | ············return null; |
88 | ········} |
89 | |
90 | ········RelationChangeRecord DeserializeRelationChangeRecord(JToken jobj) |
91 | ········{ |
92 | ············var parent = this.pm.FindObject((string)jobj["parent"]["_oid"]); |
93 | ············parent.NDOStateManager = null;··// Detach object |
94 | ············var child = this.pm.FindObject((string)jobj["child"]["_oid"]); |
95 | ············child.NDOStateManager = null;··// Detach object |
96 | |
97 | ············var relationName = (string)jobj["relationName"]; |
98 | ············var isAdded = (bool)jobj["isAdded"]; |
99 | ············return new RelationChangeRecord(parent, child, relationName, isAdded); |
100 | ········} |
101 | |
102 | ········void FixRelations( JToken jObj, IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
103 | ········{ |
104 | ············var t = e.GetType(); |
105 | ············FieldMap fm = new FieldMap(t); |
106 | var mc = Metaclasses. GetClass( t) ; |
107 | ············foreach (var fi in fm.Relations) |
108 | ············{ |
109 | ················var token = jObj[fi.Name]; |
110 | ················if (token == null) |
111 | ····················continue; |
112 | ················bool isArray = typeof(IList).IsAssignableFrom( fi.FieldType ); |
113 | ················if (token is JArray jarray) |
114 | ················{ |
115 | ····················if (isArray) |
116 | ····················{ |
117 | ························IList container = (IList)fi.GetValue(e);························ |
118 | ························if (container == null) |
119 | ····························throw new NDOException( 20002, $"Container object of relation {t.Name}.{fi.Name} is not initialized. Please initialize the field in your class constructor." ); |
120 | |
121 | ························container.Clear(); |
122 | ························foreach (var relJObj in jarray) |
123 | ························{ |
124 | ····························container.Add( DeserializeObject( relJObj ) ); |
125 | ························} |
126 | ····················} |
127 | ················} |
128 | ················else |
129 | ················{ |
130 | ····················if (!isArray) |
131 | ····················{ |
132 | ························if (token.Type == JTokenType.Null) |
133 | ····························fi.SetValue( e, null ); |
134 | ························else |
135 | ····························fi.SetValue( e, DeserializeObject( token ) ); |
136 | ····················} |
137 | ················} |
138 | ············} |
139 | ········} |
140 | |
141 | ········IList DeserializeChangeSetContainer(JToken rootArray) |
142 | ········{ |
143 | ············// 0: AddedObjects = new List<IPersistenceCapable>(); |
144 | ············// 1: DeletedObjects = new List<ObjectId>(); |
145 | ············// 2: ChangedObjects = new List<IPersistenceCapable>(); |
146 | ············// 3: RelationChanges = new List<RelationChangeRecord>(); |
147 | |
148 | ············ArrayList arrayList = new ArrayList(new object[4]); |
149 | |
150 | ············for (int i = 0; i < 4; i++) |
151 | ············{ |
152 | ················var partialArray = rootArray[i]; |
153 | ················if (i == 0 || i == 2) |
154 | ················{ |
155 | ····················var partialList = new List<IPersistenceCapable>(); |
156 | ····················arrayList[i] = partialList; |
157 | ····················foreach (var item in partialArray) |
158 | ····················{ |
159 | ························partialList.Add(DeserializeObject(item)); |
160 | ····················} |
161 | ················} |
162 | ················if (i == 1) |
163 | ················{ |
164 | ····················var partialList = new List<IPersistenceCapable>(); |
165 | ····················arrayList[i] = partialList; |
166 | ····················foreach (var item in partialArray) |
167 | ····················{ |
168 | ························partialList.Add(DeserializeHollowObject(item)); |
169 | ····················} |
170 | ················} |
171 | ················if (i == 3) |
172 | ················{ |
173 | ····················var partialList = new List<RelationChangeRecord>(); |
174 | ····················arrayList[i] = partialList; |
175 | ····················foreach (var item in partialArray) |
176 | ····················{ |
177 | ························partialList.Add(DeserializeRelationChangeRecord(item)); |
178 | ····················} |
179 | ················} |
180 | ············} |
181 | |
182 | ············return arrayList; |
183 | ········} |
184 | |
185 | ········object DeserializeRootArray( JToken rootArray ) |
186 | ········{ |
187 | ············var rootObjectsToken = (JArray)rootArray["rootObjects"]; |
188 | ············var additionalObjectsToken = (JArray)rootArray["additionalObjects"]; |
189 | ············if (rootObjectsToken.Count >= 4 && rootObjectsToken[0] is JArray) |
190 | ················return DeserializeChangeSetContainer(rootObjectsToken); |
191 | |
192 | ············List<IPersistenceCapable> rootObjects = new List<IPersistenceCapable>(); |
193 | ············List<IPersistenceCapable> additionalObjects = new List<IPersistenceCapable>(); |
194 | |
195 | ············foreach (var jObj in rootObjectsToken) |
196 | ············{ |
197 | ················IPersistenceCapable e = DeserializeObject(jObj); |
198 | ················if (e != null) |
199 | ····················rootObjects.Add( e ); |
200 | ············} |
201 | |
202 | ············foreach (var jObj in additionalObjectsToken) |
203 | ············{ |
204 | ················IPersistenceCapable e = DeserializeObject(jObj); |
205 | ················if (e != null) |
206 | ····················additionalObjects.Add( e ); |
207 | ············} |
208 | |
209 | ············foreach (var jObj in rootObjectsToken) |
210 | ············{ |
211 | ················var shortId = (string)jObj["_oid"]; |
212 | ················IPersistenceCapable e = rootObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
213 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
214 | ············} |
215 | |
216 | ············foreach (var jObj in additionalObjectsToken) |
217 | ············{ |
218 | ················var shortId = (string)jObj["_oid"]; |
219 | ················IPersistenceCapable e = additionalObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
220 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
221 | ············} |
222 | |
223 | ············return new ArrayList( rootObjects ); |
224 | ········} |
225 | |
226 | ········internal class Metaclasses |
227 | ········{ |
228 | ············private static Dictionary<Type, IMetaClass2> theClasses = new Dictionary<Type, IMetaClass2>(); |
229 | |
230 | ············internal static IMetaClass2 GetClass( Type t ) |
231 | ············{ |
232 | ················if (t.IsGenericTypeDefinition) |
233 | ····················return null; |
234 | |
235 | ················IMetaClass2 mc; |
236 | |
237 | ················if (!theClasses.TryGetValue( t, out mc )) |
238 | ················{ |
239 | ····················lock (theClasses) |
240 | ····················{ |
241 | ························if (!theClasses.TryGetValue( t, out mc ))··// Threading double check |
242 | ························{ |
243 | ····························Type mcType = t.GetNestedType( "MetaClass", BindingFlags.NonPublic | BindingFlags.Public ); |
244 | ····························if (null == mcType) |
245 | ································throw new NDOException( 13, "Missing nested class 'MetaClass' for type '" + t.Name + "'; the type doesn't seem to be enhanced." ); |
246 | ····························Type t2 = mcType; |
247 | ····························if (t2.IsGenericTypeDefinition) |
248 | ································t2 = t2.MakeGenericType( t.GetGenericArguments() ); |
249 | ····························var o = Activator.CreateInstance( t2, t ); |
250 | ····························if (o is IMetaClass2 mc2) |
251 | ································mc = mc2; |
252 | ····························else |
253 | ································throw new NDOException( 101010, $"MetaClass for type '{t.FullName}' must implement IMetaClass2, but doesn't. Recompile the assembly with NDO v. >= 4.0.9" ); |
254 | ····························theClasses.Add( t, mc ); |
255 | ························} |
256 | ····················} |
257 | ················} |
258 | |
259 | ················return mc; |
260 | ············} |
261 | ········} |
262 | |
263 | |
264 | ········/// <summary> |
265 | ········/// Deserializes a Container from a stream. |
266 | ········/// </summary> |
267 | ········/// <param name="serializationStream"></param> |
268 | ········/// <returns></returns> |
269 | ········public object Deserialize( Stream serializationStream ) |
270 | ········{ |
271 | ············if (this.pm == null) |
272 | ················throw new NDOException( 20001, "PersistenceManager is not initialized. Provide a PersistenceManager in the formatter constructor." ); |
273 | |
274 | ············JsonSerializer serializer = new JsonSerializer(); |
275 | ············TextReader textReader = new StreamReader( serializationStream ); |
276 | ············var rootObject = (JToken)serializer.Deserialize(textReader, typeof(JToken)); |
277 | ············if (rootObject == null || rootObject.Type == JTokenType.Null) |
278 | ················return null; |
279 | ············var result = DeserializeRootArray( rootObject ); |
280 | ············this.pm.UnloadCache(); |
281 | ············return result; |
282 | ········} |
283 | |
284 | ········IDictionary<string, object> MakeDict( IPersistenceCapable pc ) |
285 | ········{ |
286 | ············var dict = pc.ToDictionary(pm); |
287 | var shortId = ( ( IPersistenceCapable) pc) . ShortId( ) ; |
288 | ············var t = pc.GetType(); |
289 | ············FieldMap fm = new FieldMap(t); |
290 | var mc = Metaclasses. GetClass( t) ; |
291 | ············foreach (var fi in fm.Relations) |
292 | ············{ |
293 | ················var fiName = fi.Name; |
294 | if ( ( ( IPersistenceCapable) pc ) . NDOGetLoadState( mc. GetRelationOrdinal( fiName ) ) ) |
295 | ················{ |
296 | ····················object relationObj = fi.GetValue(pc); |
297 | ····················if (relationObj is IList list) |
298 | ····················{ |
299 | ························List<object> dictList = new List<object>(); |
300 | ························foreach (IPersistenceCapable relObject in list) |
301 | ························{ |
302 | ····························shortId = ( (IPersistenceCapable) relObject ).ShortId(); |
303 | ····························dictList.Add( new { _oid = shortId } ); |
304 | ························} |
305 | ························dict.Add( fiName, dictList ); |
306 | ····················} |
307 | ····················else |
308 | ····················{ |
309 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
310 | ························if (relationObj == null || ((IPersistenceCapable)relationObj).NDOObjectState == NDOObjectState.Hollow) |
311 | ························{ |
312 | ····························dict.Add( fiName, null ); |
313 | ························} |
314 | ························else |
315 | ························{ |
316 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
317 | ····························shortId = ( (IPersistenceCapable) relIPersistenceCapable ).ShortId(); |
318 | ····························dict.Add( fiName, new { _oid = shortId } ); |
319 | ························} |
320 | ····················} |
321 | ················} |
322 | ············} |
323 | |
324 | ············return dict; |
325 | ········} |
326 | |
327 | ········void InitializePm( object graph ) |
328 | ········{ |
329 | ············IPersistenceCapable pc; |
330 | ············if (graph is IList list) |
331 | ············{ |
332 | ················if (list.Count == 0) |
333 | ····················return; |
334 | ················pc = (IPersistenceCapable) list[0]; |
335 | ············} |
336 | ············else |
337 | ············{ |
338 | ················pc = (IPersistenceCapable) graph; |
339 | ············} |
340 | |
341 | ············this.pm = (PersistenceManager) ( pc.NDOStateManager.PersistenceManager ); |
342 | ········} |
343 | |
344 | ········void RecursivelyAddAdditionalObjects( IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
345 | ········{ |
346 | ············var t = e.GetType(); |
347 | ············FieldMap fm = new FieldMap(t); |
348 | var mc = Metaclasses. GetClass( t) ; |
349 | ············foreach (var fi in fm.Relations) |
350 | ············{ |
351 | if ( ( ( IPersistenceCapable) e ) . NDOGetLoadState( mc. GetRelationOrdinal( fi. Name ) ) ) |
352 | ················{ |
353 | ····················object relationObj = fi.GetValue(e); |
354 | ····················if (relationObj is IList list) |
355 | ····················{ |
356 | ························List<object> dictList = new List<object>(); |
357 | ························foreach (IPersistenceCapable relIPersistenceCapable in list) |
358 | ························{ |
359 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
360 | ····························{ |
361 | ································additionalObjects.Add( relIPersistenceCapable ); |
362 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
363 | ····························} |
364 | ························} |
365 | ····················} |
366 | ····················else |
367 | ····················{ |
368 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
369 | ························if (relationObj != null && ((IPersistenceCapable)relationObj).NDOObjectState != NDOObjectState.Hollow) |
370 | ························{ |
371 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
372 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
373 | ····························{ |
374 | ································additionalObjects.Add( relIPersistenceCapable ); |
375 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
376 | ····························} |
377 | ························} |
378 | ····················} |
379 | ················} |
380 | ············} |
381 | ········} |
382 | |
383 | ········object MapRelationChangeRecord(RelationChangeRecord rcr) |
384 | ········{ |
385 | ············return new |
386 | ············{ |
387 | ················parent = new { _oid = rcr.Parent.NDOObjectId.ToShortId() }, |
388 | ················child··= new { _oid = rcr.Child.NDOObjectId.ToShortId() }, |
389 | ················isAdded = rcr.IsAdded, |
390 | ················relationName = rcr.RelationName, |
391 | ················_oid = "RelationChangeRecord" |
392 | ············}; |
393 | ········} |
394 | |
395 | ········void SerializeChangeSet(Stream serializationStream, IList graph) |
396 | ········{ |
397 | ············// A ChangeSetContainer consists of 4 lists of deleted, added, changed objects, and the relation changes. |
398 | ············// AddedObjects = new List<IPersistenceCapable>(); |
399 | ············// DeletedObjects = new List<ObjectId>(); |
400 | ············// ChangedObjects = new List<IPersistenceCapable>(); |
401 | ············// RelationChanges = new List<RelationChangeRecord>(); |
402 | ············List<List<object>> resultObjects = new List<List<object>>(graph.Count); |
403 | ············foreach (IList list in graph) |
404 | ············{ |
405 | ················List<object> partialResult = new List<object>(); |
406 | ················resultObjects.Add(partialResult); |
407 | ················foreach (var item in list) |
408 | ················{ |
409 | ····················if (item is IPersistenceCapable pc) |
410 | ························partialResult.Add(MakeDict(pc)); |
411 | ····················else if (item is ObjectId oid) |
412 | ························partialResult.Add(new { _oid = oid.ToShortId() }); |
413 | ····················else if (item is RelationChangeRecord rcr) |
414 | ························partialResult.Add(MapRelationChangeRecord(rcr)); |
415 | ····················else |
416 | ························throw new NDOException(20003, $"NDOJsonFormatter: unknown element of type {item.GetType().FullName} in ChangeSetContainer."); |
417 | ················} |
418 | ············} |
419 | |
420 | ············var json = JsonConvert.SerializeObject(new { rootObjects = resultObjects, additionalObjects = new object[] { } }); |
421 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
422 | ············serializationStream.Write(byteArray, 0, byteArray.Length); |
423 | ········} |
424 | |
425 | ········/// <summary> |
426 | ········/// Serializes an object graph to a stream. |
427 | ········/// </summary> |
428 | ········/// <param name="serializationStream"></param> |
429 | ········/// <param name="graph"></param> |
430 | ········public void Serialize( Stream serializationStream, object graph ) |
431 | ········{ |
432 | ············string json = null; |
433 | ············if (this.pm == null) |
434 | ················InitializePm( graph ); |
435 | ············List<object> rootObjects = new List<object>(); |
436 | ············List<object> additionalObjects = new List<object>(); |
437 | ············List<IPersistenceCapable> rootObjectList = new List<IPersistenceCapable>(); |
438 | ············List<IPersistenceCapable> additionalObjectList = new List<IPersistenceCapable>(); |
439 | |
440 | ············IList list = graph as IList; |
441 | ············if (list != null) |
442 | ············{ |
443 | ················if (list.Count > 0 && list[0] is IList) |
444 | ················{ |
445 | ····················// Change Set |
446 | ····················SerializeChangeSet(serializationStream, list); |
447 | ····················return; |
448 | ················} |
449 | ················foreach (IPersistenceCapable e in list) |
450 | ················{ |
451 | ····················rootObjectList.Add( e ); |
452 | ················} |
453 | ············} |
454 | ············else if (graph is IPersistenceCapable e) |
455 | ············{ |
456 | ················rootObjectList.Add( e ); |
457 | ············} |
458 | |
459 | ············foreach (var e in rootObjectList) |
460 | ············{ |
461 | ················RecursivelyAddAdditionalObjects( e, rootObjectList, additionalObjectList ); |
462 | ············} |
463 | |
464 | ············foreach (var e in rootObjectList) |
465 | ············{ |
466 | ················rootObjects.Add( MakeDict( e ) ); |
467 | ············} |
468 | |
469 | ············foreach (var e in additionalObjectList) |
470 | ············{ |
471 | ················additionalObjects.Add( MakeDict( e ) ); |
472 | ············} |
473 | |
474 | ············json = JsonConvert.SerializeObject( new { rootObjects, additionalObjects } ); |
475 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
476 | ············serializationStream.Write( byteArray, 0, byteArray.Length ); |
477 | ········} |
478 | ····} |
479 | } |
480 |
New Commit (182cfd7)
1 | // |
2 | // Copyright (c) 2002-2024 Mirko Matytschak |
3 | // (www.netdataobjects.de) |
4 | // |
5 | // Author: Mirko Matytschak |
6 | // |
7 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
8 | // documentation files (the "Software"), to deal in the Software without restriction, including without limitation |
9 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
10 | // Software, and to permit persons to whom the Software is furnished to do so, subject to the following |
11 | // conditions: |
12 | |
13 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions |
14 | // of the Software. |
15 | // |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
17 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
18 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
19 | // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
20 | // DEALINGS IN THE SOFTWARE. |
21 | |
22 | using System; |
23 | using System.Collections; |
24 | using System.Collections.Generic; |
25 | using System.IO; |
26 | using System.Linq; |
27 | using System.Reflection; |
28 | using System.Text; |
29 | using NDO.ShortId; |
30 | using NDO.Mapping; |
31 | using Newtonsoft.Json; |
32 | using Newtonsoft.Json.Linq; |
33 | using NDOInterfaces; |
34 | |
35 | namespace NDO.JsonFormatter |
36 | { |
37 | ····/// <summary> |
38 | ····/// Formatter implementation which serializes NDO ObjectContainers and ChangeSetContainers into Json. |
39 | ····/// </summary> |
40 | ····public class NdoJsonFormatter : INdoFormatter |
41 | ····{ |
42 | ········PersistenceManager pm; |
43 | |
44 | ········/// <summary> |
45 | ········/// Constructs an NdoJsonFormatter object. This constructor is sufficient for serialization. |
46 | ········/// </summary> |
47 | ········public NdoJsonFormatter() |
48 | ········{ |
49 | ············this.pm = null; |
50 | ········} |
51 | |
52 | ········/// <summary> |
53 | ········/// Constructs an NdoJsonFormatter object. This constructor should be used for deserialization. |
54 | ········/// </summary> |
55 | ········/// <param name="pm"></param> |
56 | ········public NdoJsonFormatter( PersistenceManager pm ) |
57 | ········{ |
58 | ············this.pm = pm; |
59 | ········} |
60 | |
61 | ········IPersistenceCapable DeserializeObject( JToken jobj ) |
62 | ········{ |
63 | ············var shortId = (string)jobj["_oid"]; |
64 | |
65 | ············if (shortId != null) |
66 | ············{ |
67 | ················var pc = this.pm.FindObject(shortId); |
68 | ················pc.NDOStateManager = null;··// Detach object |
69 | ················pc.FromJToken( jobj ); |
70 | ················return pc; |
71 | ············} |
72 | |
73 | ············return null; |
74 | ········} |
75 | |
76 | ········IPersistenceCapable DeserializeHollowObject(JToken jobj) |
77 | ········{ |
78 | ············var shortId = (string)jobj["_oid"]; |
79 | |
80 | ············if (shortId != null) |
81 | ············{ |
82 | ················var pc = this.pm.FindObject(shortId); |
83 | ················pc.NDOStateManager = null;··// Detach object |
84 | ················return pc; |
85 | ············} |
86 | |
87 | ············return null; |
88 | ········} |
89 | |
90 | ········RelationChangeRecord DeserializeRelationChangeRecord(JToken jobj) |
91 | ········{ |
92 | ············var parent = this.pm.FindObject((string)jobj["parent"]["_oid"]); |
93 | ············parent.NDOStateManager = null;··// Detach object |
94 | ············var child = this.pm.FindObject((string)jobj["child"]["_oid"]); |
95 | ············child.NDOStateManager = null;··// Detach object |
96 | |
97 | ············var relationName = (string)jobj["relationName"]; |
98 | ············var isAdded = (bool)jobj["isAdded"]; |
99 | ············return new RelationChangeRecord(parent, child, relationName, isAdded); |
100 | ········} |
101 | |
102 | ········void FixRelations( JToken jObj, IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
103 | ········{ |
104 | ············var t = e.GetType(); |
105 | ············FieldMap fm = new FieldMap(t); |
106 | |
107 | ············foreach (var fi in fm.Relations) |
108 | ············{ |
109 | ················var token = jObj[fi.Name]; |
110 | ················if (token == null) |
111 | ····················continue; |
112 | ················bool isArray = typeof(IList).IsAssignableFrom( fi.FieldType ); |
113 | ················if (token is JArray jarray) |
114 | ················{ |
115 | ····················if (isArray) |
116 | ····················{ |
117 | ························IList container = (IList)fi.GetValue(e);························ |
118 | ························if (container == null) |
119 | ····························throw new NDOException( 20002, $"Container object of relation {t.Name}.{fi.Name} is not initialized. Please initialize the field in your class constructor." ); |
120 | |
121 | ························container.Clear(); |
122 | ························foreach (var relJObj in jarray) |
123 | ························{ |
124 | ····························container.Add( DeserializeObject( relJObj ) ); |
125 | ························} |
126 | ····················} |
127 | ················} |
128 | ················else |
129 | ················{ |
130 | ····················if (!isArray) |
131 | ····················{ |
132 | ························if (token.Type == JTokenType.Null) |
133 | ····························fi.SetValue( e, null ); |
134 | ························else |
135 | ····························fi.SetValue( e, DeserializeObject( token ) ); |
136 | ····················} |
137 | ················} |
138 | ············} |
139 | ········} |
140 | |
141 | ········IList DeserializeChangeSetContainer(JToken rootArray) |
142 | ········{ |
143 | ············// 0: AddedObjects = new List<IPersistenceCapable>(); |
144 | ············// 1: DeletedObjects = new List<ObjectId>(); |
145 | ············// 2: ChangedObjects = new List<IPersistenceCapable>(); |
146 | ············// 3: RelationChanges = new List<RelationChangeRecord>(); |
147 | |
148 | ············ArrayList arrayList = new ArrayList(new object[4]); |
149 | |
150 | ············for (int i = 0; i < 4; i++) |
151 | ············{ |
152 | ················var partialArray = rootArray[i]; |
153 | ················if (i == 0 || i == 2) |
154 | ················{ |
155 | ····················var partialList = new List<IPersistenceCapable>(); |
156 | ····················arrayList[i] = partialList; |
157 | ····················foreach (var item in partialArray) |
158 | ····················{ |
159 | ························partialList.Add(DeserializeObject(item)); |
160 | ····················} |
161 | ················} |
162 | ················if (i == 1) |
163 | ················{ |
164 | ····················var partialList = new List<IPersistenceCapable>(); |
165 | ····················arrayList[i] = partialList; |
166 | ····················foreach (var item in partialArray) |
167 | ····················{ |
168 | ························partialList.Add(DeserializeHollowObject(item)); |
169 | ····················} |
170 | ················} |
171 | ················if (i == 3) |
172 | ················{ |
173 | ····················var partialList = new List<RelationChangeRecord>(); |
174 | ····················arrayList[i] = partialList; |
175 | ····················foreach (var item in partialArray) |
176 | ····················{ |
177 | ························partialList.Add(DeserializeRelationChangeRecord(item)); |
178 | ····················} |
179 | ················} |
180 | ············} |
181 | |
182 | ············return arrayList; |
183 | ········} |
184 | |
185 | ········object DeserializeRootArray( JToken rootArray ) |
186 | ········{ |
187 | ············var rootObjectsToken = (JArray)rootArray["rootObjects"]; |
188 | ············var additionalObjectsToken = (JArray)rootArray["additionalObjects"]; |
189 | ············if (rootObjectsToken.Count >= 4 && rootObjectsToken[0] is JArray) |
190 | ················return DeserializeChangeSetContainer(rootObjectsToken); |
191 | |
192 | ············List<IPersistenceCapable> rootObjects = new List<IPersistenceCapable>(); |
193 | ············List<IPersistenceCapable> additionalObjects = new List<IPersistenceCapable>(); |
194 | |
195 | ············foreach (var jObj in rootObjectsToken) |
196 | ············{ |
197 | ················IPersistenceCapable e = DeserializeObject(jObj); |
198 | ················if (e != null) |
199 | ····················rootObjects.Add( e ); |
200 | ············} |
201 | |
202 | ············foreach (var jObj in additionalObjectsToken) |
203 | ············{ |
204 | ················IPersistenceCapable e = DeserializeObject(jObj); |
205 | ················if (e != null) |
206 | ····················additionalObjects.Add( e ); |
207 | ············} |
208 | |
209 | ············foreach (var jObj in rootObjectsToken) |
210 | ············{ |
211 | ················var shortId = (string)jObj["_oid"]; |
212 | ················IPersistenceCapable e = rootObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
213 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
214 | ············} |
215 | |
216 | ············foreach (var jObj in additionalObjectsToken) |
217 | ············{ |
218 | ················var shortId = (string)jObj["_oid"]; |
219 | ················IPersistenceCapable e = additionalObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
220 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
221 | ············} |
222 | |
223 | ············return new ArrayList( rootObjects ); |
224 | ········} |
225 | |
226 | |
227 | ········/// <summary> |
228 | ········/// Deserializes a Container from a stream. |
229 | ········/// </summary> |
230 | ········/// <param name="serializationStream"></param> |
231 | ········/// <returns></returns> |
232 | ········public object Deserialize( Stream serializationStream ) |
233 | ········{ |
234 | ············if (this.pm == null) |
235 | ················throw new NDOException( 20001, "PersistenceManager is not initialized. Provide a PersistenceManager in the formatter constructor." ); |
236 | |
237 | ············JsonSerializer serializer = new JsonSerializer(); |
238 | ············TextReader textReader = new StreamReader( serializationStream ); |
239 | ············var rootObject = (JToken)serializer.Deserialize(textReader, typeof(JToken)); |
240 | ············if (rootObject == null || rootObject.Type == JTokenType.Null) |
241 | ················return null; |
242 | ············var result = DeserializeRootArray( rootObject ); |
243 | ············this.pm.UnloadCache(); |
244 | ············return result; |
245 | ········} |
246 | |
247 | ········IDictionary<string, object> MakeDict( IPersistenceCapable pc ) |
248 | ········{ |
249 | ············var t = pc.GetType(); |
250 | ············if (pc.NDOObjectState == NDOObjectState.Transient) |
251 | ················throw new Exception( $"Object of type {t.FullName} is transient. It can't be serialized by NDO." ); |
252 | ············var dict = pc.ToDictionary(pm); |
253 | var shortId = pc. ShortId( ) ; |
254 | ············FieldMap fm = new FieldMap(t); |
255 | |
256 | ············var mapping = pc.NDOStateManager?.PersistenceManager?.NDOMapping; |
257 | ············if (mapping == null) |
258 | ················throw new Exception( $"Can't determine mapping information of class {t.FullName}." ); |
259 | |
260 | ············var cls = mapping.FindClass(t);············ |
261 | ············if (cls == null) |
262 | ················throw new Exception( $"Can't find class mapping of class {t.FullName}. Check your mapping file." ); |
263 | |
264 | ············foreach (var fi in fm.Relations) |
265 | ············{ |
266 | ················var fiName = fi.Name; |
267 | var r = cls. FindRelation( fiName ) ; |
268 | ················var ordinal = ((ILoadStateSupport) r).Ordinal; |
269 | ················if (pc.NDOGetLoadState( ordinal )) |
270 | ················{ |
271 | ····················object relationObj = fi.GetValue(pc); |
272 | ····················if (relationObj is IList list) |
273 | ····················{ |
274 | ························List<object> dictList = new List<object>(); |
275 | ························foreach (IPersistenceCapable relObject in list) |
276 | ························{ |
277 | ····························shortId = ( (IPersistenceCapable) relObject ).ShortId(); |
278 | ····························dictList.Add( new { _oid = shortId } ); |
279 | ························} |
280 | ························dict.Add( fiName, dictList ); |
281 | ····················} |
282 | ····················else |
283 | ····················{ |
284 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
285 | ························if (relationObj == null || ((IPersistenceCapable)relationObj).NDOObjectState == NDOObjectState.Hollow) |
286 | ························{ |
287 | ····························dict.Add( fiName, null ); |
288 | ························} |
289 | ························else |
290 | ························{ |
291 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
292 | ····························shortId = ( (IPersistenceCapable) relIPersistenceCapable ).ShortId(); |
293 | ····························dict.Add( fiName, new { _oid = shortId } ); |
294 | ························} |
295 | ····················} |
296 | ················} |
297 | ············} |
298 | |
299 | ············return dict; |
300 | ········} |
301 | |
302 | ········void InitializePm( object graph ) |
303 | ········{ |
304 | ············IPersistenceCapable pc; |
305 | ············if (graph is IList list) |
306 | ············{ |
307 | ················if (list.Count == 0) |
308 | ····················return; |
309 | ················pc = (IPersistenceCapable) list[0]; |
310 | ············} |
311 | ············else |
312 | ············{ |
313 | ················pc = (IPersistenceCapable) graph; |
314 | ············} |
315 | |
316 | ············this.pm = (PersistenceManager) ( pc.NDOStateManager.PersistenceManager ); |
317 | ········} |
318 | |
319 | ········void RecursivelyAddAdditionalObjects( IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
320 | ········{ |
321 | ············var t = e.GetType(); |
322 | ············FieldMap fm = new FieldMap(t); |
323 | var cls = e. NDOStateManager. PersistenceManager. NDOMapping. FindClass( t) ; |
324 | ············foreach (var fi in fm.Relations) |
325 | ············{ |
326 | var r = cls. FindRelation( fi. Name) ; |
327 | ················if (r == null) |
328 | ····················throw new Exception( $"Can't find relation {fi.Name} in class {t.FullName}" ); |
329 | ················if (e.NDOGetLoadState( ( (ILoadStateSupport) r ).Ordinal )) |
330 | ················{ |
331 | ····················object relationObj = fi.GetValue(e); |
332 | ····················if (relationObj is IList list) |
333 | ····················{ |
334 | ························List<object> dictList = new List<object>(); |
335 | ························foreach (IPersistenceCapable relIPersistenceCapable in list) |
336 | ························{ |
337 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
338 | ····························{ |
339 | ································additionalObjects.Add( relIPersistenceCapable ); |
340 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
341 | ····························} |
342 | ························} |
343 | ····················} |
344 | ····················else |
345 | ····················{ |
346 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
347 | ························if (relationObj != null && ((IPersistenceCapable)relationObj).NDOObjectState != NDOObjectState.Hollow) |
348 | ························{ |
349 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
350 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
351 | ····························{ |
352 | ································additionalObjects.Add( relIPersistenceCapable ); |
353 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
354 | ····························} |
355 | ························} |
356 | ····················} |
357 | ················} |
358 | ············} |
359 | ········} |
360 | |
361 | ········object MapRelationChangeRecord(RelationChangeRecord rcr) |
362 | ········{ |
363 | ············return new |
364 | ············{ |
365 | ················parent = new { _oid = rcr.Parent.NDOObjectId.ToShortId() }, |
366 | ················child··= new { _oid = rcr.Child.NDOObjectId.ToShortId() }, |
367 | ················isAdded = rcr.IsAdded, |
368 | ················relationName = rcr.RelationName, |
369 | ················_oid = "RelationChangeRecord" |
370 | ············}; |
371 | ········} |
372 | |
373 | ········void SerializeChangeSet(Stream serializationStream, IList graph) |
374 | ········{ |
375 | ············// A ChangeSetContainer consists of 4 lists of deleted, added, changed objects, and the relation changes. |
376 | ············// AddedObjects = new List<IPersistenceCapable>(); |
377 | ············// DeletedObjects = new List<ObjectId>(); |
378 | ············// ChangedObjects = new List<IPersistenceCapable>(); |
379 | ············// RelationChanges = new List<RelationChangeRecord>(); |
380 | ············List<List<object>> resultObjects = new List<List<object>>(graph.Count); |
381 | ············foreach (IList list in graph) |
382 | ············{ |
383 | ················List<object> partialResult = new List<object>(); |
384 | ················resultObjects.Add(partialResult); |
385 | ················foreach (var item in list) |
386 | ················{ |
387 | ····················if (item is IPersistenceCapable pc) |
388 | ························partialResult.Add(MakeDict(pc)); |
389 | ····················else if (item is ObjectId oid) |
390 | ························partialResult.Add(new { _oid = oid.ToShortId() }); |
391 | ····················else if (item is RelationChangeRecord rcr) |
392 | ························partialResult.Add(MapRelationChangeRecord(rcr)); |
393 | ····················else |
394 | ························throw new NDOException(20003, $"NDOJsonFormatter: unknown element of type {item.GetType().FullName} in ChangeSetContainer."); |
395 | ················} |
396 | ············} |
397 | |
398 | ············var json = JsonConvert.SerializeObject(new { rootObjects = resultObjects, additionalObjects = new object[] { } }); |
399 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
400 | ············serializationStream.Write(byteArray, 0, byteArray.Length); |
401 | ········} |
402 | |
403 | ········/// <summary> |
404 | ········/// Serializes an object graph to a stream. |
405 | ········/// </summary> |
406 | ········/// <param name="serializationStream"></param> |
407 | ········/// <param name="graph"></param> |
408 | ········public void Serialize( Stream serializationStream, object graph ) |
409 | ········{ |
410 | ············string json = null; |
411 | ············if (this.pm == null) |
412 | ················InitializePm( graph ); |
413 | ············List<object> rootObjects = new List<object>(); |
414 | ············List<object> additionalObjects = new List<object>(); |
415 | ············List<IPersistenceCapable> rootObjectList = new List<IPersistenceCapable>(); |
416 | ············List<IPersistenceCapable> additionalObjectList = new List<IPersistenceCapable>(); |
417 | |
418 | ············IList list = graph as IList; |
419 | ············if (list != null) |
420 | ············{ |
421 | ················if (list.Count > 0 && list[0] is IList) |
422 | ················{ |
423 | ····················// Change Set |
424 | ····················SerializeChangeSet(serializationStream, list); |
425 | ····················return; |
426 | ················} |
427 | ················foreach (IPersistenceCapable e in list) |
428 | ················{ |
429 | ····················rootObjectList.Add( e ); |
430 | ················} |
431 | ············} |
432 | ············else if (graph is IPersistenceCapable e) |
433 | ············{ |
434 | ················rootObjectList.Add( e ); |
435 | ············} |
436 | |
437 | ············foreach (var e in rootObjectList) |
438 | ············{ |
439 | ················RecursivelyAddAdditionalObjects( e, rootObjectList, additionalObjectList ); |
440 | ············} |
441 | |
442 | ············foreach (var e in rootObjectList) |
443 | ············{ |
444 | ················rootObjects.Add( MakeDict( e ) ); |
445 | ············} |
446 | |
447 | ············foreach (var e in additionalObjectList) |
448 | ············{ |
449 | ················additionalObjects.Add( MakeDict( e ) ); |
450 | ············} |
451 | |
452 | ············json = JsonConvert.SerializeObject( new { rootObjects, additionalObjects } ); |
453 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
454 | ············serializationStream.Write( byteArray, 0, byteArray.Length ); |
455 | ········} |
456 | ····} |
457 | } |
458 |