/ CoreData / NSXMLPersistentStore.m
NSXMLPersistentStore.m
  1  #import "NSXMLPersistentStore.h"
  2  #import <CoreData/NSAtomicStoreCacheNode.h>
  3  #import <CoreData/NSAttributeDescription.h>
  4  #import <CoreData/NSEntityDescription.h>
  5  #import <CoreData/NSManagedObject.h>
  6  #import <CoreData/NSManagedObjectModel.h>
  7  #import <CoreData/NSPersistentStoreCoordinator.h>
  8  #import <CoreData/NSRelationshipDescription.h>
  9  #import <CoreFoundation/CFUUID.h>
 10  #import <Foundation/NSDate.h>
 11  #import <Foundation/NSDictionary.h>
 12  #import <Foundation/NSXMLDocument.h>
 13  #import <Foundation/NSXMLElement.h>
 14  
 15  /* TODO:
 16     - date formatting
 17     - binary data??
 18   */
 19  
 20  @implementation NSXMLPersistentStore
 21  
 22  + (NSDictionary *) metadataForPersistentStoreWithURL: (NSURL *) url
 23                                                 error: (NSError **) error
 24  {
 25      NSData *data = [[NSData alloc] initWithContentsOfURL: url
 26                                                   options: 0
 27                                                     error: error];
 28      NSInteger options =
 29              NSXMLNodePreserveCharacterReferences | NSXMLNodePreserveWhitespace;
 30  
 31      if ([data length] == 0)
 32          return nil;
 33  
 34      NSXMLDocument *xml =
 35              [[[NSXMLDocument alloc] initWithContentsOfURL: url
 36                                                    options: options
 37                                                      error: error] autorelease];
 38  
 39      if (xml == nil)
 40          return nil;
 41  
 42      NSXMLElement *database = [[xml nodesForXPath: @"database"
 43                                             error: nil] lastObject];
 44      NSXMLElement *databaseInfo =
 45              [[database elementsForName: @"databaseInfo"] lastObject];
 46      NSXMLElement *uuid = [[databaseInfo elementsForName: @"UUID"] lastObject];
 47  
 48      return [NSDictionary
 49              dictionaryWithObjectsAndKeys: [uuid stringValue], NSStoreUUIDKey,
 50                                            NSXMLStoreType, NSStoreTypeKey, nil];
 51  }
 52  
 53  - (NSString *) type {
 54      return NSXMLStoreType;
 55  }
 56  
 57  - (NSXMLElement *) databaseElement {
 58      return [[_document nodesForXPath: @"database" error: nil] lastObject];
 59  }
 60  
 61  - (NSXMLElement *) databaseInfoElement {
 62      return [[[self databaseElement] elementsForName: @"databaseInfo"]
 63              lastObject];
 64  }
 65  
 66  - (NSXMLElement *) metadataElement {
 67      return [[[self databaseElement] elementsForName: @"metadata"] lastObject];
 68  }
 69  
 70  - (NSXMLElement *) identifierElement {
 71      return [[[self databaseInfoElement] elementsForName: @"UUID"] lastObject];
 72  }
 73  
 74  - (NSXMLElement *) nextObjectIDElement {
 75      return [[[self databaseInfoElement] elementsForName: @"nextObjectID"]
 76              lastObject];
 77  }
 78  
 79  - initWithPersistentStoreCoordinator:
 80            (NSPersistentStoreCoordinator *) coordinator
 81                     configurationName: (NSString *) configurationName
 82                                   URL: (NSURL *) url
 83                               options: (NSDictionary *) options
 84  {
 85      if ([super initWithPersistentStoreCoordinator: coordinator
 86                                  configurationName: configurationName
 87                                                URL: url
 88                                            options: options] == nil)
 89          return nil;
 90  
 91      NSData *data = [[NSData alloc] initWithContentsOfURL: [self URL]
 92                                                   options: 0
 93                                                     error: NULL];
 94      NSInteger xmlOptions =
 95              NSXMLNodePreserveCharacterReferences | NSXMLNodePreserveWhitespace;
 96  
 97      // A valid load can be from a non-existent file or a zero file, zero length
 98      // checks for both
 99  
100      if ([data length] != 0) {
101          if ((_document = [[NSXMLDocument alloc] initWithData: data
102                                                       options: xmlOptions
103                                                         error: NULL]) == nil) {
104              [self dealloc];
105              return nil;
106          }
107      } else {
108          _document = [[NSXMLDocument alloc] initWithKind: NSXMLDocumentXMLKind
109                                                  options: xmlOptions];
110  
111          NSXMLElement *database =
112                  [[NSXMLElement alloc] initWithName: @"database"];
113          NSXMLElement *databaseInfo =
114                  [[NSXMLElement alloc] initWithName: @"databaseInfo"];
115          NSXMLElement *versionElement =
116                  [[NSXMLElement alloc] initWithName: @"version"
117                                         stringValue: @"1"];
118          NSXMLElement *uuidElement =
119                  [[NSXMLElement alloc] initWithName: @"UUID"
120                                         stringValue: [self identifier]];
121          NSXMLElement *nextObjectID =
122                  [[NSXMLElement alloc] initWithName: @"nextObjectID"
123                                         stringValue: @"1"];
124          NSXMLElement *metadata =
125                  [[NSXMLElement alloc] initWithName: @"metadata"];
126  
127          [_document addChild: database];
128          [database addChild: databaseInfo];
129          [databaseInfo addChild: versionElement];
130          [databaseInfo addChild: uuidElement];
131          [databaseInfo addChild: nextObjectID];
132          [databaseInfo addChild: metadata];
133  
134          [metadata release];
135          [nextObjectID release];
136          [uuidElement release];
137          [versionElement release];
138          [databaseInfo release];
139          [database release];
140      }
141  
142      [super setIdentifier: [[self identifierElement] stringValue]];
143      [self setMetadata: [NSDictionary
144                                 dictionaryWithObjectsAndKeys: [self identifier],
145                                                               NSStoreUUIDKey,
146                                                               [self type],
147                                                               NSStoreTypeKey,
148                                                               nil]];
149  
150      _referenceToCacheNode = [[NSMutableDictionary alloc] init];
151      _referenceToElement = [[NSMutableDictionary alloc] init];
152      _usedReferences = [[NSMutableSet alloc] init];
153  
154      return self;
155  }
156  
157  - (void) dealloc {
158      [_document release];
159      [_referenceToCacheNode release];
160      [_referenceToElement release];
161      [_usedReferences release];
162      [super dealloc];
163  }
164  
165  - (void) setIdentifier: (NSString *) identifier {
166      [super setIdentifier: identifier];
167      [[self identifierElement] setStringValue: identifier];
168  }
169  
170  - (NSAtomicStoreCacheNode *) cacheNodeForEntity: (NSEntityDescription *) entity
171                                  referenceObject: reference
172  {
173      NSAtomicStoreCacheNode *result =
174              [_referenceToCacheNode objectForKey: reference];
175  
176      if (result == nil) {
177          NSManagedObjectID *objectID = [self objectIDForEntity: entity
178                                                referenceObject: reference];
179  
180          result = [[NSAtomicStoreCacheNode alloc] initWithObjectID: objectID];
181  
182          [_referenceToCacheNode setObject: result forKey: reference];
183  
184          [result release];
185      }
186  
187      return result;
188  }
189  
190  - (NSAtomicStoreCacheNode *) loadEntityElement: (NSXMLElement *) entityElement
191                                           model: (NSManagedObjectModel *) model
192  {
193      NSString *entityName =
194              [[entityElement attributeForName: @"type"] stringValue];
195      NSString *entityReference =
196              [[entityElement attributeForName: @"id"] stringValue];
197      NSArray *attributeElements = [entityElement elementsForName: @"attribute"];
198      NSArray *relationshipElements =
199              [entityElement elementsForName: @"relationship"];
200  
201      NSEntityDescription *entity =
202              [[model entitiesByName] objectForKey: entityName];
203  
204      if (entity == nil) {
205          NSLog(@"Unable to find entity %@ in model", entityName);
206          return nil;
207      }
208  
209      [_referenceToElement setObject: entityElement forKey: entityReference];
210  
211      NSAtomicStoreCacheNode *cacheNode =
212              [self cacheNodeForEntity: entity referenceObject: entityReference];
213  
214      NSDictionary *attributesByName = [entity attributesByName];
215  
216      for (NSXMLElement *attribute in attributeElements) {
217          NSString *name = [[attribute attributeForName: @"name"] stringValue];
218          NSAttributeDescription *description =
219                  [attributesByName objectForKey: name];
220  
221          if (description == nil) {
222              NSLog(@"Unable to find attribute named %@ for entity named %@",
223                    name, entityName);
224              continue;
225          }
226  
227          NSString *type = [[attribute attributeForName: @"type"] stringValue];
228          NSString *stringValue = [attribute stringValue];
229          id objectValue = nil;
230  
231          switch ([description attributeType]) {
232  
233          case NSUndefinedAttributeType:
234              NSLog(@"Unhandled attribute type NSUndefinedAttributeType");
235              break;
236  
237          case NSInteger16AttributeType:
238              objectValue =
239                      [NSNumber numberWithInteger: [stringValue integerValue]];
240              break;
241  
242          case NSInteger32AttributeType:
243              objectValue =
244                      [NSNumber numberWithInteger: [stringValue integerValue]];
245              break;
246  
247          case NSInteger64AttributeType:
248              objectValue =
249                      [NSNumber numberWithInteger: [stringValue integerValue]];
250              break;
251  
252          case NSDecimalAttributeType:
253              // decimal types not supported right now, use double
254              objectValue =
255                      [NSNumber numberWithDouble: [stringValue doubleValue]];
256              //      objectValue=[NSDecimalNumber
257              //      decimalNumberWithString:stringValue];
258              break;
259  
260          case NSDoubleAttributeType:
261              objectValue =
262                      [NSNumber numberWithDouble: [stringValue doubleValue]];
263              break;
264  
265          case NSFloatAttributeType:
266              objectValue = [NSNumber numberWithFloat: [stringValue floatValue]];
267              break;
268  
269          case NSStringAttributeType:
270              objectValue = stringValue;
271              break;
272  
273          case NSBooleanAttributeType:
274              objectValue = [NSNumber numberWithBool: [stringValue intValue]];
275              break;
276  
277          case NSDateAttributeType:
278              objectValue = nil;
279              // we don't want to use NSCalendarDate
280              //   objectValue=[NSCalendarDate
281              //   dateWithNaturalLanguageString:stringValue];
282              break;
283  
284          case NSBinaryDataAttributeType:
285              NSLog(@"Unhandled attribute type NSBinaryDataAttributeType");
286              break;
287  
288          case NSTransformableAttributeType:
289              NSLog(@"Unhandled attribute type NSTransformableAttributeType");
290              break;
291          }
292  
293          if (objectValue != nil)
294              [cacheNode setValue: objectValue forKey: name];
295      }
296  
297      NSDictionary *relationshipsByName = [entity relationshipsByName];
298  
299      for (NSXMLElement *relationship in relationshipElements) {
300          NSString *name = [[relationship attributeForName: @"name"] stringValue];
301          NSRelationshipDescription *description =
302                  [relationshipsByName objectForKey: name];
303  
304          if (description == nil) {
305              NSLog(@"No description for relationship name %@ in %@", name,
306                    entityName);
307              continue;
308          }
309  
310          NSString *destinationEntityName =
311                  [[relationship attributeForName: @"destination"] stringValue];
312          NSEntityDescription *destinationEntity =
313                  [[model entitiesByName] objectForKey: destinationEntityName];
314          NSString *type = [[relationship attributeForName: @"type"] stringValue];
315          NSString *idrefsString =
316                  [[relationship attributeForName: @"idrefs"] stringValue];
317          NSArray *idrefs =
318                  [idrefsString length]
319                          ? [idrefsString componentsSeparatedByString: @" "]
320                          : nil;
321          id objectValue = [NSMutableSet set];
322  
323          for (NSString *ref in idrefs) {
324              NSAtomicStoreCacheNode *cacheNode =
325                      [self cacheNodeForEntity: destinationEntity
326                               referenceObject: ref];
327  
328              [objectValue addObject: cacheNode];
329          }
330  
331          if (![description isToMany]) {
332  
333              if ([objectValue count] > 1) {
334                  NSLog(@"relationship description is not to many, but "
335                        @"destination is %d",
336                        [objectValue count]);
337              }
338  
339              objectValue = [objectValue anyObject];
340          }
341  
342          [cacheNode setValue: objectValue forKey: name];
343      }
344  
345      return cacheNode;
346  }
347  
348  - (BOOL) load: (NSError **) errorp {
349  
350      NSManagedObjectModel *model =
351              [[self persistentStoreCoordinator] managedObjectModel];
352  
353      NSXMLElement *database = [self databaseElement];
354      NSArray *objects = [database elementsForName: @"object"];
355      int i, count = [objects count];
356      NSMutableSet *newNodes = [NSMutableSet set];
357  
358      for (i = 0; i < count; i++) {
359          NSXMLElement *element = [objects objectAtIndex: i];
360          NSAtomicStoreCacheNode *node = [self loadEntityElement: element
361                                                           model: model];
362  
363          if (node != nil)
364              [newNodes addObject: node];
365      }
366  
367      [self addCacheNodes: newNodes];
368  
369      return YES;
370  }
371  
372  - (BOOL) save: (NSError **) error {
373  
374      NSData *data = [_document XMLData];
375  
376      return [data writeToURL: [self URL] atomically: YES];
377  }
378  
379  - (NSXMLElement *) entityElementForObjectID: (NSManagedObjectID *) objectID {
380      id reference = [self referenceObjectForObjectID: objectID];
381  
382      return [_referenceToElement objectForKey: reference];
383  }
384  
385  - (void) updateCacheNode: (NSAtomicStoreCacheNode *) node
386          fromManagedObject: (NSManagedObject *) managedObject
387  {
388      NSXMLElement *entityElement =
389              [self entityElementForObjectID: [managedObject objectID]];
390      NSDictionary *attributesByName = [[managedObject entity] attributesByName];
391      NSArray *attributeKeys = [attributesByName allKeys];
392      NSMutableArray *children = [NSMutableArray array];
393  
394      for (NSString *attributeName in attributeKeys) {
395          NSAttributeDescription *attributeDescription =
396                  [attributesByName objectForKey: attributeName];
397          NSXMLElement *attributeElement =
398                  [NSXMLNode elementWithName: @"attribute"];
399          id value = [managedObject primitiveValueForKey: attributeName];
400          NSString *type = nil;
401          NSString *stringValue = nil;
402  
403          switch ([attributeDescription attributeType]) {
404          case NSUndefinedAttributeType:
405              NSLog(@"Unhandled attribute type NSUndefinedAttributeType");
406              break;
407  
408          case NSInteger16AttributeType:
409              type = @"int16";
410              stringValue = [value description];
411              break;
412  
413          case NSInteger32AttributeType:
414              type = @"int32";
415              stringValue = [value description];
416              break;
417  
418          case NSInteger64AttributeType:
419              type = @"int64";
420              stringValue = [value description];
421              break;
422  
423          case NSDecimalAttributeType:
424              type = @"decimal";
425              stringValue = [value description];
426              break;
427  
428          case NSDoubleAttributeType:
429              type = @"double";
430              stringValue = [value description];
431              break;
432  
433          case NSFloatAttributeType:
434              type = @"float";
435              stringValue = [value description];
436              break;
437  
438          case NSStringAttributeType:
439              type = @"string";
440              stringValue = [value description];
441              break;
442  
443          case NSBooleanAttributeType:
444              type = @"bool";
445              stringValue = [value description];
446              break;
447  
448          case NSDateAttributeType:
449              type = @"date";
450              stringValue = [value description];
451              break;
452  
453          case NSBinaryDataAttributeType:
454              type = @"bin";
455              NSLog(@"Unhandled attribute type NSBinaryDataAttributeType");
456              break;
457  
458          case NSTransformableAttributeType:
459              NSLog(@"Unhandled attribute type NSTransformableAttributeType");
460              break;
461          }
462  
463          [attributeElement setStringValue: stringValue];
464          [attributeElement
465                  addAttribute: [NSXMLNode attributeWithName: @"name"
466                                                 stringValue: attributeName]];
467          [attributeElement addAttribute: [NSXMLNode attributeWithName: @"type"
468                                                           stringValue: type]];
469  
470          [children addObject: attributeElement];
471  
472          [node setValue: value forKey: attributeName];
473      }
474  
475      NSDictionary *relationshipsByName =
476              [[managedObject entity] relationshipsByName];
477      NSArray *relationshipKeys = [relationshipsByName allKeys];
478  
479      for (NSString *relationshipName in relationshipKeys) {
480          NSRelationshipDescription *relationshipDescription =
481                  [relationshipsByName objectForKey: relationshipName];
482          NSXMLElement *relationshipElement =
483                  [NSXMLNode elementWithName: @"relationship"];
484          NSString *relationshipType = [NSString
485                  stringWithFormat: @"%d/%d", [relationshipDescription minCount],
486                                    [relationshipDescription maxCount]];
487          NSEntityDescription *destinationEntity =
488                  [relationshipDescription destinationEntity];
489          id value = [managedObject primitiveValueForKey: relationshipName];
490          NSSet *valueSet;
491          NSMutableSet *cacheNodeSet = [NSMutableSet set];
492  
493          if ([relationshipDescription isToMany]) {
494              if (value != nil && ![value isKindOfClass: [NSSet class]]) {
495                  NSLog(@"relationship isToMany, value is not a set");
496                  continue;
497              }
498              valueSet = value;
499          } else {
500              valueSet = [NSSet setWithObject: value];
501          }
502  
503          [relationshipElement
504                  addAttribute: [NSXMLNode attributeWithName: @"name"
505                                                 stringValue: relationshipName]];
506          [relationshipElement
507                  addAttribute: [NSXMLNode attributeWithName: @"type"
508                                                 stringValue: relationshipType]];
509          [relationshipElement
510                  addAttribute: [NSXMLNode attributeWithName: @"destination"
511                                                 stringValue: [destinationEntity
512                                                                      name]]];
513  
514          NSMutableArray *idrefArray = [NSMutableArray array];
515  
516          for (NSManagedObjectID *objectID in valueSet) {
517              id referenceObject = [self referenceObjectForObjectID: objectID];
518  
519              [idrefArray addObject: referenceObject];
520  
521              NSAtomicStoreCacheNode *relNode =
522                      [self cacheNodeForEntity: destinationEntity
523                               referenceObject: referenceObject];
524  
525              [cacheNodeSet addObject: relNode];
526          }
527  
528          [relationshipElement
529                  addAttribute:
530                          [NSXMLNode
531                                  attributeWithName: @"idrefs"
532                                        stringValue:
533                                                [idrefArray
534                                                        componentsJoinedByString:
535                                                                @" "]]];
536  
537          [children addObject: relationshipElement];
538  
539          if ([relationshipDescription isToMany]) {
540              [node setValue: cacheNodeSet forKey: relationshipName];
541          } else {
542              if ([cacheNodeSet count] > 1) {
543                  NSLog(@"relationship is one to one, yet cacheNodeSet count is "
544                        @"%d",
545                        [cacheNodeSet count]);
546                  continue;
547              }
548  
549              if ([cacheNodeSet count] == 0)
550                  [node setValue: nil forKey: relationshipName];
551              else
552                  [node setValue: [cacheNodeSet anyObject]
553                          forKey: relationshipName];
554          }
555      }
556  
557      [entityElement setChildren: children];
558  }
559  
560  - (NSAtomicStoreCacheNode *) newCacheNodeForManagedObject:
561          (NSManagedObject *) managedObject
562  {
563      NSEntityDescription *entity = [managedObject entity];
564      NSManagedObjectID *objectID = [managedObject objectID];
565      id reference = [self referenceObjectForObjectID: objectID];
566      NSAtomicStoreCacheNode *cacheNode =
567              [[NSAtomicStoreCacheNode alloc] initWithObjectID: objectID];
568  
569      NSXMLElement *entityElement =
570              [[NSXMLElement alloc] initWithName: @"object"];
571      NSXMLNode *nameAttribute = [NSXMLNode attributeWithName: @"type"
572                                                  stringValue: [entity name]];
573      NSXMLNode *idAttribute = [NSXMLNode attributeWithName: @"id"
574                                                stringValue: reference];
575  
576      [entityElement addAttribute: nameAttribute];
577      [entityElement addAttribute: idAttribute];
578  
579      [_referenceToElement setObject: entityElement forKey: reference];
580  
581      [[self databaseElement] addChild: entityElement];
582  
583      [self updateCacheNode: cacheNode fromManagedObject: managedObject];
584  
585      return cacheNode;
586  }
587  
588  - newReferenceObjectForManagedObject: (NSManagedObject *) managedObject {
589      NSXMLElement *nextObjectIDElement = [self nextObjectIDElement];
590      NSInteger nextInteger = [[nextObjectIDElement stringValue] integerValue];
591      NSNumber *check = nil;
592  
593      do {
594          [check release];
595  
596          check = [[NSNumber alloc] initWithInteger: nextInteger];
597  
598          if (![_usedReferences containsObject: check])
599              break;
600  
601          nextInteger++;
602  
603      } while (YES);
604  
605      [_usedReferences addObject: check];
606  
607      [check release];
608  
609      [nextObjectIDElement
610              setStringValue: [NSString
611                                      stringWithFormat: @"%d", nextInteger + 1]];
612  
613      return [[NSString alloc] initWithFormat: @"r%d", nextInteger];
614  }
615  
616  - (void) willRemoveCacheNodes: (NSSet *) cacheNodes {
617      NSXMLElement *database = [self databaseElement];
618  
619      for (NSAtomicStoreCacheNode *node in cacheNodes) {
620          NSManagedObjectID *objectID = [node objectID];
621          NSEntityDescription *entity = [objectID entity];
622          NSXMLElement *entityElement = [self entityElementForObjectID: objectID];
623          id entityReference = [self referenceObjectForObjectID: objectID];
624          NSInteger index =
625                  [[database children] indexOfObjectIdenticalTo: entityElement];
626  
627          if (index == NSNotFound)
628              NSLog(@"unable to remove object %@ from database - not found",
629                    objectID);
630          else
631              [[self databaseElement] removeChildAtIndex: index];
632  
633          [_referenceToElement removeObjectForKey: entityReference];
634  
635          // Should really be maintaining our own set for this
636          [_cacheNodes removeObject: node];
637      }
638  }
639  
640  - (void) willRemoveFromPersistentStoreCoordinator:
641          (NSPersistentStoreCoordinator *) coordinator
642  {
643      [_document release];
644      _document = nil;
645      [super willRemoveFromPersistentStoreCoordinator: coordinator];
646  }
647  
648  @end