1 | /** |
---|
2 | * SimpleWizardController Controler |
---|
3 | * |
---|
4 | * Description of my controller |
---|
5 | * |
---|
6 | * @author your email (+name?) |
---|
7 | * @since 2010mmdd |
---|
8 | * @package ??? |
---|
9 | * |
---|
10 | * Revision information: |
---|
11 | * $Rev: 1430 $ |
---|
12 | * $Author: work@osx.eu $ |
---|
13 | * $Date: 2011-01-21 21:05:36 +0100 (Fri, 21 Jan 2011) $ |
---|
14 | */ |
---|
15 | package dbnp.studycapturing |
---|
16 | |
---|
17 | import org.dbnp.gdt.* |
---|
18 | import grails.plugins.springsecurity.Secured |
---|
19 | import dbnp.authentication.SecUser |
---|
20 | import dbnp.importer.ImportCell |
---|
21 | import dbnp.importer.ImportRecord |
---|
22 | import dbnp.importer.MappingColumn |
---|
23 | |
---|
24 | @Secured(['IS_AUTHENTICATED_REMEMBERED']) |
---|
25 | class SimpleWizardController extends StudyWizardController { |
---|
26 | def authenticationService |
---|
27 | def fileService |
---|
28 | def importerService |
---|
29 | def gdtService |
---|
30 | |
---|
31 | /** |
---|
32 | * index closure |
---|
33 | */ |
---|
34 | def index = { |
---|
35 | redirect( action: "study" ); |
---|
36 | } |
---|
37 | |
---|
38 | /** |
---|
39 | * Shows the study page |
---|
40 | */ |
---|
41 | def study = { |
---|
42 | // Retrieve the correct study |
---|
43 | Study study = getStudyInWizard( params ); |
---|
44 | |
---|
45 | // If no study is found in the wizard, this is the entry page of the |
---|
46 | // wizard. Retrieve the study from request parameters |
---|
47 | if( !study ) { |
---|
48 | study = getStudyFromRequest( params ); |
---|
49 | |
---|
50 | // Some error might have occurred during the retrieval of the study |
---|
51 | if( !study ) { |
---|
52 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
53 | return; |
---|
54 | } |
---|
55 | |
---|
56 | session.simpleWizard = [ study: study ] |
---|
57 | } |
---|
58 | |
---|
59 | def event = getEvent(params); |
---|
60 | |
---|
61 | // If any event on this page is triggered, we should save the entered data. |
---|
62 | // If no event is triggered, the user gets here from another page. In that case, |
---|
63 | // we don't set the values |
---|
64 | if( event ) { |
---|
65 | // Only continue to the next or previous page if the information entered is correct |
---|
66 | if( handleStudy( study, params ) ) { |
---|
67 | // Now determine what action to perform |
---|
68 | // If the user clicks next, the study should be validated |
---|
69 | if( event == "next" && validateObject( study ) ) { |
---|
70 | toPage( "samples" ); |
---|
71 | return; |
---|
72 | } |
---|
73 | } |
---|
74 | } |
---|
75 | |
---|
76 | // Give the study to the user |
---|
77 | [ study: study ] |
---|
78 | } |
---|
79 | |
---|
80 | /** |
---|
81 | * Shows the samples page |
---|
82 | */ |
---|
83 | def samples = { |
---|
84 | // Retrieve the correct study |
---|
85 | study = getStudyInWizard( params ); |
---|
86 | if( !study ) { |
---|
87 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
88 | return; |
---|
89 | } |
---|
90 | |
---|
91 | def event = getEvent(params); |
---|
92 | |
---|
93 | // If any event on this page is triggered, we should save the entered data. |
---|
94 | // If no event is triggered, the user gets here from another page. In that case, |
---|
95 | // we don't set the values |
---|
96 | if( event ) { |
---|
97 | // Now determine what action to perform |
---|
98 | if( event == "next" && handleSamples( study, params )) { |
---|
99 | // Only continue to the next page if the information entered is correct |
---|
100 | toPage( "columns" ); |
---|
101 | return; |
---|
102 | } else if( event == "previous" ) { |
---|
103 | // The user may go to the previous page, even if none of the data entered is OK. |
---|
104 | toPage( "study" ); |
---|
105 | return; |
---|
106 | } else if( event == "skip" ) { |
---|
107 | // The user may skip the complete samples page |
---|
108 | toPage( "assays" ); |
---|
109 | return; |
---|
110 | } |
---|
111 | } |
---|
112 | |
---|
113 | // Give the study and other data to the user |
---|
114 | [ study: study, sampleTemplates: Template.findAllByEntity( Sample.class ), encodedEntity: gdtService.encryptEntity( Sample.class.name ), sampleForm: session.simpleWizard.sampleForm ] |
---|
115 | } |
---|
116 | |
---|
117 | /** |
---|
118 | * Shows the columns page |
---|
119 | */ |
---|
120 | def columns = { |
---|
121 | // Retrieve the correct study |
---|
122 | study = getStudyInWizard( params ); |
---|
123 | if( !study ) { |
---|
124 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
125 | return; |
---|
126 | } |
---|
127 | |
---|
128 | def event = getEvent(params); |
---|
129 | |
---|
130 | // If any event on this page is triggered, we should save the entered data. |
---|
131 | // If no event is triggered, the user gets here from another page. In that case, |
---|
132 | // we don't set the values |
---|
133 | if( event ) { |
---|
134 | // Now determine what action to perform |
---|
135 | if( event == "next" && handleColumns( study, params ) ) { |
---|
136 | // Only continue to the next page if the information entered is correct |
---|
137 | if( session.simpleWizard.imported.numInvalidEntities > 0 ) { |
---|
138 | toPage( "missingFields" ); |
---|
139 | } else { |
---|
140 | // The import of the excel file has finished. Now delete the excelfile |
---|
141 | if( session.simpleWizard.sampleForm.importFile ) |
---|
142 | fileService.delete( session.simpleWizard.sampleForm.importFile ); |
---|
143 | |
---|
144 | toPage( "assays" ); |
---|
145 | } |
---|
146 | return; |
---|
147 | } else if( event == "previous" ) { |
---|
148 | // THe user may go to the previous page, even if the data is not correct |
---|
149 | toPage( "samples" ); |
---|
150 | return; |
---|
151 | } |
---|
152 | } |
---|
153 | |
---|
154 | // Give the study and other data to the user |
---|
155 | [ study: study, |
---|
156 | filename: session.simpleWizard.sampleForm.importFile, |
---|
157 | template: Template.get( session.simpleWizard.sampleForm.templateId ), |
---|
158 | excel: session.simpleWizard.excel] |
---|
159 | } |
---|
160 | |
---|
161 | /** |
---|
162 | * Shows the page where missing fields can be filled in |
---|
163 | */ |
---|
164 | def missingFields = { |
---|
165 | // Retrieve the correct study |
---|
166 | study = getStudyInWizard( params ); |
---|
167 | if( !study ) { |
---|
168 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
169 | return; |
---|
170 | } |
---|
171 | |
---|
172 | def event = getEvent(params); |
---|
173 | |
---|
174 | // If any event on this page is triggered, we should save the entered data. |
---|
175 | // If no event is triggered, the user gets here from another page. In that case, |
---|
176 | // we don't set the values |
---|
177 | if( event ) { |
---|
178 | // Now determine what action to perform |
---|
179 | if( event == "next" && handleMissingFields( study, params ) ) { |
---|
180 | if( session.simpleWizard.imported.numInvalidEntities == 0 ) { |
---|
181 | // Only continue to the next page if the information entered is correct |
---|
182 | |
---|
183 | // The import of the excel file has finished. Now delete the excelfile |
---|
184 | if( session.simpleWizard.sampleForm.importFile ) |
---|
185 | fileService.delete( session.simpleWizard.sampleForm.importFile ); |
---|
186 | |
---|
187 | toPage( "assays" ); |
---|
188 | return; |
---|
189 | } |
---|
190 | } else if( event == "previous" ) { |
---|
191 | // THe user may go to the previous page, even if the data is not correct |
---|
192 | toPage( "columns" ); |
---|
193 | return; |
---|
194 | } |
---|
195 | } |
---|
196 | |
---|
197 | // If any errors have occurred during validation, show them to the user. However, |
---|
198 | // the same error might have occurred for multiple entities. For that reason, |
---|
199 | // we only show unique errors |
---|
200 | def rules |
---|
201 | if( session.simpleWizard.imported.errors ) { |
---|
202 | rules = session.simpleWizard.imported.errors*.values().flatten().unique().join( "<br /> \n" ); |
---|
203 | } |
---|
204 | |
---|
205 | // Give the study and other data to the user |
---|
206 | [ study: study, imported: session.simpleWizard.imported, rules: rules ] |
---|
207 | } |
---|
208 | |
---|
209 | /** |
---|
210 | * Shows the assay page |
---|
211 | */ |
---|
212 | def assays = { |
---|
213 | // Retrieve the correct study |
---|
214 | Study study = getStudyInWizard( params ); |
---|
215 | if( !study ) { |
---|
216 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
217 | return; |
---|
218 | } |
---|
219 | |
---|
220 | Assay assay |
---|
221 | if( study.assays?.size() ) { |
---|
222 | assay = study.assays[0]; |
---|
223 | study.removeFromAssays( assay ); |
---|
224 | } else { |
---|
225 | assay = new Assay(); |
---|
226 | } |
---|
227 | |
---|
228 | def event = getEvent(params); |
---|
229 | |
---|
230 | // If any event on this page is triggered, we should save the entered data. |
---|
231 | // If no event is triggered, the user gets here from another page. In that case, |
---|
232 | // we don't set the values |
---|
233 | if( event ) { |
---|
234 | // Only continue to the next or previous page if the information entered is correct |
---|
235 | if( event == "skip" ) { |
---|
236 | // The user may skip the complete assay page |
---|
237 | toPage( "overview" ); |
---|
238 | return; |
---|
239 | } else if( handleAssays( assay, params ) ) { |
---|
240 | study.addToAssays( assay ); |
---|
241 | |
---|
242 | // Now determine what action to perform |
---|
243 | if( event == "next" && validateObject( study ) ) { |
---|
244 | toPage( "overview" ); |
---|
245 | return; |
---|
246 | } else if( event == "previous" ) { |
---|
247 | toPage( "samples" ) |
---|
248 | return; |
---|
249 | } |
---|
250 | } |
---|
251 | } |
---|
252 | |
---|
253 | // Give the study to the user |
---|
254 | [ study: study, assay: assay ] |
---|
255 | } |
---|
256 | |
---|
257 | /** |
---|
258 | * Shows an overview of the entered study |
---|
259 | */ |
---|
260 | def overview = { |
---|
261 | // Retrieve the correct study |
---|
262 | Study study = getStudyInWizard( params ); |
---|
263 | if( !study ) { |
---|
264 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
265 | return; |
---|
266 | } |
---|
267 | |
---|
268 | def event = getEvent(params); |
---|
269 | |
---|
270 | // If any event on this page is triggered, we should save the entered data. |
---|
271 | // If no event is triggered, the user gets here from another page. In that case, |
---|
272 | // we don't set the values |
---|
273 | if( event ) { |
---|
274 | // Now determine what action to perform |
---|
275 | if( event == "save" ) { |
---|
276 | toPage( "save" ); |
---|
277 | return; |
---|
278 | } else if( event == "previous" ) { |
---|
279 | toPage( "assay" ) |
---|
280 | return; |
---|
281 | } |
---|
282 | } |
---|
283 | |
---|
284 | // Give the study to the user |
---|
285 | [ study: study ] |
---|
286 | } |
---|
287 | |
---|
288 | def save = { |
---|
289 | // Retrieve the correct study |
---|
290 | Study study = getStudyInWizard( params ); |
---|
291 | if( !study ) { |
---|
292 | redirect( controller: 'simpleWizard', action: 'study' ); |
---|
293 | return; |
---|
294 | } |
---|
295 | |
---|
296 | // Make sure all samples are attached to all assays |
---|
297 | study.assays.each { assay -> |
---|
298 | assay.samples?.clear(); |
---|
299 | study.samples.each { sample -> |
---|
300 | assay.addToSamples( sample ) |
---|
301 | } |
---|
302 | } |
---|
303 | |
---|
304 | // Save the study |
---|
305 | if( study.save( flush: true ) ) { |
---|
306 | // Clear session |
---|
307 | session.simpleWizard = null; |
---|
308 | |
---|
309 | flash.message = "Your study is succesfully saved."; |
---|
310 | } else { |
---|
311 | flash.error = "An error occurred while saving your study"; |
---|
312 | //validateObject( study ); |
---|
313 | } |
---|
314 | |
---|
315 | // Give the study to the user |
---|
316 | [ study: study ] |
---|
317 | } |
---|
318 | |
---|
319 | /** |
---|
320 | * Handles study input |
---|
321 | * @param study Study to update |
---|
322 | * @param params Request parameter map |
---|
323 | * @return True if everything went OK, false otherwise. An error message is put in flash.error |
---|
324 | */ |
---|
325 | def handleStudy( study, params ) { |
---|
326 | // did the study template change? |
---|
327 | if (params.get('template') && study.template?.name != params.get('template')) { |
---|
328 | // set the template |
---|
329 | study.template = Template.findByName(params.remove('template')) |
---|
330 | } |
---|
331 | |
---|
332 | // does the study have a template set? |
---|
333 | if (study.template && study.template instanceof Template) { |
---|
334 | // yes, iterate through template fields |
---|
335 | study.giveFields().each() { |
---|
336 | // and set their values |
---|
337 | study.setFieldValue(it.name, params.get(it.escapedName())) |
---|
338 | } |
---|
339 | } |
---|
340 | |
---|
341 | // handle public checkbox |
---|
342 | if (params.get("publicstudy")) { |
---|
343 | study.publicstudy = params.get("publicstudy") |
---|
344 | } |
---|
345 | |
---|
346 | // handle publications |
---|
347 | handleStudyPublications(study, params) |
---|
348 | |
---|
349 | // handle contacts |
---|
350 | handleStudyContacts(study, params) |
---|
351 | |
---|
352 | // handle users (readers, writers) |
---|
353 | handleStudyUsers(study, params, 'readers') |
---|
354 | handleStudyUsers(study, params, 'writers') |
---|
355 | |
---|
356 | return true |
---|
357 | } |
---|
358 | |
---|
359 | /** |
---|
360 | * Handles the upload of sample data |
---|
361 | * @param study Study to update |
---|
362 | * @param params Request parameter map |
---|
363 | * @return True if everything went OK, false otherwise. An error message is put in flash.error |
---|
364 | */ |
---|
365 | def handleSamples( study, params ) { |
---|
366 | def filename = params.get( 'importfile' ); |
---|
367 | |
---|
368 | // Handle 'existing*' in front of the filename. This is put in front to make a distinction between |
---|
369 | // an already uploaded file test.txt (maybe moved to some other directory) and a newly uploaded file test.txt |
---|
370 | // still being in the temporary directory. |
---|
371 | // This import step doesn't have to make that distinction, since all files remain in the temporary directory. |
---|
372 | if( filename[0..8] == 'existing*' ) |
---|
373 | filename = filename[9..-1] |
---|
374 | |
---|
375 | def templateId = params.long( 'template_id' ) |
---|
376 | int sheetIndex = (params.int( 'sheetindex' ) ?: 1 ) |
---|
377 | int dataMatrixStart = (params.int( 'datamatrix_start' ) ?: 2 ) |
---|
378 | int headerRow = (params.int( 'headerrow' ) ?: 1 ) |
---|
379 | |
---|
380 | // Save form data in session |
---|
381 | session.simpleWizard.sampleForm = [ |
---|
382 | importFile: filename, |
---|
383 | templateId: templateId, |
---|
384 | sheetIndex: sheetIndex, |
---|
385 | dataMatrixStart: dataMatrixStart, |
---|
386 | headerRow: headerRow |
---|
387 | ]; |
---|
388 | |
---|
389 | // Check whether the template exists |
---|
390 | if (!templateId || !Template.get( templateId ) ){ |
---|
391 | log.error ".simple study wizard not all fields are filled in" |
---|
392 | flash.error = "No template was chosen. Please choose a template for the samples you provided." |
---|
393 | return false |
---|
394 | } |
---|
395 | |
---|
396 | def importedfile = fileService.get( filename ) |
---|
397 | def workbook |
---|
398 | if (importedfile.exists()) { |
---|
399 | try { |
---|
400 | workbook = importerService.getWorkbook(new FileInputStream(importedfile)) |
---|
401 | } catch (Exception e) { |
---|
402 | log.error ".simple study wizard could not load file: " + e |
---|
403 | flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples."; |
---|
404 | return false |
---|
405 | } |
---|
406 | } else { |
---|
407 | log.error ".simple study wizard no file given"; |
---|
408 | flash.error = "No file was given. Please provide an excel file for entering samples."; |
---|
409 | return false; |
---|
410 | } |
---|
411 | |
---|
412 | if( !workbook ) { |
---|
413 | log.error ".simple study wizard could not load file into a workbook" |
---|
414 | flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples."; |
---|
415 | return false |
---|
416 | } |
---|
417 | |
---|
418 | def selectedentities = [] |
---|
419 | |
---|
420 | if( !excelChecks( workbook, sheetIndex, headerRow, dataMatrixStart ) ) |
---|
421 | return false; |
---|
422 | |
---|
423 | // Get the header from the Excel file using the arguments given in the first step of the wizard |
---|
424 | def importerHeader; |
---|
425 | def importerDataMatrix; |
---|
426 | |
---|
427 | try { |
---|
428 | importerHeader = importerService.getHeader(workbook, |
---|
429 | sheetIndex - 1, // 0 == first sheet |
---|
430 | headerRow, // 1 == first row :s |
---|
431 | dataMatrixStart - 1, // 0 == first row |
---|
432 | Sample.class) |
---|
433 | |
---|
434 | importerDataMatrix = importerService.getDatamatrix( |
---|
435 | workbook, |
---|
436 | importerHeader, |
---|
437 | sheetIndex - 1, // 0 == first sheet |
---|
438 | dataMatrixStart - 1, // 0 == first row |
---|
439 | 5) |
---|
440 | } catch( Exception e ) { |
---|
441 | // An error occurred while reading the excel file. |
---|
442 | log.error ".simple study wizard error while reading the excel file"; |
---|
443 | e.printStackTrace(); |
---|
444 | |
---|
445 | // Show a message to the user |
---|
446 | flash.error = "An error occurred while reading the excel file. Have you provided the right sheet number and row numbers. Contact your system administrator if this problem persists."; |
---|
447 | return false; |
---|
448 | } |
---|
449 | |
---|
450 | // Save read excel data into session |
---|
451 | session.simpleWizard.excel = [ |
---|
452 | workbook: workbook, |
---|
453 | sheetIndex: sheetIndex, |
---|
454 | dataMatrixStart: dataMatrixStart, |
---|
455 | headerRow: headerRow, |
---|
456 | data: [ |
---|
457 | header: importerHeader, |
---|
458 | dataMatrix: importerDataMatrix |
---|
459 | ] |
---|
460 | ] |
---|
461 | |
---|
462 | return true |
---|
463 | } |
---|
464 | |
---|
465 | /** |
---|
466 | * Handles the matching of template fields with excel columns by the user |
---|
467 | * @param study Study to update |
---|
468 | * @param params Request parameter map |
---|
469 | * @return True if everything went OK, false otherwise. An error message is put in flash.error |
---|
470 | * The field session.simpleWizard.imported.numInvalidEntities reflects the number of |
---|
471 | * entities that have errors, and should be fixed before saving. The errors for those entities |
---|
472 | * are saved into session.simpleWizard.imported.errors |
---|
473 | */ |
---|
474 | def handleColumns( study, params ) { |
---|
475 | // Find actual Template object from the chosen template name |
---|
476 | def template = Template.get(session.simpleWizard.sampleForm.templateId) |
---|
477 | def headers = session.simpleWizard.excel.data.header; |
---|
478 | |
---|
479 | if( !params.matches ) { |
---|
480 | log.error( ".simple study wizard no column matches given" ); |
---|
481 | flash.error = "No column matches given"; |
---|
482 | return false; |
---|
483 | } |
---|
484 | |
---|
485 | // Retrieve the chosen matches from the request parameters and put them into |
---|
486 | // the headers-structure, for later reference |
---|
487 | params.matches.index.each { columnindex, property -> |
---|
488 | // Create an actual class instance of the selected entity with the selected template |
---|
489 | // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities |
---|
490 | def entityClass = Class.forName( headers[columnindex.toInteger()].entityclass.getName(), true, this.getClass().getClassLoader()) |
---|
491 | def entityObj = entityClass.newInstance(template: template) |
---|
492 | |
---|
493 | // Store the selected property for this column into the column map for the ImporterService |
---|
494 | headers[columnindex.toInteger()].property = property |
---|
495 | |
---|
496 | // Look up the template field type of the target TemplateField and store it also in the map |
---|
497 | headers[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property) |
---|
498 | |
---|
499 | // Is a "Don't import" property assigned to the column? |
---|
500 | headers[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false |
---|
501 | |
---|
502 | //if it's an identifier set the mapping column true or false |
---|
503 | entityObj.giveFields().each { |
---|
504 | headers[columnindex.toInteger()].identifier = ( it.preferredIdentifier && (it.name == property) ) |
---|
505 | } |
---|
506 | } |
---|
507 | |
---|
508 | // Import the workbook and store the table with entity records and store the failed cells |
---|
509 | def (table, failedcells) = importerService.importData(session.simpleWizard.sampleForm.templateId, |
---|
510 | session.simpleWizard.excel.workbook, |
---|
511 | session.simpleWizard.excel.sheetIndex - 1, |
---|
512 | session.simpleWizard.excel.dataMatrixStart - 1, |
---|
513 | session.simpleWizard.excel.data.header) |
---|
514 | |
---|
515 | session.simpleWizard.imported = [ |
---|
516 | data: table, |
---|
517 | failedCells: failedcells |
---|
518 | ]; |
---|
519 | |
---|
520 | // loop through all entities to validate them and add them to failedcells if an error occurs |
---|
521 | def numInvalidEntities = 0; |
---|
522 | def errors = []; |
---|
523 | |
---|
524 | // Remove all samples |
---|
525 | study.samples?.clear(); |
---|
526 | |
---|
527 | table.each { record -> |
---|
528 | record.each { entity -> |
---|
529 | // Determine entity class and add a parent |
---|
530 | entity.parent = study |
---|
531 | study.addToSamples( entity ); |
---|
532 | |
---|
533 | if (!entity.validate()) { |
---|
534 | numInvalidEntities++; |
---|
535 | |
---|
536 | // Add this field to the list of failed cells, in order to give the user feedback |
---|
537 | failedcells = addNonValidatingCells( failedcells, entity ) |
---|
538 | |
---|
539 | // Also create a full list of errors |
---|
540 | errors += getHumanReadableErrors( entity ); |
---|
541 | } |
---|
542 | } |
---|
543 | } |
---|
544 | |
---|
545 | session.simpleWizard.imported.numInvalidEntities = numInvalidEntities; |
---|
546 | session.simpleWizard.imported.errors = errors; |
---|
547 | |
---|
548 | return true |
---|
549 | } |
---|
550 | |
---|
551 | /** |
---|
552 | * Handles the update of the edited fields by the user |
---|
553 | * @param study Study to update |
---|
554 | * @param params Request parameter map |
---|
555 | * @return True if everything went OK, false otherwise. An error message is put in flash.error. |
---|
556 | * The field session.simpleWizard.imported.numInvalidEntities reflects the number of |
---|
557 | * entities that still have errors, and should be fixed before saving. The errors for those entities |
---|
558 | * are saved into session.simpleWizard.imported.errors |
---|
559 | */ |
---|
560 | def handleMissingFields( study, params ) { |
---|
561 | def numInvalidEntities = 0; |
---|
562 | def errors = []; |
---|
563 | |
---|
564 | // Remove all samples before adding them again |
---|
565 | study.samples?.clear(); |
---|
566 | |
---|
567 | // Check which fields failed previously |
---|
568 | def failedCells = session.simpleWizard.imported.failedCells |
---|
569 | |
---|
570 | session.simpleWizard.imported.data.each { table -> |
---|
571 | table.each { entity -> |
---|
572 | def invalidFields = 0 |
---|
573 | |
---|
574 | // Set the fields for this entity by retrieving values from the params |
---|
575 | entity.giveFields().each { field -> |
---|
576 | def fieldName = importerService.getFieldNameInTableEditor( entity, field ); |
---|
577 | |
---|
578 | if( params[ fieldName ] == "#invalidterm" ) { |
---|
579 | // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect |
---|
580 | invalidFields++; |
---|
581 | } else { |
---|
582 | if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) { |
---|
583 | // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from |
---|
584 | // the failedCells list |
---|
585 | importerService.removeFailedCell( failedCells, entity, field ) |
---|
586 | } |
---|
587 | |
---|
588 | // Update the field, regardless of the type of field |
---|
589 | entity.setFieldValue(field.name, params[ fieldName ] ) |
---|
590 | } |
---|
591 | } |
---|
592 | |
---|
593 | // Determine entity class and add a parent |
---|
594 | entity.parent = study; |
---|
595 | study.addToSamples( entity ); |
---|
596 | |
---|
597 | // Try to validate the entity now all fields have been set. If it fails, return an error |
---|
598 | if (!entity.validate() || invalidFields) { |
---|
599 | numInvalidEntities++; |
---|
600 | |
---|
601 | // Add this field to the list of failed cells, in order to give the user feedback |
---|
602 | failedCells = addNonValidatingCells( failedCells, entity ) |
---|
603 | |
---|
604 | // Also create a full list of errors |
---|
605 | errors += getHumanReadableErrors( entity ); |
---|
606 | } else { |
---|
607 | importerService.removeFailedCell( failedCells, entity ) |
---|
608 | } |
---|
609 | } // end of record |
---|
610 | } // end of table |
---|
611 | |
---|
612 | session.simpleWizard.imported.numInvalidEntities = numInvalidEntities; |
---|
613 | session.simpleWizard.imported.errors = errors; |
---|
614 | |
---|
615 | return true |
---|
616 | } |
---|
617 | |
---|
618 | /** |
---|
619 | * Handles assay input |
---|
620 | * @param study Study to update |
---|
621 | * @param params Request parameter map |
---|
622 | * @return True if everything went OK, false otherwise. An error message is put in flash.error |
---|
623 | */ |
---|
624 | def handleAssays( assay, params ) { |
---|
625 | // did the study template change? |
---|
626 | if (params.get('template') && assay.template?.name != params.get('template')) { |
---|
627 | // set the template |
---|
628 | assay.template = Template.findByName(params.remove('template')) |
---|
629 | } |
---|
630 | |
---|
631 | // does the study have a template set? |
---|
632 | if (assay.template && assay.template instanceof Template) { |
---|
633 | // yes, iterate through template fields |
---|
634 | assay.giveFields().each() { |
---|
635 | // and set their values |
---|
636 | assay.setFieldValue(it.name, params.get(it.escapedName())) |
---|
637 | } |
---|
638 | } |
---|
639 | |
---|
640 | return true |
---|
641 | } |
---|
642 | |
---|
643 | /** |
---|
644 | * Validates an object and puts human readable errors in validationErrors variable |
---|
645 | * @param entity Entity to validate |
---|
646 | * @return True iff the entity validates, false otherwise |
---|
647 | */ |
---|
648 | protected boolean validateObject( def entity ) { |
---|
649 | if( !entity.validate() ) { |
---|
650 | flash.validationErrors = getHumanReadableErrors( entity ) |
---|
651 | return false; |
---|
652 | } |
---|
653 | return true; |
---|
654 | } |
---|
655 | |
---|
656 | |
---|
657 | /** |
---|
658 | * Adds all fields of this entity that have given an error when validating to the failedcells list |
---|
659 | * @param failedcells Current list of ImportRecords |
---|
660 | * @param entity Entity to check. The entity must have been validated before |
---|
661 | * @return Updated list of ImportRecords |
---|
662 | */ |
---|
663 | protected def addNonValidatingCells( failedcells, entity ) { |
---|
664 | // Add this entity and the fields with an error to the failedCells list |
---|
665 | ImportRecord failedRecord = new ImportRecord(); |
---|
666 | |
---|
667 | entity.getErrors().getFieldErrors().each { error -> |
---|
668 | String field = error.getField(); |
---|
669 | def mc = importerService.findMappingColumn( session.simpleWizard.excel.data.header, field ); |
---|
670 | def mcInstance = new MappingColumn( name: field, entityClass: Sample.class, index: -1, property: field.toLowerCase(), templateFieldType: entity.giveFieldType( field ) ); |
---|
671 | |
---|
672 | // Create a clone of the mapping column |
---|
673 | if( mc ) { |
---|
674 | mcInstance.properties = mc.properties |
---|
675 | } |
---|
676 | |
---|
677 | failedRecord.addToImportcells( new ImportCell(mappingcolumn: mcInstance, value: error.getRejectedValue(), entityidentifier: importerService.getFieldNameInTableEditor( entity, field ) ) ) |
---|
678 | } |
---|
679 | failedcells.add( failedRecord ); |
---|
680 | |
---|
681 | return failedcells |
---|
682 | } |
---|
683 | |
---|
684 | |
---|
685 | |
---|
686 | |
---|
687 | /** |
---|
688 | * Checks an excel workbook whether the given sheetindex and rownumbers are correct |
---|
689 | * @param workbook Excel workbook to read |
---|
690 | * @param sheetIndex 1-based sheet index for the sheet to read (1=first sheet) |
---|
691 | * @param headerRow 1-based row number for the header row (1=first row) |
---|
692 | * @param dataMatrixStart 1-based row number for the first data row (1=first row) |
---|
693 | * @return True if the sheet index and row numbers are correct. |
---|
694 | */ |
---|
695 | protected boolean excelChecks( def workbook, int sheetIndex, int headerRow, int dataMatrixStart ) { |
---|
696 | // Perform some basic checks on the excel file. These checks should be performed by the importerservice |
---|
697 | // in a perfect scenario. |
---|
698 | if( sheetIndex > workbook.getNumberOfSheets() ) { |
---|
699 | log.error ".simple study wizard Sheet index is too high: " + sheetIndex + " / " + workbook.getNumberOfSheets(); |
---|
700 | flash.error = "The sheet number you provided is too high. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s)."; |
---|
701 | return false |
---|
702 | } |
---|
703 | |
---|
704 | def sheet = workbook.getSheetAt(sheetIndex - 1); |
---|
705 | def firstRowNum = sheet.getFirstRowNum(); |
---|
706 | def lastRowNum = sheet.getLastRowNum(); |
---|
707 | def numRows = lastRowNum - firstRowNum + 1; |
---|
708 | |
---|
709 | if( headerRow > numRows ) { |
---|
710 | log.error ".simple study wizard Header row number is incorrect: " + headerRow + " / " + numRows; |
---|
711 | flash.error = "The header row number you provided is too high. Please provide a number equal to or below " + numRows; |
---|
712 | return false |
---|
713 | } |
---|
714 | |
---|
715 | if( dataMatrixStart > numRows ) { |
---|
716 | log.error ".simple study wizard Data row number is incorrect: " + dataMatrixStart + " / " + numRows; |
---|
717 | flash.error = "The data row number you provided is too high. Please provide a number equal to or below " + numRows; |
---|
718 | return false |
---|
719 | } |
---|
720 | |
---|
721 | return true; |
---|
722 | } |
---|
723 | |
---|
724 | /** |
---|
725 | * Redirects the user to the page with the given name |
---|
726 | * @param action |
---|
727 | */ |
---|
728 | protected void toPage( String action ) { |
---|
729 | println "Redirecting to: " + action; |
---|
730 | redirect( action: action, params: [ "wizard": true ] ); |
---|
731 | } |
---|
732 | |
---|
733 | /** |
---|
734 | * Returns the event that is specified by the user form |
---|
735 | * @param params |
---|
736 | * @return |
---|
737 | */ |
---|
738 | protected String getEvent( def params ) { |
---|
739 | return params.get( 'event' ); |
---|
740 | } |
---|
741 | |
---|
742 | /** |
---|
743 | * Retrieves the required study from the database or return an empty Study object if |
---|
744 | * no id is given |
---|
745 | * |
---|
746 | * @param params Request parameters with params.id being the ID of the study to be retrieved |
---|
747 | * @return A study from the database or an empty study if no id was given |
---|
748 | */ |
---|
749 | protected Study getStudyFromRequest( def params ) { |
---|
750 | int id = params.int( "id" ); |
---|
751 | |
---|
752 | if( !id ) { |
---|
753 | return new Study( title: "New study", owner: authenticationService.getLoggedInUser() ); |
---|
754 | } |
---|
755 | |
---|
756 | Study s = Study.get( id ); |
---|
757 | |
---|
758 | if( !s.canWrite( authenticationService.getLoggedInUser() ) ) { |
---|
759 | flash.error = "No authorization to edit this study." |
---|
760 | return null; |
---|
761 | } |
---|
762 | |
---|
763 | return s |
---|
764 | } |
---|
765 | |
---|
766 | /** |
---|
767 | * Retrieves the study that is saved in the wizard, |
---|
768 | * |
---|
769 | * @param params Request parameters |
---|
770 | * @return The found study object, or null if no study object is found |
---|
771 | */ |
---|
772 | protected Study getStudyInWizard( def params ) { |
---|
773 | if( params.wizard && session.simpleWizard && session.simpleWizard.study ) { |
---|
774 | // The user came here by clicking previous or a link on another page. Use the existing study |
---|
775 | return session.simpleWizard.study; |
---|
776 | } else { |
---|
777 | // The user didn't get here from the wizard or no study is found |
---|
778 | return null; |
---|
779 | } |
---|
780 | } |
---|
781 | |
---|
782 | /** |
---|
783 | * transform domain class validation errors into a human readable |
---|
784 | * linked hash map |
---|
785 | * @param object validated domain class |
---|
786 | * @return object linkedHashMap |
---|
787 | */ |
---|
788 | def getHumanReadableErrors(object) { |
---|
789 | def errors = [:] |
---|
790 | object.errors.getAllErrors().each() { error -> |
---|
791 | // error.codes.each() { code -> println code } |
---|
792 | |
---|
793 | // generally speaking g.message(...) should work, |
---|
794 | // however it fails in some steps of the wizard |
---|
795 | // (add event, add assay, etc) so g is not always |
---|
796 | // availably. Using our own instance of the |
---|
797 | // validationTagLib instead so it is always |
---|
798 | // available to us |
---|
799 | errors[error.getArguments()[0]] = validationTagLib.message(error: error) |
---|
800 | } |
---|
801 | |
---|
802 | return errors |
---|
803 | } |
---|
804 | |
---|
805 | } |
---|