comparison LTPDAConnectionManager.m @ 0:d5fef23867bb

First workig implementation.
author Daniele Nicolodi <daniele@science.unitn.it>
date Sun, 23 May 2010 10:51:35 +0200
parents
children c706c10a76bd
comparison
equal deleted inserted replaced
-1:000000000000 0:d5fef23867bb
1 classdef LTPDAConnectionManager < handle
2
3 properties(SetAccess=private)
4
5 connections = {};
6 credentials = {};
7
8 end % private properties
9
10 properties(Dependent=true)
11
12 credentialsExpiry; % seconds
13 cachePassword; % 0=no 1=yes 2=ask
14 maxConnectionsNumber;
15
16 end % dependent properties
17
18 methods(Static)
19
20 function reset()
21 setappdata(0, LTPDAConnectionManager.appdataKey, []);
22 end
23
24 function key = appdataKey()
25 % defined as static method to be acessible by the reset static method
26 key = 'LTPDAConnectionManager';
27 end
28
29 end % static methods
30
31 methods
32
33 function cm = LTPDAConnectionManager(pl)
34
35 % load state from appdata
36 acm = getappdata(0, cm.appdataKey());
37
38 if isempty(acm)
39 % take those from user preferences
40 cm.credentials{end+1} = credentials('localhost', 'one');
41 cm.credentials{end+1} = credentials('localhost', 'two', 'daniele');
42
43 % store state in appdata
44 setappdata(0, cm.appdataKey(), cm);
45
46 import utils.const.*
47 utils.helper.msg(msg.PROC1, 'new connection manager');
48 else
49 cm = acm;
50 end
51 end
52
53
54 function val = get.credentialsExpiry(cm)
55 % obtain from user preferences
56 p = getappdata(0, 'LTPDApreferences');
57 val = p.cm.credentialsExpiry;
58 end
59
60
61 function val = get.cachePassword(cm)
62 % obtain from user preferences
63 p = getappdata(0, 'LTPDApreferences');
64 val = p.cm.cachePassword;
65 end
66
67
68 function val = get.maxConnectionsNumber(cm)
69 % obtain from user preferences
70 p = getappdata(0, 'LTPDApreferences');
71 val = p.cm.maxConnectionsNumber;
72 end
73
74
75 function n = count(cm)
76 import utils.const.*
77 % find closed connections in the pool
78 mask = false(numel(cm.connections), 1);
79 for kk = 1:numel(cm.connections)
80 if cm.connections{kk}.isClosed()
81 utils.helper.msg(msg.PROC1, 'connection id=%d closed', kk);
82 mask(kk) = true;
83 end
84 end
85
86 % remove them
87 cm.connections(mask) = [];
88
89 % count remainig ones
90 n = numel(cm.connections);
91 end
92
93
94 function clear(cm)
95 % remove all cached credentials
96 cm.credentials = {};
97 end
98
99
100 function conn = connect(cm, varargin)
101 import utils.const.*
102
103 % save current credentials cache
104 cache = cm.credentials;
105
106 % count open connections in the pool
107 count = cm.count();
108
109 % check parameters
110 if numel(varargin) == 1 && isa(varargin{1}, 'plist')
111
112 % extract parameters from plist
113 pl = varargin{1};
114
115 % check if we have a connection parameter
116 conn = find(pl, 'connection');
117 if ~isempty(conn)
118 % check that it implements java.sql.Connection interface
119 if ~isa(conn, 'java.sql.Connection')
120 error('### connection is not valid database connection');
121 end
122 % return this connection
123 return;
124 end
125
126 % otherwise
127 hostname = find(pl, 'hostname');
128 database = find(pl, 'database');
129 username = find(pl, 'username');
130 password = find(pl, 'password');
131
132 % if there is no hostname and database ignore other parameters
133 if ~ischar(hostname) || ~ischar(database)
134 varargin = {};
135 end
136 % password can not be null but can be an empty string
137 if ~ischar(password)
138 varargin = {hostname, database, username};
139 else
140 varargin = {hostname, database, username, password};
141 end
142 end
143
144 % check number of connections
145 if count > cm.maxConnectionsNumber
146 error('### too many open connections');
147 end
148
149 % connect
150 try
151 conn = cm.getConnection(varargin{:});
152 catch ex
153 % restore our copy of the credentials cache
154 utils.helper.msg(msg.PROC1, 'undo cache changes');
155 cm.credentials = cache;
156
157 % hide implementation details
158 %ex.throwAsCaller();
159 ex.rethrow()
160 end
161 end
162
163
164 function close(cm, ids)
165 if nargin < 2
166 ids = 1:numel(cm.connections);
167 end
168 cellfun(@close, cm.connections(ids));
169 end
170
171
172 function add(cm, c)
173 if nargin < 2 || ~isa(c, 'credentials')
174 error('### invalid call');
175 end
176 cm.cacheCredentials(c);
177 end
178
179 end % methods
180
181 methods(Access=private)
182
183 function conn = getConnection(cm, varargin)
184
185 import utils.const.*
186
187 switch numel(varargin)
188 case 0
189 [hostname, database, username] = cm.selectDatabase();
190 conn = cm.getConnection(hostname, database, username);
191
192 case 2
193 conn = cm.getConnection(varargin{1}, varargin{2}, []);
194
195 case 3
196 % find credentials
197 cred = cm.findCredentials(varargin{1}, varargin{2}, varargin{3});
198 if isempty(cred)
199 % no credentials found
200 cred = credentials(varargin{1}, varargin{2}, varargin{3});
201 else
202 utils.helper.msg(msg.PROC1, 'use cached credentials');
203 end
204
205 cache = false;
206 if numel(cred) > 1 || ~cred.complete
207 % ask for which username and password to use
208 [username, password, cache] = cm.inputCredentials(cred);
209
210 % cache credentials
211 cred = credentials(varargin{1}, varargin{2}, username);
212 cm.cacheCredentials(cred);
213
214 % add password to credentials
215 cred.password = password;
216 end
217
218 % try to connect
219 conn = cm.getConnection(cred.hostname, cred.database, cred.username, cred.password);
220
221 % cache password
222 if cache
223 utils.helper.msg(msg.PROC1, 'cache password');
224 cm.cacheCredentials(cred);
225 end
226
227 case 4
228 try
229 % connect
230 conn = connect(varargin{1}, varargin{2}, varargin{3}, varargin{4});
231
232 % cache credentials without password
233 cred = credentials(varargin{1}, varargin{2}, varargin{3}, []);
234 cm.cacheCredentials(cred);
235
236 catch ex
237 % look for access denied errors
238 if strcmp(ex.identifier, 'utils:jmysql:connect:AccessDenied')
239 % ask for new new credentials
240 utils.helper.msg(msg.PROC1, ex.message);
241 conn = cm.getConnection(varargin{1}, varargin{2}, varargin{3});
242 else
243 % error out
244 throw(MException('', '### connection error').addCause(ex));
245 end
246 end
247
248 % add connection to pool
249 utils.helper.msg(msg.PROC1, 'add connection to pool');
250 cm.connections{end+1} = conn;
251
252 otherwise
253 error('### invalid call')
254 end
255
256 end
257
258
259 function ids = findCredentialsId(cm, varargin)
260 import utils.const.*
261 ids = [];
262
263 for kk = 1:numel(cm.credentials)
264 % invalidate expired passwords
265 if expired(cm.credentials{kk})
266 utils.helper.msg(msg.PROC1, 'cache entry id=%d expired', kk);
267 cm.credentials{kk}.password = [];
268 cm.credentials{kk}.expiry = 0;
269 end
270
271 % match input with cache
272 if match(cm.credentials{kk}, varargin{:})
273 ids = [ ids kk ];
274 end
275 end
276 end
277
278
279 function cred = findCredentials(cm, varargin)
280 % default
281 cred = [];
282
283 % search
284 ids = findCredentialsId(cm, varargin{:});
285
286 % return an array credentials
287 if ~isempty(ids)
288 cred = [ cm.credentials{ids} ];
289 end
290 end
291
292
293 function cacheCredentials(cm, c)
294 import utils.const.*
295
296 % find entry to update
297 id = findCredentialsId(cm, c.hostname, c.database, c.username);
298
299 % sanity check
300 if numel(id) > 1
301 error('### more than one cache entry for %s', char(c, 'short'));
302 end
303
304 % set password expiry time
305 if ischar(c.password)
306 c.expiry = double(time()) + cm.credentialsExpiry;
307 end
308
309 if isempty(id)
310 % add at the end
311 utils.helper.msg(msg.PROC1, 'add cache entry %s', char(c));
312 cm.credentials{end+1} = c;
313 else
314 % update only if the cached informations are less than the one we have
315 if ~complete(cm.credentials{id})
316 utils.helper.msg(msg.PROC1, 'update cache entry id=%d %s', id, char(c));
317 cm.credentials{id} = c;
318 else
319 % always update expiry time
320 cm.credentials{id}.expiry = c.expiry;
321 end
322 end
323 end
324
325
326 function [username, password, cache] = inputCredentials(cm, cred)
327 % this is a stubb
328
329 % build a cell array of usernames and passwords
330 users = { cred(:).username };
331 passw = { cred(:).password };
332
333 % default to the latest used username
334 [e, ids] = sort([ cred(:).expiry ]);
335 default = users{ids(1)};
336
337 username = choose('Username', users, default);
338
339 % pick the corresponding password
340 ids = find(strcmp(users, username));
341 if ~isempty(ids)
342 default = passw{ids(1)};
343 else
344 default = [];
345 end
346
347 password = ask('Password', '');
348
349 if cm.cachePassword == 2
350 cache = ask('Store credentials', 'n');
351 if ~isempty(cache) && cache(1) == 'y'
352 cache = true;
353 else
354 cache = false;
355 end
356 else
357 cache = logical(cm.cachePassword);
358 end
359 end
360
361
362 function [hostname, database, username] = selectDatabase(cm)
363 % this is a stubb
364
365 for kk = 1:numel(cm.credentials)
366 fprintf('% 2d. %s\n', char(cm.credentials{kk}));
367 end
368 fprintf('%d. NEW (default)\n', numel(cm.credentials)+1);
369 str = input('Select connection: ', 's');
370 if isempty(str)
371 id = numel(cm.credentials)+1;
372 else
373 id = eval(str);
374 end
375 if id > numel(cm.credentials)
376 hostname = input('Hostname: ', 's');
377 database = input('Database: ', 's');
378 username = [];
379 else
380 hostname = cm.credentials{kk}.hostname;
381 database = cm.credentials{kk}.database;
382 username = cm.credentials{kk}.username;
383 end
384 end
385
386 end % private methods
387
388 end % classdef
389
390
391 % this should become utils.jmysql.connect
392 function conn = connect(hostname, database, username, password)
393
394 % informative message
395 import utils.const.*
396 utils.helper.msg(msg.PROC1, 'connection to mysql://%s/%s username=%s', hostname, database, username);
397
398 % connection credential
399 uri = sprintf('jdbc:mysql://%s/%s', hostname, database);
400 db = javaObject('com.mysql.jdbc.Driver');
401 pl = javaObject('java.util.Properties');
402 pl.setProperty(db.USER_PROPERTY_KEY, username);
403 pl.setProperty(db.PASSWORD_PROPERTY_KEY, password);
404
405 try
406 % connect
407 conn = db.connect(uri, pl);
408 catch ex
409 % haven't decided yet if this code should be here or higher in the stack
410 if strcmp(ex.identifier, 'MATLAB:Java:GenericException')
411 % exceptions handling in matlab sucks
412 if ~isempty(strfind(ex.message, 'java.sql.SQLException: Access denied'))
413 throw(MException('utils:jmysql:connect:AccessDenied', '### access denied').addCause(ex));
414 end
415 end
416 rethrow(ex);
417 end
418 end
419
420
421 function str = ask(msg, default)
422 str = input(sprintf('%s (default: %s): ', msg, default), 's');
423 if isempty(str)
424 str = default;
425 end
426 if ~ischar(str)
427 str = char(str);
428 end
429 end
430
431 function str = choose(msg, choices, default)
432 options = sprintf('%s, ', choices{:});
433 options = options(1:end-2);
434 str = input(sprintf('%s (options: %s, default: %s): ', msg, options, default), 's');
435 if isempty(str)
436 str = default;
437 end
438 if ~ischar(str)
439 str = char(str);
440 end
441 end