source: trunk/grails-app/controllers/dbnp/studycapturing/SimpleWizardController.groovy @ 1811

Last change on this file since 1811 was 1811, checked in by s.h.sikkema@…, 9 years ago

study design inferring in progress

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