source: trunk/grails-app/domain/dbnp/studycapturing/Study.groovy @ 965

Last change on this file since 965 was 965, checked in by business@…, 10 years ago

fix Study.deleteEventGroup to correctly delete samples (still TODO: assays), added 'published' property to Study for data quality verification

  • Property svn:keywords set to Author Date Rev
File size: 11.3 KB
Line 
1package dbnp.studycapturing
2
3import org.nmcdsp.plugins.aaaa.SecUser
4
5/**
6 * Domain class describing the basic entity in the study capture part: the Study class.
7 *
8 * Revision information:
9 * $Rev: 965 $
10 * $Author: business@keesvanbochove.nl $
11 * $Date: 2010-10-21 13:38:30 +0000 (do, 21 okt 2010) $
12 */
13class Study extends TemplateEntity {
14        static searchable = {
15        [only: ['title', 'Description']] // the description field will be searched only if defined in a study template
16    }
17
18        SecUser owner   // The owner of the study. A new study is automatically owned by its creator.
19        String title        // The title of the study
20        String code             // currently used as the external study ID, e.g. to reference a study in a SAM module
21        Date dateCreated
22        Date lastUpdated
23        Date startDate
24        List subjects
25        List events
26        List samplingEvents
27        List eventGroups
28        List samples
29        List assays
30        boolean published = false // Determines whether a study is private (only accessable by the owner and writers) or published (also visible to readers)
31
32        static hasMany = [             
33                subjects: Subject,
34                samplingEvents: SamplingEvent,
35                events: Event,
36                eventGroups: EventGroup,
37                samples: Sample,
38                assays: Assay,
39                persons: StudyPerson,
40                publications: Publication
41        ]
42
43        static constraints = {
44                owner(nullable: true, blank: true)
45                code(nullable:false, blank:true,unique:true)
46        }
47
48        static mapping = {
49                autoTimestamp true
50
51                // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
52                templateTextFields type: 'text'
53                owner column:"studyowner"
54                title column:"studytitle"
55                code column:"studycode"
56                subjects column:"studysubjects"
57                events column:"studyevents"
58                samplingEvents column:"studysamplingevents"
59                eventGroups column:"studyeventgroups"
60                samples column:"studysamples"
61                assays column:"studyassays"
62
63        }
64
65        // The external identifier (studyToken) is currently the code of the study.
66        // It is used from within dbNP submodules to refer to particular study in this GSCF instance.
67        def getToken() { code }
68
69        /**
70         * return the domain fields for this domain class
71         * @return List
72         */
73        static List<TemplateField> giveDomainFields() { return Study.domainFields }
74
75        static final List<TemplateField> domainFields = [
76                new TemplateField(
77                        name: 'title',
78                        type: TemplateFieldType.STRING,
79                        required: true),
80                new TemplateField(
81                        name: 'code',
82                        type: TemplateFieldType.STRING,
83                        preferredIdentifier:true,
84                        comment: 'Fill out the code by which many people will recognize your study',
85                        required: true),
86                new TemplateField(
87                        name: 'startDate',
88                        type: TemplateFieldType.DATE,
89                        comment: 'Fill out the official start date or date of first action',
90                        required: true)
91        ]
92
93        /**
94         * return the title of this study
95         */
96        def String toString() {
97                return title
98        }
99
100        /**
101         * returns all events and sampling events that do not belong to a group
102         */
103        def Set<Event> getOrphanEvents() {
104                def orphans =   events.findAll { event -> !event.belongsToGroup(eventGroups) } +
105                                                samplingEvents.findAll { event -> !event.belongsToGroup(eventGroups) }
106
107                return orphans
108        }
109
110        /**
111         * Return the unique Subject templates that are used in this study
112         */
113        def Set<Template> giveSubjectTemplates() {
114                TemplateEntity.giveTemplates(subjects)
115        }
116
117        /**
118         * Return all subjects for a specific template
119         * @param Template
120         * @return ArrayList
121         */
122        def ArrayList<Subject> giveSubjectsForTemplate(Template template) {
123                subjects.findAll { it.template.equals(template) }
124        }
125
126        /**
127         * Return all unique assay templates
128         * @return Set
129         */
130        Set<Template> giveAllAssayTemplates() {
131                TemplateEntity.giveTemplates(( (assays) ? assays : [] ))
132        }
133
134        /**
135         * Return all assays for a particular template
136         * @return ArrayList
137         */
138        def ArrayList giveAssaysForTemplate(Template template) {
139                assays.findAll { it.template.equals(template) }
140        }
141
142        /**
143         * Return the unique Event and SamplingEvent templates that are used in this study
144         */
145        Set<Template> giveAllEventTemplates() {
146                // For some reason, giveAllEventTemplates() + giveAllSamplingEventTemplates()
147                // gives trouble when asking .size() to the result
148                // So we also use giveTemplates here
149                TemplateEntity.giveTemplates( ((events) ? events : []) + ((samplingEvents) ? samplingEvents : []) )
150        }
151
152
153        /**
154         * Return all events and samplingEvenets for a specific template
155         * @param Template
156         * @return ArrayList
157         */
158        def ArrayList giveEventsForTemplate(Template template) {
159                def events = events.findAll { it.template.equals(template) }
160                def samplingEvents = samplingEvents.findAll { it.template.equals(template) }
161
162                return (events) ? events : samplingEvents
163        }
164
165        /**
166         * Return the unique Event templates that are used in this study
167         */
168        Set<Template> giveEventTemplates() {
169                TemplateEntity.giveTemplates(events)
170        }
171
172        /**
173         * Return the unique SamplingEvent templates that are used in this study
174         */
175        Set<Template> giveSamplingEventTemplates() {
176                TemplateEntity.giveTemplates(samplingEvents)
177        }
178
179        /**
180         * Returns the unique Sample templates that are used in the study
181         */
182        Set<Template> giveSampleTemplates() {
183                TemplateEntity.giveTemplates(samples)
184        }
185
186        /**
187         * Return all samples for a specific template
188         * @param Template
189         * @return ArrayList
190         */
191        def ArrayList<Subject> giveSamplesForTemplate(Template template) {
192                samples.findAll { it.template.equals(template) }
193        }
194
195        /**
196         * Returns the template of the study
197         */
198        Template giveStudyTemplate() {
199                return this.template
200        }
201
202
203        /**
204         * Delete a specific subject from this study, including all its relations
205         * @param subject The subject to be deleted
206         * @return A String which contains a (user-readable) message describing the changes to the database
207         */
208        String deleteSubject(Subject subject) {
209                String msg = "Subject ${subject.name} was deleted"
210
211                // Delete the subject from the event groups it was referenced in
212                this.eventGroups.each {
213                        if (it.subjects.contains(subject)) {
214                                it.removeFromSubjects(subject)
215                                msg += ", deleted from event group '${it.name}'"
216                        }
217                }
218
219                // Delete the samples that have this subject as parent
220                this.samples.findAll { it.parentSubject.equals(subject) }.each {
221                        // This should remove the sample itself too, because of the cascading belongsTo relation
222                        this.removeFromSamples(it)
223                        // But apparently it needs an explicit delete() too
224                        it.delete()
225                        msg += ", sample '${it.name}' was deleted"
226                }
227
228                // This should remove the subject itself too, because of the cascading belongsTo relation
229                this.removeFromSubjects(subject)
230                // But apparently it needs an explicit delete() too
231                subject.delete()
232
233                return msg
234        }
235
236        /**
237         * Delete an assay from the study
238         * @param Assay
239         * @void
240         */
241        def deleteAssay(Assay assay) {
242                if (assay && assay instanceof Assay) {
243                        // iterate through linked samples
244                        assay.samples.findAll { true }.each() { sample ->
245                                assay.removeFromSamples(sample)
246                        }
247
248                        // remove this assay from the study
249                        this.removeFromAssays(assay)
250
251                        // and delete it explicitly
252                        assay.delete()
253                }
254        }
255
256        /**
257         * Delete an event from the study, including all its relations
258         * @param Event
259         * @return String
260         */
261        String deleteEvent(Event event) {
262                String msg = "Event ${event} was deleted"
263
264                // remove event from the study
265                this.removeFromEvents(event)
266
267                // remove event from eventGroups
268                this.eventGroups.each() { eventGroup ->
269                        eventGroup.removeFromEvents(event)
270                }
271
272                return msg
273        }
274
275        /**
276         * Delete a samplingEvent from the study, including all its relations
277         * @param SamplingEvent
278         * @return String
279         */
280        String deleteSamplingEvent(SamplingEvent samplingEvent) {
281                String msg = "SamplingEvent ${samplingEvent} was deleted"
282
283                // remove event from eventGroups
284                this.eventGroups.each() { eventGroup ->
285                        eventGroup.removeFromSamplingEvents(samplingEvent)
286                }
287
288                // Delete the samples that have this sampling event as parent
289                this.samples.findAll { it.parentEvent.equals(samplingEvent) }.each {
290                        // This should remove the sample itself too, because of the cascading belongsTo relation
291                        this.removeFromSamples(it)
292                        // But apparently it needs an explicit delete() too
293                        it.delete()
294                        msg += ", sample '${it.name}' was deleted"
295                }
296
297                // Remove event from the study
298                // This should remove the event group itself too, because of the cascading belongsTo relation
299                this.removeFromSamplingEvents(samplingEvent)
300
301                // But apparently it needs an explicit delete() too
302                // (Which can be verified by outcommenting this line, then SampleTests.testDeleteViaParentSamplingEvent fails
303                samplingEvent.delete()
304
305                return msg
306        }
307       
308        /**
309         * Delete an eventGroup from the study, including all its relations
310         * @param EventGroup
311         * @return String
312         */
313        String deleteEventGroup(EventGroup eventGroup) {
314                String msg = "EventGroup ${eventGroup} was deleted"
315
316                // If the event group contains sampling events
317                if (eventGroup.samplingEvents) {
318                        // remove all samples that originate from this eventGroup
319                        if (eventGroup.samplingEvents.size()) {
320                                // find all samples related to this eventGroup
321                                // - subject comparison is relatively straightforward and
322                                //   behaves as expected
323                                // - event comparison behaves strange, so now we compare
324                                //              1. database id's or,
325                                //              2. object identifiers or,
326                                //              3. objects itself
327                                //   this seems now to work as expected
328                                this.samples.findAll { sample ->
329                                        (
330                                                (eventGroup.subjects.findAll {
331                                                        it.equals(sample.parentSubject)
332                                                })
333                                                &&
334                                                (eventGroup.samplingEvents.findAll {
335                                                        (
336                                                                (it.id && sample.parentEvent.id && it.id==sample.parentEvent.id)
337                                                                ||
338                                                                (it.getIdentifier() == sample.parentEvent.getIdentifier())
339                                                                ||
340                                                                it.equals(sample.parentEvent)
341                                                        )
342                                                })
343                                        )
344                                }.each() { sample ->
345                                        // remove sample from study
346
347                                        // -------
348                                        // NOTE, the right samples are found, but the don't
349                                        // get deleted from the database!
350                                        // -------
351
352                                        println ".removing sample '${sample.name}' from study '${this.title}'"
353                                        msg += ", sample '${sample.name}' was deleted"
354                                        this.removeFromSamples( sample )
355
356                                        // remove the sample from any sampling events it belongs to
357                                        this.samplingEvents.findAll { it.samples.any { it == sample }} .each {
358                                                println ".removed sample ${sample.name} from sampling event ${it} at ${it.getStartTimeString()}"
359                                                it.removeFromSamples(sample)
360                                        }
361
362                                        // TODO: remove the sample from any assays it belongs to
363
364                                        // Also here, contrary to documentation, an extra delete() is needed
365                                        // otherwise date is not properly deleted!
366                                        sample.delete()
367                                }
368                        }
369
370                        // remove all samplingEvents from this eventGroup
371                        eventGroup.samplingEvents.findAll{}.each() {
372                                eventGroup.removeFromSamplingEvents(it)
373                                println ".removed samplingEvent '${it.name}' from eventGroup '${eventGroup.name}'"
374                                msg += ", samplingEvent '${it.name}' was removed from eventGroup '${eventGroup.name}'"
375                        }
376                }
377
378                // If the event group contains subjects
379                if (eventGroup.subjects) {
380                        // remove all subject from this eventGroup
381                        eventGroup.subjects.findAll{}.each() {
382                                eventGroup.removeFromSubjects(it)
383                                println ".removed subject '${it.name}' from eventGroup '${eventGroup.name}'"
384                                msg += ", subject '${it.name}' was removed from eventGroup '${eventGroup.name}'"
385                        }
386                }
387
388                // remove the eventGroup from the study
389                println ".remove eventGroup '${eventGroup.name}' from study '${this.title}'"
390                this.removeFromEventGroups(eventGroup)
391
392                // Also here, contrary to documentation, an extra delete() is needed
393                // otherwise cascaded deletes are not properly performed
394                eventGroup.delete()
395
396                return msg
397        }
398}
Note: See TracBrowser for help on using the repository browser.