Datei: NdoJsonFormatter/NdoJsonFormatter/NdoJsonFormatter.cs
Last Commit (57b6d0d)
1 | // |
2 | // Copyright (c) 2002-2020 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.Runtime.Serialization; |
29 | using System.Text; |
30 | using System.Threading.Tasks; |
31 | using NDO; |
32 | using NDO.ShortId; |
33 | using NDO.Mapping; |
34 | using Newtonsoft.Json; |
35 | using Newtonsoft.Json.Linq; |
36 | |
37 | namespace NDO.JsonFormatter |
38 | { |
39 | ····/// <summary> |
40 | ····/// Formatter implementation which serializes NDO ObjectContainers and ChangeSetContainers into Json. |
41 | ····/// </summary> |
42 | ····public class NdoJsonFormatter : IFormatter |
43 | ····{ |
44 | ········/// <inheritdoc/> |
45 | ········public ISurrogateSelector SurrogateSelector { get; set; } |
46 | ········/// <inheritdoc/> |
47 | ········public SerializationBinder Binder { get; set; } |
48 | ········/// <inheritdoc/> |
49 | ········public StreamingContext Context { get; set; } |
50 | |
51 | ········PersistenceManager pm; |
52 | |
53 | ········/// <summary> |
54 | ········/// Constructs an NdoJsonFormatter object. This constructor is sufficient for serialization. |
55 | ········/// </summary> |
56 | ········public NdoJsonFormatter() |
57 | ········{ |
58 | ············this.pm = null; |
59 | ········} |
60 | |
61 | ········/// <summary> |
62 | ········/// Constructs an NdoJsonFormatter object. This constructor should be used for deserialization. |
63 | ········/// </summary> |
64 | ········/// <param name="pm"></param> |
65 | ········public NdoJsonFormatter( PersistenceManager pm ) |
66 | ········{ |
67 | ············this.pm = pm; |
68 | ········} |
69 | |
70 | ········IPersistenceCapable DeserializeObject( JToken jobj ) |
71 | ········{ |
72 | ············var shortId = (string)jobj["_oid"]; |
73 | |
74 | ············if (shortId != null) |
75 | ············{ |
76 | ················var pc = this.pm.FindObject(shortId); |
77 | ················pc.NDOStateManager = null;··// Detach object |
78 | ················pc.FromJToken( jobj ); |
79 | ················return pc; |
80 | ············} |
81 | |
82 | ············return null; |
83 | ········} |
84 | |
85 | ········IPersistenceCapable DeserializeHollowObject(JToken jobj) |
86 | ········{ |
87 | ············var shortId = (string)jobj["_oid"]; |
88 | |
89 | ············if (shortId != null) |
90 | ············{ |
91 | ················var pc = this.pm.FindObject(shortId); |
92 | ················pc.NDOStateManager = null;··// Detach object |
93 | ················return pc; |
94 | ············} |
95 | |
96 | ············return null; |
97 | ········} |
98 | |
99 | ········RelationChangeRecord DeserializeRelationChangeRecord(JToken jobj) |
100 | ········{ |
101 | ············var parent = this.pm.FindObject((string)jobj["parent"]["_oid"]); |
102 | ············parent.NDOStateManager = null;··// Detach object |
103 | ············var child = this.pm.FindObject((string)jobj["child"]["_oid"]); |
104 | ············child.NDOStateManager = null;··// Detach object |
105 | |
106 | ············var relationName = (string)jobj["relationName"]; |
107 | ············var isAdded = (bool)jobj["isAdded"]; |
108 | ············return new RelationChangeRecord(parent, child, relationName, isAdded); |
109 | ········} |
110 | |
111 | ········void FixRelations( JToken jObj, IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
112 | ········{ |
113 | ············var t = e.GetType(); |
114 | ············FieldMap fm = new FieldMap(t); |
115 | ············var mc = Metaclasses.GetClass(t); |
116 | ············foreach (var fi in fm.Relations) |
117 | ············{ |
118 | ················var token = jObj[fi.Name]; |
119 | ················if (token == null) |
120 | ····················continue; |
121 | ················bool isArray = typeof(IList).IsAssignableFrom( fi.FieldType ); |
122 | ················if (token is JArray jarray) |
123 | ················{ |
124 | ····················if (isArray) |
125 | ····················{ |
126 | ························IList container = (IList)fi.GetValue(e);························ |
127 | ························if (container == null) |
128 | ····························throw new NDOException( 20002, $"Container object of relation {t.Name}.{fi.Name} is not initialized. Please initialize the field in your class constructor." ); |
129 | |
130 | ························container.Clear(); |
131 | ························foreach (var relJObj in jarray) |
132 | ························{ |
133 | ····························container.Add( DeserializeObject( relJObj ) ); |
134 | ························} |
135 | ····················} |
136 | ················} |
137 | ················else |
138 | ················{ |
139 | ····················if (!isArray) |
140 | ····················{ |
141 | ························if (token.Type == JTokenType.Null) |
142 | ····························fi.SetValue( e, null ); |
143 | ························else |
144 | ····························fi.SetValue( e, DeserializeObject( token ) ); |
145 | ····················} |
146 | ················} |
147 | ············} |
148 | ········} |
149 | |
150 | ········IList DeserializeChangeSetContainer(JToken rootArray) |
151 | ········{ |
152 | ············// 0: AddedObjects = new List<IPersistenceCapable>(); |
153 | ············// 1: DeletedObjects = new List<ObjectId>(); |
154 | ············// 2: ChangedObjects = new List<IPersistenceCapable>(); |
155 | ············// 3: RelationChanges = new List<RelationChangeRecord>(); |
156 | |
157 | ············ArrayList arrayList = new ArrayList(new object[4]); |
158 | |
159 | ············for (int i = 0; i < 4; i++) |
160 | ············{ |
161 | ················var partialArray = rootArray[i]; |
162 | ················if (i == 0 || i == 2) |
163 | ················{ |
164 | ····················var partialList = new List<IPersistenceCapable>(); |
165 | ····················arrayList[i] = partialList; |
166 | ····················foreach (var item in partialArray) |
167 | ····················{ |
168 | ························partialList.Add(DeserializeObject(item)); |
169 | ····················} |
170 | ················} |
171 | ················if (i == 1) |
172 | ················{ |
173 | ····················var partialList = new List<IPersistenceCapable>(); |
174 | ····················arrayList[i] = partialList; |
175 | ····················foreach (var item in partialArray) |
176 | ····················{ |
177 | ························partialList.Add(DeserializeHollowObject(item)); |
178 | ····················} |
179 | ················} |
180 | ················if (i == 3) |
181 | ················{ |
182 | ····················var partialList = new List<RelationChangeRecord>(); |
183 | ····················arrayList[i] = partialList; |
184 | ····················foreach (var item in partialArray) |
185 | ····················{ |
186 | ························partialList.Add(DeserializeRelationChangeRecord(item)); |
187 | ····················} |
188 | ················} |
189 | ············} |
190 | |
191 | ············return arrayList; |
192 | ········} |
193 | |
194 | ········object DeserializeRootArray( JToken rootArray ) |
195 | ········{ |
196 | ············var rootObjectsToken = (JArray)rootArray["rootObjects"]; |
197 | ············var additionalObjectsToken = (JArray)rootArray["additionalObjects"]; |
198 | ············if (rootObjectsToken.Count >= 4 && rootObjectsToken[0] is JArray) |
199 | ················return DeserializeChangeSetContainer(rootObjectsToken); |
200 | |
201 | ············List<IPersistenceCapable> rootObjects = new List<IPersistenceCapable>(); |
202 | ············List<IPersistenceCapable> additionalObjects = new List<IPersistenceCapable>(); |
203 | |
204 | ············foreach (var jObj in rootObjectsToken) |
205 | ············{ |
206 | ················IPersistenceCapable e = DeserializeObject(jObj); |
207 | ················if (e != null) |
208 | ····················rootObjects.Add( e ); |
209 | ············} |
210 | |
211 | ············foreach (var jObj in additionalObjectsToken) |
212 | ············{ |
213 | ················IPersistenceCapable e = DeserializeObject(jObj); |
214 | ················if (e != null) |
215 | ····················additionalObjects.Add( e ); |
216 | ············} |
217 | |
218 | ············foreach (var jObj in rootObjectsToken) |
219 | ············{ |
220 | ················var shortId = (string)jObj["_oid"]; |
221 | ················IPersistenceCapable e = rootObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
222 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
223 | ············} |
224 | |
225 | ············foreach (var jObj in additionalObjectsToken) |
226 | ············{ |
227 | ················var shortId = (string)jObj["_oid"]; |
228 | ················IPersistenceCapable e = additionalObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
229 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
230 | ············} |
231 | |
232 | ············return new ArrayList( rootObjects ); |
233 | ········} |
234 | |
235 | class Metaclasses |
236 | ········{ |
237 | private static Hashtable theClasses = new Hashtable( ) ; |
238 | |
239 | internal static IMetaClass GetClass( Type t ) |
240 | ············{ |
241 | ················if (t.IsGenericTypeDefinition) |
242 | ····················return null; |
243 | |
244 | IMetaClass mc; |
245 | |
246 | ················lock (theClasses) |
247 | ················{ |
248 | if ( null == ( mc = ( IMetaClass) theClasses[t] ) ) |
249 | ····················{ |
250 | Type mcType = t. GetNestedType( "MetaClass", BindingFlags. NonPublic | BindingFlags. Public) ; |
251 | ························if (null == mcType) |
252 | ····························throw new NDOException( 13, "Missing nested class 'MetaClass' for type '" + t.Name + "'; the type doesn't seem to be enhanced." ); |
253 | ························Type t2 = mcType; |
254 | ························if (t2.IsGenericTypeDefinition) |
255 | ····························t2 = t2.MakeGenericType( t.GetGenericArguments() ); |
256 | mc = ( IMetaClass) Activator. CreateInstance( t2 ) ; |
257 | ························theClasses.Add( t, mc ); |
258 | ····················} |
259 | ················} |
260 | |
261 | ················return mc; |
262 | ············} |
263 | ········} |
264 | |
265 | ········/// <summary> |
266 | ········/// Deserializes a Container from a stream. |
267 | ········/// </summary> |
268 | ········/// <param name="serializationStream"></param> |
269 | ········/// <returns></returns> |
270 | ········public object Deserialize( Stream serializationStream ) |
271 | ········{ |
272 | ············if (this.pm == null) |
273 | ················throw new NDOException( 20001, "PersistenceManager is not initialized. Provide a PersistenceManager in the formatter constructor." ); |
274 | |
275 | ············JsonSerializer serializer = new JsonSerializer(); |
276 | ············TextReader textReader = new StreamReader( serializationStream ); |
277 | ············var rootObject = (JToken)serializer.Deserialize(textReader, typeof(JToken)); |
278 | ············if (rootObject == null || rootObject.Type == JTokenType.Null) |
279 | ················return null; |
280 | ············var result = DeserializeRootArray( rootObject ); |
281 | ············this.pm.UnloadCache(); |
282 | ············return result; |
283 | ········} |
284 | |
285 | ········IDictionary<string, object> MakeDict( IPersistenceCapable pc ) |
286 | ········{ |
287 | ············var dict = pc.ToDictionary(pm); |
288 | ············var shortId = ((IPersistenceCapable)pc).ShortId(); |
289 | ············var t = pc.GetType(); |
290 | ············FieldMap fm = new FieldMap(t); |
291 | ············var mc = Metaclasses.GetClass(t); |
292 | ············foreach (var fi in fm.Relations) |
293 | ············{ |
294 | ················var fiName = fi.Name; |
295 | ················if (( (IPersistenceCapable) pc ).NDOGetLoadState( mc.GetRelationOrdinal( fiName ) )) |
296 | ················{ |
297 | ····················object relationObj = fi.GetValue(pc); |
298 | ····················if (relationObj is IList list) |
299 | ····················{ |
300 | ························List<object> dictList = new List<object>(); |
301 | ························foreach (IPersistenceCapable relObject in list) |
302 | ························{ |
303 | ····························shortId = ( (IPersistenceCapable) relObject ).ShortId(); |
304 | ····························dictList.Add( new { _oid = shortId } ); |
305 | ························} |
306 | ························dict.Add( fiName, dictList ); |
307 | ····················} |
308 | ····················else |
309 | ····················{ |
310 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
311 | ························if (relationObj == null || ((IPersistenceCapable)relationObj).NDOObjectState == NDOObjectState.Hollow) |
312 | ························{ |
313 | ····························dict.Add( fiName, null ); |
314 | ························} |
315 | ························else |
316 | ························{ |
317 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
318 | ····························shortId = ( (IPersistenceCapable) relIPersistenceCapable ).ShortId(); |
319 | ····························dict.Add( fiName, new { _oid = shortId } ); |
320 | ························} |
321 | ····················} |
322 | ················} |
323 | ············} |
324 | |
325 | ············return dict; |
326 | ········} |
327 | |
328 | ········void InitializePm( object graph ) |
329 | ········{ |
330 | ············IPersistenceCapable pc; |
331 | ············if (graph is IList list) |
332 | ············{ |
333 | ················if (list.Count == 0) |
334 | ····················return; |
335 | ················pc = (IPersistenceCapable) list[0]; |
336 | ············} |
337 | ············else |
338 | ············{ |
339 | ················pc = (IPersistenceCapable) graph; |
340 | ············} |
341 | |
342 | ············this.pm = (PersistenceManager) ( pc.NDOStateManager.PersistenceManager ); |
343 | ········} |
344 | |
345 | ········void RecursivelyAddAdditionalObjects( IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
346 | ········{ |
347 | ············var t = e.GetType(); |
348 | ············FieldMap fm = new FieldMap(t); |
349 | ············var mc = Metaclasses.GetClass(t); |
350 | ············foreach (var fi in fm.Relations) |
351 | ············{ |
352 | ················if (( (IPersistenceCapable) e ).NDOGetLoadState( mc.GetRelationOrdinal( fi.Name ) )) |
353 | ················{ |
354 | ····················object relationObj = fi.GetValue(e); |
355 | ····················if (relationObj is IList list) |
356 | ····················{ |
357 | ························List<object> dictList = new List<object>(); |
358 | ························foreach (IPersistenceCapable relIPersistenceCapable in list) |
359 | ························{ |
360 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
361 | ····························{ |
362 | ································additionalObjects.Add( relIPersistenceCapable ); |
363 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
364 | ····························} |
365 | ························} |
366 | ····················} |
367 | ····················else |
368 | ····················{ |
369 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
370 | ························if (relationObj != null && ((IPersistenceCapable)relationObj).NDOObjectState != NDOObjectState.Hollow) |
371 | ························{ |
372 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
373 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
374 | ····························{ |
375 | ································additionalObjects.Add( relIPersistenceCapable ); |
376 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
377 | ····························} |
378 | ························} |
379 | ····················} |
380 | ················} |
381 | ············} |
382 | ········} |
383 | |
384 | ········object MapRelationChangeRecord(RelationChangeRecord rcr) |
385 | ········{ |
386 | ············return new |
387 | ············{ |
388 | ················parent = new { _oid = rcr.Parent.NDOObjectId.ToShortId() }, |
389 | ················child··= new { _oid = rcr.Child.NDOObjectId.ToShortId() }, |
390 | ················isAdded = rcr.IsAdded, |
391 | ················relationName = rcr.RelationName, |
392 | ················_oid = "RelationChangeRecord" |
393 | ············}; |
394 | ········} |
395 | |
396 | ········void SerializeChangeSet(Stream serializationStream, IList graph) |
397 | ········{ |
398 | ············// A ChangeSetContainer consists of 4 lists of deleted, added, changed objects, and the relation changes. |
399 | ············// AddedObjects = new List<IPersistenceCapable>(); |
400 | ············// DeletedObjects = new List<ObjectId>(); |
401 | ············// ChangedObjects = new List<IPersistenceCapable>(); |
402 | ············// RelationChanges = new List<RelationChangeRecord>(); |
403 | ············List<List<object>> resultObjects = new List<List<object>>(graph.Count); |
404 | ············foreach (IList list in graph) |
405 | ············{ |
406 | ················List<object> partialResult = new List<object>(); |
407 | ················resultObjects.Add(partialResult); |
408 | ················foreach (var item in list) |
409 | ················{ |
410 | ····················if (item is IPersistenceCapable pc) |
411 | ························partialResult.Add(MakeDict(pc)); |
412 | ····················else if (item is ObjectId oid) |
413 | ························partialResult.Add(new { _oid = oid.ToShortId() }); |
414 | ····················else if (item is RelationChangeRecord rcr) |
415 | ························partialResult.Add(MapRelationChangeRecord(rcr)); |
416 | ····················else |
417 | ························throw new NDOException(20003, $"NDOJsonFormatter: unknown element of type {item.GetType().FullName} in ChangeSetContainer."); |
418 | ················} |
419 | ············} |
420 | |
421 | ············var json = JsonConvert.SerializeObject(new { rootObjects = resultObjects, additionalObjects = new object[] { } }); |
422 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
423 | ············serializationStream.Write(byteArray, 0, byteArray.Length); |
424 | ········} |
425 | |
426 | ········/// <summary> |
427 | ········/// Serializes an object graph to a stream. |
428 | ········/// </summary> |
429 | ········/// <param name="serializationStream"></param> |
430 | ········/// <param name="graph"></param> |
431 | ········public void Serialize( Stream serializationStream, object graph ) |
432 | ········{ |
433 | ············string json = null; |
434 | ············if (this.pm == null) |
435 | ················InitializePm( graph ); |
436 | ············List<object> rootObjects = new List<object>(); |
437 | ············List<object> additionalObjects = new List<object>(); |
438 | ············List<IPersistenceCapable> rootObjectList = new List<IPersistenceCapable>(); |
439 | ············List<IPersistenceCapable> additionalObjectList = new List<IPersistenceCapable>(); |
440 | |
441 | ············IList list = graph as IList; |
442 | ············if (list != null) |
443 | ············{ |
444 | ················if (list.Count > 0 && list[0] is IList) |
445 | ················{ |
446 | ····················// Change Set |
447 | ····················SerializeChangeSet(serializationStream, list); |
448 | ····················return; |
449 | ················} |
450 | ················foreach (IPersistenceCapable e in list) |
451 | ················{ |
452 | ····················rootObjectList.Add( e ); |
453 | ················} |
454 | ············} |
455 | ············else if (graph is IPersistenceCapable e) |
456 | ············{ |
457 | ················rootObjectList.Add( e ); |
458 | ············} |
459 | |
460 | ············foreach (var e in rootObjectList) |
461 | ············{ |
462 | ················RecursivelyAddAdditionalObjects( e, rootObjectList, additionalObjectList ); |
463 | ············} |
464 | |
465 | ············foreach (var e in rootObjectList) |
466 | ············{ |
467 | ················rootObjects.Add( MakeDict( e ) ); |
468 | ············} |
469 | |
470 | ············foreach (var e in additionalObjectList) |
471 | ············{ |
472 | ················additionalObjects.Add( MakeDict( e ) ); |
473 | ············} |
474 | |
475 | ············json = JsonConvert.SerializeObject( new { rootObjects, additionalObjects } ); |
476 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
477 | ············serializationStream.Write( byteArray, 0, byteArray.Length ); |
478 | ········} |
479 | ····} |
480 | } |
481 |
New Commit (4a7e8ab)
1 | // |
2 | // Copyright (c) 2002-2020 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.Runtime.Serialization; |
29 | using System.Text; |
30 | using System.Threading.Tasks; |
31 | using NDO; |
32 | using NDO.ShortId; |
33 | using NDO.Mapping; |
34 | using Newtonsoft.Json; |
35 | using Newtonsoft.Json.Linq; |
36 | |
37 | namespace NDO.JsonFormatter |
38 | { |
39 | ····/// <summary> |
40 | ····/// Formatter implementation which serializes NDO ObjectContainers and ChangeSetContainers into Json. |
41 | ····/// </summary> |
42 | ····public class NdoJsonFormatter : IFormatter |
43 | ····{ |
44 | ········/// <inheritdoc/> |
45 | ········public ISurrogateSelector SurrogateSelector { get; set; } |
46 | ········/// <inheritdoc/> |
47 | ········public SerializationBinder Binder { get; set; } |
48 | ········/// <inheritdoc/> |
49 | ········public StreamingContext Context { get; set; } |
50 | |
51 | ········PersistenceManager pm; |
52 | |
53 | ········/// <summary> |
54 | ········/// Constructs an NdoJsonFormatter object. This constructor is sufficient for serialization. |
55 | ········/// </summary> |
56 | ········public NdoJsonFormatter() |
57 | ········{ |
58 | ············this.pm = null; |
59 | ········} |
60 | |
61 | ········/// <summary> |
62 | ········/// Constructs an NdoJsonFormatter object. This constructor should be used for deserialization. |
63 | ········/// </summary> |
64 | ········/// <param name="pm"></param> |
65 | ········public NdoJsonFormatter( PersistenceManager pm ) |
66 | ········{ |
67 | ············this.pm = pm; |
68 | ········} |
69 | |
70 | ········IPersistenceCapable DeserializeObject( JToken jobj ) |
71 | ········{ |
72 | ············var shortId = (string)jobj["_oid"]; |
73 | |
74 | ············if (shortId != null) |
75 | ············{ |
76 | ················var pc = this.pm.FindObject(shortId); |
77 | ················pc.NDOStateManager = null;··// Detach object |
78 | ················pc.FromJToken( jobj ); |
79 | ················return pc; |
80 | ············} |
81 | |
82 | ············return null; |
83 | ········} |
84 | |
85 | ········IPersistenceCapable DeserializeHollowObject(JToken jobj) |
86 | ········{ |
87 | ············var shortId = (string)jobj["_oid"]; |
88 | |
89 | ············if (shortId != null) |
90 | ············{ |
91 | ················var pc = this.pm.FindObject(shortId); |
92 | ················pc.NDOStateManager = null;··// Detach object |
93 | ················return pc; |
94 | ············} |
95 | |
96 | ············return null; |
97 | ········} |
98 | |
99 | ········RelationChangeRecord DeserializeRelationChangeRecord(JToken jobj) |
100 | ········{ |
101 | ············var parent = this.pm.FindObject((string)jobj["parent"]["_oid"]); |
102 | ············parent.NDOStateManager = null;··// Detach object |
103 | ············var child = this.pm.FindObject((string)jobj["child"]["_oid"]); |
104 | ············child.NDOStateManager = null;··// Detach object |
105 | |
106 | ············var relationName = (string)jobj["relationName"]; |
107 | ············var isAdded = (bool)jobj["isAdded"]; |
108 | ············return new RelationChangeRecord(parent, child, relationName, isAdded); |
109 | ········} |
110 | |
111 | ········void FixRelations( JToken jObj, IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
112 | ········{ |
113 | ············var t = e.GetType(); |
114 | ············FieldMap fm = new FieldMap(t); |
115 | ············var mc = Metaclasses.GetClass(t); |
116 | ············foreach (var fi in fm.Relations) |
117 | ············{ |
118 | ················var token = jObj[fi.Name]; |
119 | ················if (token == null) |
120 | ····················continue; |
121 | ················bool isArray = typeof(IList).IsAssignableFrom( fi.FieldType ); |
122 | ················if (token is JArray jarray) |
123 | ················{ |
124 | ····················if (isArray) |
125 | ····················{ |
126 | ························IList container = (IList)fi.GetValue(e);························ |
127 | ························if (container == null) |
128 | ····························throw new NDOException( 20002, $"Container object of relation {t.Name}.{fi.Name} is not initialized. Please initialize the field in your class constructor." ); |
129 | |
130 | ························container.Clear(); |
131 | ························foreach (var relJObj in jarray) |
132 | ························{ |
133 | ····························container.Add( DeserializeObject( relJObj ) ); |
134 | ························} |
135 | ····················} |
136 | ················} |
137 | ················else |
138 | ················{ |
139 | ····················if (!isArray) |
140 | ····················{ |
141 | ························if (token.Type == JTokenType.Null) |
142 | ····························fi.SetValue( e, null ); |
143 | ························else |
144 | ····························fi.SetValue( e, DeserializeObject( token ) ); |
145 | ····················} |
146 | ················} |
147 | ············} |
148 | ········} |
149 | |
150 | ········IList DeserializeChangeSetContainer(JToken rootArray) |
151 | ········{ |
152 | ············// 0: AddedObjects = new List<IPersistenceCapable>(); |
153 | ············// 1: DeletedObjects = new List<ObjectId>(); |
154 | ············// 2: ChangedObjects = new List<IPersistenceCapable>(); |
155 | ············// 3: RelationChanges = new List<RelationChangeRecord>(); |
156 | |
157 | ············ArrayList arrayList = new ArrayList(new object[4]); |
158 | |
159 | ············for (int i = 0; i < 4; i++) |
160 | ············{ |
161 | ················var partialArray = rootArray[i]; |
162 | ················if (i == 0 || i == 2) |
163 | ················{ |
164 | ····················var partialList = new List<IPersistenceCapable>(); |
165 | ····················arrayList[i] = partialList; |
166 | ····················foreach (var item in partialArray) |
167 | ····················{ |
168 | ························partialList.Add(DeserializeObject(item)); |
169 | ····················} |
170 | ················} |
171 | ················if (i == 1) |
172 | ················{ |
173 | ····················var partialList = new List<IPersistenceCapable>(); |
174 | ····················arrayList[i] = partialList; |
175 | ····················foreach (var item in partialArray) |
176 | ····················{ |
177 | ························partialList.Add(DeserializeHollowObject(item)); |
178 | ····················} |
179 | ················} |
180 | ················if (i == 3) |
181 | ················{ |
182 | ····················var partialList = new List<RelationChangeRecord>(); |
183 | ····················arrayList[i] = partialList; |
184 | ····················foreach (var item in partialArray) |
185 | ····················{ |
186 | ························partialList.Add(DeserializeRelationChangeRecord(item)); |
187 | ····················} |
188 | ················} |
189 | ············} |
190 | |
191 | ············return arrayList; |
192 | ········} |
193 | |
194 | ········object DeserializeRootArray( JToken rootArray ) |
195 | ········{ |
196 | ············var rootObjectsToken = (JArray)rootArray["rootObjects"]; |
197 | ············var additionalObjectsToken = (JArray)rootArray["additionalObjects"]; |
198 | ············if (rootObjectsToken.Count >= 4 && rootObjectsToken[0] is JArray) |
199 | ················return DeserializeChangeSetContainer(rootObjectsToken); |
200 | |
201 | ············List<IPersistenceCapable> rootObjects = new List<IPersistenceCapable>(); |
202 | ············List<IPersistenceCapable> additionalObjects = new List<IPersistenceCapable>(); |
203 | |
204 | ············foreach (var jObj in rootObjectsToken) |
205 | ············{ |
206 | ················IPersistenceCapable e = DeserializeObject(jObj); |
207 | ················if (e != null) |
208 | ····················rootObjects.Add( e ); |
209 | ············} |
210 | |
211 | ············foreach (var jObj in additionalObjectsToken) |
212 | ············{ |
213 | ················IPersistenceCapable e = DeserializeObject(jObj); |
214 | ················if (e != null) |
215 | ····················additionalObjects.Add( e ); |
216 | ············} |
217 | |
218 | ············foreach (var jObj in rootObjectsToken) |
219 | ············{ |
220 | ················var shortId = (string)jObj["_oid"]; |
221 | ················IPersistenceCapable e = rootObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
222 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
223 | ············} |
224 | |
225 | ············foreach (var jObj in additionalObjectsToken) |
226 | ············{ |
227 | ················var shortId = (string)jObj["_oid"]; |
228 | ················IPersistenceCapable e = additionalObjects.First(o=>((IPersistenceCapable)o).ShortId() == shortId); |
229 | ················FixRelations( jObj, e, rootObjects, additionalObjects ); |
230 | ············} |
231 | |
232 | ············return new ArrayList( rootObjects ); |
233 | ········} |
234 | |
235 | internal class Metaclasses |
236 | ········{ |
237 | private static Dictionary<Type, IMetaClass2> theClasses = new Dictionary<Type, IMetaClass2>( ) ; |
238 | |
239 | internal static IMetaClass2 GetClass( Type t ) |
240 | ············{ |
241 | ················if (t.IsGenericTypeDefinition) |
242 | ····················return null; |
243 | |
244 | IMetaClass2 mc; |
245 | |
246 | ················if (!theClasses.TryGetValue( t, out mc )) |
247 | ················{ |
248 | ····················lock (theClasses) |
249 | ····················{ |
250 | if ( !theClasses. TryGetValue( t, out mc ) ) // Threading double check |
251 | ························{ |
252 | Type mcType = t. GetNestedType( "MetaClass", BindingFlags. NonPublic | BindingFlags. Public ) ; |
253 | ····························if (null == mcType) |
254 | ································throw new NDOException( 13, "Missing nested class 'MetaClass' for type '" + t.Name + "'; the type doesn't seem to be enhanced." ); |
255 | ····························Type t2 = mcType; |
256 | ····························if (t2.IsGenericTypeDefinition) |
257 | ································t2 = t2.MakeGenericType( t.GetGenericArguments() ); |
258 | var o = Activator. CreateInstance( t2, t ) ; |
259 | ····························if (o is IMetaClass2 mc2) |
260 | ································mc = mc2; |
261 | ····························else |
262 | ································throw new NDOException( 101010, $"MetaClass for type '{t.FullName}' must implement IMetaClass2, but doesn't. Recompile the assembly with NDO v. >= 4.0.9" ); |
263 | ····························theClasses.Add( t, mc ); |
264 | ························} |
265 | ····················} |
266 | ················} |
267 | |
268 | ················return mc; |
269 | ············} |
270 | ········} |
271 | |
272 | |
273 | ········/// <summary> |
274 | ········/// Deserializes a Container from a stream. |
275 | ········/// </summary> |
276 | ········/// <param name="serializationStream"></param> |
277 | ········/// <returns></returns> |
278 | ········public object Deserialize( Stream serializationStream ) |
279 | ········{ |
280 | ············if (this.pm == null) |
281 | ················throw new NDOException( 20001, "PersistenceManager is not initialized. Provide a PersistenceManager in the formatter constructor." ); |
282 | |
283 | ············JsonSerializer serializer = new JsonSerializer(); |
284 | ············TextReader textReader = new StreamReader( serializationStream ); |
285 | ············var rootObject = (JToken)serializer.Deserialize(textReader, typeof(JToken)); |
286 | ············if (rootObject == null || rootObject.Type == JTokenType.Null) |
287 | ················return null; |
288 | ············var result = DeserializeRootArray( rootObject ); |
289 | ············this.pm.UnloadCache(); |
290 | ············return result; |
291 | ········} |
292 | |
293 | ········IDictionary<string, object> MakeDict( IPersistenceCapable pc ) |
294 | ········{ |
295 | ············var dict = pc.ToDictionary(pm); |
296 | ············var shortId = ((IPersistenceCapable)pc).ShortId(); |
297 | ············var t = pc.GetType(); |
298 | ············FieldMap fm = new FieldMap(t); |
299 | ············var mc = Metaclasses.GetClass(t); |
300 | ············foreach (var fi in fm.Relations) |
301 | ············{ |
302 | ················var fiName = fi.Name; |
303 | ················if (( (IPersistenceCapable) pc ).NDOGetLoadState( mc.GetRelationOrdinal( fiName ) )) |
304 | ················{ |
305 | ····················object relationObj = fi.GetValue(pc); |
306 | ····················if (relationObj is IList list) |
307 | ····················{ |
308 | ························List<object> dictList = new List<object>(); |
309 | ························foreach (IPersistenceCapable relObject in list) |
310 | ························{ |
311 | ····························shortId = ( (IPersistenceCapable) relObject ).ShortId(); |
312 | ····························dictList.Add( new { _oid = shortId } ); |
313 | ························} |
314 | ························dict.Add( fiName, dictList ); |
315 | ····················} |
316 | ····················else |
317 | ····················{ |
318 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
319 | ························if (relationObj == null || ((IPersistenceCapable)relationObj).NDOObjectState == NDOObjectState.Hollow) |
320 | ························{ |
321 | ····························dict.Add( fiName, null ); |
322 | ························} |
323 | ························else |
324 | ························{ |
325 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
326 | ····························shortId = ( (IPersistenceCapable) relIPersistenceCapable ).ShortId(); |
327 | ····························dict.Add( fiName, new { _oid = shortId } ); |
328 | ························} |
329 | ····················} |
330 | ················} |
331 | ············} |
332 | |
333 | ············return dict; |
334 | ········} |
335 | |
336 | ········void InitializePm( object graph ) |
337 | ········{ |
338 | ············IPersistenceCapable pc; |
339 | ············if (graph is IList list) |
340 | ············{ |
341 | ················if (list.Count == 0) |
342 | ····················return; |
343 | ················pc = (IPersistenceCapable) list[0]; |
344 | ············} |
345 | ············else |
346 | ············{ |
347 | ················pc = (IPersistenceCapable) graph; |
348 | ············} |
349 | |
350 | ············this.pm = (PersistenceManager) ( pc.NDOStateManager.PersistenceManager ); |
351 | ········} |
352 | |
353 | ········void RecursivelyAddAdditionalObjects( IPersistenceCapable e, List<IPersistenceCapable> rootObjects, List<IPersistenceCapable> additionalObjects ) |
354 | ········{ |
355 | ············var t = e.GetType(); |
356 | ············FieldMap fm = new FieldMap(t); |
357 | ············var mc = Metaclasses.GetClass(t); |
358 | ············foreach (var fi in fm.Relations) |
359 | ············{ |
360 | ················if (( (IPersistenceCapable) e ).NDOGetLoadState( mc.GetRelationOrdinal( fi.Name ) )) |
361 | ················{ |
362 | ····················object relationObj = fi.GetValue(e); |
363 | ····················if (relationObj is IList list) |
364 | ····················{ |
365 | ························List<object> dictList = new List<object>(); |
366 | ························foreach (IPersistenceCapable relIPersistenceCapable in list) |
367 | ························{ |
368 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
369 | ····························{ |
370 | ································additionalObjects.Add( relIPersistenceCapable ); |
371 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
372 | ····························} |
373 | ························} |
374 | ····················} |
375 | ····················else |
376 | ····················{ |
377 | ························// Hollow object means, that we don't want to transfer the object to the other side. |
378 | ························if (relationObj != null && ((IPersistenceCapable)relationObj).NDOObjectState != NDOObjectState.Hollow) |
379 | ························{ |
380 | ····························IPersistenceCapable relIPersistenceCapable = (IPersistenceCapable) relationObj; |
381 | ····························if (!rootObjects.Contains( relIPersistenceCapable ) && !additionalObjects.Contains( relIPersistenceCapable )) |
382 | ····························{ |
383 | ································additionalObjects.Add( relIPersistenceCapable ); |
384 | ································RecursivelyAddAdditionalObjects( relIPersistenceCapable, rootObjects, additionalObjects ); |
385 | ····························} |
386 | ························} |
387 | ····················} |
388 | ················} |
389 | ············} |
390 | ········} |
391 | |
392 | ········object MapRelationChangeRecord(RelationChangeRecord rcr) |
393 | ········{ |
394 | ············return new |
395 | ············{ |
396 | ················parent = new { _oid = rcr.Parent.NDOObjectId.ToShortId() }, |
397 | ················child··= new { _oid = rcr.Child.NDOObjectId.ToShortId() }, |
398 | ················isAdded = rcr.IsAdded, |
399 | ················relationName = rcr.RelationName, |
400 | ················_oid = "RelationChangeRecord" |
401 | ············}; |
402 | ········} |
403 | |
404 | ········void SerializeChangeSet(Stream serializationStream, IList graph) |
405 | ········{ |
406 | ············// A ChangeSetContainer consists of 4 lists of deleted, added, changed objects, and the relation changes. |
407 | ············// AddedObjects = new List<IPersistenceCapable>(); |
408 | ············// DeletedObjects = new List<ObjectId>(); |
409 | ············// ChangedObjects = new List<IPersistenceCapable>(); |
410 | ············// RelationChanges = new List<RelationChangeRecord>(); |
411 | ············List<List<object>> resultObjects = new List<List<object>>(graph.Count); |
412 | ············foreach (IList list in graph) |
413 | ············{ |
414 | ················List<object> partialResult = new List<object>(); |
415 | ················resultObjects.Add(partialResult); |
416 | ················foreach (var item in list) |
417 | ················{ |
418 | ····················if (item is IPersistenceCapable pc) |
419 | ························partialResult.Add(MakeDict(pc)); |
420 | ····················else if (item is ObjectId oid) |
421 | ························partialResult.Add(new { _oid = oid.ToShortId() }); |
422 | ····················else if (item is RelationChangeRecord rcr) |
423 | ························partialResult.Add(MapRelationChangeRecord(rcr)); |
424 | ····················else |
425 | ························throw new NDOException(20003, $"NDOJsonFormatter: unknown element of type {item.GetType().FullName} in ChangeSetContainer."); |
426 | ················} |
427 | ············} |
428 | |
429 | ············var json = JsonConvert.SerializeObject(new { rootObjects = resultObjects, additionalObjects = new object[] { } }); |
430 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
431 | ············serializationStream.Write(byteArray, 0, byteArray.Length); |
432 | ········} |
433 | |
434 | ········/// <summary> |
435 | ········/// Serializes an object graph to a stream. |
436 | ········/// </summary> |
437 | ········/// <param name="serializationStream"></param> |
438 | ········/// <param name="graph"></param> |
439 | ········public void Serialize( Stream serializationStream, object graph ) |
440 | ········{ |
441 | ············string json = null; |
442 | ············if (this.pm == null) |
443 | ················InitializePm( graph ); |
444 | ············List<object> rootObjects = new List<object>(); |
445 | ············List<object> additionalObjects = new List<object>(); |
446 | ············List<IPersistenceCapable> rootObjectList = new List<IPersistenceCapable>(); |
447 | ············List<IPersistenceCapable> additionalObjectList = new List<IPersistenceCapable>(); |
448 | |
449 | ············IList list = graph as IList; |
450 | ············if (list != null) |
451 | ············{ |
452 | ················if (list.Count > 0 && list[0] is IList) |
453 | ················{ |
454 | ····················// Change Set |
455 | ····················SerializeChangeSet(serializationStream, list); |
456 | ····················return; |
457 | ················} |
458 | ················foreach (IPersistenceCapable e in list) |
459 | ················{ |
460 | ····················rootObjectList.Add( e ); |
461 | ················} |
462 | ············} |
463 | ············else if (graph is IPersistenceCapable e) |
464 | ············{ |
465 | ················rootObjectList.Add( e ); |
466 | ············} |
467 | |
468 | ············foreach (var e in rootObjectList) |
469 | ············{ |
470 | ················RecursivelyAddAdditionalObjects( e, rootObjectList, additionalObjectList ); |
471 | ············} |
472 | |
473 | ············foreach (var e in rootObjectList) |
474 | ············{ |
475 | ················rootObjects.Add( MakeDict( e ) ); |
476 | ············} |
477 | |
478 | ············foreach (var e in additionalObjectList) |
479 | ············{ |
480 | ················additionalObjects.Add( MakeDict( e ) ); |
481 | ············} |
482 | |
483 | ············json = JsonConvert.SerializeObject( new { rootObjects, additionalObjects } ); |
484 | ············var byteArray = Encoding.UTF8.GetBytes(json); |
485 | ············serializationStream.Write( byteArray, 0, byteArray.Length ); |
486 | ········} |
487 | ····} |
488 | } |
489 |