]> err.no Git - sope/blob - sope-appserver/SoOFS/OFSPropertyListObject.m
workaround for current gstep-base KVC problem, bumped framework versions
[sope] / sope-appserver / SoOFS / OFSPropertyListObject.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "OFSPropertyListObject.h"
23 #include "OFSFactoryContext.h"
24 #include <WebDAV/SoObject+SoDAV.h>
25 #include "common.h"
26
27 @interface OFSPropertyListObjectClassDescription : NSClassDescription
28 {
29 @public
30   OFSPropertyListObject *object;
31 }
32
33 @end
34
35 @implementation OFSPropertyListObject
36
37 static int debugOn = 0;
38
39 + (int)version {
40   return [super version] + 0 /* v1 */;
41 }
42 + (void)initialize {
43   static BOOL didInit = NO;
44   if (!didInit) {
45     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
46     didInit = YES;
47     NSAssert2([super version] == 1,
48               @"invalid superclass (%@) version %i !",
49               NSStringFromClass([self superclass]), [super version]);
50     
51     debugOn = [ud boolForKey:@"SoOFSDebugPlistObject"] ? 1 : 0;
52   }
53 }
54
55 - (void)dealloc {
56   [self->recordKeys release];
57   [self->record     release];
58   [super dealloc];
59 }
60
61 /* storage */
62
63 - (NSStringEncoding)stringEncoding {
64   return [NSString defaultCStringEncoding];
65 }
66
67 - (void)setSoResourceClassName:(NSString *)_name {} // ignore
68 - (void)setSoClassName:(NSString *)_name         {} // ignore
69 - (NSString *)soResourceClassName { // deprecated
70   return [[self soClass] className];
71 }
72 - (NSString *)soClassName {
73   return [[self soClass] className];
74 }
75
76 - (NSArray *)attributeKeys {
77   [[self restoreObject] raise];
78   return self->recordKeys;
79 }
80
81 - (NSArray *)allKeys {
82   return [self attributeKeys];
83 }
84
85 - (NSClassDescription *)soClassDescription {
86   OFSPropertyListObjectClassDescription *cd;
87   cd = [[OFSPropertyListObjectClassDescription alloc] init];
88   cd->object = [self retain];
89   return [cd autorelease];
90 }
91
92 - (NSString *)contentAsString {
93   /* do not allow access to raw contents ! */
94   return nil;
95 }
96
97 - (void)removeSpecialKeysFromRestoreDictionary:(NSMutableDictionary *)_md {
98   /* remove special storage keys like SoClassName to plist */
99   [_md removeObjectForKey:@"SoClassName"];
100   [_md removeObjectForKey:@"SoResourceClassName"];
101 }
102 - (void)addSpecialKeysToSaveDictionary:(NSMutableDictionary *)_md {
103   /* add special storage keys like SoClassName to plist */
104   [_md setObject:[self soClassName] forKey:@"SoClassName"];
105 }
106
107 - (NSException *)restoreObject {
108   NSMutableDictionary *plist = nil;
109   NSException *e;
110   NSData      *content;
111   NSString    *s;
112   id fm;
113   
114   if (self->flags.isLoaded) return nil;
115   if ((fm = [self fileManager]) == nil) {
116     e = [NSException exceptionWithHTTPStatus:500
117                      reason:@"plist object has no filemanager ??"];
118     return e;
119   }
120   s = [self storagePath];
121   if ([s length] == 0) {
122     e = [NSException exceptionWithHTTPStatus:500
123                      reason:@"plist object has no storage path ??"];
124     return e;
125   }
126   
127   self->flags.isLoading = 1;
128   self->flags.isLoaded  = 1;
129   e = nil;
130   
131   /* load file, convert into string, then into a property list */
132   
133   if ((content = [fm contentsAtPath:s])==nil) {
134     if (([fm respondsToSelector:@selector(lastException)])) {
135       e = [fm lastException];
136       goto done;
137     }
138     else {
139       e = [NSException exceptionWithHTTPStatus:404 /* not found */
140                        reason:@"failed to load property list file ..."];
141       goto done;
142     }
143   }
144   s = [[NSString alloc] initWithData:content encoding:[self stringEncoding]];
145   if (s == nil) {
146     e = [NSException exceptionWithHTTPStatus:500
147                      reason:@"failed to create string from file ..."];
148     goto done;
149   }
150   plist = [[s propertyList] mutableCopy];
151   [s release];
152   if (plist == nil) {
153     e = [NSException exceptionWithHTTPStatus:500
154                      reason:@"failed to create property list from file ..."];
155     goto done;
156   }
157   
158   [self removeSpecialKeysFromRestoreDictionary:plist];
159   
160   self->recordKeys = [[plist allKeys] copy];
161   if (debugOn)
162     [self debugWithFormat:@"taking values of: %@", plist];
163   [self takeValuesFromDictionary:plist];
164   [plist release];
165   
166  done:
167   self->flags.isEdited  = 0;
168   self->flags.isLoading = 0;
169   return e;
170 }
171
172 - (void)willChange {
173   if (!self->flags.isLoading)
174     self->flags.isEdited = 1;
175 }
176 - (BOOL)isRestored {
177   return self->flags.isLoaded ? YES : NO;
178 }
179
180 - (NSException *)saveObject {
181   NSMutableDictionary *d;
182   NSException  *e;
183   NSString     *s;
184   NSData       *content;
185   id           fm;
186   
187   e = (self->flags.isNew)
188     ? [self validateForInsert]
189     : [self validateForSave];
190   if (e) return e;
191   
192   if (!self->flags.isNew) {
193     if ((e = [self restoreObject]))
194       return e;
195   }
196   
197   d = (self->recordKeys)
198     ? [[self valuesForKeys:self->recordKeys] mutableCopy]
199     : [self->record mutableCopy];
200   
201   if (d == nil) {
202     [self logWithFormat:@"got no dict to save ..."];
203     return [NSException exceptionWithHTTPStatus:500
204                         reason:@"got no record to save ..."];
205   }
206   
207   [self addSpecialKeysToSaveDictionary:d];
208   
209   s = [d description];
210   [d release];
211   
212   content = [s dataUsingEncoding:[self stringEncoding]];
213   
214   fm = [self fileManager];
215   
216   if (![fm writeContents:content atPath:[self storagePath]]) {
217     [self logWithFormat:@"failed to update file: %@", [self storagePath]];
218     
219     if (([fm respondsToSelector:@selector(lastException)]))
220       return [fm lastException];
221     else {
222       return [NSException exceptionWithHTTPStatus:500
223                           reason:@"failed to update property list file ..."];
224     }
225   }
226   
227   self->flags.isNew = 0;
228   return nil;
229 }
230
231 /* KVC */
232
233 - (void)handleTakeValue:(id)_value forUnboundKey:(NSString *)_key {
234   id oldValue;
235   
236   if ((oldValue = [self->record objectForKey:_key]) == nil) {
237     if (_value == nil) return;
238   }
239   else if (![_value isNotNull]) {
240     [self willChange];
241     [self->record removeObjectForKey:_key];
242     return;
243   }
244   else if (oldValue == _value) {
245     return;
246   }
247   else if ([oldValue isEqual:_value])
248     return;
249   
250   [self willChange];
251   
252   if (self->record == nil)
253     self->record = [[NSMutableDictionary alloc] initWithCapacity:16];
254   
255   if (!self->flags.isLoading && debugOn)
256     [self debugWithFormat:@"set unbound key: %@", _key];
257   
258   if (![self->recordKeys containsObject:_key]) {
259     NSMutableArray *rk;
260     
261     rk = [self->recordKeys mutableCopy];
262     [rk addObject:_key];
263     [self->recordKeys release];
264     self->recordKeys = rk;
265   }
266   
267   [self->record setObject:_value?_value:@"" forKey:_key];
268 }
269
270 /* ZNeK: due to a bug in gstep-base 1.12.0 KVC, we need to add this */
271 - (void)setValue:(id)_value forUndefinedKey:(NSString *)_key {
272   [self handleTakeValue:_value forUnboundKey:_key];
273 }
274
275 - (BOOL)isStoredKey:(NSString *)_key {
276   /* says whether we need to restore the object to access the key */
277   if ([_key hasPrefix:@"NS"]) {
278     if ([_key isEqualToString:@"NSFileSubject"])
279       return YES;
280     return NO;
281   }
282   return YES;
283 }
284
285 - (void)takeValue:(id)_value forKey:(NSString *)_name {
286   if (!self->flags.isLoaded && !self->flags.isLoading) {
287     if ([self isStoredKey:_name])
288       [[self restoreObject] raise];
289   }
290   
291   [super takeValue:_value forKey:_name];
292 }
293
294 - (id)valueForKey:(NSString *)_name {
295   id v = nil;
296   
297   if ([_name hasPrefix:@"NS"]) {
298     if ([_name isEqualToString:@"NSFileSize"])
299       v = [self davContentLength];
300     else if ([_name isEqualToString:@"NSFileSubject"])
301       v = [self davDisplayName];
302     else
303       /* this implies that stored keys never begin with NS ! (good ?) */
304       v = [super valueForKey:_name];
305   }
306   else if ([self isStoredKey:_name]) {
307     if ((v = [self restoreObject]))
308       /* v is the restoration exception, do not want to raise */;
309     else if ((v = [self->record objectForKey:_name]))
310       /* a record value */;
311     else
312       /* stored-key doesn't say *where* it is stored ! */
313       v = [super valueForKey:_name];
314   }
315   else
316     v = [super valueForKey:_name];
317   
318   return v;
319 }
320
321 /* operations */
322
323 - (id)GETAction:(WOContext *)_ctx {
324   NSException *e;
325   
326   if ((e = [self restoreObject]))
327     return e;
328   
329   /* let the renderer deal with our representation ... */
330   return self;
331 }
332
333 - (id)PUTAction:(WOContext *)_ctx {
334   return [NSException exceptionWithHTTPStatus:405 /* method not allowed */
335                       reason:@"HTTP PUT not yet allowed on plist objects"];
336 }
337
338 /* WebDAV support */
339
340 - (NSString *)davDisplayName {
341   return [[self nameInContainer] stringByDeletingPathExtension];
342 }
343 - (id)davContentLength {
344   static NSNumber *zero = nil;
345   if (zero == nil) zero = [[NSNumber numberWithInt:0] retain];
346   return zero;
347 }
348
349 - (NSException *)davSetProperties:(NSDictionary *)_setProps
350   removePropertiesNamed:(NSArray *)_delProps 
351   inContext:(id)_ctx
352 {
353   NSException *e;
354   
355   if (debugOn)
356     [self debugWithFormat:@"patch: %@, del: %@", _setProps, _delProps];
357   
358   if ((e = [self restoreObject]))
359     return e;
360   
361   if ([_setProps count] > 0)
362     [self takeValuesFromDictionary:_setProps];
363   
364   if ([_delProps count] > 0) {
365     NSMutableArray *rk;
366     
367     [self->record removeObjectsForKeys:_delProps];
368     rk = [self->recordKeys mutableCopy];
369     [rk removeObjectsInArray:_delProps];
370     [self->recordKeys release];
371     self->recordKeys = rk;
372   }
373   
374   if ((e = [self saveObject])) {
375     [self logWithFormat:@"update failed ..."];
376     return e;
377   }
378   
379   return nil;
380 }
381
382 /* factory */
383
384 + (id)instantiateInFactoryContext:(OFSFactoryContext *)_ctx {
385   /* look into plist for class */
386   NSException  *e;
387   OFSPropertyListObject *object;
388   SoClass      *clazz;
389   
390   if ([_ctx isNewObject]) {
391     /* create a new object in the storage */
392     clazz = [self soClass];
393     
394     /* instantiate */
395     if (debugOn) {
396       [self debugWithFormat:@"instantiate child %@ from class %@",
397             [_ctx nameInContainer], clazz];
398     }
399     
400     object = [clazz instantiateObject];
401     [object takeStorageInfoFromContext:_ctx];
402     
403     if ([object isKindOfClass:[OFSPropertyListObject class]])
404       object->flags.isNew = 1;
405
406     if ((e = [object saveObject])) {
407       [self debugWithFormat:@"  save failed: %@", e];
408       return e;
409     }
410   }
411   else {
412     /* restore object from storage */
413     NSDictionary *plist;
414     NSData       *content;
415     NSString     *string;
416     NSString     *className;
417     
418     content = [[_ctx fileManager] contentsAtPath:[_ctx storagePath]];
419     if (content == nil)
420       /* hm, file doesn't exist ? */
421       return [super instantiateInFactoryContext:_ctx];
422     
423     /* parse the existing plist file */
424     
425     string = [[NSString alloc] initWithData:content
426                                encoding:[NSString defaultCStringEncoding]];
427     if (string == nil) {
428       [self logWithFormat:@"could not make string for stored data."];
429       return [NSException exceptionWithHTTPStatus:500
430                         reason:@"stored property list is corrupted"];
431     }
432     
433     if ((plist = [string propertyList]) == nil) {
434       [string release];
435       [self logWithFormat:@"could not make plist for stored data."];
436       return [NSException exceptionWithHTTPStatus:500
437                         reason:
438                           @"stored property list is corrupted "
439                           @"(not in plist format)"];
440     }
441     [string release];
442   
443     /* lookup the classname in plist */
444     
445     className = [plist objectForKey:@"SoClassName"];
446     if ([className length] == 0)
447       /* no special class assigned, use default */
448       clazz = [self soClass];
449     else {
450       clazz = [[SoClassRegistry sharedClassRegistry] soClassWithName:className];
451       if (clazz == nil) {
452         [self logWithFormat:@"did not find SoClass: %@", className];
453         return nil;
454       }
455     }
456     
457     /* instantiate */
458     
459     if (debugOn) {
460       [self debugWithFormat:@"instantiate child %@ from class %@",
461             [_ctx nameInContainer], clazz];
462     }
463     
464     object = [clazz instantiateObject];
465     [object takeStorageInfoFromContext:_ctx];
466     
467     /* restore */
468     
469     if (debugOn) {
470       [self debugWithFormat:@"restore child %@: %@",
471             [_ctx nameInContainer], object];
472     }
473     
474     if ((e = [object restoreObject])) {
475       [self debugWithFormat:@"  restore failed: %@", e];
476       return e;
477     }
478   }  
479   return object;
480 }
481
482 /* debugging */
483
484 - (BOOL)isDebuggingEnabled {
485   return debugOn ? YES : NO;
486 }
487
488 @end /* OFSPropertyListObject */
489
490 @implementation OFSPropertyListObjectClassDescription
491
492 - (void)dealloc {
493   [self->object release];
494   [super dealloc];
495 }
496
497 - (NSArray *)attributeKeys {
498   return [self->object attributeKeys];
499 }
500
501 @end /* OFSPropertyListObjectClassDescription */