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

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

added Study 'published' field to domainFields

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