source: trunk/grails-app/controllers/dbnp/studycapturing/SimpleController.groovy @ 1601

Last change on this file since 1601 was 1601, checked in by robert@…, 11 years ago
File size: 30.0 KB
Line 
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: 1591 $
12 * $Author: robert@isdat.nl $
13 * $Date: 2011-03-07 12:01:52 +0100 (Mon, 07 Mar 2011) $
14 */
15package dbnp.studycapturing
16
17import org.apache.poi.ss.usermodel.DataFormatter
18import org.dbnp.gdt.*
19import grails.plugins.springsecurity.Secured
20import dbnp.authentication.SecUser
21import dbnp.importer.ImportCell
22import dbnp.importer.ImportRecord
23import dbnp.importer.MappingColumn
24
25@Secured(['IS_AUTHENTICATED_REMEMBERED'])
26class SimpleController extends StudyWizardController {
27        def authenticationService
28        def fileService
29        def importerService
30        def gdtService = new GdtService()
31
32        /**
33         * index closure
34         */
35        def index = {
36                redirect( action: "simpleWizard" );
37        }
38
39        def simpleWizardFlow = {
40                entry {
41                        action{
42                                flow.study = getStudyFromRequest( params )
43                                if (!flow.study) retrievalError()
44                        }
45                        on("retrievalError").to "handleError"
46                        on("success").to "study"
47                }
48
49                study {
50                        on("next") {
51                                handleStudy( flow.study, params )
52                                if( !validateObject( flow.study ) )
53                                        error()
54                        }.to "decisionState"
55                        on("refresh") { handleStudy( flow.study, params ); }.to "study"
56                        on( "success" ) { handleStudy( flow.study, params ) }.to "study"
57                }
58
59                decisionState {
60                        action {
61                                // Create data in the flow
62                                flow.templates = [
63                                                        'Sample': Template.findAllByEntity( Sample.class ),
64                                                        'Subject': Template.findAllByEntity( Subject.class ),
65                                                        'Event': Template.findAllByEntity( Event.class ),
66                                                        'SamplingEvent': Template.findAllByEntity( SamplingEvent.class )
67                                ];
68                                flow.encodedEntity = [
69                                                        'Sample': gdtService.encryptEntity( Sample.class.name ),
70                                                        'Subject': gdtService.encryptEntity( Subject.class.name ),
71                                                        'Event': gdtService.encryptEntity( Event.class.name ),
72                                                        'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name )
73                                                ]
74
75                                if (flow.study.samples)
76                                        checkStudySimplicity(flow.study) ? existingSamples() : complexStudy()
77                                else
78                                        samples()
79                        }
80                        on ("existingSamples").to "startExistingSamples"
81                        on ("complexStudy").to "complexStudy"
82                        on ("samples").to "samples"
83                }
84               
85                startExistingSamples {
86                        action {
87                                def records = importerService.getRecords( flow.study );
88                                flow.records = records
89                                flow.templateCombinations = records.templateCombination.unique()
90                               
91                                success();
92                        }
93                        on( "success" ).to "existingSamples"
94                }
95
96                existingSamples {
97                        on("next") {
98                                handleExistingSamples( flow.study, params, flow ) ? success() : error()
99                        }.to "startAssays"
100                        on("previous").to "study"
101                        on("update") {
102                                handleExistingSamples( flow.study, params, flow ) ? success() : error()
103                        }.to "samples"
104
105                        on("skip").to "startAssays"
106                }
107
108                complexStudy {
109                        on("save").to "save"
110                        on("previous").to "study"
111                }
112
113                samples {
114                        on("next") {
115                                handleSamples( flow.study, params, flow ) ? success() : error ()
116                               
117                                // Add domain fields for all entities
118                                flow.domainFields = [:]
119                               
120                                flow.templates.each { 
121                                        if( it.value ) {
122                                                flow.domainFields[ it.key ] = it.value[0].entity.giveDomainFields();
123                                        }
124                                }
125                               
126                        }.to "columns"
127                        on("previous").to "returnFromSamples"
128                        on("study").to "study"
129                        on("skip").to "startAssays"
130                }
131
132                returnFromSamples {
133                        action {
134                                flow.study.samples ? existingSamples() : study();
135                        }
136                        on( "existingSamples" ).to "startExistingSamples"
137                        on( "study" ).to "study"
138                }
139               
140                columns {
141                        on( "next" ) {
142                                handleColumns( flow.study, params, flow ) ? success() : error()
143                        }.to "checkImportedEntities"
144                        on( "previous" ).to "samples" 
145                }
146               
147                checkImportedEntities {
148                        action {
149                                // Only continue to the next page if the information entered is correct
150                                if( flow.imported.numInvalidEntities > 0 ) {
151                                        missingFields();
152                                } else {
153                                        // The import of the excel file has finished. Now delete the excelfile
154                                        if( flow.excel.filename )
155                                                fileService.delete( flow.excel.filename );
156       
157                                        flow.sampleForm = null
158       
159                                        assays();
160                                }
161                        }
162                        on( "missingFields" ).to "missingFields"
163                        on( "assays" ).to "startAssays" 
164                }
165               
166                missingFields {
167                        on( "next" ) {
168                                if( !handleMissingFields( flow.study, params ) )
169                                        error();
170                               
171                                // The import of the excel file has finished. Now delete the excelfile
172                                if( flow.excel.filename )
173                                        fileService.delete( flow.excel.filename );
174
175                                flow.sampleForm = null
176                                       
177                        }.to "startAssays"
178                        on( "previous" ).to "columns"
179                }
180               
181                startAssays {
182                        action {
183                                println "Assay: " + flow.assay
184                                if( !flow.assay ) 
185                                        flow.assay = new Assay( parent: flow.study );
186                                       
187                                success();
188                        }
189                        on( "success" ).to "assays"
190                }
191               
192                assays {
193                        on( "next" ) { 
194                                handleAssays( flow.assay, params, flow );
195                                if( !validateObject( flow.assay ) )
196                                        error();
197                         }.to "overview"
198                        on( "skip" ) {
199                                // In case the user has created an assay before he clicked 'skip', it should only be kept if it
200                                // existed before this step
201                                if( flow.assay != null && !flow.assay.id ) {
202                                        flow.remove( "assay" )
203                                }
204
205                         }.to "overview"
206                        on( "previous" ).to "returnFromAssays"
207                        on("refresh") { handleAssays( flow.assay, params, flow ); }.to "assays"
208                }
209
210                returnFromAssays {
211                        action {
212                                flow.study.samples ? existingSamples() : samples();
213                        }
214                        on( "existingSamples" ).to "existingSamples"
215                        on( "samples" ).to "samples"
216                }
217               
218                overview { 
219                        on( "save" ).to "saveStudy" 
220                        on( "previous" ).to "startAssays"
221                }
222               
223                saveStudy {
224                        action {
225                                if( flow.assay && !flow.study.assays?.contains( flow.assay ) ) {
226                                        flow.study.addToAssays( flow.assay );
227                                }
228                               
229                                if( flow.study.save( flush: true ) ) {
230                                        // Make sure all samples are attached to all assays
231                                        flow.study.assays.each { assay ->
232                                                def l = []+ assay.samples;
233                                                l.each { sample ->
234                                                        if( sample )
235                                                                assay.removeFromSamples( sample );
236                                                }
237                                                assay.samples?.clear();
238               
239                                                flow.study.samples.each { sample ->
240                                                        assay.addToSamples( sample )
241                                                }
242                                        }
243                       
244                                        flash.message = "Your study is succesfully saved.";
245                                       
246                                        finish();
247                                } else {
248                                        // Remove the assay from the study again, since it is still available
249                                        // in the session
250                                        if( flow.assay ) {
251                                                flow.study.removeFromAssays( flow.assay );
252                                                flow.assay.parent = flow.study;
253                                        }
254                                       
255                                        overview();
256                                }
257                        }
258                        on( "finish" ).to "finish"
259                        on( "overview" ).to "overview"
260                }
261               
262                finish()
263               
264                handleError{
265                        redirect action: "errorPage"
266                }
267        }
268
269        /**
270         * Retrieves the required study from the database or return an empty Study object if
271         * no id is given
272         *
273         * @param params        Request parameters with params.id being the ID of the study to be retrieved
274         * @return                      A study from the database or an empty study if no id was given
275         */
276        protected Study getStudyFromRequest( def params ) {
277                int id = params.int( "id" );
278
279                if( !id ) {
280                        return new Study( title: "New study", owner: authenticationService.getLoggedInUser() );
281                }
282
283                Study s = Study.get( id );
284
285                if( !s ) {
286                        flash.error = "No study found with given id";
287                        return null;
288                }
289                if( !s.canWrite( authenticationService.getLoggedInUser() ) ) {
290                        flash.error = "No authorization to edit this study."
291                        return null;
292                }
293
294                return s
295        }
296
297        /**
298         * Handles study input
299         * @param study         Study to update
300         * @param params        Request parameter map
301         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
302         */
303        def handleStudy( study, params ) {
304                // did the study template change?
305                if (params.get('template') && study.template?.name != params.get('template')) {
306                        // set the template
307                        study.template = Template.findByName(params.remove('template'))
308                }
309
310                // does the study have a template set?
311                if (study.template && study.template instanceof Template) {
312                        // yes, iterate through template fields
313                        study.giveFields().each() {
314                                // and set their values
315                                study.setFieldValue(it.name, params.get(it.escapedName()))
316                        }
317                }
318
319                // handle public checkbox
320                if (params.get("publicstudy")) {
321                        study.publicstudy = params.get("publicstudy")
322                }
323
324                // handle publications
325                handleStudyPublications(study, params)
326
327                // handle contacts
328                handleStudyContacts(study, params)
329
330                // handle users (readers, writers)
331                handleStudyUsers(study, params, 'readers')
332                handleStudyUsers(study, params, 'writers')
333
334                return true
335        }
336       
337        /**
338        * Handles the editing of existing samples
339        * @param study          Study to update
340        * @param params         Request parameter map
341        * @return                       True if everything went OK, false otherwise. An error message is put in flash.error
342        */
343   def handleExistingSamples( study, params, flow ) {
344           flash.validationErrors = [:];
345
346           def errors = false;
347           
348           // iterate through objects; set field values and validate the object
349           def eventgroups = study.samples.parentEventGroup.findAll { it }
350           def events;
351           if( !eventgroups )
352                   events = []
353           else
354                   events = eventgroups.events?.getAt(0);
355           
356           def objects = [
357                   'Sample': study.samples,
358                   'Subject': study.samples.parentSubject.findAll { it },
359                   'SamplingEvent': study.samples.parentEvent.findAll { it },
360                   'Event': events.flatten().findAll { it }
361           ];
362   
363           objects.each {
364                   def type = it.key;
365                   def entities = it.value;
366                   
367                   entities.each { entity ->
368                           // iterate through entity fields
369                           entity.giveFields().each() { field ->
370                                   def value = params.get( type.toLowerCase() + '_' + entity.getIdentifier() + '_' + field.escapedName())
371
372                                   // set field value; name cannot be set to an empty value
373                                   if (field.name != 'name' || value) {
374                                           log.info "setting "+field.name+" to "+value
375                                           entity.setFieldValue(field.name, value)
376                                   }
377                           }
378                           
379                           // has the template changed?
380                           def templateName = params.get(type.toLowerCase() + '_' + entity.getIdentifier() + '_template')
381                           if (templateName && entity.template?.name != templateName) {
382                                   entity.template = Template.findByName(templateName)
383                           }
384   
385                           // validate sample
386                           if (!entity.validate()) {
387                                   errors = true;
388                                   flash.validationErrors << getHumanReadableErrors( entity )
389                           }
390                   }
391           }
392
393           return !errors
394   }
395
396        /**
397         * Handles the upload of sample data
398         * @param study         Study to update
399         * @param params        Request parameter map
400         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
401         */
402        def handleSamples( study, params, flow ) {
403                def filename = params.get( 'importfile' );
404
405                // Handle 'existing*' in front of the filename. This is put in front to make a distinction between
406                // an already uploaded file test.txt (maybe moved to some other directory) and a newly uploaded file test.txt
407                // still being in the temporary directory.
408                // This import step doesn't have to make that distinction, since all files remain in the temporary directory.
409                if( filename == 'existing*' )
410                        filename = '';
411                else if( filename[0..8] == 'existing*' )
412                        filename = filename[9..-1]
413
414                def sampleTemplateId  = params.long( 'sample_template_id' )
415                def subjectTemplateId  = params.long( 'subject_template_id' )
416                def eventTemplateId  = params.long( 'event_template_id' )
417                def samplingEventTemplateId  = params.long( 'samplingEvent_template_id' )
418
419                // These fields have been removed from the form, so will always contain
420                // their default value. The code however remains like this for future use.
421                int sheetIndex = (params.int( 'sheetindex' ) ?: 1 )
422                int dataMatrixStart = (params.int( 'datamatrix_start' ) ?: 2 )
423                int headerRow = (params.int( 'headerrow' ) ?: 1 )
424
425                // Save form data in session
426                flow.sampleForm = [
427                                        importFile: filename,
428                                        templateId: [
429                                                'Sample': sampleTemplateId,
430                                                'Subject': subjectTemplateId,
431                                                'Event': eventTemplateId,
432                                                'SampingEvent': samplingEventTemplateId
433                                        ],
434                                        template: [
435                                                'Sample': sampleTemplateId ? Template.get( sampleTemplateId ) : null,
436                                                'Subject': subjectTemplateId ? Template.get( subjectTemplateId ) : null,
437                                                'Event': eventTemplateId ? Template.get( eventTemplateId ) : null,
438                                                'SampingEvent': samplingEventTemplateId ? Template.get( samplingEventTemplateId ) : null
439                                        ],
440                                        sheetIndex: sheetIndex,
441                                        dataMatrixStart: dataMatrixStart,
442                                        headerRow: headerRow
443                                ];
444
445                // Check whether the template exists
446                if (!sampleTemplateId || !Template.get( sampleTemplateId ) ){
447                        log.error ".simple study wizard not all fields are filled in: " + sampleTemplateId
448                        flash.error = "No template was chosen. Please choose a template for the samples you provided."
449                        return false
450                }
451
452                def importedfile = fileService.get( filename )
453                def workbook
454                if (importedfile.exists()) {
455                        try {
456                                workbook = importerService.getWorkbook(new FileInputStream(importedfile))
457                        } catch (Exception e) {
458                                log.error ".simple study wizard could not load file: " + e
459                                flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
460                                return false
461                        }
462                } else {
463                        log.error ".simple study wizard no file given";
464                        flash.error = "No file was given. Please provide an excel file for entering samples.";
465                        return false;
466                }
467
468                if( !workbook ) {
469                        log.error ".simple study wizard could not load file into a workbook"
470                        flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
471                        return false
472                }
473
474                def selectedentities = []
475
476                if( !excelChecks( workbook, sheetIndex, headerRow, dataMatrixStart ) )
477                        return false;
478
479                // Get the header from the Excel file using the arguments given in the first step of the wizard
480                def importerHeader;
481                def importerDataMatrix;
482
483                try {
484                        importerHeader = importerService.getHeader(workbook,
485                                        sheetIndex - 1,                 // 0 == first sheet
486                                        headerRow,                              // 1 == first row :s
487                                        dataMatrixStart - 1,    // 0 == first row
488                                        Sample.class)
489
490                        importerDataMatrix = importerService.getDatamatrix(
491                                        workbook,
492                                        importerHeader,
493                                        sheetIndex - 1,                 // 0 == first sheet
494                                        dataMatrixStart - 1,    // 0 == first row
495                                        5)
496                } catch( Exception e ) {
497                        // An error occurred while reading the excel file.
498                        log.error ".simple study wizard error while reading the excel file";
499                        e.printStackTrace();
500
501                        // Show a message to the user
502                        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.";
503                        return false;
504                }
505
506                // Save read excel data into session
507                def dataMatrix = [];
508                def df = new DataFormatter();
509                importerDataMatrix.each {
510                        dataMatrix << it.collect{ it ? df.formatCellValue(it) : "" }
511                }
512               
513                flow.excel = [
514                                        filename: filename,
515                                        sheetIndex: sheetIndex,
516                                        dataMatrixStart: dataMatrixStart,
517                                        headerRow: headerRow,
518                                        data: [
519                                                header: importerHeader,
520                                                dataMatrix: dataMatrix
521                                        ]
522                                ]
523
524                return true
525        }
526
527       
528        /**
529         * Handles the matching of template fields with excel columns by the user
530         * @param study         Study to update
531         * @param params        Request parameter map
532         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
533         *                                      The field session.simpleWizard.imported.numInvalidEntities reflects the number of
534         *                                      entities that have errors, and should be fixed before saving. The errors for those entities
535         *                                      are saved into session.simpleWizard.imported.errors
536         */
537        def handleColumns( study, params, flow ) {
538                // Find actual Template object from the chosen template name
539                def templates = [:];
540                flow.sampleForm.templateId.each {
541                        templates[ it.key ] = it.value ? Template.get( it.value ) : null;
542                }
543               
544                def headers = flow.excel.data.header;
545
546                if( !params.matches ) {
547                        log.error( ".simple study wizard no column matches given" );
548                        flash.error = "No column matches given";
549                        return false;
550                }
551
552                // Retrieve the chosen matches from the request parameters and put them into
553                // the headers-structure, for later reference
554                params.matches.index.each { columnindex, value ->
555                        // Determine the entity and property by splitting it
556                        def parts = value.toString().tokenize( "||" );
557                       
558                        def property
559                        def entityName
560                        if( parts.size() > 1 ) {
561                                property = parts[ 1 ];
562                                entityName = "dbnp.studycapturing." + parts[ 0 ];
563                        } else if( parts.size() == 1 ) {
564                                property = parts[ 0 ];
565                                entityName = headers[columnindex.toInteger()].entityclass.getName();
566                        }
567                       
568                        // Create an actual class instance of the selected entity with the selected template
569                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
570                        def entityClass = Class.forName( entityName, true, this.getClass().getClassLoader())
571                        def entityObj = entityClass.newInstance(template: templates[ entityName[entityName.lastIndexOf( '.' ) + 1..-1] ])
572
573                        headers[ columnindex.toInteger() ].entityclass = entityClass
574                       
575                        // Store the selected property for this column into the column map for the ImporterService
576                        headers[columnindex.toInteger()].property = property
577
578                        // Look up the template field type of the target TemplateField and store it also in the map
579                        headers[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
580
581                        // Is a "Don't import" property assigned to the column?
582                        headers[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
583
584                        //if it's an identifier set the mapping column true or false
585                        entityClass.giveDomainFields().each {
586                                headers[columnindex.toInteger()].identifier = ( it.preferredIdentifier && (it.name == property) )
587                        }
588                }
589
590                // Import the workbook and store the table with entity records and store the failed cells
591                println "Importing samples for study " + study + " (" + study.id + ")";
592               
593                def importedfile = fileService.get( flow.excel.filename )
594                def workbook
595                if (importedfile.exists()) {
596                        try {
597                                workbook = importerService.getWorkbook(new FileInputStream(importedfile))
598                        } catch (Exception e) {
599                                log.error ".simple study wizard could not load file: " + e
600                                flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
601                                return false
602                        }
603                } else {
604                        log.error ".simple study wizard no file given";
605                        flash.error = "No file was given. Please provide an excel file for entering samples.";
606                        return false;
607                }
608
609                if( !workbook ) {
610                        log.error ".simple study wizard could not load file into a workbook"
611                        flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
612                        return false
613                }
614               
615               
616                def imported = importerService.importOrUpdateDataBySampleIdentifier(templates,
617                                workbook,
618                                flow.excel.sheetIndex - 1,
619                                flow.excel.dataMatrixStart - 1,
620                                flow.excel.data.header,
621                                flow.study,
622                                true                    // Also create entities for which no data is imported but where templates were chosen
623                );
624
625                def table = imported.table
626                def failedcells = imported.failedCells
627
628                flow.imported = [
629                        data: table,
630                        failedCells: failedcells
631                ];
632
633                // loop through all entities to validate them and add them to failedcells if an error occurs
634                def numInvalidEntities = 0;
635                def errors = [];
636
637                // Add all samples
638                table.each { record ->
639                        record.each { entity ->
640                                if( entity ) {
641                                        // Determine entity class and add a parent
642                                        def preferredIdentifier = importerService.givePreferredIdentifier( entity.class );
643                                        def equalClosure = { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) }
644       
645                                        entity.parent = study
646                                       
647                                        switch( entity.class ) {
648                                                case Sample:
649                                                        if( preferredIdentifier && !study.samples?.find( equalClosure ) ) {
650                                                                study.addToSamples( entity );
651                                                        }
652                                                        break;
653                                                case Subject:
654                                                        if( preferredIdentifier && !study.subjects?.find( equalClosure ) ) {
655                                                                study.addToSubjects( entity );
656                                                        }
657                                                        break;
658                                                case Event:
659                                                        if( preferredIdentifier && !study.events?.find( equalClosure ) ) {
660                                                                study.addToEvents( entity );
661                                                        }
662                                                        break;
663                                                case SamplingEvent:
664                                                        if( preferredIdentifier && !study.samplingEvents?.find( equalClosure ) ) {
665                                                                study.addToSamplingEvents( entity );
666                                                        }
667                                                        break;
668                                        }
669                                       
670                                        if (!entity.validate()) {
671                                                numInvalidEntities++;
672                                               
673                                                // Add this field to the list of failed cells, in order to give the user feedback
674                                                failedcells = addNonValidatingCells( failedcells, entity )
675       
676                                                // Also create a full list of errors
677                                                errors += getHumanReadableErrors( entity );
678                                        }
679                                }
680                        }
681                }
682
683                flow.imported.numInvalidEntities = numInvalidEntities + failedcells?.size();
684                flow.imported.errors = errors;
685
686                return true
687        }
688
689       
690       
691        /**
692         * Handles the update of the edited fields by the user
693         * @param study         Study to update
694         * @param params                Request parameter map
695         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error.
696         *                                      The field session.simpleWizard.imported.numInvalidEntities reflects the number of
697         *                                      entities that still have errors, and should be fixed before saving. The errors for those entities
698         *                                      are saved into session.simpleWizard.imported.errors
699         */
700        def handleMissingFields( study, params, flow ) {
701                def numInvalidEntities = 0;
702                def errors = [];
703
704                // Check which fields failed previously
705                def failedCells = flow.imported.failedCells
706
707                flow.imported.data.each { table ->
708                        table.each { entity ->
709                                def invalidFields = 0
710
711                                // Set the fields for this entity by retrieving values from the params
712                                entity.giveFields().each { field ->
713                                        def fieldName = importerService.getFieldNameInTableEditor( entity, field );
714
715                                        if( params[ fieldName ] == "#invalidterm" ) {
716                                                // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect
717                                                invalidFields++;
718                                        } else {
719                                                if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) {
720                                                        // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from
721                                                        // the failedCells list
722                                                        importerService.removeFailedCell( failedCells, entity, field )
723                                                }
724
725                                                // Update the field, regardless of the type of field
726                                                entity.setFieldValue(field.name, params[ fieldName ] )
727                                        }
728                                }
729
730                                // Determine entity class and add a parent
731                                entity.parent = study;
732
733                                // Try to validate the entity now all fields have been set. If it fails, return an error
734                                if (!entity.validate() || invalidFields) {
735                                        numInvalidEntities++;
736
737                                        // Add this field to the list of failed cells, in order to give the user feedback
738                                        failedCells = addNonValidatingCells( failedCells, entity )
739
740                                        // Also create a full list of errors
741                                        errors += getHumanReadableErrors( entity );
742                                } else {
743                                        importerService.removeFailedCell( failedCells, entity )
744                                }
745                        } // end of record
746                } // end of table
747
748                flow.imported.numInvalidEntities = numInvalidEntities;
749                flow.imported.errors = errors;
750
751                return true
752        }
753       
754        /**
755        * Handles assay input
756        * @param study          Study to update
757        * @param params         Request parameter map
758        * @return                       True if everything went OK, false otherwise. An error message is put in flash.error
759        */
760   def handleAssays( assay, params, flow ) {
761           // did the study template change?
762           if (params.get('template') && assay.template?.name != params.get('template')) {
763                   // set the template
764                   assay.template = Template.findByName(params.remove('template'))
765           }
766
767           // does the study have a template set?
768           if (assay.template && assay.template instanceof Template) {
769                   // yes, iterate through template fields
770                   assay.giveFields().each() {
771                           // and set their values
772                           assay.setFieldValue(it.name, params.get(it.escapedName()))
773                   }
774           }
775
776           // Save the assay in session
777           flow.assay = assay;
778
779           return true
780   }
781       
782       
783        /**
784         * Checks whether the given study is simple enough to be edited using this controller.
785         *
786         * The study is simple enough if the samples, subjects, events and samplingEvents can be
787         * edited as a flat table. That is:
788         *              - Every subject belongs to 0 or 1 eventgroup
789         *              - Every eventgroup belongs to 0 or 1 sample
790         *              - Every eventgroup has 0 or 1 subjects, 0 or 1 event and 0 or 1 samplingEvents
791         *              - If a sample belongs to an eventgroup:
792         *                      - If that eventgroup has a samplingEvent, that same samplingEvent must also be
793         *                              the sampling event that generated this sample
794         *                      - If that eventgroup has a subject, that same subject must also be the subject
795         *                              from whom the sample was taken
796         *
797         * @param study         Study to check
798         * @return                      True if the study can be edited by this controller, false otherwise
799         */
800        def checkStudySimplicity( study ) {
801                def simplicity = true;
802
803                if( !study )
804                        return false
805
806                if( study.eventGroups ) {
807                        study.eventGroups.each { eventGroup ->
808                                // Check for simplicity of eventgroups: only 0 or 1 subject, 0 or 1 event and 0 or 1 samplingEvent
809                                if( eventGroup.subjects?.size() > 1 || eventGroup.events?.size() > 1 || eventGroup.samplingEvents?.size() > 1 ) {
810                                        flash.message = "One or more eventgroups contain multiple subjects or events."
811                                        simplicity = false;
812                                }
813
814                                // Check whether this eventgroup only belongs to (max) 1 sample
815                                def numSamples = 0;
816                                study.samples.each { sample ->
817                                        // If no id is given for the eventGroup, it has been entered in this wizard, but
818                                        // not yet saved. In that case, it is always OK
819                                        if( eventGroup.id && sample.parentEventGroup?.id == eventGroup.id )
820                                                numSamples++;
821                                }
822
823                                if( numSamples > 1 ) {
824                                        flash.message = "One or more eventgroups belong to multiple samples."
825                                        simplicity = false;
826                                }
827                        }
828
829                        if( !simplicity ) return false;
830
831                        // Check whether subject only belong to zero or one event group
832                        if( study.subjects ) {
833                                study.subjects.each { subject ->
834                                        def numEventGroups = 0
835                                        study.eventGroups.each { eventGroup ->
836                                                // If no id is given for the subject, it has been entered in this wizard, but
837                                                // not yet saved. In that case, it is always OK
838                                                if( subject.id && eventGroup.subjects[0]?.id == subject.id )
839                                                        numEventGroups++
840                                        }
841
842                                        if( numEventGroups > 1 ) {
843                                                flash.message = "One or more subjects belong to multiple eventgroups."
844                                                simplicity = false;
845                                        }
846                                }
847                        }
848
849                        if( !simplicity ) return false;
850
851                        // Check whether the samples that belong to an eventgroup have the right parentObjects
852                        study.samples.each { sample ->
853                                if( sample.parentEventGroup ) {
854                                        // If no id is given for the subject, it has been entered in this wizard, but
855                                        // not yet saved. In that case, it is always OK
856                                        if( sample.parentSubject && sample.parentSubject.id) {
857                                                if( !sample.parentEventGroup.subjects || sample.parentEventGroup.subjects[0]?.id != sample.parentSubject.id ) {
858                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
859                                                        simplicity = false;
860                                                }
861                                        }
862
863                                        // If no id is given for the sampling event, it has been entered in this wizard, but
864                                        // not yet saved. In that case, it is always OK
865                                        if( sample.parentEvent && sample.parentEvent.id) {
866                                                if( !sample.parentEventGroup.samplingEvents || sample.parentEventGroup.samplingEvents[0]?.id != sample.parentEvent.id ) {
867                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
868                                                        simplicity = false;
869                                                }
870                                        }
871                                }
872                        }
873
874                        if( !simplicity ) return false;
875                }
876
877                return simplicity;
878        }
879
880        /**
881        * Checks an excel workbook whether the given sheetindex and rownumbers are correct
882        * @param workbook                       Excel workbook to read
883        * @param sheetIndex             1-based sheet index for the sheet to read (1=first sheet)
884        * @param headerRow                      1-based row number for the header row (1=first row)
885        * @param dataMatrixStart        1-based row number for the first data row (1=first row)
886        * @return                                       True if the sheet index and row numbers are correct.
887        */
888   protected boolean excelChecks( def workbook, int sheetIndex, int headerRow, int dataMatrixStart ) {
889           // Perform some basic checks on the excel file. These checks should be performed by the importerservice
890           // in a perfect scenario.
891           if( sheetIndex > workbook.getNumberOfSheets() ) {
892                   log.error ".simple study wizard Sheet index is too high: " + sheetIndex + " / " + workbook.getNumberOfSheets();
893                   flash.error = "Your excel sheet contains too few excel sheets. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s).";
894                   return false
895           }
896
897           def sheet = workbook.getSheetAt(sheetIndex - 1);
898           def firstRowNum = sheet.getFirstRowNum();
899           def lastRowNum = sheet.getLastRowNum();
900           def numRows = lastRowNum - firstRowNum + 1;
901
902           if( headerRow > numRows  ) {
903                   log.error ".simple study wizard Header row number is incorrect: " + headerRow + " / " + numRows;
904                   flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
905                   return false
906           }
907
908           if( dataMatrixStart > numRows  ) {
909                   log.error ".simple study wizard Data row number is incorrect: " + dataMatrixStart + " / " + numRows;
910                   flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
911                   return false
912           }
913
914           return true;
915   }
916       
917        /**
918         * Validates an object and puts human readable errors in validationErrors variable
919         * @param entity                Entity to validate
920         * @return                      True iff the entity validates, false otherwise
921         */
922        protected boolean validateObject( def entity ) {
923                if( !entity.validate() ) {
924                        flash.validationErrors = getHumanReadableErrors( entity )
925                        return false;
926                }
927                return true;
928        }
929
930        /**
931         * transform domain class validation errors into a human readable
932         * linked hash map
933         * @param object validated domain class
934         * @return object  linkedHashMap
935         */
936        def getHumanReadableErrors(object) {
937                def errors = [:]
938                object.errors.getAllErrors().each() { error ->
939                        // error.codes.each() { code -> println code }
940
941                        // generally speaking g.message(...) should work,
942                        // however it fails in some steps of the wizard
943                        // (add event, add assay, etc) so g is not always
944                        // availably. Using our own instance of the
945                        // validationTagLib instead so it is always
946                        // available to us
947                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
948                }
949
950                return errors
951        }
952}
Note: See TracBrowser for help on using the repository browser.