root/trunk/grails-app/controllers/dbnp/studycapturing/SimpleWizardController.groovy @ 1834

Revision 1834, 46.8 KB (checked in by s.h.sikkema@…, 3 years ago)

Study inferring ability (in progress)

  • Property svn:keywords set to Rev Author Date
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$
12 * $Author$
13 * $Date$
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
24import org.hibernate.SessionFactory
25import org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass;
26
27@Secured(['IS_AUTHENTICATED_REMEMBERED'])
28class SimpleWizardController extends StudyWizardController {
29        def authenticationService
30        def fileService
31        def importerService
32        def gdtService = new GdtService()
33        def sessionFactory
34
35        /**
36         * index closure
37         */
38        def index = {
39                if( params.id )
40                        redirect( action: "simpleWizard", id: params.id );
41                else
42                        redirect( action: "simpleWizard" );
43        }
44
45        def simpleWizardFlow = {
46                entry {
47                        action{
48                                flow.study = getStudyFromRequest( params )
49                                if (!flow.study) retrievalError()
50                               
51                                // Search for studies
52                                flow.studies = Study.giveWritableStudies( authenticationService.getLoggedInUser(), 100 )
53                        }
54                        on("retrievalError").to "handleError"
55                        on("success").to "study"
56                }
57
58                study {
59                        on("next") {
60                                handleStudy( flow.study, params )
61                                if( !validateObject( flow.study ) )
62                                        error()
63                        }.to "decisionState"
64                        on("open") {
65                                // Send the user to the URL of the simple wizard in order
66                                // to avoid code duplication for loading the study
67                                if( params.study ) {
68                                        flow.openStudyId = params.study
69                                } else {
70                                        flash.error = "No study selected";
71                                        return error();
72                                }
73                        }.to "openStudy"
74                        on("refresh") {  handleStudy( flow.study, params ); }.to "study"
75                        on( "save" ) {
76                                handleStudy( flow.study, params );
77                                if( !validateObject( flow.study ) )
78                                        return error()
79                               
80                                if( flow.study.save( flush: true ) ) {
81                                        flash.message = "Your study is succesfully saved.";
82                                } else {
83                                        flash.error = "An error occurred while saving your study: <br />"
84                                        flow.study.getErrors().each { flash.error += it.toString() + "<br />"}
85                                }
86                                success()               
87                        }.to "study"
88                }
89               
90                openStudy {
91                        redirect( action: "simpleWizard", id: flow.openStudyId );
92                }
93
94                decisionState {
95                        action {
96                                // Create data in the flow
97                                flow.templates = [
98                                                        'Subject': Template.findAllByEntity( Subject.class ),
99                                                        'Event': Template.findAllByEntity( Event.class ),
100                                                        'SamplingEvent': Template.findAllByEntity( SamplingEvent.class ),
101                                                        'Sample': Template.findAllByEntity( Sample.class )
102                                ];
103                       
104                                flow.encodedEntity = [
105                                                        'Subject': gdtService.encryptEntity( Subject.class.name ),
106                                                        'Event': gdtService.encryptEntity( Event.class.name ),
107                                                        'SamplingEvent': gdtService.encryptEntity( SamplingEvent.class.name ),
108                                                        'Sample': gdtService.encryptEntity( Sample.class.name )
109                                ]
110
111                                if (flow.study.samples)
112                                        checkStudySimplicity(flow.study) ? existingSamples() : complexStudy()
113                                else
114                                        samples()
115                        }
116                        on ("existingSamples").to "startExistingSamples"
117                        on ("complexStudy").to "complexStudy"
118                        on ("samples").to "samples"
119                }
120               
121                startExistingSamples {
122                        action {
123                                def records = importerService.getRecords( flow.study );
124                                flow.records = records
125                                flow.templateCombinations = records.templateCombination.unique()
126                                success();
127                        }
128                        on( "success" ).to "existingSamples"
129                }
130
131                existingSamples {
132                        on("next") {
133                                handleExistingSamples( flow.study, params, flow ) ? success() : error()
134                        }.to "startAssays"
135                        on( "save" ) {
136                                if( !handleExistingSamples( flow.study, params, flow ) )
137                                        return error()
138                               
139                                if( flow.study.save( flush: true ) ) {
140                                        flash.message = "Your study is succesfully saved.";
141                                } else {
142                                        flash.error = "An error occurred while saving your study: <br />"
143                                        flow.study.getErrors().each { flash.error += it.toString() + "<br />"}
144                                }
145                                success()
146                        }.to "existingSamples"
147                        on("previous").to "study"
148                        on("refresh") {
149                                if( !handleExistingSamples( flow.study, params, flow ) )
150                                        return error()
151
152                                // Refresh the templates, since the template editor has been opened.
153                                flow.templates = [
154                                                'Subject': Template.findAllByEntity( Subject.class ).collect { it.refresh(); return it },
155                                                'Event': Template.findAllByEntity( Event.class ).collect { it.refresh(); return it },
156                                                'SamplingEvent': Template.findAllByEntity( SamplingEvent.class ).collect { it.refresh(); return it },
157                                                'Sample': Template.findAllByEntity( Sample.class ).collect { it.refresh(); return it }
158                                ];
159                        }.to "existingSamples"
160                        on("update") {
161                                handleExistingSamples( flow.study, params, flow ) ? success() : error()
162                        }.to "samples"
163
164                        on("skip").to "startAssays"
165                }
166
167                complexStudy {
168                        on("save").to "save"
169                        on("previous").to "study"
170                }
171
172                samples {
173                        on("next") {
174                                if( !handleSamples( flow.study, params, flow ) )
175                                        return error();
176                               
177                                // Add domain fields for all entities
178                                flow.domainFields = [:]
179                               
180                                flow.templates.each {
181                                        if( it.value ) {
182                                                flow.domainFields[ it.key ] = it.value[0].entity.giveDomainFields();
183                                        }
184                                }
185                               
186                                //println flow.sampleForm.template
187                        }.to "columns"
188                        on("refresh") {
189                                def filename = params.get( 'importfile' );
190               
191                                handleSampleForm( flow.study, params, flow )
192
193                                // Handle 'existing*' in front of the filename. This is put in front to make a distinction between
194                                // an already uploaded file test.txt (maybe moved to some other directory) and a newly uploaded file test.txt
195                                // still being in the temporary directory.
196                                // This import step doesn't have to make that distinction, since all files remain in the temporary directory.
197                                if( filename == 'existing*' )
198                                        filename = '';
199                                else if( filename[0..8] == 'existing*' )
200                                        filename = filename[9..-1]
201                               
202                                flow.sampleForm.importFile = filename
203                                       
204                                // Refresh the templates, since the template editor has been opened.
205                                flow.templates = [
206                                                'Subject': Template.findAllByEntity( Subject.class ).collect { it.refresh(); return it },
207                                                'Event': Template.findAllByEntity( Event.class ).collect { it.refresh(); return it },
208                                                'SamplingEvent': Template.findAllByEntity( SamplingEvent.class ).collect { it.refresh(); return it },
209                                                'Sample': Template.findAllByEntity( Sample.class ).collect { it.refresh(); return it }
210                                ];
211                        }.to "samples"
212                        on("previous").to "returnFromSamples"
213                        on("study").to "study"
214                        on("skip").to "startAssays"
215                }
216
217                returnFromSamples {
218                        action {
219                                flow.study.samples ? existingSamples() : study();
220                        }
221                        on( "existingSamples" ).to "startExistingSamples"
222                        on( "study" ).to "study"
223                }
224               
225                columns {
226                        on( "next" ) {
227                                flow.editImportedData = params.get( 'editAfterwards' ) ? true : false;
228                                handleColumns( flow.study, params, flow ) ? success() : error()
229                        }.to "checkImportedEntities"
230                        on( "previous" ).to "samples"
231                }
232               
233                checkImportedEntities {
234                        action {
235                                // Only continue to the next page if the information entered is correct
236                                if( flow.editImportedData || flow.imported.numInvalidEntities > 0 ) {
237                                        missingFields();
238                                } else {
239                                        // The import of the excel file has finished. Now delete the excelfile
240                                        if( flow.excel.filename )
241                                                fileService.delete( flow.excel.filename );
242       
243                                        flow.sampleForm = null
244       
245                                        assays();
246                                }
247                        }
248                        on( "missingFields" ).to "missingFields"
249                        on( "assays" ).to "startAssays"
250                }
251               
252                missingFields {
253                        on( "refresh" ) {
254                                handleMissingFields( flow.study, params, flow );
255                                success();
256                        }.to "missingFields"
257                        on( "next" ) {
258                                if( !handleMissingFields( flow.study, params, flow ) ) {
259                                        return error();
260                                }
261                               
262                                // The import of the excel file has finished. Now delete the excelfile
263                                if( flow.excel.filename )
264                                        fileService.delete( flow.excel.filename );
265
266                                flow.sampleForm = null
267                               
268                                success();
269                        }.to "startAssays"
270                        on( "previous" ) {
271                                // The user goes back to the previous page, so the already imported entities
272                                // (of which some gave an error) should be removed again.
273                                // Add all samples
274                                flow.imported.data.each { record ->
275                                        record.each { entity ->
276                                                if( entity ) {
277                                                        switch( entity.class ) {
278                                                                case Sample:    flow.study.removeFromSamples( entity ); break;
279                                                                case Subject:   flow.study.removeFromSubjects( entity ); break;
280                                                                case Event:             flow.study.removeFromEvents( entity ); break;
281                                                                case SamplingEvent:     flow.study.removeFromSamplingEvents( entity ); break;
282                                                        }
283                                                }
284                                        }
285                                }
286                               
287                                success();
288                        }.to "columns"
289                }
290               
291                startAssays {
292                        action {
293                                if( !flow.assay ) {
294                                        if( flow.study.assays ) {
295                                                flow.assay = flow.study.assays[ 0 ]
296                                                //println "Existing assay: " + flow.assay
297                                        } else {
298                                                flow.assay = new Assay( parent: flow.study );
299                                        }
300                                }
301                                success();
302                        }
303                        on( "success" ).to "assays"
304                }
305               
306                assays {
307                        on( "next" ) {
308                                handleAssays( flow.assay, params, flow );
309                                if( flow.assay.template && !validateObject( flow.assay ) )
310                                        error();
311                                       
312                         }.to "overview"
313                        on( "skip" ) {
314                                // In case the user has created an assay before he clicked 'skip', it should only be kept if it
315                                // existed before this step
316                                if( flow.assay != null && !flow.assay.id ) {
317                                        flow.remove( "assay" )
318                                }
319
320                         }.to "overview"
321                        on( "previous" ).to "returnFromAssays"
322                        on( "save" ) {
323                                handleAssays( flow.assay, params, flow );
324                                if( flow.assay.template && !validateObject( flow.assay ) )
325                                        error();
326
327                                if( saveStudyToDatabase( flow ) ) {
328                                        flash.message = "Your study is succesfully saved.";
329                                } else {
330                                        flash.error = "An error occurred while saving your study: <br />"
331                                        flow.study.getErrors().each { flash.error += it.toString() + "<br />"}
332                                        return error();
333                                }
334                        }.to "assays"
335                        on("refresh") {
336                                handleAssays( flow.assay, params, flow );
337                               
338                                flow.assay?.template?.refresh()
339                                success()
340                        }.to "assays"
341                }
342
343                returnFromAssays {
344                        action {
345                                flow.study.samples ? existingSamples() : samples();
346                        }
347                        on( "existingSamples" ).to "existingSamples"
348                        on( "samples" ).to "samples"
349                }
350               
351                overview {
352                        on( "save" ).to "saveStudy"
353                        on( "previous" ).to "startAssays"
354                }
355                saveStudy {
356                        action {
357                                if( saveStudyToDatabase( flow ) ) {
358                                        flash.message = "Your study is succesfully saved.";
359                                        finish();
360                                } else {
361                                        flash.error = "An error occurred while saving your study: <br />"
362                                        flow.study.getErrors().each { flash.error += it.toString() + "<br />"}
363                                        overview();
364                                }
365                        }
366                        on( "finish" ).to "finish"
367                        on( "overview" ).to "overview"
368                }
369               
370                finish()
371               
372                handleError{
373                        redirect action: "errorPage"
374                }
375        }
376       
377        /**
378         * Saves the study with assay
379         *
380         * @param flow
381         * @return true on success, false otherwise
382         */
383        protected boolean saveStudyToDatabase( def flow ) {
384                // Save the assay to the study
385                if( flow.assay && flow.assay.template && !flow.study.assays?.contains( flow.assay ) ) {
386                        flow.study.addToAssays( flow.assay );
387                }
388
389                if( flow.study.save( flush: true ) ) {
390                        // Make sure all samples are attached to all assays
391                        flow.study.assays.each { assay ->
392                                def l = []+ assay.samples;
393                                l.each { sample ->
394                                        if( sample )
395                                                assay.removeFromSamples( sample );
396                                }
397                                assay.samples?.clear();
398
399                                flow.study.samples.each { sample ->
400                                        assay.addToSamples( sample )
401                                }
402                        }
403                       
404                        return true;
405       
406                } else {
407                        // Remove the assay from the study again, since it is still available
408                        // in the session
409                        if( flow.assay ) {
410                                flow.study.removeFromAssays( flow.assay );
411                                flow.assay.parent = flow.study;
412                        }
413                       
414                        return false;
415                }
416        }
417
418        /**
419         * Retrieves the required study from the database or return an empty Study object if
420         * no id is given
421         *
422         * @param params        Request parameters with params.id being the ID of the study to be retrieved
423         * @return                      A study from the database or an empty study if no id was given
424         */
425        protected Study getStudyFromRequest( def params ) {
426                int id = params.int( "id" );
427
428                if( !id ) {
429                        return new Study( title: "New study", owner: authenticationService.getLoggedInUser() );
430                }
431
432                Study s = Study.get( id );
433
434                if( !s ) {
435                        flash.error = "No study found with given id";
436                        return null;
437                }
438                if( !s.canWrite( authenticationService.getLoggedInUser() ) ) {
439                        flash.error = "No authorization to edit this study."
440                        return null;
441                }
442
443                return s
444        }
445
446        /**
447         * Handles study input
448         * @param study         Study to update
449         * @param params        Request parameter map
450         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
451         */
452        def handleStudy( study, params ) {
453                // did the study template change?
454                if (params.get('template') && study.template?.name != params.get('template')) {
455                        // set the template
456                        study.template = Template.findByName(params.remove('template'))
457                }
458
459                // does the study have a template set?
460                if (study.template && study.template instanceof Template) {
461                        // yes, iterate through template fields
462                        study.giveFields().each() {
463                                // and set their values
464                                study.setFieldValue(it.name, params.get(it.escapedName()))
465                        }
466                }
467
468                // handle public checkbox
469                if (params.get("publicstudy")) {
470                        study.publicstudy = params.get("publicstudy")
471                }
472
473                // handle publications
474                handleStudyPublications(study, params)
475
476                // handle contacts
477                handleStudyContacts(study, params)
478
479                // handle users (readers, writers)
480                handleStudyUsers(study, params, 'readers')
481                handleStudyUsers(study, params, 'writers')
482
483                return true
484        }
485       
486        /**
487        * Handles the editing of existing samples
488        * @param study          Study to update
489        * @param params         Request parameter map
490        * @return                       True if everything went OK, false otherwise. An error message is put in flash.error
491        */
492   def handleExistingSamples( study, params, flow ) {
493           flash.validationErrors = [];
494
495           def errors = false;
496           
497           // iterate through objects; set field values and validate the object
498           def eventgroups = study.samples.parentEventGroup.findAll { it }
499           def events;
500           if( !eventgroups )
501                   events = []
502           else
503                   events = eventgroups.events?.getAt(0);
504           
505           def objects = [
506                   'Subject': study.samples.parentSubject.findAll { it },
507                   'SamplingEvent': study.samples.parentEvent.findAll { it },
508                   'Event': events.flatten().findAll { it },
509                   'Sample': study.samples
510           ];
511           objects.each {
512                   def type = it.key;
513                   def entities = it.value;
514                   
515                   entities.each { entity ->
516                           // iterate through entity fields
517                           entity.giveFields().each() { field ->
518                                   def value = params.get( type.toLowerCase() + '_' + entity.getIdentifier() + '_' + field.escapedName())
519
520                                   // set field value; name cannot be set to an empty value
521                                   if (field.name != 'name' || value) {
522                                           log.info "setting "+field.name+" to "+value
523                                           entity.setFieldValue(field.name, value)
524                                   }
525                           }
526                           
527                           // has the template changed?
528                           def templateName = params.get(type.toLowerCase() + '_' + entity.getIdentifier() + '_template')
529                           if (templateName && entity.template?.name != templateName) {
530                                   entity.template = Template.findByName(templateName)
531                           }
532   
533                           // validate sample
534                           if (!entity.validate()) {
535                                   errors = true;
536                                   
537                                   def entityName = entity.class.name[ entity.class.name.lastIndexOf( "." ) + 1 .. -1 ]
538                                   getHumanReadableErrors( entity ).each {
539                                                flash.validationErrors << [ key: it.key, value: "(" + entityName + ") " + it.value ];
540                                   }
541                           }
542                   }
543           }
544
545           return !errors
546   }
547
548        /**
549         * Handles the upload of sample data
550         * @param study         Study to update
551         * @param params        Request parameter map
552         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
553         */
554        def handleSamples( study, params, flow ) {
555                def filename = params.get( 'importfile' );
556
557                // Handle 'existing*' in front of the filename. This is put in front to make a distinction between
558                // an already uploaded file test.txt (maybe moved to some other directory) and a newly uploaded file test.txt
559                // still being in the temporary directory.
560                // This import step doesn't have to make that distinction, since all files remain in the temporary directory.
561                if( filename == 'existing*' )
562                        filename = '';
563                else if( filename[0..8] == 'existing*' )
564                        filename = filename[9..-1]
565
566                handleSampleForm( study, params, flow );
567
568                // Check whether the template exists
569                if (!flow.sampleForm.template.Sample ){
570                        log.error ".simple study wizard not all fields are filled in (sample template) "
571                        flash.error = "No template was chosen. Please choose a template for the samples you provided."
572                        return false
573                }
574               
575                // These fields have been removed from the form, so will always contain
576                // their default value. The code however remains like this for future use.
577                int sheetIndex = (params.int( 'sheetindex' ) ?: 1 )
578                int dataMatrixStart = (params.int( 'datamatrix_start' ) ?: 2 )
579                int headerRow = (params.int( 'headerrow' ) ?: 1 )
580               
581                flow.sampleForm.sheetIndex = sheetIndex;
582                flow.sampleForm.dataMatrixStart = dataMatrixStart
583                flow.sampleForm.headerRow = headerRow
584                flow.sampleForm.importFile = filename
585               
586                def importedfile = fileService.get( filename )
587                def workbook
588                if (importedfile.exists()) {
589                        try {
590                                workbook = importerService.getWorkbook(new FileInputStream(importedfile))
591                        } catch (Exception e) {
592                                log.error ".simple study wizard could not load file: " + e
593                                flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
594                                return false
595                        }
596                } else {
597                        log.error ".simple study wizard no file given";
598                        flash.error = "No file was given. Please provide an excel file for entering samples.";
599                        return false;
600                }
601
602                if( !workbook ) {
603                        log.error ".simple study wizard could not load file into a workbook"
604                        flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
605                        return false
606                }
607
608                def selectedentities = []
609
610                if( !excelChecks( workbook, sheetIndex, headerRow, dataMatrixStart ) )
611                        return false;
612
613                // Get the header from the Excel file using the arguments given in the first step of the wizard
614                def importerHeader;
615                def importerDataMatrix;
616
617                try {           
618                        importerHeader = importerService.getHeader(workbook,
619                                        sheetIndex - 1,                 // 0 == first sheet
620                                        headerRow,                              // 1 == first row :s
621                                        dataMatrixStart - 1,    // 0 == first row
622                                        Sample.class)
623               
624                        importerDataMatrix = importerService.getDatamatrix(
625                                        workbook,
626                                        importerHeader,
627                                        sheetIndex - 1,                 // 0 == first sheet
628                                        dataMatrixStart - 1,    // 0 == first row
629                                        5)
630                } catch( Exception e ) {
631                        // An error occurred while reading the excel file.
632                        log.error ".simple study wizard error while reading the excel file";
633                        e.printStackTrace();
634
635                        // Show a message to the user
636                        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.";
637                        return false;
638                }
639
640                // Match excel columns with template fields
641                def fieldNames = [];
642                flow.sampleForm.template.each { template ->
643                        if( template.value ) {
644                                def fields = template.value.entity.giveDomainFields() + template.value.getFields();
645                                fields.each { field ->
646                                        if( !field.entity )
647                                                field.entity = template.value.entity
648                                               
649                                        fieldNames << field
650                                }
651                        }
652                }
653                importerHeader.each { mc ->
654                        def bestfit = importerService.mostSimilar( mc.name, fieldNames, 0.8);
655                        if( bestfit ) {
656                                // Remove this fit from the list
657                                fieldNames.remove( bestfit );
658                               
659                                mc.entityclass = bestfit.entity
660                                mc.property = bestfit.name
661                        }
662                }
663               
664                // Save read excel data into session
665                def dataMatrix = [];
666                def df = new DataFormatter();
667                importerDataMatrix.each {
668                        dataMatrix << it.collect{ it ? df.formatCellValue(it) : "" }
669                }
670               
671                flow.excel = [
672                                        filename: filename,
673                                        sheetIndex: sheetIndex,
674                                        dataMatrixStart: dataMatrixStart,
675                                        headerRow: headerRow,
676                                        data: [
677                                                header: importerHeader,
678                                                dataMatrix: dataMatrix
679                                        ]
680                                ]
681
682                return true
683        }
684       
685        /**
686         * Copies data from the submitted sample form to the flow
687         * @param study
688         * @param params
689         * @param flow
690         * @return
691         */
692        protected def handleSampleForm( study, params, flow ) {
693                def sampleTemplateId  = params.long( 'sample_template_id' )
694                def subjectTemplateId  = params.long( 'subject_template_id' )
695                def eventTemplateId  = params.long( 'event_template_id' )
696                def samplingEventTemplateId  = params.long( 'samplingEvent_template_id' )
697
698                // Save form data in session
699                if( !flow.sampleForm )
700                        flow.sampleForm = [:]
701                       
702                flow.sampleForm.templateId = [
703                                                'Subject': subjectTemplateId,
704                                                'Event': eventTemplateId,
705                                                'SamplingEvent': samplingEventTemplateId,
706                                                'Sample': sampleTemplateId
707                ];
708                flow.sampleForm.template = [
709                                                'Subject': subjectTemplateId ? Template.get( subjectTemplateId ) : null,
710                                                'Event': eventTemplateId ? Template.get( eventTemplateId ) : null,
711                                                'SamplingEvent': samplingEventTemplateId ? Template.get( samplingEventTemplateId ) : null,
712                                                'Sample': sampleTemplateId ? Template.get( sampleTemplateId ) : null
713                ];
714        }
715       
716        /**
717         * Handles the matching of template fields with excel columns by the user
718         * @param study         Study to update
719         * @param params        Request parameter map
720         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error
721         *                                      The field session.simpleWizard.imported.numInvalidEntities reflects the number of
722         *                                      entities that have errors, and should be fixed before saving. The errors for those entities
723         *                                      are saved into session.simpleWizard.imported.errors
724         */
725        def handleColumns( study, params, flow ) {
726                // Find actual Template object from the chosen template name
727                def templates = [:];
728                flow.sampleForm.templateId.each {
729                        templates[ it.key ] = it.value ? Template.get( it.value ) : null;
730                }
731               
732                def headers = flow.excel.data.header;
733
734                if( !params.matches ) {
735                        log.error( ".simple study wizard no column matches given" );
736                        flash.error = "No column matches given";
737                        return false;
738                }
739
740                // Retrieve the chosen matches from the request parameters and put them into
741                // the headers-structure, for later reference
742                params.matches.index.each { columnindex, value ->
743                        // Determine the entity and property by splitting it
744                        def parts = value.toString().tokenize( "||" );
745                       
746                        def property
747                        def entityName
748                        if( parts.size() > 1 ) {
749                                property = parts[ 1 ];
750                                entityName = "dbnp.studycapturing." + parts[ 0 ];
751                        } else if( parts.size() == 1 ) {
752                                property = parts[ 0 ];
753                                entityName = headers[columnindex.toInteger()].entityclass.getName();
754                        }
755                       
756                        // Create an actual class instance of the selected entity with the selected template
757                        // This should be inside the closure because in some cases in the advanced importer, the fields can have different target entities
758                        def entityClass = Class.forName( entityName, true, this.getClass().getClassLoader())
759                        def entityObj = entityClass.newInstance(template: templates[ entityName[entityName.lastIndexOf( '.' ) + 1..-1] ])
760
761                        headers[ columnindex.toInteger() ].entityclass = entityClass
762                       
763                        // Store the selected property for this column into the column map for the ImporterService
764                        headers[columnindex.toInteger()].property = property
765
766                        // Look up the template field type of the target TemplateField and store it also in the map
767                        headers[columnindex.toInteger()].templatefieldtype = entityObj.giveFieldType(property)
768
769                        // Is a "Don't import" property assigned to the column?
770                        headers[columnindex.toInteger()].dontimport = (property == "dontimport") ? true : false
771
772                        //if it's an identifier set the mapping column true or false
773                        entityClass.giveDomainFields().each {
774                                headers[columnindex.toInteger()].identifier = ( it.preferredIdentifier && (it.name == property) )
775                        }
776                }
777
778                // Import the workbook and store the table with entity records and store the failed cells
779                //println "Importing samples for study " + study + " (" + study.id + ")";
780               
781                def importedfile = fileService.get( flow.excel.filename )
782                def workbook
783                if (importedfile.exists()) {
784                        try {
785                                workbook = importerService.getWorkbook(new FileInputStream(importedfile))
786                        } catch (Exception e) {
787                                log.error ".simple study wizard could not load file: " + e
788                                flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
789                                return false
790                        }
791                } else {
792                        log.error ".simple study wizard no file given";
793                        flash.error = "No file was given. Please provide an excel file for entering samples.";
794                        return false;
795                }
796
797                if( !workbook ) {
798                        log.error ".simple study wizard could not load file into a workbook"
799                        flash.error = "The given file doesn't seem to be an excel file. Please provide an excel file for entering samples.";
800                        return false
801                }
802                       
803                def imported = importerService.importOrUpdateDataBySampleIdentifier(templates,
804                                workbook,
805                                flow.excel.sheetIndex - 1,
806                                flow.excel.dataMatrixStart - 1,
807                                flow.excel.data.header,
808                                flow.study,
809                                true                    // Also create entities for which no data is imported but where templates were chosen
810                );
811
812                def table = imported.table
813                def failedcells = imported.failedCells
814
815                flow.imported = [
816                        data: table,
817                        failedCells: failedcells
818                ];
819       
820                // loop through all entities to validate them and add them to failedcells if an error occurs
821                def numInvalidEntities = 0;
822                def errors = [];
823
824                // Add all samples
825                table.each { record ->
826                        record.each { entity ->
827                                if( entity ) {
828                                        // Determine entity class and add a parent. Add the entity to the study
829                                        def preferredIdentifier = importerService.givePreferredIdentifier( entity.class );
830                                        def equalClosure = { it.getIdentifier() == entity.getIdentifier() }
831                                        def entityName = entity.class.name[ entity.class.name.lastIndexOf( "." ) + 1 .. -1 ]
832
833                                        entity.parent = study
834                                       
835                                        switch( entity.class ) {
836                                                case Sample:
837                                                        if( !study.samples?.find( equalClosure ) ) {
838                                                                study.addToSamples( entity );
839                                                        }
840                                                       
841                                                        // If an eventgroup is created, add it to the study
842                                                        // The eventgroup must have a unique name, but the user shouldn't be bothered with it
843                                                        // Add 'group ' + samplename and it that is not unique, add a number to it
844                                                        if( entity.parentEventGroup ) {
845                                                                study.addToEventGroups( entity.parentEventGroup )
846
847                                                                entity.parentEventGroup.name = "Group " + entity.name
848                                                                while( !entity.parentEventGroup.validate() ) {
849                                                                        //entity.parentEventGroup.getErrors().each { println it }
850                                                                        entity.parentEventGroup.name += "" + Math.floor( Math.random() * 100 )
851                                                                }
852                                                        }
853                                                       
854                                                        break;
855                                                case Subject:
856                                                        if( !study.samples?.find( equalClosure ) ) {
857                                                               
858                                                                if( preferredIdentifier ) {
859                                                                        // Subjects without a name should just be called 'subject'
860                                                                        if( !entity.getFieldValue( preferredIdentifier.name ) )
861                                                                                entity.setFieldValue( preferredIdentifier.name, "Subject" );
862                                                               
863                                                                        // Subjects should have unique names; if the user has entered the same name multiple times,
864                                                                        // the subject will be renamed
865                                                                        def baseName = entity.getFieldValue( preferredIdentifier.name )
866                                                                        def counter = 2;
867                                                                       
868                                                                        while( study.subjects?.find { it.getFieldValue( preferredIdentifier.name ) == entity.getFieldValue( preferredIdentifier.name ) } ) {
869                                                                                entity.setFieldValue( preferredIdentifier.name, baseName + " (" + counter++ + ")" )
870                                                                        }
871                                                                }
872                                                               
873                                                                study.addToSubjects( entity );
874                                                       
875                                                        }
876                                                       
877                                                        break;
878                                                case Event:
879                                                        if( !study.events?.find( equalClosure ) ) {
880                                                                study.addToEvents( entity );
881                                                        }
882                                                        break;
883                                                case SamplingEvent:
884                                                        // Sampling events have a 'sampleTemplate' value, which should be filled by the
885                                                        // template that is chosen for samples.
886                                                        if( !entity.getFieldValue( 'sampleTemplate' ) ) {
887                                                                entity.setFieldValue( 'sampleTemplate', flow.sampleForm.template.Sample.name )
888                                                        }
889                                               
890                                                        if( !study.samplingEvents?.find( equalClosure ) ) {
891                                                                study.addToSamplingEvents( entity );
892                                                        }
893                                                        break;
894                                        }
895                                       
896                                        if (!entity.validate()) {
897                                                numInvalidEntities++;
898                                               
899                                                // Add this field to the list of failed cells, in order to give the user feedback
900                                                failedcells = addNonValidatingCells( failedcells, entity, flow )
901       
902                                                // Also create a full list of errors
903                                                def currentErrors = getHumanReadableErrors( entity )
904                                                if( currentErrors ) {
905                                                        currentErrors.each {
906                                                                errors += "(" + entityName + ") " + it.value;
907                                                        }
908                                                }
909                                        }
910                                }
911                        }
912                }
913
914                flow.imported.numInvalidEntities = numInvalidEntities + failedcells?.size();
915                flow.imported.errors = errors;
916
917                return true
918        }
919       
920        /**
921         * Handles the update of the edited fields by the user
922         * @param study         Study to update
923         * @param params                Request parameter map
924         * @return                      True if everything went OK, false otherwise. An error message is put in flash.error.
925         *                                      The field session.simpleWizard.imported.numInvalidEntities reflects the number of
926         *                                      entities that still have errors, and should be fixed before saving. The errors for those entities
927         *                                      are saved into session.simpleWizard.imported.errors
928         */
929        def handleMissingFields( study, params, flow ) {
930                def numInvalidEntities = 0;
931                def errors = [];
932
933                // Check which fields failed previously
934                def failedCells = flow.imported.failedCells
935                def newFailedCells = [];
936
937                flow.imported.data.each { table ->
938                        table.each { entity ->
939                                def invalidFields = 0
940                                def failed = new ImportRecord();
941                                def entityName = entity.class.name[ entity.class.name.lastIndexOf( "." ) + 1 .. -1 ]
942                               
943
944                                // Set the fields for this entity by retrieving values from the params
945                                entity.giveFields().each { field ->
946                                        def fieldName = importerService.getFieldNameInTableEditor( entity, field );
947
948                                        if( params[ fieldName ] == "#invalidterm" ) {
949                                                // If the value '#invalidterm' is chosen, the user hasn't fixed anything, so this field is still incorrect
950                                                invalidFields++;
951                                               
952                                                // store the mapping column and value which failed
953                                                def identifier = entityName.toLowerCase() + "_" + entity.getIdentifier() + "_" + fieldName
954                                                def mcInstance = new MappingColumn()
955                                                failed.addToImportcells(new ImportCell(mappingcolumn: mcInstance, value: params[ fieldName ], entityidentifier: identifier))
956                                        } else {
957                                                if( field.type == org.dbnp.gdt.TemplateFieldType.ONTOLOGYTERM || field.type == org.dbnp.gdt.TemplateFieldType.STRINGLIST ) {
958                                                        // If this field is an ontologyterm field or a stringlist field, the value has changed, so remove the field from
959                                                        // the failedCells list
960                                                        importerService.removeFailedCell( failedCells, entity, field )
961                                                }
962
963                                                // Update the field, regardless of the type of field
964                                                entity.setFieldValue(field.name, params[ fieldName ] )
965                                        }
966                                }
967                               
968                                // Try to validate the entity now all fields have been set. If it fails, return an error
969                                if (!entity.validate() || invalidFields) {
970                                        numInvalidEntities++;
971
972                                        // Add this field to the list of failed cells, in order to give the user feedback
973                                        failedCells = addNonValidatingCellsToImportRecord( failed, entity, flow )
974
975                                        // Also create a full list of errors
976                                        def currentErrors = getHumanReadableErrors( entity )
977                                        if( currentErrors ) {
978                                                currentErrors.each {
979                                                        errors += "(" + entityName + ") " + it.value;
980                                                }
981                                        }
982                                       
983                                        newFailedCells << failed;
984                                } else {
985                                        importerService.removeFailedCell( failedCells, entity )
986                                }
987                        } // end of record
988                } // end of table
989
990                flow.imported.failedCells = newFailedCells
991                flow.imported.numInvalidEntities = numInvalidEntities;
992                flow.imported.errors = errors;
993
994                return numInvalidEntities == 0
995        }
996       
997        /**
998        * Handles assay input
999        * @param study          Study to update
1000        * @param params         Request parameter map
1001        * @return                       True if everything went OK, false otherwise. An error message is put in flash.error
1002        */
1003   def handleAssays( assay, params, flow ) {
1004           // did the study template change?
1005           if (params.get('template') && assay.template?.name != params.get('template')) {
1006                   // set the template
1007                   assay.template = Template.findByName(params.remove('template'))
1008           }
1009
1010           // does the study have a template set?
1011           if (assay.template && assay.template instanceof Template) {
1012                   // yes, iterate through template fields
1013                   assay.giveFields().each() {
1014                           // and set their values
1015                           assay.setFieldValue(it.name, params.get(it.escapedName()))
1016                   }
1017           }
1018
1019           return true
1020   }
1021       
1022       
1023        /**
1024         * Checks whether the given study is simple enough to be edited using this controller.
1025         *
1026         * The study is simple enough if the samples, subjects, events and samplingEvents can be
1027         * edited as a flat table. That is:
1028         *              - Every subject belongs to 0 or 1 eventgroup
1029         *              - Every eventgroup belongs to 0 or 1 sample
1030         *              - Every eventgroup has 0 or 1 subjects, 0 or 1 event and 0 or 1 samplingEvents
1031         *              - If a sample belongs to an eventgroup:
1032         *                      - If that eventgroup has a samplingEvent, that same samplingEvent must also be
1033         *                              the sampling event that generated this sample
1034         *                      - If that eventgroup has a subject, that same subject must also be the subject
1035         *                              from whom the sample was taken
1036         *
1037         * @param study         Study to check
1038         * @return                      True if the study can be edited by this controller, false otherwise
1039         */
1040        def checkStudySimplicity( study ) {
1041                def simplicity = true;
1042
1043                if( !study )
1044                        return false
1045
1046                if( study.eventGroups ) {
1047                        study.eventGroups.each { eventGroup ->
1048                                // Check for simplicity of eventgroups: only 0 or 1 subject, 0 or 1 event and 0 or 1 samplingEvent
1049                                if( eventGroup.subjects?.size() > 1 || eventGroup.events?.size() > 1 || eventGroup.samplingEvents?.size() > 1 ) {
1050                                        flash.message = "One or more eventgroups contain multiple subjects or events."
1051                                        simplicity = false;
1052                                }
1053
1054                                // Check whether this eventgroup only belongs to (max) 1 sample
1055                                def numSamples = 0;
1056                                study.samples.each { sample ->
1057                                        // If no id is given for the eventGroup, it has been entered in this wizard, but
1058                                        // not yet saved. In that case, it is always OK
1059                                        if( eventGroup.id && sample.parentEventGroup?.id == eventGroup.id )
1060                                                numSamples++;
1061                                }
1062
1063                                if( numSamples > 1 ) {
1064                                        flash.message = "One or more eventgroups belong to multiple samples."
1065                                        simplicity = false;
1066                                }
1067                        }
1068
1069                        if( !simplicity ) return false;
1070
1071                        // Check whether subject only belong to zero or one event group
1072                        if( study.subjects ) {
1073                                study.subjects.each { subject ->
1074                                        def numEventGroups = 0
1075                                        study.eventGroups.each { eventGroup ->
1076                                                // If no id is given for the subject, it has been entered in this wizard, but
1077                                                // not yet saved. In that case, it is always OK
1078                                                if( subject.id && eventGroup.subjects && eventGroup.subjects.toList()[0]?.id == subject.id )
1079                                                        numEventGroups++
1080                                        }
1081
1082                                        if( numEventGroups > 1 ) {
1083                                                flash.message = "One or more subjects belong to multiple eventgroups."
1084                                                simplicity = false;
1085                                        }
1086                                }
1087                        }
1088
1089                        if( !simplicity ) return false;
1090
1091                        // Check whether the samples that belong to an eventgroup have the right parentObjects
1092                        study.samples.each { sample ->
1093                                if( sample.parentEventGroup ) {
1094                                        // If no id is given for the subject, it has been entered in this wizard, but
1095                                        // not yet saved. In that case, it is always OK
1096                                        if( sample.parentSubject && sample.parentSubject.id) {
1097                                                if( !sample.parentEventGroup.subjects || sample.parentEventGroup.subjects.toList()[0]?.id != sample.parentSubject.id ) {
1098                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
1099                                                        simplicity = false;
1100                                                }
1101                                        }
1102
1103                                        // If no id is given for the sampling event, it has been entered in this wizard, but
1104                                        // not yet saved. In that case, it is always OK
1105                                        if( sample.parentEvent && sample.parentEvent.id) {
1106                                                if( !sample.parentEventGroup.samplingEvents || sample.parentEventGroup.samplingEvents.toList()[0]?.id != sample.parentEvent.id ) {
1107                                                        flash.message = "The structure of the eventgroups of one or more samples is too complex"
1108                                                        simplicity = false;
1109                                                }
1110                                        }
1111                                }
1112                        }
1113
1114                        if( !simplicity ) return false;
1115                }
1116
1117                return simplicity;
1118        }
1119
1120       
1121        /**
1122         * Adds all fields of this entity that have given an error when validating to the failedcells list
1123         * @param failedcells   Current list of ImportRecords
1124         * @param entity                Entity to check. The entity must have been validated before
1125         * @return                              Updated list of ImportRecords
1126         */
1127        protected def addNonValidatingCells( failedcells, entity, flow ) {
1128                // Add this entity and the fields with an error to the failedCells list
1129                ImportRecord failedRecord = addNonValidatingCellsToImportRecord( new ImportRecord(), entity, flow );
1130
1131                failedcells.add( failedRecord );
1132
1133                return failedcells
1134        }
1135       
1136        /**
1137        * Adds all fields of this entity that have given an error when validating to the failedcells list
1138        * @param failedcells    Current list of ImportRecords
1139        * @param entity         Entity to check. The entity must have been validated before
1140        * @return                               Updated list of ImportRecords
1141        */
1142   protected def addNonValidatingCellsToImportRecord( failedRecord, entity, flow ) {
1143           entity.getErrors().getFieldErrors().each { error ->
1144                   String field = error.getField();
1145                   
1146                   def mc = importerService.findMappingColumn( flow.excel.data.header, field );
1147                   def mcInstance = new MappingColumn( name: field, entityClass: Sample.class, index: -1, property: field.toLowerCase(), templateFieldType: entity.giveFieldType( field ) );
1148
1149                   // Create a clone of the mapping column
1150                   if( mc ) {
1151                           mcInstance.properties = mc.properties
1152                   }
1153
1154                   failedRecord.addToImportcells( new ImportCell(mappingcolumn: mcInstance, value: error.getRejectedValue(), entityidentifier: importerService.getFieldNameInTableEditor( entity, field ) ) )
1155           }
1156           
1157           return failedRecord
1158   }
1159
1160       
1161        /**
1162        * Checks an excel workbook whether the given sheetindex and rownumbers are correct
1163        * @param workbook                       Excel workbook to read
1164        * @param sheetIndex             1-based sheet index for the sheet to read (1=first sheet)
1165        * @param headerRow                      1-based row number for the header row (1=first row)
1166        * @param dataMatrixStart        1-based row number for the first data row (1=first row)
1167        * @return                                       True if the sheet index and row numbers are correct.
1168        */
1169   protected boolean excelChecks( def workbook, int sheetIndex, int headerRow, int dataMatrixStart ) {
1170           // Perform some basic checks on the excel file. These checks should be performed by the importerservice
1171           // in a perfect scenario.
1172           if( sheetIndex > workbook.getNumberOfSheets() ) {
1173                   log.error ".simple study wizard Sheet index is too high: " + sheetIndex + " / " + workbook.getNumberOfSheets();
1174                   flash.error = "Your excel sheet contains too few excel sheets. The provided excel sheet has only " + workbook.getNumberOfSheets() + " sheet(s).";
1175                   return false
1176           }
1177
1178           def sheet = workbook.getSheetAt(sheetIndex - 1);
1179           def firstRowNum = sheet.getFirstRowNum();
1180           def lastRowNum = sheet.getLastRowNum();
1181           def numRows = lastRowNum - firstRowNum + 1;
1182
1183           if( headerRow > numRows  ) {
1184                   log.error ".simple study wizard Header row number is incorrect: " + headerRow + " / " + numRows;
1185                   flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
1186                   return false
1187           }
1188
1189           if( dataMatrixStart > numRows  ) {
1190                   log.error ".simple study wizard Data row number is incorrect: " + dataMatrixStart + " / " + numRows;
1191                   flash.error = "Your excel sheet doesn't contain enough rows (" + numRows + "). Please provide an excel sheet with one header row and data below";
1192                   return false
1193           }
1194
1195           return true;
1196   }
1197       
1198        /**
1199         * Validates an object and puts human readable errors in validationErrors variable
1200         * @param entity                Entity to validate
1201         * @return                      True iff the entity validates, false otherwise
1202         */
1203        protected boolean validateObject( def entity ) {
1204                if( !entity.validate() ) {
1205                        flash.validationErrors = getHumanReadableErrors( entity )
1206                        return false;
1207                }
1208                return true;
1209        }
1210
1211        /**
1212         * transform domain class validation errors into a human readable
1213         * linked hash map
1214         * @param object validated domain class
1215         * @return object  linkedHashMap
1216         */
1217        def getHumanReadableErrors(object) {
1218                def errors = [:]
1219                object.errors.getAllErrors().each() { error ->
1220                        // error.codes.each() { code -> println code }
1221
1222                        // generally speaking g.message(...) should work,
1223                        // however it fails in some steps of the wizard
1224                        // (add event, add assay, etc) so g is not always
1225                        // availably. Using our own instance of the
1226                        // validationTagLib instead so it is always
1227                        // available to us
1228                        errors[error.getArguments()[0]] = validationTagLib.message(error: error)
1229                }
1230
1231                return errors
1232        }
1233
1234    // TEMPORARY ACTION TO TEST CORRECT STUDY DESIGN INFERRING: SHOULD BE REMOVED WHEN DONE
1235    def testMethod = {
1236
1237        ////////////////////////////////////////////////////////////////////////
1238        // Functionality mimics intermediate results from simple wizard       //
1239        ////////////////////////////////////////////////////////////////////////
1240
1241        def data = [//['sample name',         'subject',         'timepoint'],
1242                    [ '97___N_151_HAKA_1',    'N_151_HAKA',    '0w'],
1243                    [ '98___N_163_QUJO_3',    'N_163_QUJO',    '2w'],
1244                    [ '99___N_151_HAKA_2',    'N_151_HAKA',    '1w'],
1245                    ['100___N_163_QUJO_4',    'N_163_QUJO',    '3w'],
1246                    ['101___N_151_HAKA_3',    'N_151_HAKA',    '2w'],
1247                    ['102___N_163_QUJO_2',    'N_163_QUJO',    '1w'],
1248                    ['103___U_031_SMGI_1',    'U_031_SMGI',    '0w'],
1249                    ['104___U_031_SMGI_3',    'U_031_SMGI',    '2w'],
1250                    ['105___N_163_QUJO_1',    'N_163_QUJO',    '0w'],
1251                    ['106___U_031_SMGI_4',    'U_031_SMGI',    '3w'],
1252                    ['107___N_151_HAKA_4',    'N_151_HAKA',    '3w'],
1253                    ['108___U_031_SMGI_2',    'U_031_SMGI',    '1w'],
1254                    ['109___N_021_THAA_2',    'N_021_THAA',    '1w'],
1255                    ['110___U_029_DUJA_2',    'U_029_DUJA',    '1w'],
1256                    ['111___U_029_DUJA_3',    'U_029_DUJA',    '2w'],
1257                    ['112___N_021_THAA_3',    'N_021_THAA',    '2w'],
1258                    ['113___U_029_DUJA_4',    'U_029_DUJA',    '3w'],
1259                    ['114___N_045_SNSU_1',    'N_045_SNSU',    '0w'],
1260                    ['115___N_021_THAA_1',    'N_021_THAA',    '0w'],
1261                    ['116___N_045_SNSU_2',    'N_045_SNSU',    '1w'],
1262                    ['117___N_045_SNSU_3',    'N_045_SNSU',    '2w'],
1263                    ['118___N_045_SNSU_4',    'N_045_SNSU',    '3w'],
1264                    ['119___N_021_THAA_4',    'N_021_THAA',    '3w'],
1265                    ['120___U_029_DUJA_1',    'U_029_DUJA',    '0w'],
1266                    ['121___U_060_BRGE_3',    'U_060_BRGE',    '2w'],
1267                    ['122___N_018_WIHA_1',    'N_018_WIHA',    '0w'],
1268                    ['123___N_022_HUCA_3',    'N_022_HUCA',    '2w'],
1269                    ['124___N_022_HUCA_2',    'N_022_HUCA',    '1w']]
1270
1271        def sampleTemplate = Template.findByName ('Human blood sample')
1272        def subjectTemplate = Template.findByName ('Human')
1273        def samplingEventTemplate = Template.findByName ('Blood extraction')
1274//        def eventTemplate = Template.findByName ('Diet treatment')
1275
1276
1277        // Table is a collection of records. A records contains entities of type
1278        // Sample, Subject, and SamplingEvent. This mimics the output of
1279        // importerService.importOrUpdateDataBySampleIdentifier
1280        def table = data.collect { row ->
1281
1282            [       new Sample( name: row[0], template: sampleTemplate),
1283                    new Subject(name: row[1], template: subjectTemplate),
1284                    new SamplingEvent(template: samplingEventTemplate).setFieldValue('startTime', row[2])
1285//                    new Event(template: eventTemplate)
1286            ]
1287
1288        }
1289
1290        ////////////////////////////////////////////////////////////////////////
1291        // Functionality below should be inserted into simple wizard
1292        // We'll assume entities with pre-existing preferred identifiers have
1293        // already been loaded from the db so that updating will work.
1294        ////////////////////////////////////////////////////////////////////////
1295
1296        def inferStudyDesign = { study ->
1297
1298            // Check for duplicate samples
1299            def samples = table.collect{it[0]}
1300
1301            def uniques     = [] as Set
1302            def duplicates  = [] as Set
1303
1304            // this approach separates the unique from the duplicate entries
1305            samples*.name.each {
1306                uniques.add(it) || duplicates.add(it)
1307            }
1308
1309            duplicates.each{ duplicateName ->
1310                samples.findAllByName(duplicateName).each{ sample ->
1311                    numInvalidEntities++
1312                    failedcells = addNonValidatingCells(failedcells, sample, flow)
1313                    errors += "(Sample) duplicate name: $duplicateName"
1314                }
1315            }
1316
1317            // A closure that returns a sub list of entities from a list that have
1318            // unique values of a property indicated by propertyName
1319            def uniqueEntitiesByProperty = { entities, propertyName ->
1320
1321               entities*."$propertyName".unique().collect { uniquePropertyValue ->
1322
1323                    entities.find{ it."$propertyName" == uniquePropertyValue }
1324
1325                }
1326            }
1327
1328            // collect unique subjects and sampling events from table
1329            def uniqueSubjects =
1330                uniqueEntitiesByProperty(table.collect{it[1]}, 'name')
1331            def uniqueSamplingEvents =
1332                uniqueEntitiesByProperty(table.collect{it[2]}, 'startTime')
1333
1334            // create an event group for each unique sampling event (not much of a group, is it ...)
1335            uniqueSamplingEvents.each{
1336                new EventGroup(name: "Sampling_${it.name}_${it.startTime}").addToSamplingEvents(it)
1337                study.addToEventGroups eventGroup
1338            }
1339
1340            def addToCollectionIfNecessary = { parent, collectionName, entity, propertyName ->
1341
1342                if (!parent.'collectionName'.find{it.'propertyName' == entity.'propertyName'})
1343                    parent."addTo${collectionName.toUpperCase()}" entity
1344
1345            }
1346
1347            table.each{ record ->
1348
1349                Sample sample = record[0]
1350
1351                // gather all sample related entities
1352                def correspondingSamplingEvent  = uniqueSamplingEvents.findByStartTime(record[2].startTime)
1353                def correspondingSubject        = uniqueSubjects.findByName(record[1].name)
1354                def correspondingEventGroup     = correspondingSamplingEvent.eventGroup
1355
1356                correspondingSamplingEvent.addToSamples sample
1357                correspondingSubject.addToSamples       sample
1358                correspondingEventGroup.addToSamples    sample
1359
1360                if (!correspondingEventGroup.subjects.find{it.name == correspondingSubject.name})
1361                    correspondingEventGroup.addToSubjects correspondingSubject
1362
1363                study.addToSamples sample
1364            }
1365        }
1366
1367        // inferStudyDesign(study)
1368
1369        println 'hoi'
1370        render 'bla'
1371
1372    }
1373}
Note: See TracBrowser for help on using the browser.