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