2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
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>
33 - auto-close channels which are very old?!
34 (eg missing release due to an exception)
37 @interface GCSChannelHandle : NSObject
41 EOAdaptorChannel *channel;
43 NSDate *lastReleaseTime;
44 NSDate *lastAcquireTime;
47 - (EOAdaptorChannel *)channel;
48 - (BOOL)canHandleURL:(NSURL *)_url;
49 - (NSTimeInterval)age;
53 @implementation GCSChannelManager
55 static BOOL debugOn = NO;
56 static BOOL debugPools = NO;
57 static int ChannelExpireAge = 180;
58 static NSTimeInterval ChannelCollectionTimer = 5 * 60;
61 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
63 debugOn = [ud boolForKey:@"GCSChannelManagerDebugEnabled"];
64 debugPools = [ud boolForKey:@"GCSChannelManagerPoolDebugEnabled"];
66 ChannelExpireAge = [[ud objectForKey:@"GCSChannelExpireAge"] intValue];
67 if (ChannelExpireAge < 1)
68 ChannelExpireAge = 180;
70 ChannelCollectionTimer =
71 [[ud objectForKey:@"GCSChannelCollectionTimer"] intValue];
72 if (ChannelCollectionTimer < 1)
73 ChannelCollectionTimer = 5*60;
76 + (NSString *)adaptorNameForURLScheme:(NSString *)_scheme {
77 // TODO: map scheme to adaptors (eg 'postgresql://' to PostgreSQL
81 + (id)defaultChannelManager {
82 static GCSChannelManager *cm = nil;
84 cm = [[self alloc] 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];
94 self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
95 ChannelCollectionTimer
96 target:self selector:@selector(_garbageCollect:)
97 userInfo:nil repeats:YES] retain];
103 if (self->gcTimer) [self->gcTimer invalidate];
104 [self->gcTimer release];
106 [self->busyChannels release];
107 [self->availableChannels release];
108 [self->urlToAdaptor release];
114 - (NSString *)databaseKeyForURL:(NSURL *)_url {
116 We need to build a proper key that omits passwords and URL path components
117 which are not required.
121 key = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",
122 [_url host], [_url port],
123 [_url user], [_url gcsDatabaseName]];
129 - (NSDictionary *)connectionDictionaryForURL:(NSURL *)_url {
130 NSMutableDictionary *md;
133 md = [NSMutableDictionary dictionaryWithCapacity:4];
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"];
144 if ((tmp = [_url gcsDatabaseName]) != nil)
145 [md setObject:tmp forKey:@"databaseName"];
147 [self debugWithFormat:@"build connection dictionary for URL %@: %@",
148 [_url absoluteString], md];
152 - (EOAdaptor *)adaptorForURL:(NSURL *)_url {
158 if ((key = [self databaseKeyForURL:_url]) == nil)
160 if ((adaptor = [self->urlToAdaptor objectForKey:key]) != nil) {
161 [self debugWithFormat:@"using cached adaptor: %@", adaptor];
162 return adaptor; /* cached :-) */
165 [self debugWithFormat:@"creating new adaptor for URL: %@", _url];
167 if ([EOAdaptor respondsToSelector:@selector(adaptorForURL:)]) {
168 adaptor = [EOAdaptor adaptorForURL:_url];
171 NSString *adaptorName;
172 NSDictionary *condict;
174 adaptorName = [[self class] adaptorNameForURLScheme:[_url scheme]];
175 if ([adaptorName length] == 0) {
176 [self logWithFormat:@"ERROR: cannot handle URL: %@", _url];
180 condict = [self connectionDictionaryForURL:_url];
182 if ((adaptor = [EOAdaptor adaptorWithName:adaptorName]) == nil) {
183 [self logWithFormat:@"ERROR: did not find adaptor '%@' for URL: %@",
188 [adaptor setConnectionDictionary:condict];
191 [self->urlToAdaptor setObject:adaptor forKey:key];
197 - (GCSChannelHandle *)findBusyChannelHandleForChannel:(EOAdaptorChannel *)_ch {
199 GCSChannelHandle *handle;
201 e = [self->busyChannels objectEnumerator];
202 while ((handle = [e nextObject])) {
203 if ([handle channel] == _ch)
208 - (GCSChannelHandle *)findAvailChannelHandleForURL:(NSURL *)_url {
210 GCSChannelHandle *handle;
212 e = [self->availableChannels objectEnumerator];
213 while ((handle = [e nextObject])) {
214 if ([handle canHandleURL:_url])
218 [self logWithFormat:@"DBPOOL: cannot use handle (%@ vs %@)",
219 [_url absoluteString], [handle->url absoluteString]];
225 - (EOAdaptorChannel *)_createChannelForURL:(NSURL *)_url {
227 EOAdaptorContext *adContext;
228 EOAdaptorChannel *adChannel;
230 if ((adaptor = [self adaptorForURL:_url]) == nil)
233 if ((adContext = [adaptor createAdaptorContext]) == nil) {
234 [self logWithFormat:@"ERROR: could not create adaptor context!"];
237 if ((adChannel = [adContext createAdaptorChannel]) == nil) {
238 [self logWithFormat:@"ERROR: could not create adaptor channel!"];
244 - (EOAdaptorChannel *)acquireOpenChannelForURL:(NSURL *)_url {
245 // TODO: naive implementation, add pooling!
246 EOAdaptorChannel *channel;
247 GCSChannelHandle *handle;
250 now = [NSCalendarDate date];
252 /* look for cached handles */
254 if ((handle = [self findAvailChannelHandleForURL:_url]) != nil) {
256 [self->busyChannels addObject:handle];
257 [self->availableChannels removeObject:handle];
258 ASSIGN(handle->lastAcquireTime, now);
261 [self logWithFormat:@"DBPOOL: reused cached DB channel!"];
262 return [[handle channel] retain];
266 [self logWithFormat:@"DBPOOL: create new DB channel for URL: %@",
267 [_url absoluteString]];
272 if ((channel = [self _createChannelForURL:_url]) == nil)
275 if ([channel isOpen])
277 else if (![channel openChannel]) {
278 [self logWithFormat:@"could not open channel %@ for URL: %@",
279 channel, [_url absoluteString]];
283 /* create handle for channel */
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];
291 [self->busyChannels addObject:handle];
294 return [channel retain];
296 - (void)releaseChannel:(EOAdaptorChannel *)_channel {
297 GCSChannelHandle *handle;
299 if ((handle = [self findBusyChannelHandleForChannel:_channel]) != nil) {
302 now = [NSCalendarDate date];
304 handle = [handle retain];
305 ASSIGN(handle->lastReleaseTime, now);
307 [self->busyChannels removeObject:handle];
309 if ([[handle channel] isOpen] && [handle age] < ChannelExpireAge) {
310 // TODO: consider age
311 [self->availableChannels addObject:handle];
314 @"DBPOOL: keeping channel (age %ds, #%d): %@",
315 (int)[handle age], [self->availableChannels count],
316 [handle->url absoluteString]];
325 @"DBPOOL: freeing old channel (age %ds)", (int)[handle age]];
328 /* not reusing channel */
329 [handle release]; handle = nil;
332 if ([_channel isOpen])
333 [_channel closeChannel];
338 /* checking for tables */
340 - (BOOL)canConnect:(NSURL *)_url {
342 this can check for DB connect as well as for table URLs (whether a table
345 EOAdaptorChannel *channel;
349 if ((channel = [self acquireOpenChannelForURL:_url]) == nil) {
350 if (debugOn) [self debugWithFormat:@"could not acquire channel: %@", _url];
353 if (debugOn) [self debugWithFormat:@"acquired channel: %@", channel];
354 result = YES; /* could open channel */
356 /* check whether table exists */
358 table = [_url gcsTableName];
359 if ([table length] > 0)
360 result = [channel tableExistsWithName:table];
362 /* release channel */
364 [self releaseChannel:channel]; channel = nil;
369 /* collect old channels */
371 - (void)_garbageCollect:(NSTimer *)_timer {
372 NSMutableArray *handlesToRemove;
375 if ((count = [self->availableChannels count]) == 0)
376 /* no available channels */
379 /* collect channels to expire */
381 handlesToRemove = [[NSMutableArray alloc] initWithCapacity:4];
382 for (i = 0; i < count; i++) {
383 GCSChannelHandle *handle;
385 handle = [self->availableChannels objectAtIndex:i];
386 if (![[handle channel] isOpen]) {
387 [handlesToRemove addObject:handle];
390 if ([handle age] > ChannelExpireAge) {
391 [handlesToRemove addObject:handle];
396 /* remove channels */
397 count = [handlesToRemove count];
399 [self logWithFormat:@"DBPOOL: garbage collecting %d channels.", count];
400 for (i = 0; i < count; i++) {
401 GCSChannelHandle *handle;
403 handle = [[handlesToRemove objectAtIndex:i] retain];
404 [self->availableChannels removeObject:handle];
405 if ([[handle channel] isOpen])
406 [[handle channel] closeChannel];
410 [handlesToRemove release];
415 - (BOOL)isDebuggingEnabled {
421 - (NSString *)description {
424 ms = [NSMutableString stringWithCapacity:256];
425 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
427 [ms appendFormat:@" #adaptors=%d", [self->urlToAdaptor count]];
429 [ms appendString:@">"];
433 @end /* GCSChannelManager */
435 @implementation GCSChannelHandle
438 [self->channel release];
439 [self->creationTime release];
440 [self->lastReleaseTime release];
441 [self->lastAcquireTime release];
447 - (EOAdaptorChannel *)channel {
448 return self->channel;
451 - (BOOL)canHandleURL:(NSURL *)_url {
455 [self logWithFormat:@"MISMATCH: no url .."];
458 if (_url == self->url)
461 isSQLite = [[_url scheme] isEqualToString:@"sqlite"];
463 if (!isSQLite && ![[self->url host] isEqual:[_url host]]) {
464 [self logWithFormat:@"MISMATCH: different host .."];
467 if (![[self->url gcsDatabaseName] isEqualToString:[_url gcsDatabaseName]]) {
468 [self logWithFormat:@"MISMATCH: different db .."];
472 if (![[self->url user] isEqual:[_url user]]) {
473 [self logWithFormat:@"MISMATCH: different user .."];
476 if ([[self->url port] intValue] != [[_url port] intValue]) {
477 [self logWithFormat:@"MISMATCH: different port (%@ vs %@) ..",
478 [self->url port], [_url port]];
485 - (NSTimeInterval)age {
486 return [[NSCalendarDate calendarDate]
487 timeIntervalSinceDate:self->creationTime];
492 - (id)copyWithZone:(NSZone *)_zone {
493 return [self retain];
498 - (NSString *)description {
501 ms = [NSMutableString stringWithCapacity:256];
502 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
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];
511 [ms appendString:@">"];
515 @end /* GCSChannelHandle */