source: trunk/grails-app/controllers/dbnp/studycapturing/WizardController.groovy @ 739

Last change on this file since 739 was 739, checked in by duh, 11 years ago
  • showing a bigger warning message on the edit study wizard page, and disabled saving of editted studies in production environment because this leads to data inconsitency (known issues: entities and data staying in the database without being linked to anything, event groupings is messed up, and probably a lot more)
  • Property svn:keywords set to Date Author Rev
File size: 39.1 KB
Line 
1package dbnp.studycapturing
2
3import dbnp.data.*
4
5// Grails convertors is imported in order to create JSON objects
6import grails.converters.*
7
8
9/**
10 * Wizard Controler
11 *
12 * The wizard controller handles the handeling of pages and data flow
13 * through the study capturing wizard.
14 *
15 * @author Jeroen Wesbeek
16 * @since 20100107
17 * @package studycapturing
18 *
19 * Revision information:
20 * $Rev: 739 $
21 * $Author: duh $
22 * $Date: 2010-07-28 11:31:14 +0000 (wo, 28 jul 2010) $
23 */
24class WizardController {
25        /**
26         * index method, redirect to the webflow
27         * @void
28         */
29        def index = {
30                def jump = [:]
31
32                // allow quickjumps to:
33                //      edit a study    : /wizard?jump=edit&id=1
34                //      create a study  : /wizard?jump=create
35                if (params.get('jump')) {
36                        switch (params.get('jump')) {
37                                case 'create':
38                                        jump = [
39                                            action: 'create'
40                                        ]
41                                        break
42                                case 'edit':
43                                        jump = [
44                                            action      : 'edit',
45                                                id              : params.get('id')
46                                        ]
47                                        break
48                                default:
49                                        break
50                        }
51                }
52
53                // store in session
54                session.jump = jump
55
56                /**
57                 * Do you believe it in your head?
58                 * I can go with the flow
59                 * Don't say it doesn't matter (with the flow) matter anymore
60                 * I can go with the flow (I can go)
61                 * Do you believe it in your head?
62                 */
63                redirect(action: 'pages')
64        }
65
66        /**
67         * WebFlow definition
68         * @see http://grails.org/WebFlow
69         * @void
70         */
71        def pagesFlow = {
72                // start the flow
73                onStart {
74                        // define flow variables
75                        flow.page = 0
76                        flow.pages = [
77                                //[title: 'Templates'],                 // templates
78                                [title: 'Start'],                               // load or create a study
79                                [title: 'Study'],                               // study
80                                [title: 'Subjects'],                    // subjects
81                                [title: 'Events'],                              // events and event grouping
82                                [title: 'Groups'],                              // groups
83                                [title: 'Samples'],                             // samples
84                                [title: 'Confirmation'],                // confirmation page
85                                [title: 'Done']                                 // finish page
86                        ]
87                        flow.jump = session.jump
88                        success()
89                }
90
91                // render the main wizard page which immediately
92                // triggers the 'next' action (hence, the main
93                // page dynamically renders the study template
94                // and makes the flow jump to the study logic)
95                mainPage {
96                        render(view: "/wizard/index")
97                        onRender {
98                                flow.page = 1
99                                success()
100                        }
101                        on("next").to "handleJump"
102                }
103
104                // handle the jump parameter
105                //
106                // I came to get down [2x]
107                // So get out your seats and jump around
108                // Jump around [3x]
109                // Jump up Jump up and get down
110                // Jump [18x]
111                handleJump {
112                        action {
113                                if (flow.jump && flow.jump.action == 'edit' && flow.jump.id) {
114                                        // load study
115                                        if (this.loadStudy(flow, flash, [studyid:flow.jump.id])) {
116                                                toStudyPage()
117                                        } else {
118                                                toStartPage()
119                                        }
120                                } else if (flow.jump && flow.jump.action == 'create') {
121                                        toStudyPage()
122                                } else {
123                                        toStartPage()
124                                }
125                        }
126                        on("toStartPage").to "start"
127                        on("toStudyPage").to "study"
128                }
129
130                // create or modify a study
131                start {
132                        render(view: "_start")
133                        onRender {
134                                flow.page = 1
135                                success()
136                        }
137                        on("next") {
138                                // clean the flow scope
139                                flow.remove('study')
140                                flow.remove('subjects')
141                                flow.remove('subjectTemplates')
142                                flow.remove('event')
143                                flow.remove('events')
144                                flow.remove('eventGroups')
145                                flow.remove('eventTemplates')
146                                flow.remove('samples')
147                                flow.remove('sampleTemplates')
148
149                                // set 'quicksave' variable to false
150                                flow.quickSave = false
151                        }.to "study"
152                        on("modify").to "modify"
153                        on("import").to "redirectToImport"
154                }
155
156                // redirect to the import wizard
157                redirectToImport {
158                        render(view: "_redirect")
159                        onRender {
160                                flash.uri = "/importer/index"
161                        }
162                        on("next").to "start"
163                }
164
165                // load a study to modify
166                modify {
167                        render(view: "_modify")
168                        onRender {
169                                flow.page = 1
170                                flash.cancel = true
171                                success()
172                        }
173                        on("cancel") {
174                                flow.study = null
175
176                                success()
177                        }.to "start"
178                        on("next") {
179                                // load study
180                                if (this.loadStudy(flow, flash, params)) {
181                                        success()
182                                } else {
183                                        error()
184                                }
185                        }.to "study"
186                }
187
188                // render and handle the study page
189                study {
190                        render(view: "_study")
191                        onRender {
192                                flow.page = 2
193                                success()
194                        }
195                        on("refresh") {
196                                println ".refreshing...."
197                                flash.values = params
198
199                                // handle study data
200                                this.handleStudy(flow, flash, params)
201
202                                // force refresh of the template
203                                if (flow.study.template) {
204                                        flow.study.template.refresh()
205                                }
206
207                                // remove errors as we don't want any warnings now
208                                flash.errors = [:]
209
210                                success()
211                        }.to "study"
212            on("switchTemplate") {
213                                flash.values = params
214
215                                // handle study data
216                                this.handleStudy(flow, flash, params)
217
218                                // force refresh of the template
219                                if (flow.study.template) {
220                                        flow.study.template.refresh()
221                                }
222
223                                // remove errors as we don't want any warnings now
224                                flash.errors = [:]
225
226                                success()
227                        }.to "study"
228                        on("previous") {
229                                flash.errors = [:]
230
231                                // handle the study
232                                this.handleStudy(flow, flash, params)
233
234                                // reset errors
235                                flash.errors = [:]
236
237                                success()
238                        }.to "start"
239                        on("next") {
240                                flash.errors = [:]
241
242                                if (this.handleStudy(flow, flash, params)) {
243                                        success()
244                                } else {
245                                        error()
246                                }
247                        }.to "subjects"
248                        on("quickSave") {
249                                flash.errors = [:]
250
251                                if (this.handleStudy(flow, flash, params)) {
252                                        success()
253                                } else {
254                                        error()
255                                }
256                        }.to "waitForSave"
257                }
258
259                // render and handle subjects page
260                subjects {
261                        render(view: "_subjects")
262                        onRender {
263                                flow.page = 3
264
265                                if (!flow.subjects) {
266                                        flow.subjects = [:]
267                                        flow.subjectTemplates = [:]
268                                }
269
270                                if (!flash.values) flash.values = [addNumber:1]
271
272                                success()
273                        }
274                        on("refresh") {
275                                flash.values = params
276
277                                // refresh templates
278                                flow.subjectTemplates.each() {
279                                        it.value.template.refresh()
280                                }
281
282                                success()
283                        }.to "subjects"
284                        on("add") {
285                                flash.errors = [:]
286
287                                // handle subjects
288                                this.handleSubjects(flow, flash, params)
289
290                                flash.errors = [:]
291                                flash.values = params
292
293                                def speciesTerm = Term.findByName(params.species)
294                                def subjectTemplateName = params.get('template')
295                                def subjectTemplate = Template.findByName(subjectTemplateName)
296
297                                // got a species and a subjectTemplate?
298                                if (speciesTerm && subjectTemplate) {
299                                        // add this subject template to the subject template array
300                                        if (!flow.subjectTemplates[subjectTemplateName]) {
301                                                flow.subjectTemplates[subjectTemplateName] = [
302                                                        name: subjectTemplateName,
303                                                        template: subjectTemplate,
304                                                        subjects: [:]
305                                                ]
306                                        }
307
308                                        // add x subjects of species y
309                                        (params.addNumber as int).times {
310                                                def increment = (flow.subjects.size()) ? (flow.subjects.keySet().max() + 1) : 0
311                                                def subject = new Subject(
312                                                        name: 'Subject ' + (increment + 1),
313                                                        species: speciesTerm,
314                                                        template: subjectTemplate
315                                                )
316
317                                                // instantiate a new Subject
318                                                flow.subjects[increment] = subject
319
320                                                // add the subject to the study
321                                                flow.study.addToSubjects( subject )
322
323                                                // and remember the subject id with the template
324                                                def subjectsSize = (flow.subjectTemplates[subjectTemplateName].subjects.size()) ? (flow.subjectTemplates[subjectTemplateName].subjects.keySet().max() + 1) : 0
325                                                flow.subjectTemplates[subjectTemplateName].subjects[subjectsSize] = increment
326                                        }
327
328                                        success()
329                                } else {
330                                        // add feedback
331                                        if (!speciesTerm) this.appendErrorMap(['species': 'You need to select a species, or add one if it is not yet present'], flash.errors)
332                                        if (!subjectTemplate) this.appendErrorMap(['template': 'You need to select a template, or add one if it is not yet present'], flash.errors)
333
334                                        error()
335                                }
336                        }.to "subjects"
337                        on("delete") {
338                                // handle subjects
339                                this.handleSubjects(flow, flash, params)
340
341                                flash.errors = [:]
342                                def delete = params.get('do') as int
343
344                                // remove subject
345                                if (flow.subjects[ delete ] && flow.subjects[ delete ] instanceof Subject) {
346                                        // from study as well
347                                        flow.study.removeFromSubjects( flow.subjects[ delete ] )
348
349                                        // remove subject from templates
350                                        flow.subjectTemplates.each() { templateName, templateData ->
351                                                templateData.subjects.values().remove( delete )
352                                        }
353
354                                        // remove templates that contain no subject
355                                        flow.subjectTemplates.find{!it.value.subjects.size()}.each{ flow.subjectTemplates.remove( it.key ) }
356
357                                        // remove subject altogether
358                                        flow.subjects.remove( delete )
359                                }
360                        }.to "subjects"
361                        on("previous") {
362                                flash.errors = [:]
363
364                                // handle form data
365                                if (!this.handleSubjects(flow, flash, params)) {
366                                        error()
367                                } else {
368                                        success()
369                                }
370                        }.to "study"
371                        on("next") {
372                                flash.errors = [:]
373
374                                // check form data
375                                if (!this.handleSubjects(flow, flash, params)) {
376                                        error()
377                                } else {
378                                        success()
379                                }
380                        }.to "events"
381                        on("quickSave") {                               
382                                flash.errors = [:]
383
384                                // check form data
385                                if (!this.handleSubjects(flow, flash, params)) {
386                                        error()
387                                } else {
388                                        success()
389                                }
390                        }.to "waitForSave"
391                }
392
393                // render events page
394                events {
395                        render(view: "_events")
396                        onRender {
397                                flow.page = 4
398
399                                if (!flow.event) {
400                                        flow.event                      = new Event()
401                                        flow.events                     = [:]
402                                        flow.eventGroups        = [ new EventGroup(name: 'Group 1') ]
403                                        flow.eventTemplates     = [:]
404
405                                        // add initial eventGroup to study
406                                        flow.study.addToEventGroups( flow.eventGroups[ 0 ] )
407                                } else if (!flash.values) {
408                                        // set flash.values.templateType based on the event instance
409                                        flash.values = [:]
410                                        flash.values.templateType = (flow.event instanceof Event) ? 'event' : 'sample'
411                                }
412                                success()
413                        }
414                        on("clear") {
415                                flow.remove('event')
416                                success()
417                        }.to "events"
418                        on("switchTemplate") {
419                                flash.values = params
420
421                                // handle study data
422                                this.handleEvents(flow, flash, params)
423
424                                // refresh event templates
425                                flow.eventTemplates.each() {
426                                        it.value.template.refresh()
427                                }
428
429                                // refresh flow template
430                                if (flow.event.template) flow.event.template.refresh()
431
432                                // remove errors as we don't want any warnings now
433                                flash.errors = [:]
434                        }.to "events"
435                        on("refresh") {
436                                flash.values = params
437
438                                // handle study data
439                                this.handleEvents(flow, flash, params)
440
441                                // refresh templates
442                                flow.eventTemplates.each() {
443                                        it.value.template.refresh()
444                                }
445
446                                // refresh flow template
447                                if (flow.event.template) flow.event.template.refresh()
448
449                                // remove errors as we don't want any warnings now
450                                flash.errors = [:]
451                        }.to "events"
452                        on("add") {
453                                flash.values                    = params
454                                def eventTemplateName   = (params.get('eventType') == 'event') ? params.get('eventTemplate') : params.get('sampleTemplate')
455                                def eventTemplate               = Template.findByName(eventTemplateName)
456
457                                // handle study data
458                                this.handleEvents(flow, flash, params)
459
460                                // validate event object
461                                if (flow.event.validate()) {
462                                        // add this event template to the event template array
463                                        if (!flow.eventTemplates[ eventTemplateName ]) {
464                                                flow.eventTemplates[ eventTemplateName ] = [
465                                                        name: eventTemplateName,
466                                                        template: eventTemplate,
467                                                        events: []
468                                                ]
469                                        }
470
471                                        // it validated! Duplicate the event object...
472                                        def newEvent    = flow.event
473                                        def increment   = flow.events.size()
474
475                                        // ...store it in the events map in the flow scope...
476                                        flow.events[ increment ] = newEvent
477
478                                        // ...add it to the study...
479                                        if (newEvent instanceof SamplingEvent) {
480                                                flow.study.addToSamplingEvents( newEvent )
481                                        } else {
482                                                flow.study.addToEvents( newEvent )
483                                        }
484
485                                        // ...and 'reset' the event object in the flow scope
486                                        flow.event = new Event(template: newEvent.template)
487                                       
488                                        // remember the event id with the template
489                                        def eventSize = flow.eventTemplates[ eventTemplateName ]['events'].size()
490                                        flow.eventTemplates[ eventTemplateName ]['events'][ eventSize ] = increment
491
492                                        success()
493                                } else {
494                                        // it does not validate, show error feedback
495                                        flash.errors = [:]
496                                        this.appendErrors(flow.event, flash.errors)
497                                        error()
498                                }
499                        }.to "events"
500                        on("deleteEvent") {
501                                flash.values = params
502                                def delete = params.get('do') as int
503
504                                // handle event groupings
505                                this.handleEventGrouping(flow, flash, params)
506
507                                // remove event
508                                if (flow.events[ delete ] && flow.events[ delete ] instanceof Event) {
509                                        // remove it from the study
510                                        if ( flow.events[ delete ] instanceof SamplingEvent ) {
511                                                flow.study.removeFromSamplingEvents( flow.events[ delete ] )
512                                        } else {
513                                                flow.study.removeFromEvents( flow.events[ delete ] )
514                                        }
515
516                                        // remove it from the map
517                                        flow.events.remove(delete)
518                                        flow.eventTemplates.each() { eventTemplate ->
519                                                eventTemplate.value.events = eventTemplate.value.events.minus(delete)
520                                        }
521
522                                        // find eventTemplates without events
523                                flow.eventTemplates.find { eventTemplate ->
524                                                eventTemplate.value.events.size() < 1
525                                        }.each() {
526                                                // remove eventTemplate
527                                                flow.eventTemplates.remove( it.value.name )
528                                        }
529                                }
530
531                                success()
532                        }.to "events"
533                        on("addEventGroup") {
534                                flash.values = params
535                               
536                                // handle study data
537                                this.handleEvents(flow, flash, params)
538
539                                // handle event groupings
540                                this.handleEventGrouping(flow, flash, params)
541
542                                def increment = flow.eventGroups.size()
543                                def groupName = "Group " + (increment + 1)
544
545                                // check if group name exists
546                                def nameExists = true
547                                def u = 0
548
549                                // make sure a unique name is generated
550                                while (nameExists) {
551                                        u++
552                                        def count = 0
553                                       
554                                        flow.eventGroups.each() {
555                                                if (it.name == groupName) {
556                                                        groupName = "Group " + (increment + 1) + "," + u
557                                                } else {
558                                                        count++
559                                                }
560                                        }
561
562                                        nameExists = !(count == flow.eventGroups.size())
563                                }
564
565                                // remember eventGroup
566                                flow.eventGroups[ increment ] = new EventGroup( name: groupName )
567
568                                // and add the group to the study
569                                flow.study.addToEventGroups( flow.eventGroups[ increment ] )
570
571                                success()
572                        }.to "events"
573                        on("deleteEventGroup") {
574                                flash.values = params
575                                def delete = params.get('do') as int
576
577                                // handle event groupings
578                                this.handleEventGrouping(flow, flash, params)
579
580                                // remove the group with this specific id
581                                if (flow.eventGroups[delete] && flow.eventGroups[delete] instanceof EventGroup) {
582                                        // remove the eventGroup from the study
583                                        flow.study.removeFromEventGroups( flow.eventGroups[ delete ] )
584
585                                        // remove this eventGroup
586                                        flow.eventGroups.remove(delete)
587                                }
588
589                                success()
590                        }.to "events"
591                        on("previous") {
592                                // handle event groupings
593                                this.handleEventGrouping(flow, flash, params)
594                        }.to "subjects"
595                        on("next") {
596                                flash.values = params
597                                flash.errors = [:]
598
599                                // handle study data
600                                /* turned off by request of Kees / Leny / Jildau
601                                if (!flow.eventTemplates.find { eventTemplate -> eventTemplate.value.template.entity == SamplingEvent }) {
602                                        // append error map
603                                        this.appendErrorMap(['events': 'You need to create at least one sampling event for your study'], flash.errors)
604                                        error()                                         
605                                } else
606                                */
607                                if (this.handleEvents(flow, flash, params)) {
608                                        success()
609                                } else {
610                                        error()
611                                }
612                        }.to "groups"
613                        on("quickSave") {
614                                flash.values = params
615                                flash.errors = [:]
616
617                                // handle study data
618                                /* turned off by request of Kees / Leny / Jildau
619                                if (!flow.eventTemplates.find { eventTemplate -> eventTemplate.value.template.entity == SamplingEvent }) {
620                                        // append error map
621                                        this.appendErrorMap(['events': 'You need to create at least one sampling event for your study'], flash.errors)
622                                        error()
623                                } else
624                                */
625                                if (this.handleEvents(flow, flash, params)) {
626                                        success()
627                                } else {
628                                        error()
629                                }
630                        }.to "waitForSave"
631                }
632
633                // groups page
634                groups {
635                        render(view: "_groups")
636                        onRender {
637                                flow.page = 5
638                                success()
639                        }
640                        on("previous") {
641                                this.handleSubjectGrouping(flow, flash, params)
642                                success()
643                        }.to "events"
644                        on("next") {
645                                this.handleSubjectGrouping(flow, flash, params)
646                                flash.check = true
647                                success()
648                        }.to "samples"
649                        on("quickSave") {
650                                this.handleSubjectGrouping(flow, flash, params)
651                                success()
652                        }.to "waitForSave"
653                }
654
655                // sample 'previous' page with warning
656                samplePrevious {
657                        render(view: "_samples_previous_warning")
658                        onRender {
659                                flow.page = 6
660
661                                // TEMPORARY FIX TO REMOVE ALL SAMPLES AND REGENERATE THEM
662                                // THEN USER BROWSED BACK
663                                println ".removing samples from study"
664
665                                // remove samples from study
666                                flow.samples.each() {
667                                        flow.study.removeFromSamples(it.sample)
668                                }
669
670                                // remove samples from flow
671                                flow.remove('samples')
672                                // END FIX
673                        }
674                        on("next").to "samples"
675                        on("previous").to "groups"
676                }
677
678                // samples page
679                samples {
680                        render(view: "_samples")
681                        onRender {
682                                flow.page = 6
683
684                                // iterate through eventGroups
685                                if (!flow.samples) {
686                                        println ".generating samples"
687                                        flow.samplesWithTemplate = 0
688                                        flow.samples = []
689                                        flow.sampleTemplates = [:]
690                                        flow.eventGroups.each() { eventGroup ->
691                                                // iterate through events
692                                                eventGroup.events.each() { event ->
693                                                        if (event.isSamplingEvent()) {
694                                                                def eventName = this.ucwords(event.template.name)
695
696                                                                // iterate through subjects
697                                                                eventGroup.subjects.each() { subject ->
698                                                                        def sampleName = (this.ucwords(subject.name) + '_' + eventName + '_' + new RelTime(event.startTime).toString()).replaceAll("([ ]{1,})", "")
699                                                                        def incrementor = flow.samples.size()
700
701                                                                        flow.samples[ incrementor ] = [
702                                                                                sample: new Sample(
703                                                                                        parent: flow.study,
704                                                                                        parentSubject: subject,
705                                                                                        parentEvent: event,
706                                                                                        name: sampleName
707                                                                                ),
708                                                                                name: sampleName,
709                                                                                eventGroup: eventGroup,
710                                                                                event: event,
711                                                                                subject: subject
712                                                                        ]
713
714                                                                        // and add this sample to the study
715                                                                        flow.study.addToSamples( flow.samples[ incrementor ].sample )
716                                                                }
717                                                        }
718                                                }
719                                        }
720                                } else if (flash.check) {
721                                        println "CHECKING SAMPLE CONSISTENCY"
722                                        // check the consistency of the samples
723                                        flow.samples.each() { sampleData ->
724                                                println sampleData
725                                                println sampleData.event.template
726                                        }
727                                }
728
729                                success()
730                        }
731                        on("switchTemplate") {
732                                //println params
733                                handleSamples(flow, flash, params)
734
735                                // ignore errors
736                                flash.errors = [:]
737                               
738                                succes()
739                        }.to "samples"
740                        on("refresh") {
741                                println ".refresh ${flow.sampleTemplates.size()} sample templates (${flow.samples.size()} samples present)"
742
743                                // refresh templates
744                                flow.sampleTemplates.each() {
745                                        println ".refresh template ["+it.value.name+"]"
746                                        it.value.template.refresh()
747                                        println "  --> fields: "+it.value.template.fields
748                                }
749
750                                // handle samples
751                                handleSamples(flow, flash, params)
752
753                                // ignore errors
754                                flash.errors = [:]
755
756                                success()
757                        }.to "samples"
758                        on("regenerate") {
759                                println ".removing 'samples' and 'sampleTemplates' from the flowscope, triggering regeneration of the samples..."
760                                flow.samples.each() {
761                                        flow.study.removeFromSamples( it.sample )
762                                }
763                                flow.remove('samples')
764                                flow.remove('sampleTemplates')
765                                println flow.study.samples
766                                success()
767                        }.to "samples"
768                        on("previous") {
769                                // handle samples
770                                handleSamples(flow, flash, params)
771
772                                // ignore errors
773                                flash.errors = [:]
774
775                                success()
776                        }.to "samplePrevious"
777                        on("next") {
778                                flash.values = params
779                                flash.errors = [:]
780
781// for now, development only!
782if (grails.util.GrailsUtil.environment == "development") {
783                                // do all samples have a template assigned?
784                                if (flow.samplesWithTemplate < flow.samples.size()) {
785                                        // handle samples
786                                        this.handleSamples(flow, flash, params)
787
788                                        // ignore errors
789                                        flash.errors = [:]
790                                       
791                                        // add error
792                                        this.appendErrorMap(['samples': 'you need to select a template for each sample'], flash.errors)
793
794                                        error()
795                                } else if (this.handleSamples(flow, flash, params)) {
796                                        success()
797                                } else {
798                                        error()
799                                }
800} else {
801        success()
802}
803                        }.to "confirm"
804                        on("quickSave") {
805                                // handle samples
806                                if (handleSamples(flow, flash, params)) {
807                                        success()
808                                } else {
809                                        error()
810                                }
811                        }.to "waitForSave"
812                }
813
814                // confirmation
815                confirm {
816                        render(view: "_confirmation")
817                        onRender {
818                                flow.page = 7
819                        }
820                        on("toStudy").to "study"
821                        on("toSubjects").to "subjects"
822                        on("toEvents").to "events"
823                        on("toGroups").to "groups"
824                        on("previous").to "samples"
825                        on("next").to "waitForSave"
826                        on("quickSave").to "waitForSave"
827                }
828
829                waitForSave {
830                        render(view: "_wait")
831                        onRender {
832                                flow.page = 8
833                        }
834                        on("next").to "save"
835                }
836
837                // store all study data
838                save {
839                        action {
840                                println "saving..."
841                                flash.errors = [:]
842
843                                // persist data to the database
844                                try {
845                                        // user modifying?
846                                        if (flow.quickSave && grails.util.GrailsUtil.environment == 'production') {
847                                                // yes, not allowed on production as this results in data inconsistency
848                                                println ".saving is not allowed in study edit wizard"
849                                                throw new Exception("Saving is not allowed in the edit study wizard because this leads to data inconsistency in the database!")
850                                        }
851
852                                        // save study
853                                        println ".saving study"
854                                        if (!flow.study.save()) {
855                                                this.appendErrors(flow.study, flash.errors)
856                                                throw new Exception('error saving study')
857                                        }
858                                        println ".saved study "+flow.study+" (id: "+flow.study.id+")"
859
860                                        success()
861                                } catch (Exception e) {
862                                        // rollback
863                                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace' ], flash.errors)
864
865                                        // stacktrace in flash scope
866                                        flash.debug = e.getStackTrace()
867
868                                        error()
869                                }
870                        }
871                        on("error").to "error"
872                        on(Exception).to "error"
873                        on("success").to "done"
874                }
875
876                // error storing data
877                error {
878                        render(view: "_error")
879                        onRender {
880                                flow.page = 7
881                        }
882                        on("next").to "waitForSave"
883                        on("previous").to "samples"
884                }
885
886                // render finish page
887                done {
888                        render(view: "_done")
889                        onRender {
890                                flow.page = 8
891                        }
892                        onEnd {
893                                // clean flow scope
894                                flow.clear()
895                        }
896                }
897        }
898
899        /**
900         * load a study
901         * @param Map LocalAttributeMap (the flow scope)
902         * @param Map localAttributeMap (the flash scope)
903         * @param Map GrailsParameterMap (the flow parameters = form data)
904         * @returns boolean
905         */
906        def loadStudy(flow, flash, params) {
907                // load study
908                try {
909                        // load study
910                        flow.study = (params.studyid) ? Study.findById( params.studyid ) : Study.findByTitle( params.study )
911
912                        // recreate subjects
913                        flow.subjects = [:]
914                        flow.subjectTemplates = [:]
915                        flow.study.subjects.each() { subject ->
916                                def subjectIncrement = flow.subjects.size()
917                                flow.subjects[subjectIncrement] = subject
918
919                                // add subject template?
920                                if (!flow.subjectTemplates[subject.template.name]) {
921                                        flow.subjectTemplates[subject.template.name] = [
922                                                name: subject.template.name,
923                                                template: subject.template,
924                                                subjects: [:]
925                                        ]
926                                }
927
928                                // reference subject in template
929                                flow.subjectTemplates[subject.template.name].subjects[flow.subjectTemplates[subject.template.name].subjects.size()] = subjectIncrement
930                        }
931
932                        // recreate events
933                        flow.events = [:]
934                        flow.eventGroups = []
935                        flow.eventTemplates = [:]
936                        flow.study.events.each() { event ->
937                                def eventIncrement = flow.events.size()
938                                flow.events[eventIncrement] = event
939
940                                // add event template?
941                                if (!flow.eventTemplates[event.template.name]) {
942                                        flow.eventTemplates[event.template.name] = [
943                                                name: event.template.name,
944                                                template: event.template,
945                                                events: new ArrayList()
946                                        ]
947                                }
948
949                                // reference event in template
950                                flow.eventTemplates[event.template.name].events[flow.eventTemplates[event.template.name].events.size()] = eventIncrement
951
952                                // set dummy event
953                                flow.event = event
954                        }
955
956                        // recreate sample events
957                        flow.study.samplingEvents.each() { event ->
958                                def eventIncrement = flow.events.size()
959                                flow.events[eventIncrement] = event
960
961                                // add event template?
962                                if (!flow.eventTemplates[event.template.name]) {
963                                        flow.eventTemplates[event.template.name] = [
964                                                name: event.template.name,
965                                                template: event.template,
966                                                events: new ArrayList()
967                                        ]
968                                }
969
970                                // reference event in template
971                                flow.eventTemplates[event.template.name].events[flow.eventTemplates[event.template.name].events.size()] = eventIncrement
972
973                                // set dummy event
974                                flow.event = event
975                        }
976
977                        // recreate eventGroups
978                        flow.study.eventGroups.each() { eventGroup ->
979                                flow.eventGroups[flow.eventGroups.size()] = eventGroup
980                        }
981
982                        // set 'quicksave' variable
983                        flow.quickSave = true
984
985                        return true
986                } catch (Exception e) {
987                        // rollback
988                        this.appendErrorMap(['exception': e.toString() + ', see log for stacktrace'], flash.errors)
989
990                        return false
991                }
992        }
993
994        /**
995         * re-usable code for handling study form data in a web flow
996         * @param Map LocalAttributeMap (the flow scope)
997         * @param Map localAttributeMap (the flash scope)
998         * @param Map GrailsParameterMap (the flow parameters = form data)
999         * @returns boolean
1000         */
1001        def handleStudy(flow, flash, params) {
1002                // create study instance if we have none
1003                if (!flow.study) flow.study = new Study()
1004
1005                // create date instance from date string?
1006                // @see WizardTagLibrary::dateElement{...}
1007                if (params.get('startDate')) {
1008                        params.startDate = new Date().parse("d/M/yyyy", params.get('startDate').toString())
1009                } else {
1010                        params.remove('startDate')
1011                }
1012
1013                // if a template is selected, get template instance
1014                def template = params.remove('template')
1015                if (template instanceof String && template.size() > 0) {
1016                        flow.study.template = Template.findByName(template)
1017                } else if (template instanceof Template) {
1018                        flow.study.template = template
1019                }
1020
1021                // iterate through fields
1022                if (flow.study.template) {
1023                        flow.study.giveFields().each() {
1024                                flow.study.setFieldValue(it.name, params.get(it.escapedName()))
1025                        }
1026                }
1027
1028                // handle Publications and Contacts
1029                handlePublications(flow, flash, params)
1030                handleContacts(flow, flash, params)
1031
1032                // validate study
1033                if (flow.study.validate()) {
1034                        return true
1035                } else {
1036                        // validation failed, feedback errors
1037                        flash.errors = [:]
1038                        this.appendErrors(flow.study, flash.errors)
1039                        return false
1040                }
1041        }
1042
1043        /**
1044         * re-usable code for handling publications form data in a web flow
1045         * @param Map LocalAttributeMap (the flow scope)
1046         * @param Map localAttributeMap (the flash scope)
1047         * @param Map GrailsParameterMap (the flow parameters = form data)
1048         * @returns boolean
1049         */
1050        def handlePublications(flow, flash, params) {
1051                // create study instance if we have none
1052                if (!flow.study) flow.study = new Study()
1053                if (!flow.study.publications) flow.study.publications = []
1054
1055                // Check the ids of the pubblications that should be attached
1056                // to this study. If they are already attached, keep 'm. If
1057                // studies are attached that are not in the selected (i.e. the
1058                // user deleted them), remove them
1059                def publicationIDs = params.get('publication_ids')
1060                if (publicationIDs) {
1061                        // Find the individual IDs and make integers
1062                        publicationIDs = publicationIDs.split(',').collect { Integer.parseInt(it, 10) }
1063
1064                        // First remove the publication that are not present in the array
1065                        flow.study.publications.removeAll { publication -> !publicationIDs.find { id -> id == publication.id } }
1066
1067                        // Add those publications not yet present in the database
1068                        publicationIDs.each { id ->
1069                                if (!flow.study.publications.find { publication -> id == publication.id }) {
1070                                        def publication = Publication.get(id)
1071                                        if (publication) {
1072                                                flow.study.addToPublications(publication)
1073                                        } else {
1074                                                println('.publication with ID ' + id + ' not found in database.')
1075                                        }
1076                                }
1077                        }
1078
1079                } else {
1080                        println('.no publications selected.')
1081                        flow.study.publications.clear()
1082                }
1083
1084        }
1085
1086        /**
1087         * re-usable code for handling contacts form data in a web flow
1088         * @param Map LocalAttributeMap (the flow scope)
1089         * @param Map localAttributeMap (the flash scope)
1090         * @param Map GrailsParameterMap (the flow parameters = form data)
1091         * @return boolean
1092         */
1093        def handleContacts(flow, flash, params) {
1094                // create study instance if we have none
1095                if (!flow.study) flow.study = new Study()
1096                if (!flow.study.persons) flow.study.persons = []
1097
1098                // Check the ids of the contacts that should be attached
1099                // to this study. If they are already attached, keep 'm. If
1100                // studies are attached that are not in the selected (i.e. the
1101                // user deleted them), remove them
1102
1103                // Contacts are saved as [person_id]-[role_id]
1104                def contactIDs = params.get('contacts_ids')
1105                if (contactIDs) {
1106                        // Find the individual IDs and make integers
1107                        contactIDs = contactIDs.split(',').collect {
1108                                def parts = it.split('-')
1109                                return [person: Integer.parseInt(parts[0]), role: Integer.parseInt(parts[1])]
1110                        }
1111
1112                        // First remove the contacts that are not present in the array
1113                        flow.study.persons.removeAll {
1114                                studyperson -> !contactIDs.find { ids -> (ids.person == studyperson.person.id) && (ids.role == studyperson.role.id) }
1115                        }
1116
1117                        // Add those contacts not yet present in the database
1118                        contactIDs.each { ids ->
1119                                if (!flow.study.persons.find { studyperson -> (ids.person == studyperson.person.id) && (ids.role == studyperson.role.id) }) {
1120                                        def person = Person.get(ids.person)
1121                                        def role = PersonRole.get(ids.role)
1122                                        if (person && role) {
1123                                                // Find a studyperson object with these parameters
1124                                                def studyPerson = StudyPerson.findAll().find { studyperson -> studyperson.person.id == person.id && studyperson.role.id == role.id }
1125
1126                                                // If if does not yet exist, save the example
1127                                                if (!studyPerson) {
1128                                                        studyPerson = new StudyPerson(
1129                                                                person: person,
1130                                                                role: role
1131                                                        )
1132                                                        studyPerson.save(flush: true)
1133                                                }
1134
1135                                                flow.study.addToPersons(studyPerson)
1136                                        } else {
1137                                                println('.person ' + ids.person + ' or Role ' + ids.role + ' not found in database.')
1138                                        }
1139                                }
1140                        }
1141                } else {
1142                        println('.no persons selected.')
1143                        flow.study.persons.clear()
1144                }
1145
1146        }
1147
1148        /**
1149         * re-usable code for handling subject form data in a web flow
1150         * @param Map LocalAttributeMap (the flow scope)
1151         * @param Map localAttributeMap (the flash scope)
1152         * @param Map GrailsParameterMap (the flow parameters = form data)
1153         * @return boolean
1154         */
1155        def handleSubjects(flow, flash, params) {
1156                def names = [:]
1157                def errors = false
1158                def id = 0
1159
1160                // iterate through subject templates
1161                flow.subjectTemplates.each() { subjectTemplate ->
1162                        // iterate through subjects
1163                        subjectTemplate.value.subjects.each() { subjectIncrement, subjectId ->
1164                                // iterate through fields (= template fields and domain properties)
1165                                flow.subjects[ subjectId ].giveFields().each() { subjectField ->
1166                                        // set the field
1167                                        flow.subjects[ subjectId ].setFieldValue(
1168                                                subjectField.name,
1169                                                params.get( 'subject_' + subjectId + '_' + subjectField.escapedName() )
1170                                        )
1171                                }
1172
1173                                // validate subject
1174                                if (!flow.subjects[ subjectId ].validate()) {
1175                                        errors = true
1176                                        this.appendErrors(flow.subjects[ subjectId ], flash.errors, 'subject_' + subjectId + '_')
1177                                }
1178                        }
1179                }
1180
1181                return !errors
1182        }
1183
1184        /**
1185         * re-usable code for handling event form data in a web flow
1186         * @param Map LocalAttributeMap (the flow scope)
1187         * @param Map localAttributeMap (the flash scope)
1188         * @param Map GrailsParameterMap (the flow parameters = form data)
1189         * @return boolean
1190         */
1191        def handleEvents(flow, flash, params) {
1192                def errors = false
1193                def template = null
1194
1195                // handle the type of event
1196                if (params.eventType == 'event') {
1197                        flow.event = new Event()
1198                        template = params.remove('eventTemplate')
1199                } else if (params.eventType == 'sample') {
1200                        flow.event = new SamplingEvent()
1201                        template = params.remove('sampleTemplate')
1202                }
1203
1204                // if a template is selected, get template instance
1205                if (template instanceof String && template.size() > 0) {
1206                        params.template = Template.findByName(template)
1207                } else if (template instanceof Template) {
1208                        params.template = template
1209                } else {
1210                        params.template = null
1211                }
1212
1213                // set template
1214                if (params.template) flow.event.template = params.template
1215
1216                // update event instance with parameters
1217                flow.event.giveFields().each() { eventField ->
1218                        flow.event.setFieldValue(eventField.name, params[ eventField.escapedName() ])   
1219                }
1220
1221                // handle event objects
1222                flow.eventTemplates.each() { eventTemplate ->
1223                        // iterate through events
1224                        eventTemplate.getValue().events.each() { eventId ->
1225                                // iterate through template fields
1226                                flow.events[ eventId ].giveFields().each() { eventField ->
1227                                        if ( params.containsKey( 'event_' + eventId + '_' + eventField.escapedName() ) ) {
1228                                                flow.events[ eventId ].setFieldValue(eventField.name, params.get( 'event_' + eventId + '_' + eventField.escapedName() ) )
1229                                        }
1230                                }
1231
1232                                // validate event
1233                                if (!flow.events[ eventId ].validate()) {
1234                                        errors = true
1235                                        this.appendErrors(flow.events[ eventId ], flash.errors, 'event_' + eventId + '_')
1236                                }
1237                        }
1238                }
1239
1240                // handle event grouping
1241                handleEventGrouping(flow, flash, params)
1242
1243                return !errors
1244        }
1245
1246        /**
1247         * re-usable code for handling event grouping in a web flow
1248         * @param Map LocalAttributeMap (the flow scope)
1249         * @param Map localAttributeMap (the flash scope)
1250         * @param Map GrailsParameterMap (the flow parameters = form data)
1251         * @return boolean
1252         */
1253        def handleEventGrouping(flow, flash, params) {
1254                // walk through eventGroups
1255                def g = 0
1256                flow.eventGroups.each() { eventGroup ->
1257                        def e = 0
1258
1259                        // reset events
1260                        eventGroup.events = new HashSet()
1261
1262                        // iterate through events
1263                        flow.events.each() {
1264                                if (params.get('event_' + e + '_group_' + g) == 'on') {
1265                                        eventGroup.addToEvents(it.value)
1266                                }
1267                                e++
1268                        }
1269                        g++
1270                }
1271        }
1272
1273        /**
1274         * re-usable code for handling subject grouping in a web flow
1275         * @param Map LocalAttributeMap (the flow scope)
1276         * @param Map localAttributeMap (the flash scope)
1277         * @param Map GrailsParameterMap (the flow parameters = form data)
1278         * @return boolean
1279         */
1280        def handleSubjectGrouping(flow, flash, params) {
1281                // iterate through event groups
1282                def g = 0
1283                flow.eventGroups.each() { eventGroup ->
1284                        // reset subjects
1285                        eventGroup.subjects = new HashSet()
1286
1287                        // iterate through subjects
1288                        flow.subjects.each() { subjectId, subject ->
1289                                // is this combination set?
1290                                if (params.get('subject_' + subjectId + '_group_' + g) != null) {
1291                                        eventGroup.addToSubjects(subject)
1292                                }
1293                        }
1294
1295                        g++
1296                }
1297        }
1298
1299
1300        /**
1301         * re-usable code for handling samples
1302         * @param Map LocalAttributeMap (the flow scope)
1303         * @param Map localAttributeMap (the flash scope)
1304         * @param Map GrailsParameterMap (the flow parameters = form data)
1305         * @return boolean
1306         */
1307        def handleSamples(flow, flash, params) {
1308                flash.errors = [:]
1309                def errors = false             
1310                def id = 0
1311
1312                // iterate through samples
1313                flow.samples.each() { sampleData ->
1314                        def sample = sampleData.sample
1315                        def sampleTemplateName = params.get('template_'+id)
1316                        def oldSampleTemplateName = sampleData.sample.template.toString()
1317
1318                        // has the sample template for this sample changed
1319                        if (sampleTemplateName && sampleTemplateName.size() > 0 && oldSampleTemplateName != sampleTemplateName) {
1320                                // yes, has the template changed?
1321                                println ".changing template for sample ${id} to ${sampleTemplateName}"
1322
1323                                // decrease previous template count
1324                                if (oldSampleTemplateName && flow.sampleTemplates[ oldSampleTemplateName ]) {
1325                                        flow.sampleTemplates[ oldSampleTemplateName ].count--
1326
1327                                        if (flow.sampleTemplates[ oldSampleTemplateName ].count < 1) {
1328                                                // no samples left, remove template altogether
1329                                                flow.sampleTemplates.remove( oldSampleTemplateName )
1330                                        }
1331                                } else {
1332                                        // increate main template counter?
1333                                        flow.samplesWithTemplate++
1334                                }
1335
1336                                // increase current template count
1337                                if (!flow.sampleTemplates[ sampleTemplateName ]) {
1338                                        flow.sampleTemplates[ sampleTemplateName ] = [
1339                                                name            : sampleTemplateName,
1340                                                template        : Template.findByName( sampleTemplateName ),
1341                                                count           : 1
1342                                        ]
1343                                } else {
1344                                        // increase count
1345                                        flow.sampleTemplates[ sampleTemplateName ].count++
1346                                }
1347
1348                                // change template
1349                                sampleData.sample.template = flow.sampleTemplates[ sampleTemplateName ].template
1350                        }
1351
1352                        // handle values
1353                        sampleData.sample.giveFields().each() { sampleField ->
1354                                if ( params.containsKey( 'sample_'+id+'_'+sampleField.escapedName() ) ) {
1355                                        sampleData.sample.setFieldValue( sampleField.name, params.get( 'sample_'+id+'_'+sampleField.escapedName() ) )
1356                                }
1357                        }
1358
1359                        // validate sample
1360                        if (!sampleData.sample.validate()) {
1361                                errors = true
1362                                this.appendErrors(sampleData.sample, flash.errors, 'sample_' + id + '_' )
1363                        }
1364
1365                        // increase counter
1366                        id++
1367                }
1368
1369                return !errors
1370        }
1371
1372        /**
1373         * groovy / java equivalent of php's ucwords function
1374         *
1375         * Capitalize all first letters of seperate words
1376         *
1377         * @param String
1378         * @return String
1379         */
1380        def ucwords(String text) {
1381                def newText = ''
1382
1383                // change case to lowercase
1384                text = text.toLowerCase()
1385
1386                // iterate through words
1387                text.split(" ").each() {
1388                        newText += it[0].toUpperCase() + it.substring(1) + " "
1389                }
1390
1391                return newText.substring(0, newText.size()-1)
1392        }
1393
1394        /**
1395         * return the object from a map of objects by searching for a name
1396         * @param String name
1397         * @param Map map of objects
1398         * @return Object
1399         */
1400        def getObjectByName(name, map) {
1401                def result = null
1402                map.each() {
1403                        if (it.name == name) {
1404                                result = it
1405                        }
1406                }
1407
1408                return result
1409        }
1410
1411        /**
1412         * transform domain class validation errors into a human readable
1413         * linked hash map
1414         * @param object validated domain class
1415         * @return object  linkedHashMap
1416         */
1417        def getHumanReadableErrors(object) {
1418                def errors = [:]
1419                object.errors.getAllErrors().each() {
1420                        def message = it.toString()
1421
1422                        //errors[it.getArguments()[0]] = it.getDefaultMessage()
1423                        errors[it.getArguments()[0]] = message.substring(0, message.indexOf(';'))
1424                }
1425
1426                return errors
1427        }
1428
1429        /**
1430         * append errors of a particular object to a map
1431         * @param object
1432         * @param map linkedHashMap
1433         * @void
1434         */
1435        def appendErrors(object, map) {
1436                this.appendErrorMap(this.getHumanReadableErrors(object), map)
1437        }
1438
1439        def appendErrors(object, map, prepend) {
1440                this.appendErrorMap(this.getHumanReadableErrors(object), map, prepend)
1441        }
1442
1443        /**
1444         * append errors of one map to another map
1445         * @param map linkedHashMap
1446         * @param map linkedHashMap
1447         * @void
1448         */
1449        def appendErrorMap(map, mapToExtend) {
1450                map.each() {key, value ->
1451                        mapToExtend[key] = ['key': key, 'value': value, 'dynamic': false]
1452                }
1453        }
1454
1455        def appendErrorMap(map, mapToExtend, prepend) {
1456                map.each() {key, value ->
1457                        mapToExtend[prepend + key] = ['key': key, 'value': value, 'dynamic': true]
1458                }
1459        }
1460
1461        /**
1462         * Parses a RelTime string and returns a nice human readable string
1463         *
1464         * @return Human Readable string or a HTTP response code 400 on error
1465         */
1466        def ajaxParseRelTime = {
1467                if (params.reltime == null) {
1468                        response.status = 400
1469                        render('reltime parameter is expected')
1470                }
1471
1472                try {
1473                        def reltime = RelTime.parseRelTime(params.reltime)
1474                        render reltime.toPrettyString()
1475                } catch (IllegalArgumentException e) {
1476                        response.status = 400
1477                        render(e.getMessage())
1478                }
1479        }
1480
1481        /**
1482         * Proxy for searching PubMed articles (or other articles from the Entrez DB).
1483         *
1484         * This proxy is needed because it is not allowed to fetch XML directly from a different
1485         * domain using javascript. So we have the javascript call a function on our own domain
1486         * and the proxy will fetch the data from Entrez
1487         *
1488         * @since       20100609
1489         * @param       _utility        The name of the utility, without the complete path. Example: 'esearch.fcgi'
1490         * @return      XML
1491         */
1492        def entrezProxy = {
1493                // Remove unnecessary parameters
1494                params.remove( "action" )
1495                params.remove( "controller" )
1496
1497                def url = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils";
1498                def util = params.remove( "_utility" )
1499                def paramString = params.collect { k, v -> k + '=' + v.encodeAsURL() }.join( '&' );
1500
1501                def fullUrl = url + '/' + util + '?' + paramString;
1502
1503                // Return the output of the request
1504                // render fullUrl;
1505                render(
1506                    text:           new URL( fullUrl ).getText(),
1507                    contentType:    "text/xml",
1508                    encoding:       "UTF-8"
1509                );
1510        }
1511}
Note: See TracBrowser for help on using the repository browser.