changeset 0:d5fef23867bb

First workig implementation.
author Daniele Nicolodi <daniele@science.unitn.it>
date Sun, 23 May 2010 10:51:35 +0200
parents
children c4b57991935a
files LTPDAConnectionManager.m README credentials.m test_ltpda_connection_manager.m
diffstat 4 files changed, 1055 insertions(+), 0 deletions(-) [+]
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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Sun May 23 10:51:35 2010 +0200
@@ -0,0 +1,394 @@
+* Requirements
+
+  1. Provide an interface for the user to insert connection
+     credentials: server hostname, database name, user name and
+     password.
+
+  2. Avoid that the user has to enter those information too often,
+     like when retrieving many objects from the repository. This is
+     done caching the credentials for a configurable amount of time
+
+  3. Provide understandable error messages.
+  
+  4. Track open connections, and avoid excessive proliferation of open
+     connections to the database, due to miss behaving user functions.
+
+  This is all about managing connections, so I would propose to call
+  this component LTPDAConnectionManager.
+
+
+* Interface definition
+
+** LTPDAConnectionManager()
+
+   Isntantiates a singleton class managing credentials and connections.
+
+   Credentials are cached. Credentials, except password are cached
+   forever, passwords are cached for a configurable amount of time.
+
+   Connections are tracked putting each handed out connection into a
+   list. When a new connection is requested, or when requested via a
+   dedicated method, the connection list is walked and closed
+   connections are removed from it. When the connection number exceeds
+   a configurable maximum, no more connection are instantiated.
+
+   The singleton clas is implemented as an handle matlab class that
+   stores an handle to itself in appdata storage. When the class is
+   instantiates it returns an handle to the copy referenced in
+   appdata.
+
+*** appdataKey
+
+    Static mehod that returns the key used to store the handle to
+    instance data in appdata.
+
+*** credentialsExpiry
+    
+    Property. Taken from user preferences. Time after which the
+    credentials are not valid anymore.
+
+*** maxConnectionsNumber
+
+    Property. Taken from user preferences. The maximum number of
+    connections that can be open at the same time toward the same
+    host.
+
+*** cachePassword
+
+    Property. Taken from user preferences. Defines if password should
+    be cached along with other database access credentials. It may
+    take the values:
+
+    0. passwords are not cached,
+    1. passwords are always cached,
+    2. ask if the users desires to cache the given password.
+
+*** connect(hostname, database, username, password)
+
+    Try to connect to the database with the given credentials. If the
+    connection attempt fails with an 'access denied' error, prompt the
+    user for username using the given username as default, and
+    password. Present the option to cache password. Cache username and
+    password accordingly to user choice. Do not cache passwords by
+    default.
+
+    Return an object implementing the java.sql.Connection interface,
+    representing a connection to the given database, with the given
+    credentials.
+
+    Connection errors, other than access denied are reported as
+    fatal exceptions. User hitting the cancel button on input forms is
+    reported as an 'user canceled' exception.
+
+*** connect(hostname, database, username)
+
+    Search the credentials cache for user password. If it is not
+    found, or the connection attempt fails with an access denied
+    error, prompt the user for username, using the given username as
+    default, and password. Present the option to cache password. Try
+    to connect to the database with the given credentials. When the
+    connections succeeds, cache username and password accordingly to
+    user choice.
+
+    Return an object implementing the java.sql.Connection interface,
+    representing a connection to the given database, with the given
+    credentials.
+
+    Errors, other than acces denied, are reported as fatal
+    exceptions. User hitting the cancel button on input forms is
+    reported as an 'user canceled' exception. Access denied errors are
+    catched and credentials are asked again.
+
+*** connect(hostname, database)
+
+    Search the credentials cache looking for usernames and passwords
+    for accessing the given database. If credentials are not found,
+    there is more than one set of credentials matching, or the
+    connection attempt fails with an 'acces denied' error, prompt the
+    user for username and password. Present the option to cache
+    password. When the connections succeeds, cache username and
+    password accordingly to user choice.
+
+    Return an object implementing the java.sql.Connection interface,
+    representing a connection to the given database, with the given
+    credentials.
+
+    Errors, other than acces denied, are reported as fatal
+    exceptions. User hitting the cancel button on input forms is
+    reported as an 'user canceled' exception. Access denied errors are
+    catched and credentials are asked again.
+
+*** connect()
+
+    Make the user choose between a list of hostname-database-username
+    tuples obtained from the credentials cache, and the possibility of
+    entering a hostname and a database. Then proceed as in the case of
+    the call to connect(hostname, database).
+
+    The list of hostname-database pairs can be initialized with data
+    from the LTPDA toolbox preferences or witj the add() method.
+
+*** connect(plist)
+
+    Same as the previous calls but taking parameters from PLIST.
+
+    If PLIST has a 'connection' parameter, and if this is a valid java
+    object, implementing the java.sql.Connection, return it instead of
+    creating a new connection. If the object does not fulfill the
+    requirements throw a meaningful error.
+
+*** count()
+
+    Return the number of open connections in the connection pool. It
+    has the side effect of removing from the pool any close connection.
+
+*** close(ids)
+
+    Close connections with the given IDs in the connections pool. If no
+    ID is given close all connection.
+
+*** clear()
+
+    Clear credentials cache.
+
+*** add(credentials)
+
+    Add the given credentials to the cache.
+
+
+** Utilities
+
+*** utils.jmysql
+
+    Collection of utility functions for interfacing to a database from
+    a pure matlab environment. Except connect() all the functions take
+    a java object implementing java.sql.Connection as first parameter.
+
+*** utils.jmysql.connect(hostname, database, username, password)
+
+    Return an object implementing the java.sql.Connection interface,
+    representing a connection to the given database, with the given
+    credentials.
+
+    Throw an error if the connection fails.
+
+    This function can be used when the programmer wants full controll
+    on the connection, stepping aside of the connection manager.
+
+    Should not be used in toolbox functions or by the toolbox users.
+
+*** utils.jmysql.execute(conn, query, varargin)
+
+    Execute the query QUERY, with the parameters specified in VARARGIN
+    through the connection CONN. Returns the results in a 2d cell
+    array. See the actual implementation.
+
+    QUERY is a string, CONN is a java object implementing
+    java.sql.Connection, VARARGIN can contain any base matlab type. we
+    can think about introducing the marshalling for time() objects
+    into SQL strings.
+
+    See current implementation in CVS.
+
+*** plist.REPOSITORY_CREDENTIALS_PLIST
+
+    Static method that returns a default plist that specifies the
+    credentials required to connect to a repository.  Should be
+    combined in the default plist of the ltpda methods that require
+    a connection to the database. Those parameters are:
+
+      hostname - Server hostname
+      database - Database name
+      username - User login name
+      password - Password
+
+    In addition to those any ltpda method requiring a database
+    connection should accept a 'connection' parameter in his plist. An
+    open connection can be provided though this parameter. The caller
+    is responsible of closing the connection once it is done with it.
+
+
+* Examples
+
+   function out = useless(a, b, plist)
+
+     % obtain a database connection
+     cm = LTPDAConnectionManager();
+     conn = cm.connect(pl)
+     
+     % ask the database to sum A and B
+     out = utils.jmysql.execute(conn, 'SELECT ?+?', a, b);
+
+     % out is a cell array containing A + B
+     disp out{1};
+
+     % check who is in charge of the connection
+     if conn ~= find(pl, 'connection')
+       % close connection
+       conn.close()
+     end
+
+   end
+
+   This function can be used as follows:
+
+   - useless(1, 2) 
+
+     Will ask the user for hostname, database, username, password.
+
+   - useless(1, 2, plist('hostname', 'localhost', 'database', 'test'))
+
+     Will ask the user for username and password.
+
+   - useless(1, 2, plist('hostname', 'localhost', 'database', 'test', 'username', 'daniele'))
+
+     Will ask the user for password.
+
+   - useless(1, 2, plist('hostname', 'localhost', 'database', 'test', 'username', 'daniele', 'password', 'passwd'))
+
+     Will not ask the user.
+
+   - conn = utils.jmysql.conn('localhost', 'test', 'daniele', 'passwd')
+     useless(1, 2, plist('connection', conn));
+     useless(3, 4, plist('connection', conn));
+     conn.close();
+
+     Anything is asked to the user. The connection is provided by the
+     user and should not be closed inside the function. This is
+     usefull for continuously running automated processes.
+
+
+   This code is a stupid example of connections not being closed properly:
+
+   pl = plist('hostname', 'localhost', 'database', 'test', 'username', 'daniele', 'password', 'passwd');
+   cm = LTPDAConnectionManager();
+   cm.maxConnectionsNumber = 20;
+   for kk = 1:100
+     conn{kk} = cm.connect(pl);
+   end
+   
+   It should fail at iteration 21 with an error similat to
+
+   ### too many connections open to 'localhost'
+
+
+* Use cases
+
+** Interactive usage
+
+   A connection can be obtained from the connection manager in the
+   following ways, requiring user interaction:
+
+   1. Open a connection without providing any credential.
+
+      A. If there are cached credentials show a list of (hostname,
+      database, username) touple, give the possibiity to chose between
+      any of those and creating a new one.
+
+      B. If there are no cached credentials for the (hostname,
+      database, username) touple, or the user choses 'new' in the
+      previeus step, a dialog box asks the user for hostname and
+      database. The hostname field is an editable drop down lists
+      populated with data from the user preferences and cached
+      credentials. ADVANCED: the database filed is an editable drop
+      down list, when the user presses the drop down button, a list of
+      databases is fetch from the server.
+
+      C. If there are cached credentials for the (hostname, database,
+      username) touple go to E.
+
+      D. A dialog box asks the user for username and password and
+      gives the option to remember credentials. Username field is an
+      editable drop down populated with usernames from the credentials
+      cache, the username last entered username this (hostname,
+      database) pair is the default. The provided username is cached
+      into the credential cache.
+
+      E. Connection to the database it attempt. If the connection
+      fails go back to C. If the connection succeds and the user
+      selected the 'store credentials' options save the password in
+      the credentials cache.
+
+      F. Return the connection to the caller.
+
+   2. Open a connection providing hostname and database.
+
+      A. If there are cached credentials go to 1.E.
+
+      B. If there are no cached credentials go to 1.D.
+      
+   3. Open a connection providing hostname, database, username.
+
+      A. If there are cached credentials for the (hostname, database,
+      username) touple go to 1.E.
+
+      A. If there are no cached credentials for the (hostname, database,
+      username) touple go to 1.D.
+      
+   4. Open a connection providing hostname, database, username, password.
+
+      A. Go to 1.E.
+
+** Non interactive usage
+
+   A connection can be obtained without requiring user interaction in
+   the following ways:
+
+   1. Known good credendials can be provided to the connection manager
+      connect() method. If called with four arguments it will not
+      require user interaction if it connects successfully to the
+      database.
+
+   2. Known good credentials can be added to the cache with the connection
+      manager add() method. Any call to the connection manager
+      connect() method specifying at least hostname and database
+      matching the ones inserted in the cache, will then use the
+      cached credentials. User interaction is required if the
+      connection fails.
+
+   3. A connection can be obtained with the utils.jmysql.connect()
+      function and given to the ltpda methods requiring a database
+      connection with the 'connection' plist parameter. In this way
+      the caller has full control on the connection.
+
+** Functions chaining
+
+   In the case where a procedure needs to call several ltpda methods
+   requiring database access, it is recommended that the connection is
+   created in the outermost function, and passed to called functions
+   using the 'connection' plist parameter.
+
+   This avoids to query the user for connection credentials more than
+   once during the executing of the same procedure, even when password
+   caching is disabled. It also avoids the performance penalty of
+   connecting multiple times to the server.
+
+** Multiple users scenario
+
+   Consider the case where two users are sharing the same matlab
+   instance. The two users must be able to interleave the creation of
+   database connections, each one with his own credentials. This can
+   be accomplished in different ways:
+
+   1. If the interleaving of the operations of two users is frequent,
+      the connection manager can be configured, via the user
+      preferences, to never cache passwords. The users will be always
+      queried for their credentials.
+
+      If procedures are well written, each one should not require to
+      enter user credentials more than once.
+
+   2. If the interleaving of the two users is not so frequent, the
+      connection manager can be configured, via the user preferences,
+      to cache passwords for a short time.
+
+   3. The connection manager can be tricked into creating multiple
+      user profiles for the same database connection. For example:
+
+      cm = LTPDAConnectionManager();
+      cm.add(credentials('host', 'db', 'user1');
+      cm.add(credentials('host', 'db', 'user2');
+      
+      Password caching can be enabled. The connection manager will
+      make the user decide which credentials to use. However the use
+      of the user own credentials is not enforced.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/credentials.m	Sun May 23 10:51:35 2010 +0200
@@ -0,0 +1,109 @@
+classdef credentials
+  properties
+  
+    hostname = [];
+    database = [];
+    username = [];
+    password = [];
+    expiry = 0;
+    
+  end % properties
+
+  methods
+
+    % contructor
+    function obj = credentials(hostname, database, username, password)
+      switch nargin
+        case 1
+          obj.hostname = hostname;
+        case 2
+          obj.hostname = hostname;
+          obj.database = database;
+        case 3
+          obj.hostname = hostname;
+          obj.database = database;
+          obj.username = username;
+        case 4
+          obj.hostname = hostname;
+          obj.database = database;
+          obj.username = username;
+          obj.password = password;
+      end
+    end
+
+    % convert to string representation
+    function str = char(obj, mode)
+      if nargin < 2
+        mode = '';
+      end
+      switch mode
+        case 'short'
+          % do not show password
+          frm = 'mysql://%s/%s username=%s';  
+          str = sprintf(frm, obj.hostname, obj.database, obj.username);
+        case 'full'
+          % show password
+          frm = 'mysql://%s/%s username=%s password=%s';
+          str = sprintf(frm, obj.hostname, obj.database, obj.username, obj.password);
+        otherwise
+          % by default only show if a password is known              
+          passwd = [];
+          if ischar(obj.password)
+            passwd = 'YES';
+          end
+          frm = 'mysql://%s/%s username=%s password=%s';
+          str = sprintf(frm, obj.hostname, obj.database, obj.username, passwd);
+      end
+    end
+    
+    % display
+    function disp(obj)
+      disp(['    ' char(obj) char(10)]);
+    end
+    
+    % check that a credentials object contails all the required informations
+    function rv = complete(obj)
+      info = {'hostname', 'database', 'username', 'password'};
+      for kk = 1:numel(info)
+        if isempty(obj.(info{kk}))
+          rv = false;
+          return;
+        end
+      end
+      rv = true;
+    end
+    
+    % check if the credentials are expired
+    function rv = expired(obj)
+      rv = false;
+      if obj.expiry > 0 && double(time()) > obj.expiry
+        rv = true;
+      end
+    end
+    
+    % check if the credentials object matches the given informations
+    function rv = match(obj, hostname, database, username)
+      if nargin < 4
+        username = [];
+      end
+      
+      % default value
+      rv = true;
+      
+      if ~strcmp(obj.hostname, hostname)
+        rv = false;
+        return;
+      end
+      if ~strcmp(obj.database, database)
+        rv = false;
+        return;
+      end
+      if ischar(username) && ischar(obj.username) && ~strcmp(obj.username, username)
+        rv = false;
+        return;
+      end
+    end
+    
+  end % methods
+
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_ltpda_connection_manager.m	Sun May 23 10:51:35 2010 +0200
@@ -0,0 +1,111 @@
+function test_ltpda_connection_manager()
+
+  % change with valid credentials
+  host   = '127.0.0.1'; % sometimes using localhost causes problems
+  db     = 'one';
+  user   = 'daniele';
+  passwd = 'daniele';
+
+  % hack user properties
+  p = getappdata(0, 'LTPDApreferences');
+  p.cm.credentialsExpiry = 120; % seconds
+  p.cm.cachePassword = 2; % 0=no 1=yes 2=ask
+  p.cm.maxConnectionsNumber = 10;
+  setappdata(0, 'LTPDApreferences', p);
+
+
+  % reset
+  setappdata(0, 'LTPDAConnectionManager', []);
+  % connection manager
+  cm = LTPDAConnectionManager();
+
+
+  % add credentials
+  n = numel(cm.credentials);
+  cm.add(credentials(host, 'db', 'user', 'passwd'));
+  assert(numel(cm.credentials) == n+1);
+
+  % try again
+  cm.add(credentials(host, 'db', 'user', 'passwd'));
+  % just updated previous entry
+  assert(numel(cm.credentials) == n+1);
+
+  % add other credentials
+  cm.add(credentials('host', 'db'));
+  assert(numel(cm.credentials) == n+2);
+
+  % add user
+  cm.add(credentials('host', 'db', 'user'));
+  assert(numel(cm.credentials) == n+2);
+
+  % add  password
+  cm.add(credentials('host', 'db', 'user', 'passwd'));
+  assert(numel(cm.credentials) == n+2);
+
+  % add the oned will be used now on
+  cm.add(credentials(host, db, user, passwd));
+  assert(numel(cm.credentials) == n+3);
+
+
+  % use the credentials cache
+  c = cm.connect(host, db);
+  % one connection in the pool
+  assert(cm.count() == 1);
+  c.close();
+  % still there
+  assert(numel(cm.connections) == 1);
+  n = cm.count();
+  % count has the side effect of removing closed connections from the pool
+  assert(n == 0);
+
+  % open two connections
+  c1 = cm.connect(host, db);
+  c2 = cm.connect(host, db, user);
+  % one connection in the pool
+  assert(numel(cm.connections) == 2);
+  % even when we remove the closed ones
+  assert(cm.count() == 2);
+  % clean up
+  c1.close();
+  c2.close();
+  % no more connections in the pool
+  assert(cm.count() == 0);
+  assert(numel(cm.connections) == 0);
+
+  % plist parameters
+  c = cm.connect(plist('hostname', host, 'database', db));
+  % one more connection in the pool
+  assert(numel(cm.connections) == 1);
+  % clen up
+  c.close();
+
+  % create a connection
+  c1 = cm.connect(host, db);
+  n = cm.count();
+  % specify it as connction parameter
+  c2 = cm.connect(plist('connection', c1));
+  % we should get back the same connection
+  assert(c2 == c1);
+  % it should not have been added to the connection pool
+  assert(numel(cm.connections) == n);
+  % clean up
+  c1.close();
+
+  % no connections in the pool
+  assert(cm.count() == 0);
+  assert(numel(cm.connections) == 0);
+
+  % open a bunch of connections
+  c = cm.connect(host, db);
+  c = cm.connect(host, db);
+  c = cm.connect(host, db);
+  c = cm.connect(host, db);
+  c = cm.connect(host, db);
+  assert(numel(cm.connections) == 5);
+  assert(cm.count() == 5);
+  % close them all
+  cm.close();
+  % check
+  assert(cm.count() == 0);
+
+end