Mercurial > hg > ltpda
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f0afece42f48 |
---|---|
1 % SUBMIT Submits the given objects to an LTPDA repository | |
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
3 % | |
4 % DESCRIPTION: Submits the given objects to an LTPDA repository. If multiple | |
5 % objects are submitted together a corresponding collection entry will be made. | |
6 % | |
7 % If not explicitly disabled the user will be prompt for entering submission | |
8 % metadata and for chosing the database where to submit the objects. | |
9 % | |
10 % CALL: [IDS, CIDS] = submit(O1, PL) | |
11 % [IDS, CIDS] = submit(O1, O2, PL) | |
12 % | |
13 % INPUTS: O1, O2, ... - objects to be submitted | |
14 % PL - plist whih submission and repository informations | |
15 % | |
16 % OUTPUTS: IDS - IDs assigned to the submitted objects | |
17 % CID - ID of the collection entry | |
18 % | |
19 % <a href="matlab:utils.helper.displayMethodInfo('ltpda_uo', 'submit')">Parameters Description</a> | |
20 % | |
21 % METADATA: | |
22 % | |
23 % 'experiment title' - title for the submission (required >4 characters) | |
24 % 'experiment description' - description of this submission (required >10 characters) | |
25 % 'analysis description' - description of the analysis performed (required >10 characters) | |
26 % 'quantity' - the physical quantity represented by the data | |
27 % 'keywords' - comma-delimited list of keywords | |
28 % 'reference ids' - comma-delimited list object IDs | |
29 % 'additional comments' - additional comments | |
30 % 'additional authors' - additional author names | |
31 % | |
32 % VERSION: $Id: submit.m,v 1.91 2011/11/18 08:08:26 mauro Exp $ | |
33 % | |
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
35 | |
36 | |
37 % Notes on submission | |
38 % | |
39 % We can check ask the database for a list of allowed modules. This needs a | |
40 % new table in the database. Then this list is passed to validate so that | |
41 % if the 'validate' plist option (which needs to be added) is set to true, | |
42 % then we call validate on the object before submitting. If validate is | |
43 % true, then we set the validated flag in the database after submission if | |
44 % it passes. | |
45 % | |
46 % | |
47 | |
48 | |
49 function varargout = submit(varargin) | |
50 | |
51 % check if this is a call for parameters | |
52 if utils.helper.isinfocall(varargin{:}) | |
53 varargout{1} = getInfo(varargin{3}); | |
54 return | |
55 end | |
56 | |
57 import utils.const.* | |
58 utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename); | |
59 | |
60 % collect all AOs | |
61 [pls, invars, rest] = utils.helper.collect_objects(varargin(:), 'plist'); | |
62 [sinfo, invars, objs] = utils.helper.collect_objects(rest(:), 'struct'); | |
63 | |
64 % identify plists which are only used for the submission process | |
65 mask = false(numel(pls), 1); | |
66 for ii = 1:numel(pls) | |
67 if ~utils.helper.isSubmissionPlist(pls(ii)) | |
68 mask(ii) = true; | |
69 end | |
70 end | |
71 % add all plist that do not look to contain parameters for the | |
72 % submission to the list of objects submitted to the repository | |
73 if sum(mask) | |
74 objs = [objs, {pls(mask)}]; | |
75 end | |
76 % keep all the other as parameters plist | |
77 if sum(~mask) | |
78 pls = combine(pls(~mask)); | |
79 end | |
80 | |
81 % rearrange nested objects lists into a single cell array | |
82 objs = flatten(objs); | |
83 | |
84 if isempty(objs) | |
85 error('### input at least one object to submit to the repository'); | |
86 end | |
87 | |
88 % combine user plist with default | |
89 pls = fixPlist(pls); | |
90 dpl = getDefaultPlist(); | |
91 pls = combine(pls, dpl.pset('HOSTNAME', '')); | |
92 | |
93 % for backwards compatibility convert any user supplied sinfo-structure into a plist | |
94 pls = ltpda_uo.convertSinfo2Plist(pls, sinfo); | |
95 | |
96 % read XML submission informations file | |
97 filename = pls.find('sinfo filename'); | |
98 if ~isempty(filename) | |
99 try | |
100 pl = fixPlist(utils.xml.read_sinfo_xml(filename)); | |
101 pls = combine(pl, pls); | |
102 catch err | |
103 error('### unable to read specified file: %s', filename); | |
104 end | |
105 end | |
106 | |
107 % collect additional informations | |
108 sinfo = ltpda_uo.submitDialog(pls); | |
109 if isempty(sinfo) | |
110 [varargout{1}, varargout{2}] = userCanceled(); | |
111 return | |
112 end | |
113 | |
114 % check completeness of user supplied informations | |
115 sinfo = checkSinfo(sinfo); | |
116 | |
117 % user supplied connection | |
118 conn = find(pls, 'conn'); | |
119 | |
120 % obtain a connection from the connection manager | |
121 rm = LTPDARepositoryManager(); | |
122 | |
123 if isempty(conn) | |
124 connections = rm.findConnections(pls); | |
125 if isempty(connections) | |
126 % no connection found. create a new one | |
127 conn = rm.newConnection(pls); | |
128 if isempty(conn) | |
129 [varargout{1}, varargout{2}] = userCanceled(); | |
130 return | |
131 end | |
132 elseif numel(connections) == 1 && ~utils.prog.yes2true(pls.find('use selector')) | |
133 % found only one connection and we are not forcing the use of the selector | |
134 conn = connections(1); | |
135 else | |
136 % make the user select the desired database connection | |
137 conn = rm.manager.selectConnection([]); | |
138 if isempty(conn) | |
139 [varargout{1}, varargout{2}] = userCanceled(); | |
140 return | |
141 end | |
142 end | |
143 end | |
144 | |
145 % avoid to ask for a password if one was specified in the plist | |
146 if isjava(conn) && ~conn.isConnected() | |
147 passwd = pls.find('password'); | |
148 if ~isempty(passwd) | |
149 conn.setPassword(passwd); | |
150 end | |
151 end | |
152 | |
153 % connect to the database | |
154 if isempty(conn.openConnection()) | |
155 error('### unable to connect to the database') | |
156 end | |
157 | |
158 utils.helper.msg(msg.PROC1, 'submitting %d objects to repository', numel(objs)); | |
159 | |
160 try | |
161 % lock connection to avoid expire during processing | |
162 conn.setLocked(true); | |
163 | |
164 % reload connection table if we have a GUI | |
165 if ~isempty(rm.gui) | |
166 rm.gui.reloadConnectionTable(); | |
167 end | |
168 | |
169 % look-up user id | |
170 userid = utils.jmysql.getUserID(conn); | |
171 username = conn.getUsername(); | |
172 | |
173 utils.helper.msg(msg.PROC1, 'got user id %d for user: %s', userid, char(username)); | |
174 if userid < 1 || isnan(userid) || strcmp(userid, 'No Data') || ischar(userid) | |
175 error('### Unknown username.'); | |
176 end | |
177 | |
178 % author of the data: let's take the username | |
179 author = username; | |
180 | |
181 % date for the transaction.transdata and objmeta.submitted columns as UTC time string | |
182 t = time(); | |
183 tdate = format(t, 'yyyy-mm-dd HH:MM:SS', 'UTC'); | |
184 | |
185 % machine details | |
186 prov = provenance(); | |
187 | |
188 utils.helper.msg(msg.IMPORTANT, 'submitting to %s@%s:%s', ... | |
189 char(conn.getUsername), char(conn.getHostname), char(conn.getDatabase)); | |
190 | |
191 % obtain the underlying connection | |
192 c = conn.getConn(); | |
193 | |
194 % start a transaction. either we submitt all objects or we roll back all changes | |
195 c.setAutoCommit(false); | |
196 | |
197 % process each object and collect id numbers | |
198 ids = zeros(numel(objs), 1); cid = []; | |
199 for kk = 1:numel(objs) | |
200 | |
201 % this object | |
202 obj = objs{kk}; | |
203 | |
204 utils.helper.msg(msg.PROC1, 'submitting object: %s / %s', class(obj), obj.name); | |
205 | |
206 % format object creation time as UTC time string | |
207 if isa(obj, 'plist') | |
208 % plist-objects stores creatins time as milli secs since the epoch | |
209 created = time().format('yyyy-mm-dd HH:MM:SS', 'UTC'); | |
210 else | |
211 created = obj.created.format('yyyy-mm-dd HH:MM:SS', 'UTC'); | |
212 end | |
213 | |
214 % Set the UUID if it is empty. This should only happen for PLIST | |
215 % objects. | |
216 if isempty(obj.UUID) | |
217 obj.UUID = char(java.util.UUID.randomUUID); | |
218 end | |
219 | |
220 % create an XML representaion of the object | |
221 if utils.prog.yes2true(pls.find('binary')); | |
222 utils.helper.msg(msg.PROC2, 'binary submit'); | |
223 otxt = ['binary submit ' datestr(now)]; | |
224 else | |
225 utils.helper.msg(msg.PROC2, 'xml submit'); | |
226 otxt = utils.prog.obj2xml(obj); | |
227 end | |
228 | |
229 % create an MD5 hash of the xml representation | |
230 md5hash = utils.prog.hash(otxt, 'MD5'); | |
231 | |
232 % create a binary representaion of the object | |
233 bobj = utils.prog.obj2binary(obj); | |
234 if isempty(bobj) | |
235 error('### failed to obtain a binary representation'); | |
236 end | |
237 | |
238 % submit object to objs table | |
239 stmt = c.prepareStatement(... | |
240 'INSERT INTO objs (xml, hash, uuid) VALUES (?, ?, ?)'); | |
241 stmt.setObject(1, otxt); | |
242 stmt.setObject(2, char(md5hash)); | |
243 stmt.setObject(3, obj.UUID); | |
244 stmt.executeUpdate(); | |
245 | |
246 % obtain object id | |
247 rs = stmt.getGeneratedKeys(); | |
248 if rs.next() | |
249 objid = rs.getInt(1); | |
250 else | |
251 objid = []; | |
252 end | |
253 rs.close(); | |
254 stmt.close(); | |
255 | |
256 % insert binary representation | |
257 stmt = c.prepareStatement(... | |
258 'INSERT INTO bobjs (obj_id, mat) VALUES (?,?)'); | |
259 stmt.setObject(1, objid); | |
260 stmt.setObject(2, bobj); | |
261 stmt.execute(); | |
262 stmt.close(); | |
263 | |
264 % reference IDs are stored in a CSV string | |
265 if ischar(sinfo.reference_ids) | |
266 refids = sinfo.reference_ids; | |
267 else | |
268 refids = utils.prog.csv(sinfo.reference_ids); | |
269 end | |
270 | |
271 % insert object meta data | |
272 stmt = c.prepareStatement(... | |
273 [ 'INSERT INTO objmeta (obj_id, obj_type, name, created, version, ' ... | |
274 'ip, hostname, os, submitted, experiment_title, experiment_desc, ' ... | |
275 'reference_ids, additional_comments, additional_authors, keywords, ' ... | |
276 'quantity, analysis_desc, author) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)' ]); | |
277 stmt.setObject( 1, objid); | |
278 stmt.setObject( 2, java.lang.String(class(obj))); | |
279 stmt.setObject( 3, java.lang.String(obj.name)); | |
280 stmt.setObject( 4, java.lang.String(created)); | |
281 stmt.setObject( 5, java.lang.String(getappdata(0, 'ltpda_version'))); | |
282 stmt.setObject( 6, java.lang.String(prov.ip)); | |
283 stmt.setObject( 7, java.lang.String(prov.hostname)); | |
284 stmt.setObject( 8, java.lang.String(prov.os)); | |
285 stmt.setObject( 9, java.lang.String(tdate)); | |
286 stmt.setObject(10, java.lang.String(sinfo.experiment_title)); | |
287 stmt.setObject(11, java.lang.String(sinfo.experiment_description)); | |
288 stmt.setObject(12, java.lang.String(refids)); | |
289 stmt.setObject(13, java.lang.String(sinfo.additional_comments)); | |
290 stmt.setObject(14, java.lang.String(sinfo.additional_authors)); | |
291 stmt.setObject(15, java.lang.String(sinfo.keywords)); | |
292 stmt.setObject(16, java.lang.String(sinfo.quantity)); | |
293 stmt.setObject(17, java.lang.String(sinfo.analysis_description)); | |
294 stmt.setObject(18, java.lang.String(author)); | |
295 stmt.execute(); | |
296 stmt.close(); | |
297 | |
298 % update other meta-data tables | |
299 cols = utils.jmysql.execute(c, 'SHOW COLUMNS FROM tsdata'); | |
300 if utils.helper.ismember('obj_id', cols(:,1)) | |
301 % the tsdata table contains an obj id column. use the new database schema | |
302 utils.jmysql.insertObjMetadata(c, obj, objid); | |
303 else | |
304 % otherwise use the old one | |
305 utils.helper.msg(msg.PROC2, 'using back-compatibility code'); | |
306 utils.jmysql.insertObjMetadataV1(c, obj, objid); | |
307 end | |
308 | |
309 % update transactions table | |
310 stmt = c.prepareStatement(... | |
311 'INSERT INTO transactions (obj_id, user_id, transdate, direction) VALUES (?, ?, ?, ?)'); | |
312 stmt.setObject(1, objid); | |
313 stmt.setObject(2, userid); | |
314 stmt.setObject(3, java.lang.String(tdate)); | |
315 stmt.setObject(4, java.lang.String('in')); | |
316 stmt.execute(); | |
317 stmt.close(); | |
318 | |
319 % collect the id of the submitted object | |
320 ids(kk) = objid; | |
321 end | |
322 | |
323 % make collection entry | |
324 if numel(objs) > 1 | |
325 | |
326 % insert record into collections table | |
327 stmt = c.prepareStatement(... | |
328 'INSERT INTO collections (nobjs, obj_ids) VALUES (?, ?)'); | |
329 stmt.setObject(1, length(ids)); | |
330 stmt.setObject(2, java.lang.String(utils.prog.csv(ids))); | |
331 stmt.executeUpdate(); | |
332 | |
333 % obtain collection id | |
334 rs = stmt.getGeneratedKeys(); | |
335 if rs.next() | |
336 cid = rs.getInt(1); | |
337 else | |
338 cid = []; | |
339 end | |
340 rs.close(); | |
341 stmt.close(); | |
342 | |
343 end | |
344 | |
345 catch ex | |
346 utils.helper.msg(msg.IMPORTANT, 'submission error. no object submitted') | |
347 c.close() | |
348 conn.setLocked(false); | |
349 rethrow(ex) | |
350 end | |
351 | |
352 % commit the transaction | |
353 c.commit(); | |
354 | |
355 % report IDs of the inserted objects | |
356 for kk = 1:numel(objs) | |
357 utils.helper.msg(msg.IMPORTANT, 'submitted %s object with ID: %d, UUID: %s, name: %s', ... | |
358 class(objs{kk}), ids(kk), objs{kk}.UUID, objs{kk}.name); | |
359 end | |
360 if ~isempty(cid) | |
361 utils.helper.msg(msg.IMPORTANT, 'made collection entry with ID: %d', cid); | |
362 end | |
363 | |
364 % unlock connection expire | |
365 conn.setLocked(false); | |
366 | |
367 % reset timer | |
368 LTPDARepositoryManager.resetTimer(rm.timerClearPass, conn); | |
369 LTPDARepositoryManager.resetTimer(rm.timerDisconnect, conn); | |
370 | |
371 % pass back outputs | |
372 if nargout > 0 | |
373 varargout{1} = ids; | |
374 end | |
375 if nargout > 1 | |
376 varargout{2} = cid; | |
377 end | |
378 end | |
379 | |
380 | |
381 function varargout = userCanceled() | |
382 % signal that the user cancelled the submission | |
383 import utils.const.* | |
384 utils.helper.msg(msg.PROC1, 'user cancelled'); | |
385 varargout{1} = []; | |
386 varargout{2} = []; | |
387 end | |
388 | |
389 | |
390 function sinfo = checkSinfo(sinfo) | |
391 % check sinfo structure | |
392 | |
393 import utils.const.* | |
394 | |
395 % fieldnames | |
396 mainfields = {'experiment_title', 'experiment_description', 'analysis_description'}; | |
397 extrafields = {'quantity', 'keywords', 'reference_ids', 'additional_comments', 'author', 'additional_authors'}; | |
398 | |
399 % fieldnames of the input structure | |
400 fnames = fieldnames(sinfo); | |
401 | |
402 % check mandatory fields | |
403 for jj = 1:length(mainfields) | |
404 if ~ismember(fnames, mainfields{jj}) | |
405 error('### the sinfo structure should contain a ''%s'' field', mainfields{jj}); | |
406 end | |
407 end | |
408 | |
409 % check extra fields | |
410 for jj = 1:length(extrafields) | |
411 if ~ismember(fnames, extrafields{jj}) | |
412 utils.helper.msg(msg.PROC2, 'setting default for field %s', extrafields{jj}); | |
413 sinfo.(extrafields{jj}) = ''; | |
414 end | |
415 end | |
416 | |
417 % additional checks | |
418 if length(sinfo.experiment_title) < 5 | |
419 error('### ''experiment title'' should be at least 5 characters long'); | |
420 end | |
421 if length(sinfo.experiment_description) < 10 | |
422 error('### ''experiment description'' should be at least 10 characters long'); | |
423 end | |
424 if length(sinfo.analysis_description) < 10 | |
425 error('### ''analysis description'' should be at least 10 characters long'); | |
426 end | |
427 | |
428 end | |
429 | |
430 | |
431 function ii = getInfo(varargin) | |
432 if nargin == 1 && strcmpi(varargin{1}, 'None') | |
433 sets = {}; | |
434 pl = []; | |
435 else | |
436 sets = {'Default'}; | |
437 pl = getDefaultPlist(); | |
438 end | |
439 % Build info object | |
440 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); | |
441 ii.setModifier(false); | |
442 end | |
443 | |
444 | |
445 function plout = getDefaultPlist() | |
446 persistent pl; | |
447 if ~exist('pl', 'var') || isempty(pl) | |
448 pl = buildplist(); | |
449 end | |
450 plout = pl; | |
451 end | |
452 | |
453 function plo = buildplist() | |
454 | |
455 plo = plist.TO_REPOSITORY_PLIST; | |
456 | |
457 p = param({'sinfo filename', 'Path to an XML file containing submission metadata'}, paramValue.EMPTY_STRING); | |
458 plo.append(p); | |
459 | |
460 p = param({'binary', 'Submit only binary version of the objects'}, paramValue.FALSE_TRUE); | |
461 plo.append(p); | |
462 end | |
463 | |
464 | |
465 function pl = fixPlist(pl) | |
466 % accept parameters where words are separated either by spaces or underscore | |
467 if ~isempty(pl) | |
468 for ii = 1:pl.nparams | |
469 pl.params(ii).setKey(strrep(pl.params(ii).key, '_', ' ')); | |
470 end | |
471 end | |
472 end | |
473 | |
474 | |
475 function flat = flatten(objs) | |
476 % flatten nested lists into a single cell array | |
477 | |
478 flat = {}; | |
479 | |
480 while iscell(objs) && numel(objs) == 1 | |
481 objs = objs{1}; | |
482 end | |
483 | |
484 if numel(objs) == 1 | |
485 flat = {objs}; | |
486 return; | |
487 end | |
488 | |
489 for jj = 1:numel(objs) | |
490 obj = flatten(objs(jj)); | |
491 for kk = 1:numel(obj) | |
492 flat = [ flat obj(kk) ]; | |
493 end | |
494 end | |
495 | |
496 end |