root/trunk/grails-app/domain/dbnp/studycapturing/Study.groovy @ 1013

Revision 1013, 12.9 KB (checked in by business@…, 3 years ago)

changed Ontology ncboId to unique, extended range of fulltext query

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