Mercurial > hg > ltpda-connection-manager
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LTPDAConnectionManager.m Sun May 23 10:51:35 2010 +0200 @@ -0,0 +1,441 @@ +classdef LTPDAConnectionManager < handle + + properties(SetAccess=private) + + connections = {}; + credentials = {}; + + end % private properties + + properties(Dependent=true) + + credentialsExpiry; % seconds + cachePassword; % 0=no 1=yes 2=ask + maxConnectionsNumber; + + end % dependent properties + + methods(Static) + + function reset() + setappdata(0, LTPDAConnectionManager.appdataKey, []); + end + + function key = appdataKey() + % defined as static method to be acessible by the reset static method + key = 'LTPDAConnectionManager'; + end + + end % static methods + + methods + + function cm = LTPDAConnectionManager(pl) + + % load state from appdata + acm = getappdata(0, cm.appdataKey()); + + if isempty(acm) + % take those from user preferences + cm.credentials{end+1} = credentials('localhost', 'one'); + cm.credentials{end+1} = credentials('localhost', 'two', 'daniele'); + + % store state in appdata + setappdata(0, cm.appdataKey(), cm); + + import utils.const.* + utils.helper.msg(msg.PROC1, 'new connection manager'); + else + cm = acm; + end + end + + + function val = get.credentialsExpiry(cm) + % obtain from user preferences + p = getappdata(0, 'LTPDApreferences'); + val = p.cm.credentialsExpiry; + end + + + function val = get.cachePassword(cm) + % obtain from user preferences + p = getappdata(0, 'LTPDApreferences'); + val = p.cm.cachePassword; + end + + + function val = get.maxConnectionsNumber(cm) + % obtain from user preferences + p = getappdata(0, 'LTPDApreferences'); + val = p.cm.maxConnectionsNumber; + end + + + function n = count(cm) + import utils.const.* + % find closed connections in the pool + mask = false(numel(cm.connections), 1); + for kk = 1:numel(cm.connections) + if cm.connections{kk}.isClosed() + utils.helper.msg(msg.PROC1, 'connection id=%d closed', kk); + mask(kk) = true; + end + end + + % remove them + cm.connections(mask) = []; + + % count remainig ones + n = numel(cm.connections); + end + + + function clear(cm) + % remove all cached credentials + cm.credentials = {}; + end + + + function conn = connect(cm, varargin) + import utils.const.* + + % save current credentials cache + cache = cm.credentials; + + % count open connections in the pool + count = cm.count(); + + % check parameters + if numel(varargin) == 1 && isa(varargin{1}, 'plist') + + % extract parameters from plist + pl = varargin{1}; + + % check if we have a connection parameter + conn = find(pl, 'connection'); + if ~isempty(conn) + % check that it implements java.sql.Connection interface + if ~isa(conn, 'java.sql.Connection') + error('### connection is not valid database connection'); + end + % return this connection + return; + end + + % otherwise + hostname = find(pl, 'hostname'); + database = find(pl, 'database'); + username = find(pl, 'username'); + password = find(pl, 'password'); + + % if there is no hostname and database ignore other parameters + if ~ischar(hostname) || ~ischar(database) + varargin = {}; + end + % password can not be null but can be an empty string + if ~ischar(password) + varargin = {hostname, database, username}; + else + varargin = {hostname, database, username, password}; + end + end + + % check number of connections + if count > cm.maxConnectionsNumber + error('### too many open connections'); + end + + % connect + try + conn = cm.getConnection(varargin{:}); + catch ex + % restore our copy of the credentials cache + utils.helper.msg(msg.PROC1, 'undo cache changes'); + cm.credentials = cache; + + % hide implementation details + %ex.throwAsCaller(); + ex.rethrow() + end + end + + + function close(cm, ids) + if nargin < 2 + ids = 1:numel(cm.connections); + end + cellfun(@close, cm.connections(ids)); + end + + + function add(cm, c) + if nargin < 2 || ~isa(c, 'credentials') + error('### invalid call'); + end + cm.cacheCredentials(c); + end + + end % methods + + methods(Access=private) + + function conn = getConnection(cm, varargin) + + import utils.const.* + + switch numel(varargin) + case 0 + [hostname, database, username] = cm.selectDatabase(); + conn = cm.getConnection(hostname, database, username); + + case 2 + conn = cm.getConnection(varargin{1}, varargin{2}, []); + + case 3 + % find credentials + cred = cm.findCredentials(varargin{1}, varargin{2}, varargin{3}); + if isempty(cred) + % no credentials found + cred = credentials(varargin{1}, varargin{2}, varargin{3}); + else + utils.helper.msg(msg.PROC1, 'use cached credentials'); + end + + cache = false; + if numel(cred) > 1 || ~cred.complete + % ask for which username and password to use + [username, password, cache] = cm.inputCredentials(cred); + + % cache credentials + cred = credentials(varargin{1}, varargin{2}, username); + cm.cacheCredentials(cred); + + % add password to credentials + cred.password = password; + end + + % try to connect + conn = cm.getConnection(cred.hostname, cred.database, cred.username, cred.password); + + % cache password + if cache + utils.helper.msg(msg.PROC1, 'cache password'); + cm.cacheCredentials(cred); + end + + case 4 + try + % connect + conn = connect(varargin{1}, varargin{2}, varargin{3}, varargin{4}); + + % cache credentials without password + cred = credentials(varargin{1}, varargin{2}, varargin{3}, []); + cm.cacheCredentials(cred); + + catch ex + % look for access denied errors + if strcmp(ex.identifier, 'utils:jmysql:connect:AccessDenied') + % ask for new new credentials + utils.helper.msg(msg.PROC1, ex.message); + conn = cm.getConnection(varargin{1}, varargin{2}, varargin{3}); + else + % error out + throw(MException('', '### connection error').addCause(ex)); + end + end + + % add connection to pool + utils.helper.msg(msg.PROC1, 'add connection to pool'); + cm.connections{end+1} = conn; + + otherwise + error('### invalid call') + end + + end + + + function ids = findCredentialsId(cm, varargin) + import utils.const.* + ids = []; + + for kk = 1:numel(cm.credentials) + % invalidate expired passwords + if expired(cm.credentials{kk}) + utils.helper.msg(msg.PROC1, 'cache entry id=%d expired', kk); + cm.credentials{kk}.password = []; + cm.credentials{kk}.expiry = 0; + end + + % match input with cache + if match(cm.credentials{kk}, varargin{:}) + ids = [ ids kk ]; + end + end + end + + + function cred = findCredentials(cm, varargin) + % default + cred = []; + + % search + ids = findCredentialsId(cm, varargin{:}); + + % return an array credentials + if ~isempty(ids) + cred = [ cm.credentials{ids} ]; + end + end + + + function cacheCredentials(cm, c) + import utils.const.* + + % find entry to update + id = findCredentialsId(cm, c.hostname, c.database, c.username); + + % sanity check + if numel(id) > 1 + error('### more than one cache entry for %s', char(c, 'short')); + end + + % set password expiry time + if ischar(c.password) + c.expiry = double(time()) + cm.credentialsExpiry; + end + + if isempty(id) + % add at the end + utils.helper.msg(msg.PROC1, 'add cache entry %s', char(c)); + cm.credentials{end+1} = c; + else + % update only if the cached informations are less than the one we have + if ~complete(cm.credentials{id}) + utils.helper.msg(msg.PROC1, 'update cache entry id=%d %s', id, char(c)); + cm.credentials{id} = c; + else + % always update expiry time + cm.credentials{id}.expiry = c.expiry; + end + end + end + + + function [username, password, cache] = inputCredentials(cm, cred) + % this is a stubb + + % build a cell array of usernames and passwords + users = { cred(:).username }; + passw = { cred(:).password }; + + % default to the latest used username + [e, ids] = sort([ cred(:).expiry ]); + default = users{ids(1)}; + + username = choose('Username', users, default); + + % pick the corresponding password + ids = find(strcmp(users, username)); + if ~isempty(ids) + default = passw{ids(1)}; + else + default = []; + end + + password = ask('Password', ''); + + if cm.cachePassword == 2 + cache = ask('Store credentials', 'n'); + if ~isempty(cache) && cache(1) == 'y' + cache = true; + else + cache = false; + end + else + cache = logical(cm.cachePassword); + end + end + + + function [hostname, database, username] = selectDatabase(cm) + % this is a stubb + + for kk = 1:numel(cm.credentials) + fprintf('% 2d. %s\n', char(cm.credentials{kk})); + end + fprintf('%d. NEW (default)\n', numel(cm.credentials)+1); + str = input('Select connection: ', 's'); + if isempty(str) + id = numel(cm.credentials)+1; + else + id = eval(str); + end + if id > numel(cm.credentials) + hostname = input('Hostname: ', 's'); + database = input('Database: ', 's'); + username = []; + else + hostname = cm.credentials{kk}.hostname; + database = cm.credentials{kk}.database; + username = cm.credentials{kk}.username; + end + end + + end % private methods + +end % classdef + + +% this should become utils.jmysql.connect +function conn = connect(hostname, database, username, password) + + % informative message + import utils.const.* + utils.helper.msg(msg.PROC1, 'connection to mysql://%s/%s username=%s', hostname, database, username); + + % connection credential + uri = sprintf('jdbc:mysql://%s/%s', hostname, database); + db = javaObject('com.mysql.jdbc.Driver'); + pl = javaObject('java.util.Properties'); + pl.setProperty(db.USER_PROPERTY_KEY, username); + pl.setProperty(db.PASSWORD_PROPERTY_KEY, password); + + try + % connect + conn = db.connect(uri, pl); + catch ex + % haven't decided yet if this code should be here or higher in the stack + if strcmp(ex.identifier, 'MATLAB:Java:GenericException') + % exceptions handling in matlab sucks + if ~isempty(strfind(ex.message, 'java.sql.SQLException: Access denied')) + throw(MException('utils:jmysql:connect:AccessDenied', '### access denied').addCause(ex)); + end + end + rethrow(ex); + end +end + + +function str = ask(msg, default) + str = input(sprintf('%s (default: %s): ', msg, default), 's'); + if isempty(str) + str = default; + end + if ~ischar(str) + str = char(str); + end +end + +function str = choose(msg, choices, default) + options = sprintf('%s, ', choices{:}); + options = options(1:end-2); + str = input(sprintf('%s (options: %s, default: %s): ', msg, options, default), 's'); + if isempty(str) + str = default; + end + if ~ischar(str) + str = char(str); + end +end