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
RevLine 
[77]1package dbnp.studycapturing
2
[976]3import dbnp.authentication.SecUser
[796]4
[77]5/**
6 * Domain class describing the basic entity in the study capture part: the Study class.
[103]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) $
[77]12 */
[754]13class Study extends TemplateEntity {
[1233]14        static searchable = true
[640]15
[974]16        SecUser owner           // The owner of the study. A new study is automatically owned by its creator.
[1233]17        String title            // The title of the study
[1245]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
[92]20        Date dateCreated
21        Date lastUpdated
[408]22        Date startDate
[974]23        List subjects
[564]24        List events
25        List samplingEvents
26        List eventGroups
27        List samples
28        List assays
[965]29        boolean published = false // Determines whether a study is private (only accessable by the owner and writers) or published (also visible to readers)
[1233]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 = [
[212]33                subjects: Subject,
[754]34                samplingEvents: SamplingEvent,
[212]35                events: Event,
[232]36                eventGroups: EventGroup,
[224]37                samples: Sample,
[212]38                assays: Assay,
39                persons: StudyPerson,
[976]40                publications: Publication,
[1233]41                readers: SecUser,
42                writers: SecUser
[103]43        ]
[77]44
[92]45        static constraints = {
[101]46                owner(nullable: true, blank: true)
[1233]47                code(nullable: false, blank: true, unique: true)
[966]48
49                // TODO: add custom validator for 'published' to assess whether the study meets all quality criteria for publication
50                // tested by SampleTests.testStudyPublish
[92]51        }
[77]52
[92]53        static mapping = {
[136]54                autoTimestamp true
[1036]55                sort "title"
[900]56
[1327]57                // Make sure the TEXT field description is persisted with a TEXT field in the database
58                description type: 'text'
[900]59                // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
60                templateTextFields type: 'text'
[1327]61
[92]62        }
[100]63
[934]64        // The external identifier (studyToken) is currently the code of the study.
[754]65        // It is used from within dbNP submodules to refer to particular study in this GSCF instance.
[1233]66
[934]67        def getToken() { code }
[754]68
[397]69        /**
[564]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',
[874]78                        type: TemplateFieldType.STRING,
79                        required: true),
[564]80                new TemplateField(
[1245]81                        name: 'description',
82                        type: TemplateFieldType.TEXT,
83                        comment:'Give a brief synopsis of what your study is about',
[1246]84                        required: true),
[1245]85                new TemplateField(
[564]86                        name: 'code',
87                        type: TemplateFieldType.STRING,
[1233]88                        preferredIdentifier: true,
[874]89                        comment: 'Fill out the code by which many people will recognize your study',
90                        required: true),
[564]91                new TemplateField(
92                        name: 'startDate',
93                        type: TemplateFieldType.DATE,
[874]94                        comment: 'Fill out the official start date or date of first action',
[967]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.',
[974]100                        required: false)
[564]101        ]
102
103        /**
[397]104         * return the title of this study
105         */
[103]106        def String toString() {
[862]107                return title
[103]108        }
[176]109
[540]110        /**
111         * returns all events and sampling events that do not belong to a group
112         */
[1036]113        def List<Event> getOrphanEvents() {
[1233]114                def orphans = events.findAll { event -> !event.belongsToGroup(eventGroups) } +
115                        samplingEvents.findAll { event -> !event.belongsToGroup(eventGroups) }
[536]116
[540]117                return orphans
118        }
[536]119
[228]120        /**
[229]121         * Return the unique Subject templates that are used in this study
122         */
[1036]123        def List<Template> giveSubjectTemplates() {
[540]124                TemplateEntity.giveTemplates(subjects)
[229]125        }
126
[370]127        /**
[778]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        /**
[857]137         * Return all unique assay templates
138         * @return Set
139         */
[1036]140        List<Template> giveAllAssayTemplates() {
[1233]141                TemplateEntity.giveTemplates(((assays) ? assays : []))
[857]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        /**
[421]153         * Return the unique Event and SamplingEvent templates that are used in this study
154         */
[1036]155        List<Template> giveAllEventTemplates() {
[421]156                // For some reason, giveAllEventTemplates() + giveAllSamplingEventTemplates()
157                // gives trouble when asking .size() to the result
158                // So we also use giveTemplates here
[1233]159                TemplateEntity.giveTemplates(((events) ? events : []) + ((samplingEvents) ? samplingEvents : []))
[421]160        }
161
162        /**
[778]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        /**
[370]175         * Return the unique Event templates that are used in this study
176         */
[1036]177        List<Template> giveEventTemplates() {
[540]178                TemplateEntity.giveTemplates(events)
[370]179        }
[229]180
181        /**
[370]182         * Return the unique SamplingEvent templates that are used in this study
183         */
[1036]184        List<Template> giveSamplingEventTemplates() {
[540]185                TemplateEntity.giveTemplates(samplingEvents)
[370]186        }
[228]187
188        /**
[229]189         * Returns the unique Sample templates that are used in the study
[228]190         */
[1036]191        List<Template> giveSampleTemplates() {
[540]192                TemplateEntity.giveTemplates(samples)
[228]193        }
[787]194
[228]195        /**
[787]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        /**
[228]205         * Returns the template of the study
206         */
[421]207        Template giveStudyTemplate() {
[540]208                return this.template
[228]209        }
[774]210
[778]211        /**
212         * Delete a specific subject from this study, including all its relations
213         * @param subject The subject to be deleted
[1233]214         * @void
[778]215         */
[1233]216        void deleteSubject(Subject subject) {
[774]217                // Delete the subject from the event groups it was referenced in
218                this.eventGroups.each {
[1353]219                        if (it.subjects?.contains(subject)) {
[774]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 {
[1233]226                        this.deleteSample(it)
[774]227                }
228
229                // This should remove the subject itself too, because of the cascading belongsTo relation
230                this.removeFromSubjects(subject)
[1233]231
[774]232                // But apparently it needs an explicit delete() too
233                subject.delete()
234        }
[778]235
236        /**
[862]237         * Delete an assay from the study
238         * @param Assay
239         * @void
240         */
241        def deleteAssay(Assay assay) {
[863]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                }
[862]254        }
255
256        /**
[778]257         * Delete an event from the study, including all its relations
258         * @param Event
[1233]259         * @void
[778]260         */
[1233]261        void deleteEvent(Event event) {
[778]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                }
[1233]269        }
[778]270
[1233]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()
[778]293        }
294
295        /**
296         * Delete a samplingEvent from the study, including all its relations
297         * @param SamplingEvent
[1233]298         * @void
[778]299         */
[1233]300        void deleteSamplingEvent(SamplingEvent samplingEvent) {
[778]301                // remove event from eventGroups
302                this.eventGroups.each() { eventGroup ->
303                        eventGroup.removeFromSamplingEvents(samplingEvent)
304                }
305
[812]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
[1233]309                        this.deleteSample(it)
[812]310                }
311
312                // Remove event from the study
313                // This should remove the event group itself too, because of the cascading belongsTo relation
[778]314                this.removeFromSamplingEvents(samplingEvent)
[819]315
[812]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()
[1233]319        }
[778]320
321        /**
322         * Delete an eventGroup from the study, including all its relations
323         * @param EventGroup
[1233]324         * @void
[778]325         */
[1233]326        void deleteEventGroup(EventGroup eventGroup) {
[813]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                                        (
[1233]341                                        (eventGroup.subjects.findAll {
342                                                it.equals(sample.parentSubject)
343                                        })
[813]344                                                &&
345                                                (eventGroup.samplingEvents.findAll {
346                                                        (
[1233]347                                                        (it.id && sample.parentEvent.id && it.id == sample.parentEvent.id)
[813]348                                                                ||
349                                                                (it.getIdentifier() == sample.parentEvent.getIdentifier())
350                                                                ||
351                                                                it.equals(sample.parentEvent)
352                                                        )
353                                                })
354                                        )
[965]355                                }.each() { sample ->
[813]356                                        // remove sample from study
[1233]357                                        this.deleteSample(sample)
[813]358                                }
[805]359                        }
360
[813]361                        // remove all samplingEvents from this eventGroup
[1233]362                        eventGroup.samplingEvents.findAll {}.each() {
[813]363                                eventGroup.removeFromSamplingEvents(it)
364                        }
[807]365                }
[805]366
[813]367                // If the event group contains subjects
368                if (eventGroup.subjects) {
369                        // remove all subject from this eventGroup
[1233]370                        eventGroup.subjects.findAll {}.each() {
[813]371                                eventGroup.removeFromSubjects(it)
372                        }
[807]373                }
[805]374
[778]375                // remove the eventGroup from the study
376                this.removeFromEventGroups(eventGroup)
[819]377
[817]378                // Also here, contrary to documentation, an extra delete() is needed
[819]379                // otherwise cascaded deletes are not properly performed
[817]380                eventGroup.delete()
[778]381        }
[976]382
[1233]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                }
[976]391
[1222]392                // Administrators are allowed to read every study
[1233]393                if (loggedInUser.hasAdminRights()) {
[1222]394                        return true;
395                }
396
[1233]397                // Owners and writers are allowed to read this study
398                if (this.owner == loggedInUser || this.writers.contains(loggedInUser)) {
399                        return true
400                }
[976]401
[1233]402                // Readers are allowed to read this study when it is published
403                if (this.readers.contains(loggedInUser) && this.published) {
404                        return true
405                }
[1222]406
[1233]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
[1222]418                // Administrators are allowed to write every study
[1233]419                if (loggedInUser.hasAdminRights()) {
[1222]420                        return true;
421                }
422
[1233]423                return this.owner == loggedInUser || this.writers.contains(loggedInUser)
424        }
[976]425
[1233]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        }
[976]435
[1222]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
[1233]441                if (user == null)
[1245]442                        return [];
[1233]443
[1222]444                def c = Study.createCriteria()
445
446                // Administrators are allowed to read everything
[1233]447                if (user.hasAdminRights()) {
[1222]448                        return c.list {
449                                maxResults(max)
450                        }
451                }
452
453                return c.list {
454                        maxResults(max)
455                        or {
[1233]456                                eq("owner", user)
[1222]457                                writers {
[1233]458                                        eq("id", user.id)
[1222]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
[1233]470                // Administrators are allowed to read everything
471                if (user == null) {
472                        return c.list {
[1222]473                                maxResults(max)
[1233]474                                and {
475                                        eq("published", true)
476                                        eq("publicstudy", true)
477                                }
478                        }
479                } else if (user.hasAdminRights()) {
480                        return c.list {
[1222]481                                maxResults(max)
482                        }
[1233]483                } else {
484                        return c.list {
[1222]485                                maxResults(max)
[1233]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                }
[1222]500        }
[77]501}
Note: See TracBrowser for help on using the repository browser.