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

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

also added assays check in Study.deleteEventGroup, added test to check whether a study with orphan events cannot be published (functionality is still to be implemented)

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