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

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