Mercurial > hg > ltpda
diff m-toolbox/classes/@ltpda_uo/submit.m @ 0:f0afece42f48
Import.
author | Daniele Nicolodi <nicolodi@science.unitn.it> |
---|---|
date | Wed, 23 Nov 2011 19:22:13 +0100 |
parents | |
children | d58813ab1b92 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/m-toolbox/classes/@ltpda_uo/submit.m Wed Nov 23 19:22:13 2011 +0100 @@ -0,0 +1,496 @@ +% SUBMIT Submits the given objects to an LTPDA repository +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% DESCRIPTION: Submits the given objects to an LTPDA repository. If multiple +% objects are submitted together a corresponding collection entry will be made. +% +% If not explicitly disabled the user will be prompt for entering submission +% metadata and for chosing the database where to submit the objects. +% +% CALL: [IDS, CIDS] = submit(O1, PL) +% [IDS, CIDS] = submit(O1, O2, PL) +% +% INPUTS: O1, O2, ... - objects to be submitted +% PL - plist whih submission and repository informations +% +% OUTPUTS: IDS - IDs assigned to the submitted objects +% CID - ID of the collection entry +% +% <a href="matlab:utils.helper.displayMethodInfo('ltpda_uo', 'submit')">Parameters Description</a> +% +% METADATA: +% +% 'experiment title' - title for the submission (required >4 characters) +% 'experiment description' - description of this submission (required >10 characters) +% 'analysis description' - description of the analysis performed (required >10 characters) +% 'quantity' - the physical quantity represented by the data +% 'keywords' - comma-delimited list of keywords +% 'reference ids' - comma-delimited list object IDs +% 'additional comments' - additional comments +% 'additional authors' - additional author names +% +% VERSION: $Id: submit.m,v 1.91 2011/11/18 08:08:26 mauro Exp $ +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +% Notes on submission +% +% We can check ask the database for a list of allowed modules. This needs a +% new table in the database. Then this list is passed to validate so that +% if the 'validate' plist option (which needs to be added) is set to true, +% then we call validate on the object before submitting. If validate is +% true, then we set the validated flag in the database after submission if +% it passes. +% +% + + +function varargout = submit(varargin) + + % check if this is a call for parameters + if utils.helper.isinfocall(varargin{:}) + varargout{1} = getInfo(varargin{3}); + return + end + + import utils.const.* + utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename); + + % collect all AOs + [pls, invars, rest] = utils.helper.collect_objects(varargin(:), 'plist'); + [sinfo, invars, objs] = utils.helper.collect_objects(rest(:), 'struct'); + + % identify plists which are only used for the submission process + mask = false(numel(pls), 1); + for ii = 1:numel(pls) + if ~utils.helper.isSubmissionPlist(pls(ii)) + mask(ii) = true; + end + end + % add all plist that do not look to contain parameters for the + % submission to the list of objects submitted to the repository + if sum(mask) + objs = [objs, {pls(mask)}]; + end + % keep all the other as parameters plist + if sum(~mask) + pls = combine(pls(~mask)); + end + + % rearrange nested objects lists into a single cell array + objs = flatten(objs); + + if isempty(objs) + error('### input at least one object to submit to the repository'); + end + + % combine user plist with default + pls = fixPlist(pls); + dpl = getDefaultPlist(); + pls = combine(pls, dpl.pset('HOSTNAME', '')); + + % for backwards compatibility convert any user supplied sinfo-structure into a plist + pls = ltpda_uo.convertSinfo2Plist(pls, sinfo); + + % read XML submission informations file + filename = pls.find('sinfo filename'); + if ~isempty(filename) + try + pl = fixPlist(utils.xml.read_sinfo_xml(filename)); + pls = combine(pl, pls); + catch err + error('### unable to read specified file: %s', filename); + end + end + + % collect additional informations + sinfo = ltpda_uo.submitDialog(pls); + if isempty(sinfo) + [varargout{1}, varargout{2}] = userCanceled(); + return + end + + % check completeness of user supplied informations + sinfo = checkSinfo(sinfo); + + % user supplied connection + conn = find(pls, 'conn'); + + % obtain a connection from the connection manager + rm = LTPDARepositoryManager(); + + if isempty(conn) + connections = rm.findConnections(pls); + if isempty(connections) + % no connection found. create a new one + conn = rm.newConnection(pls); + if isempty(conn) + [varargout{1}, varargout{2}] = userCanceled(); + return + end + elseif numel(connections) == 1 && ~utils.prog.yes2true(pls.find('use selector')) + % found only one connection and we are not forcing the use of the selector + conn = connections(1); + else + % make the user select the desired database connection + conn = rm.manager.selectConnection([]); + if isempty(conn) + [varargout{1}, varargout{2}] = userCanceled(); + return + end + end + end + + % avoid to ask for a password if one was specified in the plist + if isjava(conn) && ~conn.isConnected() + passwd = pls.find('password'); + if ~isempty(passwd) + conn.setPassword(passwd); + end + end + + % connect to the database + if isempty(conn.openConnection()) + error('### unable to connect to the database') + end + + utils.helper.msg(msg.PROC1, 'submitting %d objects to repository', numel(objs)); + + try + % lock connection to avoid expire during processing + conn.setLocked(true); + + % reload connection table if we have a GUI + if ~isempty(rm.gui) + rm.gui.reloadConnectionTable(); + end + + % look-up user id + userid = utils.jmysql.getUserID(conn); + username = conn.getUsername(); + + utils.helper.msg(msg.PROC1, 'got user id %d for user: %s', userid, char(username)); + if userid < 1 || isnan(userid) || strcmp(userid, 'No Data') || ischar(userid) + error('### Unknown username.'); + end + + % author of the data: let's take the username + author = username; + + % date for the transaction.transdata and objmeta.submitted columns as UTC time string + t = time(); + tdate = format(t, 'yyyy-mm-dd HH:MM:SS', 'UTC'); + + % machine details + prov = provenance(); + + utils.helper.msg(msg.IMPORTANT, 'submitting to %s@%s:%s', ... + char(conn.getUsername), char(conn.getHostname), char(conn.getDatabase)); + + % obtain the underlying connection + c = conn.getConn(); + + % start a transaction. either we submitt all objects or we roll back all changes + c.setAutoCommit(false); + + % process each object and collect id numbers + ids = zeros(numel(objs), 1); cid = []; + for kk = 1:numel(objs) + + % this object + obj = objs{kk}; + + utils.helper.msg(msg.PROC1, 'submitting object: %s / %s', class(obj), obj.name); + + % format object creation time as UTC time string + if isa(obj, 'plist') + % plist-objects stores creatins time as milli secs since the epoch + created = time().format('yyyy-mm-dd HH:MM:SS', 'UTC'); + else + created = obj.created.format('yyyy-mm-dd HH:MM:SS', 'UTC'); + end + + % Set the UUID if it is empty. This should only happen for PLIST + % objects. + if isempty(obj.UUID) + obj.UUID = char(java.util.UUID.randomUUID); + end + + % create an XML representaion of the object + if utils.prog.yes2true(pls.find('binary')); + utils.helper.msg(msg.PROC2, 'binary submit'); + otxt = ['binary submit ' datestr(now)]; + else + utils.helper.msg(msg.PROC2, 'xml submit'); + otxt = utils.prog.obj2xml(obj); + end + + % create an MD5 hash of the xml representation + md5hash = utils.prog.hash(otxt, 'MD5'); + + % create a binary representaion of the object + bobj = utils.prog.obj2binary(obj); + if isempty(bobj) + error('### failed to obtain a binary representation'); + end + + % submit object to objs table + stmt = c.prepareStatement(... + 'INSERT INTO objs (xml, hash, uuid) VALUES (?, ?, ?)'); + stmt.setObject(1, otxt); + stmt.setObject(2, char(md5hash)); + stmt.setObject(3, obj.UUID); + stmt.executeUpdate(); + + % obtain object id + rs = stmt.getGeneratedKeys(); + if rs.next() + objid = rs.getInt(1); + else + objid = []; + end + rs.close(); + stmt.close(); + + % insert binary representation + stmt = c.prepareStatement(... + 'INSERT INTO bobjs (obj_id, mat) VALUES (?,?)'); + stmt.setObject(1, objid); + stmt.setObject(2, bobj); + stmt.execute(); + stmt.close(); + + % reference IDs are stored in a CSV string + if ischar(sinfo.reference_ids) + refids = sinfo.reference_ids; + else + refids = utils.prog.csv(sinfo.reference_ids); + end + + % insert object meta data + stmt = c.prepareStatement(... + [ 'INSERT INTO objmeta (obj_id, obj_type, name, created, version, ' ... + 'ip, hostname, os, submitted, experiment_title, experiment_desc, ' ... + 'reference_ids, additional_comments, additional_authors, keywords, ' ... + 'quantity, analysis_desc, author) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' ]); + stmt.setObject( 1, objid); + stmt.setObject( 2, java.lang.String(class(obj))); + stmt.setObject( 3, java.lang.String(obj.name)); + stmt.setObject( 4, java.lang.String(created)); + stmt.setObject( 5, java.lang.String(getappdata(0, 'ltpda_version'))); + stmt.setObject( 6, java.lang.String(prov.ip)); + stmt.setObject( 7, java.lang.String(prov.hostname)); + stmt.setObject( 8, java.lang.String(prov.os)); + stmt.setObject( 9, java.lang.String(tdate)); + stmt.setObject(10, java.lang.String(sinfo.experiment_title)); + stmt.setObject(11, java.lang.String(sinfo.experiment_description)); + stmt.setObject(12, java.lang.String(refids)); + stmt.setObject(13, java.lang.String(sinfo.additional_comments)); + stmt.setObject(14, java.lang.String(sinfo.additional_authors)); + stmt.setObject(15, java.lang.String(sinfo.keywords)); + stmt.setObject(16, java.lang.String(sinfo.quantity)); + stmt.setObject(17, java.lang.String(sinfo.analysis_description)); + stmt.setObject(18, java.lang.String(author)); + stmt.execute(); + stmt.close(); + + % update other meta-data tables + cols = utils.jmysql.execute(c, 'SHOW COLUMNS FROM tsdata'); + if utils.helper.ismember('obj_id', cols(:,1)) + % the tsdata table contains an obj id column. use the new database schema + utils.jmysql.insertObjMetadata(c, obj, objid); + else + % otherwise use the old one + utils.helper.msg(msg.PROC2, 'using back-compatibility code'); + utils.jmysql.insertObjMetadataV1(c, obj, objid); + end + + % update transactions table + stmt = c.prepareStatement(... + 'INSERT INTO transactions (obj_id, user_id, transdate, direction) VALUES (?, ?, ?, ?)'); + stmt.setObject(1, objid); + stmt.setObject(2, userid); + stmt.setObject(3, java.lang.String(tdate)); + stmt.setObject(4, java.lang.String('in')); + stmt.execute(); + stmt.close(); + + % collect the id of the submitted object + ids(kk) = objid; + end + + % make collection entry + if numel(objs) > 1 + + % insert record into collections table + stmt = c.prepareStatement(... + 'INSERT INTO collections (nobjs, obj_ids) VALUES (?, ?)'); + stmt.setObject(1, length(ids)); + stmt.setObject(2, java.lang.String(utils.prog.csv(ids))); + stmt.executeUpdate(); + + % obtain collection id + rs = stmt.getGeneratedKeys(); + if rs.next() + cid = rs.getInt(1); + else + cid = []; + end + rs.close(); + stmt.close(); + + end + + catch ex + utils.helper.msg(msg.IMPORTANT, 'submission error. no object submitted') + c.close() + conn.setLocked(false); + rethrow(ex) + end + + % commit the transaction + c.commit(); + + % report IDs of the inserted objects + for kk = 1:numel(objs) + utils.helper.msg(msg.IMPORTANT, 'submitted %s object with ID: %d, UUID: %s, name: %s', ... + class(objs{kk}), ids(kk), objs{kk}.UUID, objs{kk}.name); + end + if ~isempty(cid) + utils.helper.msg(msg.IMPORTANT, 'made collection entry with ID: %d', cid); + end + + % unlock connection expire + conn.setLocked(false); + + % reset timer + LTPDARepositoryManager.resetTimer(rm.timerClearPass, conn); + LTPDARepositoryManager.resetTimer(rm.timerDisconnect, conn); + + % pass back outputs + if nargout > 0 + varargout{1} = ids; + end + if nargout > 1 + varargout{2} = cid; + end +end + + +function varargout = userCanceled() + % signal that the user cancelled the submission + import utils.const.* + utils.helper.msg(msg.PROC1, 'user cancelled'); + varargout{1} = []; + varargout{2} = []; +end + + +function sinfo = checkSinfo(sinfo) + % check sinfo structure + + import utils.const.* + + % fieldnames + mainfields = {'experiment_title', 'experiment_description', 'analysis_description'}; + extrafields = {'quantity', 'keywords', 'reference_ids', 'additional_comments', 'author', 'additional_authors'}; + + % fieldnames of the input structure + fnames = fieldnames(sinfo); + + % check mandatory fields + for jj = 1:length(mainfields) + if ~ismember(fnames, mainfields{jj}) + error('### the sinfo structure should contain a ''%s'' field', mainfields{jj}); + end + end + + % check extra fields + for jj = 1:length(extrafields) + if ~ismember(fnames, extrafields{jj}) + utils.helper.msg(msg.PROC2, 'setting default for field %s', extrafields{jj}); + sinfo.(extrafields{jj}) = ''; + end + end + + % additional checks + if length(sinfo.experiment_title) < 5 + error('### ''experiment title'' should be at least 5 characters long'); + end + if length(sinfo.experiment_description) < 10 + error('### ''experiment description'' should be at least 10 characters long'); + end + if length(sinfo.analysis_description) < 10 + error('### ''analysis description'' should be at least 10 characters long'); + end + +end + + +function ii = getInfo(varargin) + if nargin == 1 && strcmpi(varargin{1}, 'None') + sets = {}; + pl = []; + else + sets = {'Default'}; + pl = getDefaultPlist(); + end + % Build info object + ii = minfo(mfilename, 'ltpda_uo', 'ltpda', utils.const.categories.internal, '$Id: submit.m,v 1.91 2011/11/18 08:08:26 mauro Exp $', sets, pl); + ii.setModifier(false); +end + + +function plout = getDefaultPlist() + persistent pl; + if ~exist('pl', 'var') || isempty(pl) + pl = buildplist(); + end + plout = pl; +end + +function plo = buildplist() + + plo = plist.TO_REPOSITORY_PLIST; + + p = param({'sinfo filename', 'Path to an XML file containing submission metadata'}, paramValue.EMPTY_STRING); + plo.append(p); + + p = param({'binary', 'Submit only binary version of the objects'}, paramValue.FALSE_TRUE); + plo.append(p); +end + + +function pl = fixPlist(pl) + % accept parameters where words are separated either by spaces or underscore + if ~isempty(pl) + for ii = 1:pl.nparams + pl.params(ii).setKey(strrep(pl.params(ii).key, '_', ' ')); + end + end +end + + +function flat = flatten(objs) + % flatten nested lists into a single cell array + + flat = {}; + + while iscell(objs) && numel(objs) == 1 + objs = objs{1}; + end + + if numel(objs) == 1 + flat = {objs}; + return; + end + + for jj = 1:numel(objs) + obj = flatten(objs(jj)); + for kk = 1:numel(obj) + flat = [ flat obj(kk) ]; + end + end + +end