Changeset 1524
- Timestamp:
- Feb 15, 2011, 3:05:23 PM (12 years ago)
- Location:
- trunk
- Files:
-
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/controllers/dbnp/query/AdvancedQueryController.groovy
r1512 r1524 153 153 } 154 154 155 // Determine the possible actions 155 // Determine the possible actions and build correct urls 156 156 def actions = determineActions(s, selectedIds ); 157 157 … … 478 478 479 479 // First check whether a search with the same criteria is already present 480 def previousSearch = retrieveSearchByCriteria( s.getCriteria() );480 def previousSearch = retrieveSearchByCriteria( s.getCriteria(), s.searchMode ); 481 481 482 482 def id … … 499 499 * @return Search that has this criteria, or null if no such search is found. 500 500 */ 501 protected Search retrieveSearchByCriteria( List criteria ) {501 protected Search retrieveSearchByCriteria( List criteria, SearchMode searchMode = SearchMode.and ) { 502 502 if( !session.queries ) 503 503 return null … … 510 510 def value = query.value; 511 511 512 if( value. criteria && value.criteria.containsAll( criteria ) && criteria.containsAll( value.criteria ) ) {512 if( value.searchMode == searchMode && value.criteria && value.criteria.containsAll( criteria ) && criteria.containsAll( value.criteria ) ) { 513 513 return value; 514 514 } -
trunk/grails-app/views/advancedQuery/list.gsp
r1512 r1524 40 40 <thead> 41 41 <tr> 42 <th class="nonsortable">< /th>42 <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th> 43 43 <th>#</th> 44 44 <th>Type</th> … … 52 52 <g:each in="${searches}" var="search"> 53 53 <tr> 54 <td><g:checkBox name="id" value="${search.id}" checked="${false}" /></td>54 <td><g:checkBox name="id" value="${search.id}" checked="${false}" onClick="updateCheckAll(this);" /></td> 55 55 <td>${search.id}</td> 56 56 <td>${search.entity}</td> -
trunk/grails-app/views/advancedQuery/results.gsp
r1512 r1524 18 18 <% 19 19 def resultFields = search.getShowableResultFields(); 20 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();20 def extraFields = search.getShowableResultFieldNames(resultFields); 21 21 %> 22 22 <table id="searchresults" class="paginate"> 23 23 <thead> 24 24 <tr> 25 <th class="nonsortable">< /th>25 <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th> 26 26 <th>Type</th> 27 27 <th>Id</th> … … 39 39 also http://datatables.net/examples/api/form.html and advancedQueryResults.js 40 40 */ %> 41 <g:checkBox name="id" value="${result.id}" checked="${false}" />41 <g:checkBox name="id" value="${result.id}" checked="${false}" onClick="updateCheckAll(this);" /> 42 42 </td> 43 43 <td>${search.entity}</td> -
trunk/grails-app/views/advancedQuery/sampleresults.gsp
r1512 r1524 18 18 <% 19 19 def resultFields = search.getShowableResultFields(); 20 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();20 def extraFields = search.getShowableResultFieldNames(resultFields); 21 21 %> 22 22 <table id="searchresults" class="paginate"> 23 23 <thead> 24 24 <tr> 25 <th class="nonsortable">< /th>25 <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th> 26 26 <th>Name</th> 27 27 <th>Study</th> … … 40 40 also http://datatables.net/examples/api/form.html and advancedQueryResults.js 41 41 */ %> 42 <g:checkBox name="id" value="${sampleInstance.id}" checked="${false}" />42 <g:checkBox name="id" value="${sampleInstance.id}" checked="${false}" onClick="updateCheckAll(this);" /> 43 43 </td> 44 44 <td>${fieldValue(bean: sampleInstance, field: "name")}</td> -
trunk/grails-app/views/advancedQuery/studyresults.gsp
r1512 r1524 18 18 <% 19 19 def resultFields = search.getShowableResultFields(); 20 def extraFields = resultFields[ search.getResults()[ 0 ].id ]?.keySet();20 def extraFields = search.getShowableResultFieldNames(resultFields); 21 21 %> 22 22 <table id="searchresults" class="paginate"> 23 23 <thead> 24 24 <tr> 25 <th class="nonsortable">< /th>25 <th class="nonsortable"><input type="checkbox" id="checkAll" onClick="checkAllPaginated(this);" /></th> 26 26 <th>Title</th> 27 27 <th>Code</th> … … 43 43 also http://datatables.net/examples/api/form.html and advancedQueryResults.js 44 44 */ %> 45 <g:checkBox name="id" value="${studyInstance.id}" checked="${false}" />45 <g:checkBox name="id" value="${studyInstance.id}" checked="${false}" onClick="updateCheckAll(this);" /> 46 46 </td> 47 47 <td> -
trunk/src/groovy/dbnp/query/SampleSearch.groovy
r1521 r1524 15 15 package dbnp.query 16 16 17 import groovy.lang.Closure; 18 17 19 import java.util.Map; 18 20 … … 23 25 class SampleSearch extends Search { 24 26 private static final log = LogFactory.getLog(this); 25 27 26 28 public SampleSearch() { 27 29 super(); 28 30 29 31 this.entity = "Sample"; 30 32 } … … 118 120 119 121 /** 120 * Searches for samples based on the given criteria. Only one of the criteria have to be satisfied and 121 * criteria for the different entities are satisfied as follows: 122 * 123 * Sample.title = 'abc' 124 * Samples are returned from studies with title 'abc' 125 * 126 * Subject.species = 'human' 127 * Samples are returned from subjects with species = 'human' 128 * 129 * Sample.name = 'sample 1' 130 * Samples are returned with name = 'sample 1' 131 * 132 * Event.startTime = '0s' 133 * Samples are returned from subjects that have had an event with start time = '0s' 134 * 135 * SamplingEvent.startTime = '0s' 136 * Samples are returned that have originated from a sampling event with start time = '0s' 137 * 138 * Assay.module = 'metagenomics' 139 * Samples are returned that have been processed in an assay with module = metagenomics 140 * 141 * When searching for more than one criterion per entity, these are taken separately. Searching for 142 * 143 * Subject.species = 'human' 144 * Subject.name = 'Jan' 145 * 146 * will result in all samples from a human subject or a subject named 'Jan'. Samples from a mouse subject 147 * named 'Jan' or a human subject named 'Kees' will also satisfy the criteria. 148 * 149 */ 150 @Override 151 void executeOr() { 152 // We expect the sample criteria to be the most discriminative, and discard 153 // the most samples. (e.g. by searching on sample title of sample type). For 154 // that reason we first look through the list of studies. However, when the 155 // user didn't enter any sample criteria, this will be an extra step, but doesn't 156 // cost much time to process. 157 def samples = [] 158 def allSamples = Sample.list().findAll { it.parent?.canRead( this.user ) }.toList(); 159 160 // If no criteria are found, return all samples 161 if( !criteria || criteria.size() == 0 ) { 162 results = allSamples 163 return; 164 } 165 166 samples = ( samples + filterSamplesOnStudyCriteria( allSamples - samples ) ).unique(); 167 samples = ( samples + filterOnSubjectCriteria( allSamples - samples ) ).unique(); 168 samples = ( samples + filterOnSampleCriteria( allSamples - samples ) ).unique(); 169 samples = ( samples + filterOnEventCriteria( allSamples - samples ) ).unique(); 170 samples = ( samples + filterOnSamplingEventCriteria( allSamples - samples ) ).unique(); 171 samples = ( samples + filterOnAssayCriteria( allSamples - samples ) ).unique(); 172 173 samples = ( samples + filterOnModuleCriteria( allSamples - samples ) ).unique(); 174 175 // Save matches 176 results = samples; 177 } 178 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 // We expect the sample criteria to be the most discriminative, and discard 155 // the most samples. (e.g. by searching on sample title of sample type). For 156 // that reason we first look through the list of studies. However, when the 157 // user didn't enter any sample criteria, this will be an extra step, but doesn't 158 // cost much time to process. 159 def samples = [] 160 def allSamples = Sample.list().findAll { it.parent?.canRead( this.user ) }.toList(); 161 162 // If no criteria are found, return all samples 163 if( !criteria || criteria.size() == 0 ) { 164 results = allSamples 165 return; 166 } 167 168 samples = ( samples + filterOnStudyCriteria( allSamples - samples ) ).unique(); 169 samples = ( samples + filterOnSubjectCriteria( allSamples - samples ) ).unique(); 170 samples = ( samples + filterOnSampleCriteria( allSamples - samples ) ).unique(); 171 samples = ( samples + filterOnEventCriteria( allSamples - samples ) ).unique(); 172 samples = ( samples + filterOnSamplingEventCriteria( allSamples - samples ) ).unique(); 173 samples = ( samples + filterOnAssayCriteria( allSamples - samples ) ).unique(); 174 175 samples = ( samples + filterOnModuleCriteria( allSamples - samples ) ).unique(); 176 177 // Save matches 178 results = samples; 179 } 180 181 /** 182 * Returns a closure for the given entitytype that determines the value for a criterion 183 * on the given object. The closure receives two parameters: the object and a criterion. 184 * 185 * For example: 186 * For a study search, the object given is a study. How to determine the value for that study of 187 * the criterion field of type sample? This is done by returning the field values for all 188 * samples in the study 189 * { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } } 190 * @return 191 */ 192 protected Closure valueCallback( String entity ) { 193 switch( entity ) { 194 case "Study": 195 return { sample, criterion -> return criterion.getFieldValue( sample.parent ) } 196 case "Subject": 197 return { sample, criterion -> return criterion.getFieldValue( sample.parentSubject ); } 198 case "Sample": 199 return { sample, criterion -> return criterion.getFieldValue( sample ) } 200 case "Event": 201 return { sample, criterion -> 202 if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 ) 203 return null 204 205 return criterion.getFieldValue( sample.parentEventGroup.events.toList() ); 206 } 207 case "SamplingEvent": 208 return { sample, criterion -> return criterion.getFieldValue( sample.parentEvent ); } 209 case "Assay": 210 return { sample, criterion -> 211 println "Find value for " + sample + " and " + criterion 212 def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) }; 213 if( sampleAssays && sampleAssays.size() > 0 ) 214 return sampleAssays.collect { criterion.getFieldValue( it ) } 215 else 216 return null 217 } 218 default: 219 return super.valueCallback( entity ); 220 } 221 } 222 179 223 /** 180 224 * Filters the given list of studies on the study criteria … … 185 229 return filterOnTemplateEntityCriteria(studies, "Study", { study, criterion -> return criterion.getFieldValue( study ) }) 186 230 } 187 188 /**189 * Filters the given list of samples on the sample criteria190 * @param samples Original list of samples191 * @return List with all samples that match the Study-criteria192 */193 protected List filterSamplesOnStudyCriteria( List samples ) {194 return filterOnTemplateEntityCriteria(samples, "Study", { study, criterion ->195 return criterion.getFieldValue( study.parent )196 })197 }198 199 200 /**201 * Filters the given list of samples on the subject criteria202 * @param samples Original list of samples203 * @return List with all samples that match the Subject-criteria204 */205 protected List filterOnSubjectCriteria( List samples ) {206 return filterOnTemplateEntityCriteria(samples, "Subject", { sample, criterion ->207 return criterion.getFieldValue( sample.parentSubject );208 })209 }210 211 /**212 * Filters the given list of samples on the sample criteria213 * @param samples Original list of samples214 * @return List with all samples that match the sample-criteria215 */216 protected List filterOnSampleCriteria( List samples ) {217 return filterOnTemplateEntityCriteria(samples, "Sample", { sample, criterion ->218 return criterion.getFieldValue( sample );219 })220 }221 222 /**223 * Filters the given list of samples on the event criteria224 * @param samples Original list of samples225 * @return List with all samples that match the event-criteria226 */227 protected List filterOnEventCriteria( List samples ) {228 return filterOnTemplateEntityCriteria(samples, "Event", { sample, criterion ->229 if( !sample || !sample.parentEventGroup || !sample.parentEventGroup.events || sample.parentEventGroup.events.size() == 0 )230 return null231 232 return criterion.getFieldValue( sample.parentEventGroup.events.toList() );233 })234 }235 236 /**237 * Filters the given list of samples on the sampling event criteria238 * @param samples Original list of samples239 * @return List with all samples that match the event-criteria240 */241 protected List filterOnSamplingEventCriteria( List samples ) {242 return filterOnTemplateEntityCriteria(samples, "SamplingEvent", { sample, criterion ->243 return criterion.getFieldValue( sample.parentEvent );244 })245 }246 231 247 232 /** … … 250 235 * @return List with all samples that match the assay-criteria 251 236 */ 237 @Override 252 238 protected List filterOnAssayCriteria( List samples ) { 253 239 if( !samples?.size() ) … … 257 243 // all assays that match the criteria 258 244 def criteria = getEntityCriteria( 'Assay' ); 259 245 260 246 if( getEntityCriteria( 'Assay' ).size() == 0 ) { 261 247 if( searchMode == SearchMode.and ) … … 292 278 return false; 293 279 } 294 295 // Save sample data for later use 296 println samples 297 println "Find values for " + matchingSamples + " and " + criteria 298 saveResultFields( matchingSamples, criteria, { sample, criterion -> 299 println "Find value for " + sample + " and " + criterion 300 def sampleAssays = Assay.findByParent( sample.parent ).findAll { it.samples?.contains( sample ) }; 301 if( sampleAssays && sampleAssays.size() > 0 ) 302 return sampleAssays.collect { criterion.getFieldValue( it ) } 303 else 304 return null 305 }); 306 280 307 281 return matchingSamples; 308 282 } -
trunk/src/groovy/dbnp/query/Search.groovy
r1501 r1524 31 31 32 32 /** 33 * Available boolean operators for searches34 * @author robert35 *36 */33 * Available boolean operators for searches 34 * @author robert 35 * 36 */ 37 37 enum SearchMode { 38 38 and, or 39 39 } 40 40 41 41 class Search { 42 /** 43 * User that is performing this search. This has impact on the search results returned. 44 */ 42 45 public SecUser user; 46 47 /** 48 * Date of execution of this search 49 */ 43 50 public Date executionDate; 44 public int id; // Is only used when this query is saved in session 45 51 52 /** 53 * Public identifier of this search. Is only used when this query is saved in session 54 */ 55 public int id; 56 57 /** 58 * Human readable entity name of the entities that can be found using this search 59 */ 46 60 public String entity; 61 62 /** 63 * Mode to search: OR or AND. 64 * @see SearchMode 65 */ 47 66 public SearchMode searchMode = SearchMode.and 48 67 49 68 protected List criteria; 50 69 protected List results; 51 70 protected Map resultFields = [:]; 52 71 72 /** 73 * Returns a list of Criteria 74 */ 53 75 public List getCriteria() { return criteria; } 76 77 /** 78 * Sets a new list of criteria 79 * @param c List with criteria objects 80 */ 54 81 public void setCriteria( List c ) { criteria = c; } 82 83 /** 84 * Adds a criterion to this query 85 * @param c Criterion 86 */ 55 87 public void addCriterion( Criterion c ) { 56 if( criteria ) 88 if( criteria ) 57 89 criteria << c; 58 90 else … … 60 92 } 61 93 94 /** 95 * Retrieves the results found using this query. The result is empty is 96 * the query has not been executed yet. 97 */ 62 98 public List getResults() { return results; } 63 public void setResults( List r ) { results = r; } 99 100 /** 101 * Returns the results found using this query, filtered by a list of ids. 102 * @param selectedIds List with ids of the entities you want to return. 103 * @return A list with only those results for which the id is in the selectedIds 104 */ 64 105 public List filterResults( List selectedIds ) { 65 106 if( !selectedIds || !results ) 66 107 return results 67 108 68 109 return results.findAll { 69 110 selectedIds.contains( it.id ) 70 111 } 71 112 } 72 73 113 114 /** 115 * Returns a list of fields for the results of this query. The fields returned are those 116 * fields that the query searched for. 117 */ 74 118 public Map getResultFields() { return resultFields; } 75 public void setResultFields( Map r ) { resultFields = r; } 76 119 120 /** 121 * Constructor of this search object. Sets the user field to the 122 * currently logged in user 123 * @see #user 124 */ 77 125 public Search() { 78 126 def ctx = ApplicationHolder.getApplication().getMainContext(); … … 85 133 this.user = null 86 134 } 87 135 88 136 /** 89 137 * Returns the number of results found by this search … … 113 161 public void execute() { 114 162 this.executionDate = new Date(); 115 163 116 164 switch( searchMode ) { 117 165 case SearchMode.and: … … 122 170 break; 123 171 } 124 } 125 172 173 // Save the value of this results for later use 174 saveResultFields(); 175 } 176 126 177 /** 127 178 * Executes an inclusive (AND) search based on the given criteria. Should be filled in by … … 129 180 */ 130 181 public void executeAnd() { 131 132 } 133 134 /** 135 * Executes an exclusive (OR) search based on the given criteria. Should be filled in by 136 * subclasses searching for a specific entity 137 */ 138 public void executeOr() { 139 140 } 182 183 } 184 185 /** 186 * Executes an exclusive (OR) search based on the given criteria. Should be filled in by 187 * subclasses searching for a specific entity 188 */ 189 public void executeOr() { 190 191 } 192 193 /************************************************************************ 194 * 195 * These methods are used in querying and should be overridden by subclasses 196 * in order to provide custom searching 197 * 198 */ 199 200 /** 201 * Returns a closure for the given entitytype that determines the value for a criterion 202 * on the given object. The closure receives two parameters: the object and a criterion. 203 * 204 * For example: when searching for studies, the object given to the closure is a Study. 205 * Also, when searching for samples, the object given is a Sample. When you have the criterion 206 * 207 * sample.name equals 'sample 1' 208 * 209 * and searching for samples, it is easy to determine the value of the object for this criterion: 210 * 211 * object.getFieldValue( "name" ) 212 * 213 * 214 * However, when searching for samples with the criterion 215 * 216 * study.title contains 'nbic' 217 * 218 * this determination is more complex: 219 * 220 * object.parent.getFieldValue( "title" ) 221 * 222 * 223 * The other way around, when searching for studies with 224 * 225 * sample.name equals 'sample 1' 226 * 227 * the value of the 'sample.name' property is a list: 228 * 229 * object.samples*.getFieldValue( "name" ) 230 * 231 * The other search methods will handle the list and see whether any of the values 232 * matches the criterion. 233 * 234 * NB. The Criterion object has a convenience method to retrieve the field value on a 235 * specific (TemplateEntity) object: getFieldValue. This method also handles 236 * non-existing fields and casts the value to the correct type. 237 * 238 * This method should be overridden by all searches 239 * 240 * @see Criterion.getFieldValue() 241 * 242 * @return Closure having 2 parameters: object and criterion 243 */ 244 protected Closure valueCallback( String entity ) { 245 switch( entity ) { 246 case "Study": 247 case "Subject": 248 case "Sample": 249 case "Event": 250 case "SamplingEvent": 251 case "Assay": 252 return { object, criterion -> return criterion.getFieldValue( object ); } 253 default: 254 return null; 255 } 256 } 257 258 /***************************************************** 259 * 260 * The other methods are helper functions for the execution of queries in subclasses 261 * 262 *****************************************************/ 141 263 142 264 /** … … 148 270 return criteria?.findAll { it.entity == entity } 149 271 } 150 272 151 273 /** 152 274 * Filters a list with entities, based on the given criteria and a closure to check whether a criterion is matched … … 167 289 return [] 168 290 } 169 291 170 292 return entities.findAll { entity -> 171 293 if( searchMode == SearchMode.and ) { … … 186 308 } 187 309 } 188 310 189 311 /** 190 312 * Prepares a value from a template entity for comparison, by giving it a correct type … … 197 319 if( value == null ) 198 320 return value 199 321 200 322 switch (type) { 201 323 case TemplateFieldType.DATE: … … 210 332 return new RelTime( value ); 211 333 } else if( value.toString().isNumber() ) { 212 return new RelTime( Long.parseLong( value.toString() ) ) 334 return new RelTime( Long.parseLong( value.toString() ) ) 213 335 } else { 214 336 return new RelTime( value ); … … 250 372 } 251 373 252 } 253 374 } 375 254 376 /** 255 377 * Filters the given list of studies on the study criteria … … 261 383 protected List filterOnTemplateEntityCriteria( List studies, String entityName, Closure valueCallback ) { 262 384 def criteria = getEntityCriteria( entityName ); 263 385 264 386 def checkCallback = { study, criterion -> 265 387 def value = valueCallback( study, criterion ); 266 388 267 389 if( value == null ) { 268 390 return false … … 276 398 } 277 399 278 // Save the value of this entity for later use279 saveResultFields( studies, criteria, valueCallback );280 281 400 return filterEntityList( studies, criteria, checkCallback); 282 401 } 283 402 284 403 /** 285 * Filters the given list of entities on the module criteria 286 * @param entities Original list of entities. Entities should expose a giveUUID() method to give the token. 287 * @return List with all entities that match the module criteria 288 */ 404 * Filters the given list of studies on the study criteria 405 * @param studies Original list of studies 406 * @return List with all studies that match the Study criteria 407 */ 408 protected List filterOnStudyCriteria( List studies ) { 409 def entity = "Study" 410 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 411 } 412 413 /** 414 * Filters the given list of studies on the subject criteria 415 * @param studies Original list of studies 416 * @return List with all studies that match the Subject-criteria 417 */ 418 protected List filterOnSubjectCriteria( List studies ) { 419 def entity = "Subject" 420 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 421 } 422 423 /** 424 * Filters the given list of studies on the sample criteria 425 * @param studies Original list of studies 426 * @return List with all studies that match the sample-criteria 427 */ 428 protected List filterOnSampleCriteria( List studies ) { 429 def entity = "Sample" 430 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 431 } 432 433 /** 434 * Filters the given list of studies on the event criteria 435 * @param studies Original list of studies 436 * @return List with all studies that match the event-criteria 437 */ 438 protected List filterOnEventCriteria( List studies ) { 439 def entity = "Event" 440 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 441 } 442 443 /** 444 * Filters the given list of studies on the sampling event criteria 445 * @param studies Original list of studies 446 * @return List with all studies that match the event-criteria 447 */ 448 protected List filterOnSamplingEventCriteria( List studies ) { 449 def entity = "SamplingEvent" 450 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 451 } 452 453 /** 454 * Filters the given list of studies on the assay criteria 455 * @param studies Original list of studies 456 * @return List with all studies that match the assay-criteria 457 */ 458 protected List filterOnAssayCriteria( List studies ) { 459 def entity = "Assay" 460 return filterOnTemplateEntityCriteria(studies, entity, valueCallback( entity ) ) 461 } 462 463 /** 464 * Filters the given list of entities on the module criteria 465 * @param entities Original list of entities. Entities should expose a giveUUID() method to give the token. 466 * @return List with all entities that match the module criteria 467 */ 289 468 protected List filterOnModuleCriteria( List entities ) { 290 469 // An empty list can't be filtered more than is has been now 291 470 if( !entities || entities.size() == 0 ) 292 471 return []; 293 472 294 473 // Determine the moduleCommunicationService. Because this object 295 474 // is mocked in the tests, it can't be converted to a ApplicationContext object 296 475 def ctx = ApplicationHolder.getApplication().getMainContext(); 297 476 def moduleCommunicationService = ctx.getBean("moduleCommunicationService"); 477 478 switch( searchMode ) { 479 case SearchMode.and: 480 // Loop through all modules and check whether criteria have been given 481 // for that module 482 AssayModule.list().each { module -> 483 // Remove 'module' from module name 484 def moduleName = module.name.replace( 'module', '' ).trim() 485 def moduleCriteria = getEntityCriteria( moduleName ); 298 486 299 def allEntities = [] 300 if( searchMode == SearchMode.or ) { 301 allEntities += entities; 302 entities = []; 303 } 487 if( moduleCriteria && moduleCriteria.size() > 0 ) { 488 def callUrl = moduleCriteriaUrl( module, entities, moduleCriteria ); 489 490 try { 491 def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl ); 492 Closure checkClosure = moduleCriterionClosure( json ); 493 entities = filterEntityList( entities, moduleCriteria, checkClosure ); 494 } catch( Exception e ) { 495 log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() ) 496 } 497 } 498 } 304 499 305 // Loop through all modules and check whether criteria have been given 306 // for that module 307 AssayModule.list().each { module -> 308 // Remove 'module' from module name 309 def moduleName = module.name.replace( 'module', '' ).trim() 310 def moduleCriteria = getEntityCriteria( moduleName ); 311 312 if( moduleCriteria && moduleCriteria.size() > 0 ) { 313 // Retrieve the data from the module 314 def tokens = entities.collect { it.giveUUID() }.unique(); 315 def fields = moduleCriteria.collect { it.field }.unique(); 500 return entities; 501 case SearchMode.or: 502 def resultingEntities = [] 316 503 317 def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity 318 tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() } 319 fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() } 504 // Loop through all modules and check whether criteria have been given 505 // for that module 506 AssayModule.list().each { module -> 507 // Remove 'module' from module name 508 def moduleName = module.name.replace( 'module', '' ).trim() 509 def moduleCriteria = getEntityCriteria( moduleName ); 510 511 if( moduleCriteria && moduleCriteria.size() > 0 ) { 512 def callUrl = moduleCriteriaUrl( module, entities, moduleCriteria ); 513 514 try { 515 def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl ); 516 Closure checkClosure = moduleCriterionClosure( json ); 517 518 resultingEntities += filterEntityList( entities, moduleCriteria, checkClosure ); 519 resultingEntities = resultingEntities.unique(); 520 521 } catch( Exception e ) { 522 log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() ) 523 } 524 } 525 } 526 527 println this.resultFields; 320 528 321 try { 322 def json = moduleCommunicationService.callModuleRestMethodJSON( module.url, callUrl ); 323 324 Closure checkClosure = { entity, criterion -> 325 // Find the value of the field in this sample. That value is still in the 326 // JSON object 327 def token = entity.giveUUID() 328 if( !json[ token ] || json[ token ][ criterion.field ] == null ) 329 return false; 330 331 // Check whether a list or string is given 332 def value = json[ token ][ criterion.field ]; 333 334 // Save the value of this entity for later use 335 saveResultField( entity.id, criterion.entity + " " + criterion.field, value ) 336 337 if( !( value instanceof Collection ) ) { 338 value = [ value ]; 339 } 340 341 // Convert numbers to a long or double in order to process them correctly 342 def values = value.collect { val -> 343 val = val.toString(); 344 if( val.isLong() ) { 345 val = Long.parseLong( val ); 346 } else if( val.isDouble() ) { 347 val = Double.parseDouble( val ); 348 } 349 return val; 350 } 351 352 // Loop through all values and match any 353 for( val in values ) { 354 if( criterion.match( val ) ) 355 return true; 356 } 357 358 return false; 529 return resultingEntities; 530 default: 531 return []; 532 } 533 } 534 535 /** 536 * Returns a closure for determining the value of a module field 537 * @param json 538 * @return 539 */ 540 protected Closure moduleCriterionClosure( def json ) { 541 return { entity, criterion -> 542 // Find the value of the field in this sample. That value is still in the 543 // JSON object 544 def token = entity.giveUUID() 545 if( !json[ token ] || json[ token ][ criterion.field ] == null ) 546 return false; 547 548 // Check whether a list or string is given 549 def value = json[ token ][ criterion.field ]; 550 551 // Save the value of this entity for later use 552 saveResultField( entity.id, criterion.entity + " " + criterion.field, value ) 553 554 if( !( value instanceof Collection ) ) { 555 value = [ value ]; 556 } 557 558 // Convert numbers to a long or double in order to process them correctly 559 def values = value.collect { val -> 560 val = val.toString(); 561 if( val.isLong() ) { 562 val = Long.parseLong( val ); 563 } else if( val.isDouble() ) { 564 val = Double.parseDouble( val ); 565 } 566 return val; 567 } 568 569 // Loop through all values and match any 570 for( val in values ) { 571 if( criterion.match( val ) ) 572 return true; 573 } 574 575 return false; 576 } 577 } 578 579 protected String moduleCriteriaUrl( module, entities, moduleCriteria ) { 580 // Retrieve the data from the module 581 def tokens = entities.collect { it.giveUUID() }.unique(); 582 def fields = moduleCriteria.collect { it.field }.unique(); 583 584 def callUrl = module.url + '/rest/getQueryableFieldData?entity=' + this.entity 585 tokens.sort().each { callUrl += "&tokens=" + it.encodeAsURL() } 586 fields.sort().each { callUrl += "&fields=" + it.encodeAsURL() } 587 588 return callUrl; 589 } 590 591 /********************************************************************* 592 * 593 * These methods are used for saving information about the search results and showing the information later on. 594 * 595 *********************************************************************/ 596 597 /** 598 * Saves data about template entities to use later on. This data is copied to a special 599 * structure to make it compatible with data fetched from other modules. 600 * @see #saveResultField() 601 */ 602 protected void saveResultFields() { 603 if( !results || !criteria ) 604 return 605 606 criteria.each { criterion -> 607 if( criterion.field ) { 608 def valueCallback = valueCallback( criterion.entity ); 609 610 if( valueCallback != null ) { 611 def name = criterion.entity + ' ' + criterion.field 612 613 results.each { result -> 614 saveResultField( result.id, name, valueCallback( result, criterion ) ); 359 615 } 360 361 // The data has been retrieved. Now walk through all criteria to filter the samples 362 if( searchMode == SearchMode.and ) { 363 entities = filterEntityList( entities, moduleCriteria, checkClosure ); 364 } else if( searchMode == SearchMode.or ) { 365 entities += filterEntityList( allEntities - entities, moduleCriteria, checkClosure ); 366 entities = entities.unique(); 367 } 368 369 } catch( Exception e ) { 370 log.error( "Error while retrieving data from " + module.name + ": " + e.getMessage() ) 371 } 372 } 373 } 374 375 return entities; 376 } 377 616 } 617 } 618 } 619 } 620 378 621 /** 379 622 * Saves data about template entities to use later on. This data is copied to a special 380 * structure to make it compatible with data fetched from other modules. Ses also saveResultField() method623 * structure to make it compatible with data fetched from other modules. 381 624 * @param entities List of template entities to find data in 382 625 * @param criteria Criteria to search for 383 626 * @param valueCallback Callback to retrieve a specific field from the entity 627 * @see #saveResultField() 384 628 */ 385 629 protected void saveResultFields( entities, criteria, valueCallback ) { … … 392 636 } 393 637 394 638 395 639 /** 396 640 * Saves a specific field of an object to use later on. Especially useful when looking up data from other modules. … … 402 646 if( resultFields[ id ] == null ) 403 647 resultFields[ id ] = [:] 404 648 405 649 // Handle special cases 406 650 if( value == null ) 407 651 value = ""; 408 652 409 653 if( value instanceof Collection ) { 410 654 value = value.findAll { it != null } 411 655 } 412 656 413 657 resultFields[ id ][ fieldName ] = value; 414 658 } 415 659 416 660 /** 417 661 * Removes all data from the result field map … … 420 664 resultFields = [:] 421 665 } 422 666 423 667 /** 424 668 * Returns the saved field data that could be shown on screen. This means, the data is filtered to show only data of the query results. … … 435 679 } 436 680 681 /** 682 * Returns the field names that are found in the map with showable result fields 683 * 684 * @param fields Map with showable result fields 685 * @see getShowableResultFields 686 * @return 687 */ 688 public List getShowableResultFieldNames( fields ) { 689 return fields.values()*.keySet().flatten().unique(); 690 } 691 437 692 public String toString() { 438 693 return ( this.entity ? this.entity + " search" : "Search" ) + " " + this.id -
trunk/src/groovy/dbnp/query/StudySearch.groovy
r1501 r1524 14 14 */ 15 15 package dbnp.query 16 17 import groovy.lang.Closure; 16 18 17 19 import java.util.List; … … 144 146 145 147 /** 146 * Filters the given list of studies on the study criteria 147 * @param studies Original list of studies 148 * @return List with all studies that match the Study criteria 149 */ 150 protected List filterOnStudyCriteria( List studies ) { 151 return filterOnTemplateEntityCriteria(studies, "Study", { study, criterion -> return criterion.getFieldValue( study ) }) 152 } 153 154 /** 155 * Filters the given list of studies on the subject criteria 156 * @param studies Original list of studies 157 * @return List with all studies that match the Subject-criteria 158 */ 159 protected List filterOnSubjectCriteria( List studies ) { 160 return filterOnTemplateEntityCriteria(studies, "Subject", { study, criterion -> 161 return study.subjects?.collect { criterion.getFieldValue( it ); } 162 }) 163 } 164 165 /** 166 * Filters the given list of studies on the sample criteria 167 * @param studies Original list of studies 168 * @return List with all studies that match the sample-criteria 169 */ 170 protected List filterOnSampleCriteria( List studies ) { 171 return filterOnTemplateEntityCriteria(studies, "Sample", { study, criterion -> 172 return study.samples?.collect { criterion.getFieldValue( it ); } 173 }) 174 } 175 176 /** 177 * Filters the given list of studies on the event criteria 178 * @param studies Original list of studies 179 * @return List with all studies that match the event-criteria 180 */ 181 protected List filterOnEventCriteria( List studies ) { 182 return filterOnTemplateEntityCriteria(studies, "Event", { study, criterion -> 183 return study.events?.collect { criterion.getFieldValue( it ); } 184 }) 185 } 186 187 /** 188 * Filters the given list of studies on the sampling event criteria 189 * @param studies Original list of studies 190 * @return List with all studies that match the event-criteria 191 */ 192 protected List filterOnSamplingEventCriteria( List studies ) { 193 return filterOnTemplateEntityCriteria(studies, "SamplingEvent", { study, criterion -> 194 return study.samplingEvents?.collect { criterion.getFieldValue( it ); } 195 }) 196 } 197 198 /** 199 * Filters the given list of studies on the assay criteria 200 * @param studies Original list of studies 201 * @return List with all studies that match the assay-criteria 202 */ 203 protected List filterOnAssayCriteria( List studies ) { 204 return filterOnTemplateEntityCriteria(studies, "Assay", { study, criterion -> 205 return study.assays?.collect { criterion.getFieldValue( it ); } 206 }) 207 } 148 * Returns a closure for the given entitytype that determines the value for a criterion 149 * on the given object. The closure receives two parameters: the object and a criterion. 150 * 151 * This method should be implemented by all searches 152 * 153 * For example: 154 * For a study search, the object given is a study. How to determine the value for that study of 155 * the criterion field of type sample? This is done by returning the field values for all 156 * samples in the study 157 * { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } } 158 * @return 159 */ 160 protected Closure valueCallback( String entity ) { 161 switch( entity ) { 162 case "Study": 163 return { study, criterion -> return criterion.getFieldValue( study ) } 164 case "Subject": 165 return { study, criterion -> return study.subjects?.collect { criterion.getFieldValue( it ); } } 166 case "Sample": 167 return { study, criterion -> return study.samples?.collect { criterion.getFieldValue( it ); } } 168 case "Event": 169 return { study, criterion -> return study.events?.collect { criterion.getFieldValue( it ); } } 170 case "SamplingEvent": 171 return { study, criterion -> return study.samplingEvents?.collect { criterion.getFieldValue( it ); } } 172 case "Assay": 173 return { study, criterion -> return study.assays?.collect { criterion.getFieldValue( it ); } } 174 default: 175 return super.valueCallback( entity ); 176 } 177 } 208 178 209 179 /** -
trunk/web-app/css/advancedQuery.css
r1512 r1524 31 31 .searchoptions ul#criteria li { margin-left: 0; padding-left: 0; display: inline; } 32 32 33 input.transparent { 34 -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)"; 35 filter: alpha(opacity=30); 36 opacity: 0.3; 37 -moz-opacity: 0.3; 38 -khtml-opacity: 0.3; 39 } 40 33 41 /** Options buttons **/ 34 42 .options { margin-top: 8px; } -
trunk/web-app/js/advancedQueryResults.js
r1501 r1524 1 function checkAllPaginated( input ) { 2 var paginatedTable = $(input).closest( '.paginate' ); 3 var dataTable = paginatedTable.closest( '.dataTables_wrapper' ); 4 var checkAll = $( '#checkAll', paginatedTable ); 5 6 var oTable = paginatedTable.dataTable(); 7 var inputs = $('input', oTable.fnGetNodes()) 8 9 // If any of the inputs is checked, uncheck all. Otherwise, check all 10 var check = false; 11 12 for(var i = 0; i < inputs.length; i++ ) { 13 if( !$(inputs[i]).attr( 'checked' ) ) { 14 check = true; 15 break; 16 } 17 } 18 19 inputs.each( function( idx, el ) { 20 $(el).attr( 'checked', check ); 21 }) 22 23 updateCheckAll( checkAll ); 24 } 25 26 function updateCheckAll( input ) { 27 var paginatedTable = $(input).closest( '.paginate' ); 28 var dataTable = paginatedTable.closest( '.dataTables_wrapper' ); 29 30 var checkAll = $( '#checkAll', paginatedTable ); 31 32 var oTable = paginatedTable.dataTable(); 33 var inputs = $('input', oTable.fnGetNodes()) 34 35 // Is none checked, are all checked or are some checked 36 var numChecked = 0 37 for(var i = 0; i < inputs.length; i++ ) { 38 if( $(inputs[i]).attr( 'checked' ) ) { 39 numChecked++; 40 } 41 } 42 43 checkAll.attr( 'checked', numChecked > 0 ); 44 45 if( numChecked > 0 && numChecked < inputs.length - 1 ) { 46 checkAll.addClass( 'transparent' ); 47 } else { 48 checkAll.removeClass( 'transparent' ); 49 } 50 } 51 1 52 function submitForm( form, url ) { 2 53 if( form == undefined || !form )
Note: See TracChangeset
for help on using the changeset viewer.