]> err.no Git - sope/blob - sope-gdl1/GDLContentStore/GCSChannelManager.m
added OGoContentStore library to sope-gdl1
[sope] / sope-gdl1 / GDLContentStore / GCSChannelManager.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo 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   OGo 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 OGo; 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 "GCSChannelManager.h"
23 #include "NSURL+GCS.h"
24 #include "EOAdaptorChannel+GCS.h"
25 #include <GDLAccess/EOAdaptor.h>
26 #include <GDLAccess/EOAdaptorContext.h>
27 #include <GDLAccess/EOAdaptorChannel.h>
28 #include "common.h"
29
30 /*
31   TODO:
32   - implemented pooling
33   - auto-close channels which are very old?! 
34     (eg missing release due to an exception)
35 */
36
37 @interface GCSChannelHandle : NSObject
38 {
39 @public
40   NSURL            *url;
41   EOAdaptorChannel *channel;
42   NSDate           *creationTime;
43   NSDate           *lastReleaseTime;
44   NSDate           *lastAcquireTime;
45 }
46
47 - (EOAdaptorChannel *)channel;
48 - (BOOL)canHandleURL:(NSURL *)_url;
49 - (NSTimeInterval)age;
50
51 @end
52
53 @implementation GCSChannelManager
54
55 static BOOL           debugOn                = NO;
56 static BOOL           debugPools             = NO;
57 static int            ChannelExpireAge       = 180;
58 static NSTimeInterval ChannelCollectionTimer = 5 * 60;
59
60 + (void)initialize {
61   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
62   
63   debugOn    = [ud boolForKey:@"GCSChannelManagerDebugEnabled"];
64   debugPools = [ud boolForKey:@"GCSChannelManagerPoolDebugEnabled"];
65   
66   ChannelExpireAge = [[ud objectForKey:@"GCSChannelExpireAge"] intValue];
67   if (ChannelExpireAge < 1)
68     ChannelExpireAge = 180;
69   
70   ChannelCollectionTimer = 
71     [[ud objectForKey:@"GCSChannelCollectionTimer"] intValue];
72   if (ChannelCollectionTimer < 1)
73     ChannelCollectionTimer = 5*60;
74 }
75
76 + (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
77   // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL
78   return @"PostgreSQL";
79 }
80
81 + (id)defaultChannelManager {
82   static GCSChannelManager *cm = nil;
83   if (cm == nil)
84     cm = [[self alloc] init];
85   return cm;
86 }
87
88 - (id)init {
89   if ((self = [super init])) {
90     self->urlToAdaptor      = [[NSMutableDictionary alloc] initWithCapacity:4];
91     self->availableChannels = [[NSMutableArray alloc] initWithCapacity:16];
92     self->busyChannels      = [[NSMutableArray alloc] initWithCapacity:16];
93
94     self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
95                                 ChannelCollectionTimer
96                               target:self selector:@selector(_garbageCollect:)
97                               userInfo:nil repeats:YES] retain];
98   }
99   return self;
100 }
101
102 - (void)dealloc {
103   if (self->gcTimer) [self->gcTimer invalidate];
104   [self->gcTimer release];
105
106   [self->busyChannels      release];
107   [self->availableChannels release];
108   [self->urlToAdaptor      release];
109   [super dealloc];
110 }
111
112 /* DB key */
113
114 - (NSString *)databaseKeyForURL:(NSURL *)_url {
115   /*
116     We need to build a proper key that omits passwords and URL path components
117     which are not required.
118   */
119   NSString *key;
120   
121   key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
122                   [_url host], [_url port],
123                   [_url user], [_url gcsDatabaseName]];
124   return key;
125 }
126
127 /* adaptors */
128
129 - (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url {
130   NSMutableDictionary *md;
131   id tmp;
132   
133   md = [NSMutableDictionary dictionaryWithCapacity:4];
134
135   if ((tmp = [_url host]) != nil) 
136     [md setObject:tmp forKey:@"hostName"];
137   if ((tmp = [_url port]) != nil) 
138     [md setObject:tmp forKey:@"port"];
139   if ((tmp = [_url user]) != nil) 
140     [md setObject:tmp forKey:@"userName"];
141   if ((tmp = [_url password]) != nil) 
142     [md setObject:tmp forKey:@"password"];
143   
144   if ((tmp = [_url gcsDatabaseName]) != nil) 
145     [md setObject:tmp forKey:@"databaseName"];
146   
147   [self debugWithFormat:@"build connection dictionary for URL %@: %@", 
148         [_url absoluteString], md];
149   return md;
150 }
151
152 - (EOAdaptor *)adaptorForURL:(NSURL *)_url {
153   EOAdaptor *adaptor;
154   NSString  *key;
155   
156   if (_url == nil)
157     return nil;
158   if ((key = [self databaseKeyForURL:_url]) == nil)
159     return nil;
160   if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) {
161     [self debugWithFormat:@"using cached adaptor: %@", adaptor];
162     return adaptor; /* cached :-) */
163   }
164   
165   [self debugWithFormat:@"creating new adaptor for URL: %@", _url];
166   
167   if ([EOAdaptor respondsToSelector:@selector(adaptorForURL:)]) {
168     adaptor = [EOAdaptor adaptorForURL:_url];
169   }
170   else {
171     NSString     *adaptorName;
172     NSDictionary *condict;
173     
174     adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]];
175     if ([adaptorName length] == 0) {
176       [self logWithFormat:@"ERROR: cannot handle URL: %@", _url];
177       return nil;
178     }
179   
180     condict = [self connectionDictionaryForURL:_url];
181   
182     if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) {
183       [self logWithFormat:@"ERROR: did not find adaptor '%@' for URL: %@", 
184             adaptorName, _url];
185       return nil;
186     }
187   
188     [adaptor setConnectionDictionary:condict];
189   }
190   
191   [self->urlToAdaptor setObject:adaptor forKey:key];
192   return adaptor;
193 }
194
195 /* channels */
196
197 - (GCSChannelHandle *)findBusyChannelHandleForChannel:(EOAdaptorChannel *)_ch {
198   NSEnumerator *e;
199   GCSChannelHandle *handle;
200   
201   e = [self->busyChannels objectEnumerator];
202   while ((handle = [e nextObject])) {
203     if ([handle channel] == _ch)
204       return handle;
205   }
206   return nil;
207 }
208 - (GCSChannelHandle *)findAvailChannelHandleForURL:(NSURL *)_url {
209   NSEnumerator *e;
210   GCSChannelHandle *handle;
211   
212   e = [self->availableChannels objectEnumerator];
213   while ((handle = [e nextObject])) {
214     if ([handle canHandleURL:_url])
215       return handle;
216     
217     if (debugPools) {
218       [self logWithFormat:@"DBPOOL: cannot use handle (%@ vs %@)",
219               [_url absoluteString], [handle->url absoluteString]];
220     }
221   }
222   return nil;
223 }
224
225 - (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url {
226   EOAdaptor        *adaptor;
227   EOAdaptorContext *adContext;
228   EOAdaptorChannel *adChannel;
229   
230   if ((adaptor = [self adaptorForURL:_url]) == nil)
231     return nil;
232   
233   if ((adContext = [adaptor createAdaptorContext]) == nil) {
234     [self logWithFormat:@"ERROR: could not create adaptor context!"];
235     return nil;
236   }
237   if ((adChannel = [adContext createAdaptorChannel]) == nil) {
238     [self logWithFormat:@"ERROR: could not create adaptor channel!"];
239     return nil;
240   }
241   return adChannel;
242 }
243
244 - (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url {
245   // TODO: naive implementation, add pooling!
246   EOAdaptorChannel *channel;
247   GCSChannelHandle *handle;
248   NSCalendarDate   *now;
249
250   now = [NSCalendarDate date];
251   
252   /* look for cached handles */
253   
254   if ((handle = [self findAvailChannelHandleForURL:_url]) != nil) {
255     // TODO: check age?
256     [self->busyChannels      addObject:handle];
257     [self->availableChannels removeObject:handle];
258     ASSIGN(handle->lastAcquireTime, now);
259     
260     if (debugPools)
261       [self logWithFormat:@"DBPOOL: reused cached DB channel!"];
262     return [[handle channel] retain];
263   }
264
265   if (debugPools) {
266     [self logWithFormat:@"DBPOOL: create new DB channel for URL: %@",
267             [_url absoluteString]];
268   }
269   
270   /* create channel */
271   
272   if ((channel = [self _createChannelForURL:_url]) == nil)
273     return nil;
274   
275   if ([channel isOpen])
276     ;
277   else if (![channel openChannel]) {
278     [self logWithFormat:@"could not open channel %@ for URL: %@",
279             channel, [_url absoluteString]];
280     return nil;
281   }
282   
283   /* create handle for channel */
284   
285   handle = [[GCSChannelHandle alloc] init];
286   handle->url             = [_url retain];
287   handle->channel         = [channel retain];
288   handle->creationTime    = [now retain];
289   handle->lastAcquireTime = [now retain];
290   
291   [self->busyChannels addObject:handle];
292   [handle release];
293   
294   return [channel retain];
295 }
296 - (void)releaseChannel:(EOAdaptorChannel *)_channel {
297   GCSChannelHandle *handle;
298   
299   if ((handle = [self findBusyChannelHandleForChannel:_channel]) != nil) {
300     NSCalendarDate *now;
301
302     now = [NSCalendarDate date];
303     
304     handle = [handle retain];
305     ASSIGN(handle->lastReleaseTime, now);
306     
307     [self->busyChannels removeObject:handle];
308     
309     if ([[handle channel] isOpen] && [handle age] < ChannelExpireAge) {
310       // TODO: consider age
311       [self->availableChannels addObject:handle];
312       if (debugPools) {
313         [self logWithFormat:
314                 @"DBPOOL: keeping channel (age %ds, #%d): %@", 
315                 (int)[handle age], [self->availableChannels count],
316                 [handle->url absoluteString]];
317       }
318       [_channel release];
319       [handle release];
320       return;
321     }
322
323     if (debugPools) {
324       [self logWithFormat:
325               @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
326     }
327     
328     /* not reusing channel */
329     [handle release]; handle = nil;
330   }
331   
332   if ([_channel isOpen])
333     [_channel closeChannel];
334   
335   [_channel release];
336 }
337
338 /* checking for tables */
339
340 - (BOOL)canConnect:(NSURL *)_url {
341   /* 
342      this can check for DB connect as well as for table URLs (whether a table
343      exists)
344   */
345   EOAdaptorChannel *channel;
346   NSString *table;
347   BOOL     result;
348   
349   if ((channel = [self acquireOpenChannelForURL:_url]) == nil) {
350     if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url];
351     return NO;
352   }
353   if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
354   result = YES; /* could open channel */
355   
356   /* check whether table exists */
357   
358   table = [_url gcsTableName];
359   if ([table length] > 0)
360     result = [channel tableExistsWithName:table];
361   
362   /* release channel */
363   
364   [self releaseChannel:channel]; channel = nil;
365   
366   return result;
367 }
368
369 /* collect old channels */
370
371 - (void)_garbageCollect:(NSTimer *)_timer {
372   NSMutableArray *handlesToRemove;
373   unsigned i, count;
374   
375   if ((count = [self->availableChannels count]) == 0)
376     /* no available channels */
377     return;
378
379   /* collect channels to expire */
380   
381   handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4];
382   for (i = 0; i < count; i++) {
383     GCSChannelHandle *handle;
384     
385     handle = [self->availableChannels objectAtIndex:i];
386     if (![[handle channel] isOpen]) {
387       [handlesToRemove addObject:handle];
388       continue;
389     }
390     if ([handle age] > ChannelExpireAge) {
391       [handlesToRemove addObject:handle];
392       continue;
393     }
394   }
395   
396   /* remove channels */
397   count = [handlesToRemove count];
398   if (debugPools) 
399     [self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count];
400   for (i = 0; i < count; i++) {
401     GCSChannelHandle *handle;
402     
403     handle = [[handlesToRemove objectAtIndex:i] retain];
404     [self->availableChannels removeObject:handle];
405     if ([[handle channel] isOpen])
406       [[handle channel] closeChannel];
407     [handle release];
408   }
409   
410   [handlesToRemove release];
411 }
412
413 /* debugging */
414
415 - (BOOL)isDebuggingEnabled {
416   return debugOn;
417 }
418
419 /* description */
420
421 - (NSString *)description {
422   NSMutableString *ms;
423
424   ms = [NSMutableString stringWithCapacity:256];
425   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
426   
427   [ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]];
428   
429   [ms appendString:@">"];
430   return ms;
431 }
432
433 @end /* GCSChannelManager */
434
435 @implementation GCSChannelHandle
436
437 - (void)dealloc {
438   [self->channel         release];
439   [self->creationTime    release];
440   [self->lastReleaseTime release];
441   [self->lastAcquireTime release];
442   [super dealloc];
443 }
444
445 /* accessors */
446
447 - (EOAdaptorChannel *)channel {
448   return self->channel;
449 }
450
451 - (BOOL)canHandleURL:(NSURL *)_url {
452   BOOL isSQLite;
453   
454   if (_url == nil) {
455     [self logWithFormat:@"MISMATCH: no url .."];
456     return NO;
457   }
458   if (_url == self->url)
459     return YES;
460
461   isSQLite = [[_url scheme] isEqualToString:@"sqlite"];
462   
463   if (!isSQLite && ![[self->url host] isEqual:[_url host]]) {
464     [self logWithFormat:@"MISMATCH: different host .."];
465     return NO;
466   }
467   if (![[self->url gcsDatabaseName] isEqualToString:[_url gcsDatabaseName]]) {
468     [self logWithFormat:@"MISMATCH: different db .."];
469     return NO;
470   }
471   if (!isSQLite) {
472     if (![[self->url user] isEqual:[_url user]]) {
473       [self logWithFormat:@"MISMATCH: different user .."];
474       return NO;
475     }
476     if ([[self->url port] intValue] != [[_url port] intValue]) {
477       [self logWithFormat:@"MISMATCH: different port (%@ vs %@) ..",
478           [self->url port], [_url port]];
479       return NO;
480     }
481   }
482   return YES;
483 }
484
485 - (NSTimeInterval)age {
486   return [[NSCalendarDate calendarDate] 
487                           timeIntervalSinceDate:self->creationTime];
488 }
489
490 /* NSCopying */
491
492 - (id)copyWithZone:(NSZone *)_zone {
493   return [self retain];
494 }
495
496 /* description */
497
498 - (NSString *)description {
499   NSMutableString *ms;
500
501   ms = [NSMutableString stringWithCapacity:256];
502   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
503   
504   [ms appendFormat:@" channel=0x%08X", self->channel];
505   if (self->creationTime) [ms appendFormat:@" created=%@", self->creationTime];
506   if (self->lastReleaseTime) 
507     [ms appendFormat:@" last-released=%@", self->lastReleaseTime];
508   if (self->lastAcquireTime) 
509     [ms appendFormat:@" last-acquired=%@", self->lastAcquireTime];
510   
511   [ms appendString:@">"];
512   return ms;
513 }
514
515 @end /* GCSChannelHandle */