-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathXMPPObjectStore.m
444 lines (394 loc) · 14.9 KB
/
XMPPObjectStore.m
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
/*
Copyright (C) 2009 Niels Grewe
Author: Niels Grewe <niels.grewe@halbordnung.de>
Date: August 2009
License: Modified BSD (see COPYING)
*/
#import "XMPPObjectStore.h"
#import "XMPPMessage.h"
#import <Foundation/Foundation.h>
#import <EtoileFoundation/ETUUID.h>
#import <EtoileFoundation/NSData+Hash.h>
#import <EtoileFoundation/Macros.h>
#import <EtoileXML/ETXMLWriter.h>
#import <EtoileXML/ETXMLParser.h>
//These are needed for interface definitions only.
#import <EtoileSerialize/ETObjectStore.h>
#import <EtoileSerialize/ETDeserializer.h>
#import <EtoileSerialize/ETDeserializerBackend.h>
@interface XMPPObjectStoreVersionTag: ETXMLNullHandler
@end
@interface XMPPObjectStoreBranchTag: ETXMLNullHandler
@end
@interface XMPPObjectStoreSerialDataTag: ETXMLNullHandler
@end
static NSDictionary *CHILD_CLASSES;
@implementation XMPPObjectStore
+ (void)initialize
{
CHILD_CLASSES = D([XMPPObjectStoreVersionTag class], @"version",
[XMPPObjectStoreBranchTag class], @"branch",
[XMPPObjectStoreSerialDataTag class], @"serialdata");
}
- (id)initWithXMLParser: (ETXMLParser *)aParser
XMLWriter: (ETXMLWriter *)aWriter
parent: (id <ETXMLParserDelegate>)aParent
key: (id)aKey
inConversation: (XMPPConversation*)aConversation
{
self = [super initWithXMLParser: aParser
key: aKey];
if (nil == self)
{
return nil;
}
writer = aWriter;
conversation = aConversation;
return self;
}
- (id)initWithXMLParser: (ETXMLParser *)aParser
parent: (id <ETXMLParserDelegate>)aParent
key: (id)aKey
{
return [self initWithXMLParser: aParser
XMLWriter: nil
parent: aParent
key: aKey
inConversation: nil];
}
- (id)initWithXMLWriter: (ETXMLWriter *)aWriter
inConversation: (XMPPConversation *)aConversation
{
return [self initWithXMLParser: nil
XMLWriter: aWriter
parent: nil
key: nil
inConversation: aConversation];
}
- (void)setXMLWriter: (ETXMLWriter*)_writer
{
writer = _writer;
}
- (void)setDeserializer: (id)aDeserializer
{
deserializer = aDeserializer;
}
- (void)setConversation: (id)aConversation
{
// The conversation that the store belongs to. Although it might create the
// object, it does not retain the store (It is handed out to the serializing
// process). So it is safe to retain it.
conversation = aConversation;
}
- (void)beginObjectWithUUID: (ETUUID*)objUUID
andApplication: (NSString *)registeredName
{
//TODO: Lock the conversation.
// If we don't have an uuid, we generate one.
if (nil == objUUID)
{
objUUID = [ETUUID UUID];
}
// Send an <coreobject> tag on the stream indicating the application and
// uudi data the receiving side may use for routing the object.
[writer startElement: @"coreobject"
attributes: D(@"http://www.etoileos.com/CoreObject", @"xmlns",
registeredName, @"application",
[objUUID stringValue], @"uuid")];
}
- (BOOL)isValidBranch: (NSString*) aBranch
{
return [branch isEqualToString: aBranch];
}
- (void)startVersion: (unsigned)aVersion inBranch:(NSString*)aBranch
{
version = (NSUInteger)aVersion;
branch = aBranch;
[writer startAndEndElement: @"branch" cdata: branch];
[writer startAndEndElement: @"version"
cdata: [[NSNumber numberWithUnsignedInteger: version] stringValue]];
}
- (void)commit
{
[writer endElement]; //</coreobject>
[writer endElement]; //</message>
}
- (unsigned)version
{
return (unsigned)version;
}
- (NSString*)branch
{
return branch;
}
// The writer does not expose to us how much data it has written.
- (unsigned)size
{
return 0;
}
// This store does not track branches.
- (NSString*)parentOfBranch:(NSString*)aBranch
{
return nil;
}
// This isn't supported either.
- (void)createBranch:(NSString*)newBranch from:(NSString*)oldBranch;
{
branch = newBranch;
}
// We usually don't expect writing any other serialization format than the XML
// one. Still, an XMPP stream can be used to do that. If you really, really want
// it.
- (void)writeBytes: (unsigned char*)bytes
count: (unsigned)count
{
NSString* b64 = [[NSData dataWithBytes: bytes length: count] base64String];
[writer startAndEndElement: @"serialdata"
attributes: D(@"unknown", @"backend")
cdata: b64];
}
// This method only works for non-XML deserializers.
- (NSData*)dataForVersion: (unsigned)aVersion
inBranch: (NSString*)aBranch
{
if (aVersion == version && [branch isEqualToString:aBranch])
{
return buffer;
}
return nil;
}
- (void)startElement: (NSString*)_name
attributes: (NSDictionary*)_attributes
{
if ([_name isEqualToString: @"coreobject"])
{
depth++;
uuid = [ETUUID UUIDWithString: [_attributes objectForKey: @"uuid"]];
proxy = [NSConnection rootProxyForConnectionWithRegisteredName: [_attributes objectForKey: @"application"]
host: nil];
if (!proxy)
{
NSLog(@"Failed to get proxy (%@) for deserialization",[_attributes objectForKey: @"application"]);
}
// TODO: If getting the handling application from the nameserver fails
// this way, we want some way to start it based on UUID and/or
// identifier.
if (![proxy conformsToProtocol:@protocol(ETDeserializerVendor)])
{
// If the remote end does not support the methods we need to call on
// it, we don't want to keep the proxy around.
NSLog(@"Receiving application (%@) does support deserialization.", [_attributes objectForKey: @"application"]);
proxy = nil;
}
else
{
[proxy setProtocolForProxy: @protocol(ETDeserializerVendor)];
}
}
else if ([_name isEqualToString: @"objects"])
{
// The <objects> element implies that we are deserializing data in XML
// format.
deserializer = [proxy deserializerWithBackend: @"ETDeserializerBackendXML"
forObjectWithUUID: uuid
from: [[(XMPPMessage*)[parser parentHandler] correspondent] jidString]];
id<ETDeserializerBackend> backend = [(ETDeserializer*)deserializer backend];
ETXMLNullHandler *handler = nil;
if (nil == backend)
{
//Ignore if the deserializer backend is not available:
NSLog(@"No backend, not deserializing");
handler = [[ETXMLNullHandler alloc] initWithXMLParser: parser
key: _name];
}
else
{
// The deserializer-backend is the new handler.
// -deserializeFromStore: will make it take over the
// parsing duties.
handler = (ETXMLNullHandler *)backend;
[backend deserializeFromStore: self];
}
// The backend will create a handler for the <objects> tag itself.
[handler startElement: _name attributes: _attributes];
}
else
{
ETXMLNullHandler *handler = [(ETXMLNullHandler*)[[CHILD_CLASSES objectForKey: _name] alloc] initWithXMLParser: parser key: _name];
if (nil == handler)
{
handler = [[ETXMLNullHandler alloc] initWithXMLParser: parser
key: _name];
}
[handler startElement: _name attributes: _attributes];
}
}
- (void)endElement: (NSString*)_name
{
if ([_name isEqualToString: @"coreobject"])
{
if ((nil != buffer) && (nil == deserializer))
{
// Handle the case that some legacy format was sent over the wire.
// This uses the backend attribute of the <serialdata> tag.
// Right now, this will always be "unknown" because object stores
// are not yet smart enough to tell us about the deserializer
// backend that is using them to store the object graph. So right
// now, we'll map "unkown" to "ETDeserializerBackendBinary" because
// it is the only other deserializer backend as of yet.
if ((backendName == nil)
|| [backendName isEqualToString: @"unknown"])
{
backendName = @"ETDeserializerBackendBinary";
}
deserializer = [proxy deserializerWithBackend: backendName
forObjectWithUUID: uuid
from: [[(XMPPMessage*)[parser parentHandler] correspondent] jidString]];
}
// The XML deserializer does not strictly need setting branch and
// version but they are needed at least for the binary backend.
[deserializer setBranch: branch];
[(ETDeserializer*)deserializer setVersion: version];
// NOTE: The deserialization process might already be complete when
// calling -restoreObjectGraph when deserializing was done as the stream
// arrived. The XML backend is aware of that and won't start all over in
// this case (cf. -deserializePrincipalObject in
// ETDeserializerBackendXML). Calling it will then merely make the
// deserializer finish the deserialization and return the pointer to the
// object just deserialized.
id theObject = [deserializer restoreObjectGraph];
[(id<ETDeserializerVendor>)proxy obtainedObject: theObject
withUUID: uuid
from: [[(XMPPMessage*)[parser parentHandler] correspondent] jidString]];
// FIXME: Setting the object as the value of this node makes it appear
// in the unknownAttributes dictionary of the message. The object will
// still be a distant object so perhaps this isn't the smartest thing to
// do. If we were piping all messages directly into some common message
// store (and thus re-serializing them), this would probably not matter
// much.
value = theObject;
// The store ceases it's parsing duties after the closing </coreobject>
// tag. So we dispose of the distant objects.
deserializer = nil;
proxy = nil;
}
[super endElement: (NSString*)_name];
}
- (void)addversion: (NSString*)aVersion
{
version = (NSUInteger)[aVersion longLongValue];
}
- (void)addbranch: (NSString*)aBranch
{
branch = aBranch;
}
- (void)addbackendName: (NSString*)aBackendName
{
backendName = aBackendName;
}
- (void)addserialdata: (NSString*)aString
{
NSData *someData = [aString base64DecodedData];
if (nil == buffer)
{
buffer = [[NSMutableData alloc] initWithData: someData];
}
else
{
[buffer appendData: someData];
}
}
- (ETXMLParser*)xmlParser
{
return parser;
}
- (BOOL)xmlParserWillRead
{
return YES;
}
- (ETXMLWriter*)xmlWriter
{
return writer;
}
- (BOOL)xmlWriterWillStore
{
return YES;
}
@end
@implementation XMPPObjectStoreVersionTag
- (void)startElement: (NSString*)_name
attributes: (NSDictionary*)_attributes
{
if (![_name isEqualToString: @"version"])
{
ETXMLNullHandler *handler = [[ETXMLNullHandler alloc] initWithXMLParser: parser key: _name];
[handler startElement: _name attributes: _attributes];
return;
}
depth++;
}
- (void)characters: (NSString*)cData
{
if (self == value)
{
value = [[NSMutableString alloc] initWithString: cData];
}
else
{
[(NSMutableString*)value appendString: cData];
}
}
@end
@implementation XMPPObjectStoreBranchTag
- (void)startElement: (NSString*)_name
attributes: (NSDictionary*)_attributes
{
if (![_name isEqualToString: @"branch"])
{
ETXMLNullHandler *handler = [[ETXMLNullHandler alloc] initWithXMLParser: parser key: _name];
[handler startElement: _name attributes: _attributes];
return;
}
depth++;
}
- (void)characters: (NSString*)cData
{
if (self == value)
{
value = [[NSMutableString alloc] initWithString: cData];
}
else
{
[(NSMutableString*)value appendString: cData];
}
}
@end
@implementation XMPPObjectStoreSerialDataTag
- (void)startElement: (NSString*)_name
attributes: (NSDictionary*)_attributes
{
if (![_name isEqualToString: @"serialdata"])
{
ETXMLNullHandler *handler = [[ETXMLNullHandler alloc] initWithXMLParser: parser key: _name];
[handler startElement: _name attributes: _attributes];
return;
}
if ([[parser parentHandler] respondsToSelector:@selector(addChild:forKey:)])
{
[[parser parentHandler] addChild: [_attributes objectForKey: @"backend"]
forKey: @"backendName"];
}
depth++;
}
- (void)characters: (NSString*)cData
{
if (self == value)
{
value = [[NSMutableString alloc] initWithString: cData];
}
else
{
[(NSMutableString*)value appendString: cData];
}
}
@end