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

Last change on this file since 1245 was 1245, checked in by work@…, 7 years ago
  • moved 'description' from study template fields into domain variables so that every study will contain a -non required- description which is more logical
  • Property svn:keywords set to Author Date Rev
File size: 12.9 KB
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: 1245 $
10 * $Author: work@osx.eu $
11 * $Date: 2010-12-09 13:51:51 +0000 (do, 09 dec 2010) $
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 description      // A brief synopsis of what the study is about
19        String code                     // currently used as the external study ID, e.g. to reference a study in a SAM module
20        Date dateCreated
21        Date lastUpdated
22        Date startDate
23        List subjects
24        List events
25        List samplingEvents
26        List eventGroups
27        List samples
28        List assays
29        boolean published = false // Determines whether a study is private (only accessable by the owner and writers) or published (also visible to readers)
30        boolean publicstudy = false  // Determines whether anonymous users are allowed to see this study. This has only effect when published = true
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                readers: SecUser,
42                writers: SecUser
43        ]
44
45        static constraints = {
46                owner(nullable: true, blank: true)
47                code(nullable: false, blank: true, unique: true)
48
49                // TODO: add custom validator for 'published' to assess whether the study meets all quality criteria for publication
50                // tested by SampleTests.testStudyPublish
51        }
52
53        static mapping = {
54                autoTimestamp true
55                sort "title"
56
57                // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
58                templateTextFields type: 'text'
59        }
60
61        // The external identifier (studyToken) is currently the code of the study.
62        // It is used from within dbNP submodules to refer to particular study in this GSCF instance.
63
64        def getToken() { code }
65
66        /**
67         * return the domain fields for this domain class
68         * @return List
69         */
70        static List<TemplateField> giveDomainFields() { return Study.domainFields }
71
72        static final List<TemplateField> domainFields = [
73                new TemplateField(
74                        name: 'title',
75                        type: TemplateFieldType.STRING,
76                        required: true),
77                new TemplateField(
78                        name: 'description',
79                        type: TemplateFieldType.TEXT,
80                        comment:'Give a brief synopsis of what your study is about',
81                        required: false),
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 List<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 List<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        List<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        List<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         * Return all events and samplingEvenets for a specific template
161         * @param Template
162         * @return ArrayList
163         */
164        def ArrayList giveEventsForTemplate(Template template) {
165                def events = events.findAll { it.template.equals(template) }
166                def samplingEvents = samplingEvents.findAll { it.template.equals(template) }
167
168                return (events) ? events : samplingEvents
169        }
170
171        /**
172         * Return the unique Event templates that are used in this study
173         */
174        List<Template> giveEventTemplates() {
175                TemplateEntity.giveTemplates(events)
176        }
177
178        /**
179         * Return the unique SamplingEvent templates that are used in this study
180         */
181        List<Template> giveSamplingEventTemplates() {
182                TemplateEntity.giveTemplates(samplingEvents)
183        }
184
185        /**
186         * Returns the unique Sample templates that are used in the study
187         */
188        List<Template> giveSampleTemplates() {
189                TemplateEntity.giveTemplates(samples)
190        }
191
192        /**
193         * Return all samples for a specific template
194         * @param Template
195         * @return ArrayList
196         */
197        def ArrayList<Subject> giveSamplesForTemplate(Template template) {
198                samples.findAll { it.template.equals(template) }
199        }
200
201        /**
202         * Returns the template of the study
203         */
204        Template giveStudyTemplate() {
205                return this.template
206        }
207
208        /**
209         * Delete a specific subject from this study, including all its relations
210         * @param subject The subject to be deleted
211         * @void
212         */
213        void deleteSubject(Subject subject) {
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                        }
219                }
220
221                // Delete the samples that have this subject as parent
222                this.samples.findAll { it.parentSubject.equals(subject) }.each {
223                        this.deleteSample(it)
224                }
225
226                // This should remove the subject itself too, because of the cascading belongsTo relation
227                this.removeFromSubjects(subject)
228
229                // But apparently it needs an explicit delete() too
230                subject.delete()
231        }
232
233        /**
234         * Delete an assay from the study
235         * @param Assay
236         * @void
237         */
238        def deleteAssay(Assay assay) {
239                if (assay && assay instanceof Assay) {
240                        // iterate through linked samples
241                        assay.samples.findAll { true }.each() { sample ->
242                                assay.removeFromSamples(sample)
243                        }
244
245                        // remove this assay from the study
246                        this.removeFromAssays(assay)
247
248                        // and delete it explicitly
249                        assay.delete()
250                }
251        }
252
253        /**
254         * Delete an event from the study, including all its relations
255         * @param Event
256         * @void
257         */
258        void deleteEvent(Event event) {
259                // remove event from the study
260                this.removeFromEvents(event)
261
262                // remove event from eventGroups
263                this.eventGroups.each() { eventGroup ->
264                        eventGroup.removeFromEvents(event)
265                }
266        }
267
268        /**
269         * Delete a sample from the study, including all its relations
270         * @param Event
271         * @void
272         */
273        void deleteSample(Sample sample) {
274                // remove the sample from the study
275                this.removeFromSamples(sample)
276
277                // remove the sample from any sampling events it belongs to
278                this.samplingEvents.findAll { it.samples.any { it == sample }}.each {
279                        it.removeFromSamples(sample)
280                }
281
282                // remove the sample from any assays it belongs to
283                this.assays.findAll { it.samples.any { it == sample }}.each {
284                        it.removeFromSamples(sample)
285                }
286
287                // Also here, contrary to documentation, an extra delete() is needed
288                // otherwise date is not properly deleted!
289                sample.delete()
290        }
291
292        /**
293         * Delete a samplingEvent from the study, including all its relations
294         * @param SamplingEvent
295         * @void
296         */
297        void deleteSamplingEvent(SamplingEvent samplingEvent) {
298                // remove event from eventGroups
299                this.eventGroups.each() { eventGroup ->
300                        eventGroup.removeFromSamplingEvents(samplingEvent)
301                }
302
303                // Delete the samples that have this sampling event as parent
304                this.samples.findAll { it.parentEvent.equals(samplingEvent) }.each {
305                        // This should remove the sample itself too, because of the cascading belongsTo relation
306                        this.deleteSample(it)
307                }
308
309                // Remove event from the study
310                // This should remove the event group itself too, because of the cascading belongsTo relation
311                this.removeFromSamplingEvents(samplingEvent)
312
313                // But apparently it needs an explicit delete() too
314                // (Which can be verified by outcommenting this line, then SampleTests.testDeleteViaParentSamplingEvent fails
315                samplingEvent.delete()
316        }
317
318        /**
319         * Delete an eventGroup from the study, including all its relations
320         * @param EventGroup
321         * @void
322         */
323        void deleteEventGroup(EventGroup eventGroup) {
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                                        this.deleteSample(sample)
355                                }
356                        }
357
358                        // remove all samplingEvents from this eventGroup
359                        eventGroup.samplingEvents.findAll {}.each() {
360                                eventGroup.removeFromSamplingEvents(it)
361                        }
362                }
363
364                // If the event group contains subjects
365                if (eventGroup.subjects) {
366                        // remove all subject from this eventGroup
367                        eventGroup.subjects.findAll {}.each() {
368                                eventGroup.removeFromSubjects(it)
369                        }
370                }
371
372                // remove the eventGroup from the study
373                this.removeFromEventGroups(eventGroup)
374
375                // Also here, contrary to documentation, an extra delete() is needed
376                // otherwise cascaded deletes are not properly performed
377                eventGroup.delete()
378        }
379
380        /**
381         * Returns true if the given user is allowed to read this study
382         */
383        public boolean canRead(SecUser loggedInUser) {
384                // Anonymous readers are only given access when published and public
385                if (loggedInUser == null) {
386                        return this.publicstudy && this.published;
387                }
388
389                // Administrators are allowed to read every study
390                if (loggedInUser.hasAdminRights()) {
391                        return true;
392                }
393
394                // Owners and writers are allowed to read this study
395                if (this.owner == loggedInUser || this.writers.contains(loggedInUser)) {
396                        return true
397                }
398
399                // Readers are allowed to read this study when it is published
400                if (this.readers.contains(loggedInUser) && this.published) {
401                        return true
402                }
403
404                return false
405        }
406
407        /**
408         * Returns true if the given user is allowed to write this study
409         */
410        public boolean canWrite(SecUser loggedInUser) {
411                if (loggedInUser == null) {
412                        return false;
413                }
414
415                // Administrators are allowed to write every study
416                if (loggedInUser.hasAdminRights()) {
417                        return true;
418                }
419
420                return this.owner == loggedInUser || this.writers.contains(loggedInUser)
421        }
422
423        /**
424         * Returns true if the given user is the owner of this study
425         */
426        public boolean isOwner(SecUser loggedInUser) {
427                if (loggedInUser == null) {
428                        return false;
429                }
430                return this.owner == loggedInUser
431        }
432
433        /**
434         * Returns a list of studies that are writable for the given user
435         */
436        public static giveWritableStudies(SecUser user, int max) {
437                // User that are not logged in, are not allowed to write to a study
438                if (user == null)
439                        return [];
440
441                def c = Study.createCriteria()
442
443                // Administrators are allowed to read everything
444                if (user.hasAdminRights()) {
445                        return c.list {
446                                maxResults(max)
447                        }
448                }
449
450                return c.list {
451                        maxResults(max)
452                        or {
453                                eq("owner", user)
454                                writers {
455                                        eq("id", user.id)
456                                }
457                        }
458                }
459        }
460
461        /**
462         * Returns a list of studies that are readable by the given user
463         */
464        public static giveReadableStudies(SecUser user, int max) {
465                def c = Study.createCriteria()
466
467                // Administrators are allowed to read everything
468                if (user == null) {
469                        return c.list {
470                                maxResults(max)
471                                and {
472                                        eq("published", true)
473                                        eq("publicstudy", true)
474                                }
475                        }
476                } else if (user.hasAdminRights()) {
477                        return c.list {
478                                maxResults(max)
479                        }
480                } else {
481                        return c.list {
482                                maxResults(max)
483                                or {
484                                        eq("owner", user)
485                                        writers {
486                                                eq("id", user.id)
487                                        }
488                                        and {
489                                                readers {
490                                                        eq("id", user.id)
491                                                }
492                                                eq("published", true)
493                                        }
494                                }
495                        }
496                }
497        }
498}
Note: See TracBrowser for help on using the repository browser.