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

Last change on this file since 1353 was 1353, checked in by work@…, 10 years ago
  • fixed some issues with ajaxbuttons that were still left unworking after refactoring into ajaxflows (improvement #183)
  • updated ajaxflow to 0.1.15
  • Property svn:keywords set to Author Date Rev
File size: 13.0 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: 1353 $
10 * $Author: work@osx.eu $
11 * $Date: 2011-01-07 17:12:04 +0000 (vr, 07 jan 2011) $
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                // Make sure the TEXT field description is persisted with a TEXT field in the database
58                description type: 'text'
59                // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
60                templateTextFields type: 'text'
61
62        }
63
64        // The external identifier (studyToken) is currently the code of the study.
65        // It is used from within dbNP submodules to refer to particular study in this GSCF instance.
66
67        def getToken() { code }
68
69        /**
70         * return the domain fields for this domain class
71         * @return List
72         */
73        static List<TemplateField> giveDomainFields() { return Study.domainFields }
74
75        static final List<TemplateField> domainFields = [
76                new TemplateField(
77                        name: 'title',
78                        type: TemplateFieldType.STRING,
79                        required: true),
80                new TemplateField(
81                        name: 'description',
82                        type: TemplateFieldType.TEXT,
83                        comment:'Give a brief synopsis of what your study is about',
84                        required: true),
85                new TemplateField(
86                        name: 'code',
87                        type: TemplateFieldType.STRING,
88                        preferredIdentifier: true,
89                        comment: 'Fill out the code by which many people will recognize your study',
90                        required: true),
91                new TemplateField(
92                        name: 'startDate',
93                        type: TemplateFieldType.DATE,
94                        comment: 'Fill out the official start date or date of first action',
95                        required: true),
96                new TemplateField(
97                        name: 'published',
98                        type: TemplateFieldType.BOOLEAN,
99                        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.',
100                        required: false)
101        ]
102
103        /**
104         * return the title of this study
105         */
106        def String toString() {
107                return title
108        }
109
110        /**
111         * returns all events and sampling events that do not belong to a group
112         */
113        def List<Event> getOrphanEvents() {
114                def orphans = events.findAll { event -> !event.belongsToGroup(eventGroups) } +
115                        samplingEvents.findAll { event -> !event.belongsToGroup(eventGroups) }
116
117                return orphans
118        }
119
120        /**
121         * Return the unique Subject templates that are used in this study
122         */
123        def List<Template> giveSubjectTemplates() {
124                TemplateEntity.giveTemplates(subjects)
125        }
126
127        /**
128         * Return all subjects for a specific template
129         * @param Template
130         * @return ArrayList
131         */
132        def ArrayList<Subject> giveSubjectsForTemplate(Template template) {
133                subjects.findAll { it.template.equals(template) }
134        }
135
136        /**
137         * Return all unique assay templates
138         * @return Set
139         */
140        List<Template> giveAllAssayTemplates() {
141                TemplateEntity.giveTemplates(((assays) ? assays : []))
142        }
143
144        /**
145         * Return all assays for a particular template
146         * @return ArrayList
147         */
148        def ArrayList giveAssaysForTemplate(Template template) {
149                assays.findAll { it.template.equals(template) }
150        }
151
152        /**
153         * Return the unique Event and SamplingEvent templates that are used in this study
154         */
155        List<Template> giveAllEventTemplates() {
156                // For some reason, giveAllEventTemplates() + giveAllSamplingEventTemplates()
157                // gives trouble when asking .size() to the result
158                // So we also use giveTemplates here
159                TemplateEntity.giveTemplates(((events) ? events : []) + ((samplingEvents) ? samplingEvents : []))
160        }
161
162        /**
163         * Return all events and samplingEvenets for a specific template
164         * @param Template
165         * @return ArrayList
166         */
167        def ArrayList giveEventsForTemplate(Template template) {
168                def events = events.findAll { it.template.equals(template) }
169                def samplingEvents = samplingEvents.findAll { it.template.equals(template) }
170
171                return (events) ? events : samplingEvents
172        }
173
174        /**
175         * Return the unique Event templates that are used in this study
176         */
177        List<Template> giveEventTemplates() {
178                TemplateEntity.giveTemplates(events)
179        }
180
181        /**
182         * Return the unique SamplingEvent templates that are used in this study
183         */
184        List<Template> giveSamplingEventTemplates() {
185                TemplateEntity.giveTemplates(samplingEvents)
186        }
187
188        /**
189         * Returns the unique Sample templates that are used in the study
190         */
191        List<Template> giveSampleTemplates() {
192                TemplateEntity.giveTemplates(samples)
193        }
194
195        /**
196         * Return all samples for a specific template
197         * @param Template
198         * @return ArrayList
199         */
200        def ArrayList<Subject> giveSamplesForTemplate(Template template) {
201                samples.findAll { it.template.equals(template) }
202        }
203
204        /**
205         * Returns the template of the study
206         */
207        Template giveStudyTemplate() {
208                return this.template
209        }
210
211        /**
212         * Delete a specific subject from this study, including all its relations
213         * @param subject The subject to be deleted
214         * @void
215         */
216        void deleteSubject(Subject subject) {
217                // Delete the subject from the event groups it was referenced in
218                this.eventGroups.each {
219                        if (it.subjects?.contains(subject)) {
220                                it.removeFromSubjects(subject)
221                        }
222                }
223
224                // Delete the samples that have this subject as parent
225                this.samples.findAll { it.parentSubject.equals(subject) }.each {
226                        this.deleteSample(it)
227                }
228
229                // This should remove the subject itself too, because of the cascading belongsTo relation
230                this.removeFromSubjects(subject)
231
232                // But apparently it needs an explicit delete() too
233                subject.delete()
234        }
235
236        /**
237         * Delete an assay from the study
238         * @param Assay
239         * @void
240         */
241        def deleteAssay(Assay assay) {
242                if (assay && assay instanceof Assay) {
243                        // iterate through linked samples
244                        assay.samples.findAll { true }.each() { sample ->
245                                assay.removeFromSamples(sample)
246                        }
247
248                        // remove this assay from the study
249                        this.removeFromAssays(assay)
250
251                        // and delete it explicitly
252                        assay.delete()
253                }
254        }
255
256        /**
257         * Delete an event from the study, including all its relations
258         * @param Event
259         * @void
260         */
261        void deleteEvent(Event event) {
262                // remove event from the study
263                this.removeFromEvents(event)
264
265                // remove event from eventGroups
266                this.eventGroups.each() { eventGroup ->
267                        eventGroup.removeFromEvents(event)
268                }
269        }
270
271        /**
272         * Delete a sample from the study, including all its relations
273         * @param Event
274         * @void
275         */
276        void deleteSample(Sample sample) {
277                // remove the sample from the study
278                this.removeFromSamples(sample)
279
280                // remove the sample from any sampling events it belongs to
281                this.samplingEvents.findAll { it.samples.any { it == sample }}.each {
282                        it.removeFromSamples(sample)
283                }
284
285                // remove the sample from any assays it belongs to
286                this.assays.findAll { it.samples.any { it == sample }}.each {
287                        it.removeFromSamples(sample)
288                }
289
290                // Also here, contrary to documentation, an extra delete() is needed
291                // otherwise date is not properly deleted!
292                sample.delete()
293        }
294
295        /**
296         * Delete a samplingEvent from the study, including all its relations
297         * @param SamplingEvent
298         * @void
299         */
300        void deleteSamplingEvent(SamplingEvent samplingEvent) {
301                // remove event from eventGroups
302                this.eventGroups.each() { eventGroup ->
303                        eventGroup.removeFromSamplingEvents(samplingEvent)
304                }
305
306                // Delete the samples that have this sampling event as parent
307                this.samples.findAll { it.parentEvent.equals(samplingEvent) }.each {
308                        // This should remove the sample itself too, because of the cascading belongsTo relation
309                        this.deleteSample(it)
310                }
311
312                // Remove event from the study
313                // This should remove the event group itself too, because of the cascading belongsTo relation
314                this.removeFromSamplingEvents(samplingEvent)
315
316                // But apparently it needs an explicit delete() too
317                // (Which can be verified by outcommenting this line, then SampleTests.testDeleteViaParentSamplingEvent fails
318                samplingEvent.delete()
319        }
320
321        /**
322         * Delete an eventGroup from the study, including all its relations
323         * @param EventGroup
324         * @void
325         */
326        void deleteEventGroup(EventGroup eventGroup) {
327                // If the event group contains sampling events
328                if (eventGroup.samplingEvents) {
329                        // remove all samples that originate from this eventGroup
330                        if (eventGroup.samplingEvents.size()) {
331                                // find all samples related to this eventGroup
332                                // - subject comparison is relatively straightforward and
333                                //   behaves as expected
334                                // - event comparison behaves strange, so now we compare
335                                //              1. database id's or,
336                                //              2. object identifiers or,
337                                //              3. objects itself
338                                //   this seems now to work as expected
339                                this.samples.findAll { sample ->
340                                        (
341                                        (eventGroup.subjects.findAll {
342                                                it.equals(sample.parentSubject)
343                                        })
344                                                &&
345                                                (eventGroup.samplingEvents.findAll {
346                                                        (
347                                                        (it.id && sample.parentEvent.id && it.id == sample.parentEvent.id)
348                                                                ||
349                                                                (it.getIdentifier() == sample.parentEvent.getIdentifier())
350                                                                ||
351                                                                it.equals(sample.parentEvent)
352                                                        )
353                                                })
354                                        )
355                                }.each() { sample ->
356                                        // remove sample from study
357                                        this.deleteSample(sample)
358                                }
359                        }
360
361                        // remove all samplingEvents from this eventGroup
362                        eventGroup.samplingEvents.findAll {}.each() {
363                                eventGroup.removeFromSamplingEvents(it)
364                        }
365                }
366
367                // If the event group contains subjects
368                if (eventGroup.subjects) {
369                        // remove all subject from this eventGroup
370                        eventGroup.subjects.findAll {}.each() {
371                                eventGroup.removeFromSubjects(it)
372                        }
373                }
374
375                // remove the eventGroup from the study
376                this.removeFromEventGroups(eventGroup)
377
378                // Also here, contrary to documentation, an extra delete() is needed
379                // otherwise cascaded deletes are not properly performed
380                eventGroup.delete()
381        }
382
383        /**
384         * Returns true if the given user is allowed to read this study
385         */
386        public boolean canRead(SecUser loggedInUser) {
387                // Anonymous readers are only given access when published and public
388                if (loggedInUser == null) {
389                        return this.publicstudy && this.published;
390                }
391
392                // Administrators are allowed to read every study
393                if (loggedInUser.hasAdminRights()) {
394                        return true;
395                }
396
397                // Owners and writers are allowed to read this study
398                if (this.owner == loggedInUser || this.writers.contains(loggedInUser)) {
399                        return true
400                }
401
402                // Readers are allowed to read this study when it is published
403                if (this.readers.contains(loggedInUser) && this.published) {
404                        return true
405                }
406
407                return false
408        }
409
410        /**
411         * Returns true if the given user is allowed to write this study
412         */
413        public boolean canWrite(SecUser loggedInUser) {
414                if (loggedInUser == null) {
415                        return false;
416                }
417
418                // Administrators are allowed to write every study
419                if (loggedInUser.hasAdminRights()) {
420                        return true;
421                }
422
423                return this.owner == loggedInUser || this.writers.contains(loggedInUser)
424        }
425
426        /**
427         * Returns true if the given user is the owner of this study
428         */
429        public boolean isOwner(SecUser loggedInUser) {
430                if (loggedInUser == null) {
431                        return false;
432                }
433                return this.owner == loggedInUser
434        }
435
436        /**
437         * Returns a list of studies that are writable for the given user
438         */
439        public static giveWritableStudies(SecUser user, int max) {
440                // User that are not logged in, are not allowed to write to a study
441                if (user == null)
442                        return [];
443
444                def c = Study.createCriteria()
445
446                // Administrators are allowed to read everything
447                if (user.hasAdminRights()) {
448                        return c.list {
449                                maxResults(max)
450                        }
451                }
452
453                return c.list {
454                        maxResults(max)
455                        or {
456                                eq("owner", user)
457                                writers {
458                                        eq("id", user.id)
459                                }
460                        }
461                }
462        }
463
464        /**
465         * Returns a list of studies that are readable by the given user
466         */
467        public static giveReadableStudies(SecUser user, int max) {
468                def c = Study.createCriteria()
469
470                // Administrators are allowed to read everything
471                if (user == null) {
472                        return c.list {
473                                maxResults(max)
474                                and {
475                                        eq("published", true)
476                                        eq("publicstudy", true)
477                                }
478                        }
479                } else if (user.hasAdminRights()) {
480                        return c.list {
481                                maxResults(max)
482                        }
483                } else {
484                        return c.list {
485                                maxResults(max)
486                                or {
487                                        eq("owner", user)
488                                        writers {
489                                                eq("id", user.id)
490                                        }
491                                        and {
492                                                readers {
493                                                        eq("id", user.id)
494                                                }
495                                                eq("published", true)
496                                        }
497                                }
498                        }
499                }
500        }
501}
Note: See TracBrowser for help on using the repository browser.