source: trunk/grails-app/domain/dbnp/studycapturing/Sample.groovy

Last change on this file was 2199, checked in by work@…, 8 years ago
  • implemented getSamplesForAssay api call
  • Property svn:keywords set to Rev Author Date
File size: 8.7 KB
RevLine 
[81]1package dbnp.studycapturing
[2199]2
[1792]3import java.util.ArrayList;
4
[1457]5import org.dbnp.gdt.*
[81]6
[84]7/**
8 * The Sample class describes an actual sample which results from a SamplingEvent.
[822]9 *
10 * Revision information:
11 * $Rev: 2199 $
12 * $Author: work@osx.eu $
13 * $Date: 2012-03-30 13:35:57 +0000 (vr, 30 mrt 2012) $
[84]14 */
[1452]15class Sample extends TemplateEntity {
[2199]16    static belongsTo = [
17            // A Sample always belongs to one study.
18            parent: Study,
[1169]19
[2199]20            // A Sample optionally has a parent Subject from which it was taken, this Subject should be in the same parent study.
21            parentSubject: Subject,
[1169]22
[2199]23            // Also, it has a parent SamplingEvent describing the actual sampling, also within the same parent study.
24            parentEvent: SamplingEvent,
[1169]25
[2199]26            // And it has a parent EventGroup which tied it to its parent subject and parent event
27            parentEventGroup: EventGroup
[1169]28
[2199]29            // We can't have parentAssay since a Sample can belong to multiple Assays
30    ]
[654]31
[2199]32    String name             // should be unique with respect to the parent study (which can be inferred)
33    Term material            // material of the sample (should normally be bound to the BRENDA ontology)
[754]34
[2199]35    /**
36     * UUID of this sample
37     */
38    String sampleUUID
[375]39
[2199]40    /**
41     * return the domain fields for this domain class
42     * @return List
43     */
44    static List<TemplateField> giveDomainFields() { return Sample.domainFields }
[1034]45
[2199]46    // We have to specify an ontology list for the material property. However, at compile time, this ontology does of course not exist.
47    // Therefore, the ontology is added at runtime in the bootstrap, possibly downloading the ontology properties if it is not present in the database yet.
48    static List<TemplateField> domainFields = [
49            new TemplateField(
50                    name: 'name',
51                    type: TemplateFieldType.STRING,
52                    preferredIdentifier: true,
53                    required: true
54            ),
55            new TemplateField(
56                    name: 'material',
57                    type: TemplateFieldType.ONTOLOGYTERM,
58                    comment: "The material is based on the BRENDA tissue / enzyme source ontology, a structured controlled vocabulary for the source of an enzyme. It comprises terms for tissues, cell lines, cell types and cell cultures from uni- and multicellular organisms. If a material is missing, please add it by using 'add more'"
59            )
60    ]
[1034]61
[2199]62    static constraints = {
63        // The parent subject is optional, e.g. in a biobank of samples the subject could be unknown or non-existing.
64        parentSubject(nullable: true)
[1169]65
[2199]66        // The same holds for parentEvent
67        parentEvent(nullable: true)
[1588]68
[2199]69        // and for parentEventGroup
70        parentEventGroup(nullable: true)
[654]71
[2199]72        // The material domain field is optional
73        material(nullable: true)
[861]74
[2199]75        sampleUUID(nullable: true, unique: true)
[861]76
[2199]77        // Check if the externalSampleId (currently defined as name) is really unique within each parent study of this sample.
78        // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraint
79        name(unique: ['parent'])
[861]80
[2199]81        // Same, but also when the other sample is not even in the database
82        // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraintAtValidate
83        name(validator: { field, obj, errors ->
84            // 'obj' refers to the actual Sample object
[861]85
[2199]86            // define a boolean
87            def error = false
[1373]88
[2199]89            // check whether obj.parent.samples is not null at this stage to avoid null pointer exception
90            if (obj.parent) {
[861]91
[2199]92                if (obj.parent.samples) {
[81]93
[2199]94                    // check if there is exactly one sample with this name in the study (this one)
95                    if (obj.parent.samples.findAll { it.name == obj.name}.size() > 1) {
96                        error = true
97                        errors.rejectValue(
98                                'name',
99                                'sample.UniqueNameViolation',
100                                [obj.name, obj.parent] as Object[],
101                                'Sample name {0} appears multiple times in study {1}'
102                        )
103                    }
104                }
105            }
106            else {
107                // if there is no parent study defined, fail immediately
108                error = true
109            }
110
111            // got an error, or not?
112            return (!error)
113        })
114    }
115
[1036]116    static mapping = {
117        sort "name"
[1198]118
119        // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
[2199]120        templateTextFields type: 'text'
[1036]121    }
122
[2199]123    static getSamplesFor(event) {
124        return Sample.findAll('from Sample s where s.parentEvent =:event', [event: event])
125    }
[651]126
[2199]127    /**
128     * Returns all assays this samples has been processed in
129     * @return List of assays
130     */
131    public List getAssays() {
132        return Assay.executeQuery('select distinct a from Assay a inner join a.samples s where s = :sample', ['sample': this])
133    }
[1482]134
[2199]135    def String toString() {
136        return name
137    }
[1792]138
[2199]139    /**
140     * Basic equals method to check whether objects are equals, by comparing the ids
141     * @param o Object to compare with
142     * @return True iff the id of the given Sample is equal to the id of this Sample
143     */
144    public boolean equals(Object o) {
145        if (o == null)
146            return false;
[1792]147
[2199]148        if (!(o instanceof Sample))
149            return false
[1792]150
[2199]151        Sample s = (Sample) o;
[1792]152
[2199]153        return this.is(s) || this.id == s.id
154    }
[1792]155
[2199]156    /**
157     * Returns the UUID of this sample and generates one if needed
158     */
159    public String giveUUID() {
160        if (!this.sampleUUID) {
161            this.sampleUUID = UUID.randomUUID().toString();
162            if (!this.save(flush: true)) {
163                //println "Couldn't save sample UUID: " + this.getErrors();
164            }
165        }
[1792]166
[2199]167        return this.sampleUUID;
168    }
169
170    /**
171     * Returns a human readable string of a list of samples, with a maximum number
172     * of characters
173     *
174     * @param sampleList List with Sample objects
175     * @param maxChars maximum number of characters returned
176     * @return human readble string with at most maxChars characters, representing the samples given.
177     */
178    public static String trimSampleNames(ArrayList sampleList, Integer maxChars) {
179        def simpleSamples = sampleList.name.join(', ');
180        def showSamples
181
182        // If the subjects will fit, show them all
183        if (!maxChars || simpleSamples.size() < maxChars) {
184            showSamples = simpleSamples;
185        } else {
186            // Always add the first name
187            def sampleNames = sampleList[0]?.name;
188
189            // Continue adding names until the length is to long
190            def id = 0;
191            sampleList.each { sample ->
192                if (id > 0) {
193                    if (sampleNames?.size() + sample.name?.size() < maxChars - 15) {
194                        sampleNames += ", " + sample.name;
195                    } else {
196                        return;
197                    }
198                }
199                id++;
200            }
201
202            // Add a postfix
203            sampleNames += " and " + (sampleList?.size() - id) + " more";
204
205            showSamples = sampleNames;
206        }
207
208        return showSamples
209    }
210
[2153]211    public static String generateSampleName(flow, subject, eventGroup, samplingEvent) {
212        def samplingEventName = ucwords(samplingEvent.template.name)
213        def eventGroupName = ucwords(eventGroup.name).replaceAll("([ ]{1,})", "")
214        def sampleTemplateName = (samplingEvent.sampleTemplate) ? ucwords(samplingEvent.sampleTemplate.name) : ''
215        def sampleName = (ucwords(subject.name) + '_' + samplingEventName + '_' + eventGroupName + '_' + new RelTime(samplingEvent.startTime).toString() + '_' + sampleTemplateName).replaceAll("([ ]{1,})", "")
216        def tempSampleIterator = 0
217        def tempSampleName = sampleName
218
219        // make sure sampleName is unique
220        if (flow.study.samples) {
221            while (flow.study.samples.find { it.name == tempSampleName }) {
222                tempSampleIterator++
223                tempSampleName = sampleName + "_" + tempSampleIterator
224            }
225            sampleName = tempSampleName
226        }
227        return sampleName
228    }
229
230    /**
[2199]231     * groovy / java equivalent of php's ucwords function
232     *
233     * Capitalize all first letters of separate words
234     *
235     * @param String
236     * @return String
237     */
238    public static ucwords(String text) {
239        def newText = ''
[2153]240
[2199]241        // change case to lowercase
242        text = text.toLowerCase()
[2153]243
[2199]244        // iterate through words
245        text.split(" ").each() {
246            newText += it[0].toUpperCase() + it.substring(1) + " "
247        }
[2153]248
[2199]249        return newText.substring(0, newText.size() - 1)
250    }
[81]251}
Note: See TracBrowser for help on using the repository browser.