source: trunk/src/groovy/dbnp/query/SampleSearch.groovy @ 1717

Last change on this file since 1717 was 1717, checked in by robert@…, 12 years ago

Implemented searching for 'any field' in advanced query

  • Property svn:keywords set to Rev Author Date
File size: 9.2 KB
Line 
1/**
2 * SampleSearch Domain Class
3 *
4 * This class provides querying capabilities for searching for samples
5 *
6 * @author  Robert Horlings (robert@isdat.nl)
7 * @since       20110118
8 * @package     dbnp.query
9 *
10 * Revision information:
11 * $Rev: 1717 $
12 * $Author: robert@isdat.nl $
13 * $Date: 2011-04-06 15:24:14 +0000 (wo, 06 apr 2011) $
14 */
15package dbnp.query
16
17import groovy.lang.Closure;
18
19import java.util.Map;
20
21import dbnp.studycapturing.*
22import org.dbnp.gdt.*
23import org.apache.commons.logging.LogFactory;
24
25class SampleSearch extends Search {
26        private static final log = LogFactory.getLog(this);
27
28        public SampleSearch() {
29                super();
30
31                this.entity = "Sample";
32        }
33
34        /**
35         * Searches for samples based on the given criteria. All criteria have to be satisfied and
36         * criteria for the different entities are satisfied as follows:
37         *
38         *              Sample.title = 'abc'           
39         *                              Only samples are returned from studies with title 'abc'
40         *             
41         *              Subject.species = 'human'
42         *                              Only samples are returned from subjects with species = 'human' 
43         *
44         *              Sample.name = 'sample 1'
45         *                              Only samples are returned with name = 'sample 1'
46         *
47         *              Event.startTime = '0s'
48         *                              Only samples are returned from subjects that have had an event with start time = '0s' 
49         *
50         *              SamplingEvent.startTime = '0s'
51         *                              Only samples are returned that have originated from a sampling event with start time = '0s' 
52         *
53         *              Assay.module = 'metagenomics'
54         *                              Only samples are returned that have been processed in an assay with module = metagenomics 
55         *
56         * When searching for more than one criterion per entity, these are taken combined. Searching for
57         *
58         *              Subject.species = 'human'
59         *              Subject.name = 'Jan'
60         *
61         *  will result in all samples from a human subject named 'Jan'. Samples from a mouse subject
62         *  named 'Jan' or a human subject named 'Kees' won't satisfy the criteria.
63         *     
64         */
65        @Override
66        void executeAnd() {
67                // If no criteria are found, return all samples
68                if( !criteria || criteria.size() == 0 ) {
69                        results = Sample.list().findAll { it.parent?.canRead( this.user ) };
70                        return;
71                }
72
73                // We expect the study criteria to be the most discriminative, and discard
74                // the most samples. (e.g. by searching on study title or study type). For
75                // that reason we first look through the list of studies. However, when the
76                // user didn't enter any study criteria, this will be an extra step, but doesn't
77                // cost much time to process.
78                def samples = []
79                if( getEntityCriteria( 'Study' ).size() > 0 ) {
80                        def studies = Study.list()
81                        if( studies )
82                                studies = studies.findAll { it.canRead( this.user ) };
83
84                        studies = filterStudiesOnStudyCriteria( studies );
85
86                        if( studies.size() == 0 ) {
87                                results = [];
88                                return;
89                        }
90
91                        def c = Sample.createCriteria()
92                        samples = c.list {
93                                'in'( 'parent', studies )
94                        }
95
96                        // Save data about the resulting studies in the
97                        // result fields array. The data that is now in the array
98                        // is saved based on the study id, not based on the sample id
99                        clearResultFields();
100                        saveResultFields( samples, getEntityCriteria( "Study" ), { sample, criterion ->
101                                return criterion.getFieldValue( sample.parent );
102                        });
103                } else {
104                        samples = Sample.list()
105                        if( samples )
106                                samples = samples.findAll { it.parent?.canRead( this.user ) }
107                }
108
109                samples = filterOnSubjectCriteria( samples );
110                samples = filterOnSampleCriteria( samples );
111                samples = filterOnEventCriteria( samples );
112                samples = filterOnSamplingEventCriteria( samples );
113                samples = filterOnAssayCriteria( samples );
114
115                samples = filterOnModuleCriteria( samples );
116
117                // Save matches
118                results = samples;
119        }
120
121        /**
122         * Searches for samples based on the given criteria. Only one of the criteria have to be satisfied and
123         * criteria for the different entities are satisfied as follows:
124         *
125         *              Sample.title = 'abc'
126         *                              Samples are returned from studies with title 'abc'
127         *
128         *              Subject.species = 'human'
129         *                              Samples are returned from subjects with species = 'human'
130         *
131         *              Sample.name = 'sample 1'
132         *                              Samples are returned with name = 'sample 1'
133         *
134         *              Event.startTime = '0s'
135         *                              Samples are returned from subjects that have had an event with start time = '0s'
136         *
137         *              SamplingEvent.startTime = '0s'
138         *                              Samples are returned that have originated from a sampling event with start time = '0s'
139         *
140         *              Assay.module = 'metagenomics'
141         *                              Samples are returned that have been processed in an assay with module = metagenomics
142         *
143         * When searching for more than one criterion per entity, these are taken separately. Searching for
144         *
145         *              Subject.species = 'human'
146         *              Subject.name = 'Jan'
147         *
148         *  will result in all samples from a human subject or a subject named 'Jan'. Samples from a mouse subject
149         *  named 'Jan' or a human subject named 'Kees' will also satisfy the criteria.
150         *
151         */
152        @Override
153        void executeOr() {
154                def allSamples = Sample.list().findAll { it.parent?.canRead( this.user ) }.toList();
155                executeOr( allSamples );
156        }
157
158        /**
159         * Returns a closure for the given entitytype that determines the value for a criterion
160         * on the given object. The closure receives two parameters: the object and a criterion.
161         *
162         * For example:
163         *              For a study search, the object given is a study. How to determine the value for that study of
164         *              the criterion field of type sample? This is done by returning the field values for all
165         *              samples in the study
166         *                      { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } }
167         * @return
168         */
169        protected Closure valueCallback( String entity ) {
170                switch( entity ) {
171                        case "Study":
172                                return { sample, criterion -> return criterion.getFieldValue( sample.parent ) }
173                        case "Subject":
174                                return { sample, criterion -> return criterion.getFieldValue( sample.parentSubject ); }
175                        case "Sample":
176                                return { sample, criterion -> return criterion.getFieldValue( sample ) }
177                        case "Event":
178                                return { sample, criterion ->
179                                        if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 )
180                                                return null
181
182                                        return sample.parentEventGroup.events?.collect { criterion.getFieldValue( it ) };
183                                }
184                        case "SamplingEvent":
185                                return { sample, criterion -> return criterion.getFieldValue( sample.parentEvent ); }
186                        case "Assay":
187                                return { sample, criterion ->
188                                        println "Find value for " + sample + " and " + criterion
189                                        def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) };
190                                        if( sampleAssays && sampleAssays.size() > 0 )
191                                                return sampleAssays.collect { criterion.getFieldValue( it ) }
192                                        else
193                                                return null
194                                }
195                        default:
196                                return super.valueCallback( entity );
197                }
198        }
199
200        /**
201         * Filters the given list of studies on the study criteria
202         * @param studies       Original list of studies
203         * @return                      List with all samples that match the Study-criteria
204         */
205        protected List filterStudiesOnStudyCriteria( List studies ) {
206                return filterOnTemplateEntityCriteria(studies, "Study", { study, criterion -> return criterion.getFieldValue( study ) })
207        }
208
209        /**
210         * Filters the given list of samples on the assay criteria
211         * @param samples       Original list of samples
212         * @return                      List with all samples that match the assay-criteria
213         */
214        @Override
215        protected List filterOnAssayCriteria( List samples ) {
216                if( !samples?.size() )
217                        return [];
218
219                // There is no sample.assays property, so we have to look for assays another way: just find
220                // all assays that match the criteria
221                def criteria = getEntityCriteria( 'Assay' );
222
223                if( getEntityCriteria( 'Assay' ).size() == 0 ) {
224                        if( searchMode == SearchMode.and )
225                                return samples
226                        else if( searchMode == SearchMode.or )
227                                return [];
228                }
229
230                def assays = filterEntityList( Assay.list(), criteria, { assay, criterion ->
231                        if( !assay )
232                                return false
233
234                        return criterion.matchOneEntity( assay );
235                });
236
237                println "Matching assays: " + assays
238       
239                // If no assays match these criteria, then no samples will match either
240                if( assays.size() == 0 )
241                        return [];
242
243                // Now filter the samples on whether they are attached to the filtered assays
244                def matchingSamples = samples.findAll { sample ->
245                        if( !sample.parent )
246                                return false;
247
248                        def studyAssays = assays.findAll { it.parent.equals( sample.parent ); }
249                       
250                        println "Assays for " + sample + " (based on study): " + studyAssays
251                       
252                        // See if this sample is present in any of the matching assays. If so,
253                        // this sample matches the criteria
254                        for( def assay in studyAssays ) {
255                                if( assay.samples?.contains( sample ) )
256                                        return true;
257                               
258                                println "Assay " + assay + " with samples " + assay.samples + " doesn't contain " + sample;
259                        }
260
261                        return false;
262                }
263
264                return matchingSamples;
265        }
266
267        /**
268         * Returns the saved field data that could be shown on screen. This means, the data
269         * is filtered to show only data of the query results. Also, the study title and sample
270         * name are filtered out, in order to be able to show all data on the screen without
271         * checking further
272         *
273         * @return      Map with the entity id as a key, and a field-value map as value
274         */
275        public Map getShowableResultFields() {
276                Map showableFields = super.getShowableResultFields()
277                showableFields.each { sampleElement ->
278                        sampleElement.value = sampleElement.value.findAll { fieldElement ->
279                                fieldElement.key != "Study title" && fieldElement.key != "Sample name"
280                        }
281                }
282        }
283}
Note: See TracBrowser for help on using the repository browser.