Changeset 2199
- Timestamp:
- Mar 30, 2012, 3:35:57 PM (11 years ago)
- Location:
- trunk/grails-app
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/grails-app/controllers/api/ApiController.groovy
r2198 r2199 204 204 } 205 205 206 def get AssayData= {207 println "api: getAssayData: ${params}"206 def getSamplesForAssay = { 207 println "api::getSamplesForAssay: ${params}" 208 208 209 209 String deviceID = (params.containsKey('deviceID')) ? params.deviceID : '' … … 223 223 response.sendError(401, 'Unauthorized') 224 224 } else { 225 def samples = apiService.flattenDomainData( assay.samples ) 226 227 // define result 228 def result = [ 229 'count' : samples.size(), 230 'samples' : samples 231 ] 232 233 // set output headers 234 response.status = 200 235 response.contentType = 'application/json;charset=UTF-8' 236 237 if (params.containsKey('callback')) { 238 render "${params.callback}(${result as JSON})" 239 } else { 240 render result as JSON 241 } 242 } 243 244 } 245 246 def getAssayData = { 247 println "api:getAssayData: ${params}" 248 249 String deviceID = (params.containsKey('deviceID')) ? params.deviceID : '' 250 String validation = (params.containsKey('validation')) ? params.validation : '' 251 String assayToken = (params.containsKey('assayToken')) ? params.assayToken : '' 252 253 // fetch user and study 254 def user = Token.findByDeviceID(deviceID)?.user 255 def assay = Assay.findByAssayUUID(assayToken) 256 257 // check 258 if (!apiService.validateRequest(deviceID,validation)) { 259 response.sendError(401, 'Unauthorized') 260 } else if (!assay) { 261 response.sendError(400, 'No such assay') 262 } else if (!assay.parent.canRead(user)) { 263 response.sendError(401, 'Unauthorized') 264 } else { 225 265 // define result 226 266 def result = [ -
trunk/grails-app/domain/dbnp/studycapturing/Sample.groovy
r2153 r2199 1 1 package dbnp.studycapturing 2 2 3 import java.util.ArrayList; 3 4 … … 13 14 */ 14 15 class Sample extends TemplateEntity { 15 16 17 parent: Study,18 19 20 parentSubject: Subject,21 22 23 parentEvent: SamplingEvent,24 25 26 27 28 29 30 31 32 Term material// material of the sample (should normally be bound to the BRENDA ontology)33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 parentSubject(nullable:true)64 65 66 parentEvent(nullable:true)67 68 69 parentEventGroup(nullable:true)70 71 72 73 74 75 76 77 78 name(unique:['parent'])79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 if (obj.parent.samples.findAll{ it.name == obj.name}.size() > 1) {95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 16 static belongsTo = [ 17 // A Sample always belongs to one study. 18 parent: Study, 19 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, 22 23 // Also, it has a parent SamplingEvent describing the actual sampling, also within the same parent study. 24 parentEvent: SamplingEvent, 25 26 // And it has a parent EventGroup which tied it to its parent subject and parent event 27 parentEventGroup: EventGroup 28 29 // We can't have parentAssay since a Sample can belong to multiple Assays 30 ] 31 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) 34 35 /** 36 * UUID of this sample 37 */ 38 String sampleUUID 39 40 /** 41 * return the domain fields for this domain class 42 * @return List 43 */ 44 static List<TemplateField> giveDomainFields() { return Sample.domainFields } 45 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 ] 61 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) 65 66 // The same holds for parentEvent 67 parentEvent(nullable: true) 68 69 // and for parentEventGroup 70 parentEventGroup(nullable: true) 71 72 // The material domain field is optional 73 material(nullable: true) 74 75 sampleUUID(nullable: true, unique: true) 76 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']) 80 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 85 86 // define a boolean 87 def error = false 88 89 // check whether obj.parent.samples is not null at this stage to avoid null pointer exception 90 if (obj.parent) { 91 92 if (obj.parent.samples) { 93 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 } 114 115 115 116 static mapping = { … … 117 118 118 119 // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754 119 120 } 121 122 static getSamplesFor( event) {123 return Sample.findAll( 'from Sample s where s.parentEvent =:event', [event:event])124 125 126 127 * Returns all assays this samples has been processed in 128 * @returnList of assays129 130 131 return Assay.executeQuery( 'select distinct a from Assay a inner join a.samples s where s = :sample', ['sample': this])132 133 134 135 136 137 138 139 140 * @param oObject to compare with141 * @returnTrue iff the id of the given Sample is equal to the id of this Sample142 143 public boolean equals( Object o) {144 if( o == null)145 146 147 if( !( o instanceof Sample ))148 149 150 151 152 153 }154 155 156 157 158 159 if( !this.sampleUUID) {160 161 if( !this.save(flush:true)) {162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 public static String trimSampleNames(ArrayList sampleList, Integer maxChars) {178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 }120 templateTextFields type: 'text' 121 } 122 123 static getSamplesFor(event) { 124 return Sample.findAll('from Sample s where s.parentEvent =:event', [event: event]) 125 } 126 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 } 134 135 def String toString() { 136 return name 137 } 138 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; 147 148 if (!(o instanceof Sample)) 149 return false 150 151 Sample s = (Sample) o; 152 153 return this.is(s) || this.id == s.id 154 } 155 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 } 166 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 } 209 210 210 211 public static String generateSampleName(flow, subject, eventGroup, samplingEvent) { … … 228 229 229 230 /** 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 return newText.substring(0, newText.size()-1)249 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 = '' 240 241 // change case to lowercase 242 text = text.toLowerCase() 243 244 // iterate through words 245 text.split(" ").each() { 246 newText += it[0].toUpperCase() + it.substring(1) + " " 247 } 248 249 return newText.substring(0, newText.size() - 1) 250 } 250 251 } -
trunk/grails-app/services/api/ApiService.groovy
r2198 r2199 61 61 * call and not to expose domain internals 62 62 * 63 * @param elements 63 * @param elements (List or Set) 64 64 * @return 65 65 */ 66 def flattenDomainData( Listelements) {66 def flattenDomainData(elements) { 67 67 def items = [] 68 68 … … 74 74 // add token 75 75 if (it.respondsTo('getToken')) { 76 // some domain methods implement getToken... 76 77 item['token'] = it.getToken() 78 } else if (it.respondsTo('giveUUID')) { 79 // ...while other implement giveUUID 80 item['token'] = it.giveUUID() 77 81 } else { 82 // and others don't at all... :S 78 83 item['id'] = it.id 79 84 } -
trunk/grails-app/views/api/index.gsp
r2197 r2199 63 63 <li><a href="#getSubjectsForStudy">getSubjectsForStudy</a> - fetch all subjects in a given study</li> 64 64 <li><a href="#getAssaysForStudy">getAssaysForStudy</a> - fetch all assays in a given study</li> 65 <li><a href="#getSamplesForAssay">getSamplesForAssay</a> - fetch all samples in a given assay</li> 65 66 <li><a href="#getAssayData">getAssayData</a> - fetch all measurement data for a given assay</li> 66 67 … … 279 280 </p> 280 281 281 <a name="get AssayData"></a>282 <h1>get AssayData</h1>283 <h3>url: <g:createLink controller="api" action="get AssayData" absolute="true" /></h3>284 <p> 285 Returns the measurementdata for a particular assay282 <a name="getSamplesForAssay"></a> 283 <h1>getSamplesForAssay</h1> 284 <h3>url: <g:createLink controller="api" action="getSamplesForAssay" absolute="true" /></h3> 285 <p> 286 Returns the samples data for a particular assay 286 287 287 288 <h2>Request parameters</h2> … … 323 324 <h2>example reply</h2> 324 325 <blockquote> 326 { 327 "count": 328 11, 329 "samples": 330 [ 331 { 332 "Remarks": 333 null, 334 "Sample measured volume": 335 null, 336 "Text on vial": 337 "T58.66620961739546", 338 "material": 339 "blood plasma", 340 "name": 341 "7_A", 342 "token": 343 "c705668a-81c4-4d80-83df-96bb477aeb0b" 344 }, 345 { 346 "Remarks": 347 null, 348 "Sample measured volume": 349 null, 350 "Text on vial": 351 "T39.7483280873287", 352 "material": 353 "blood plasma", 354 "name": 355 "9_A", 356 "token": 357 "d81bdda8-4684-45b9-b254-1ec4756cfc71" 358 }, 359 { 360 "Remarks": 361 null, 362 "Sample measured volume": 363 null, 364 "Text on vial": 365 "T43.20628871191769", 366 "material": 367 "blood plasma", 368 "name": 369 "2_A", 370 "token": 371 "2f501b55-ffdd-4bf2-a598-dcf24d3fac63" 372 }, 373 { 374 "Remarks": 375 null, 376 "Sample measured volume": 377 null, 378 "Text on vial": 379 "T88.40760089710538", 380 "material": 381 "blood plasma", 382 "name": 383 "8_A", 384 "token": 385 "f908ae2a-3df7-4eb7-be2a-0b8859c20bfc" 386 }, 387 { 388 "Remarks": 389 null, 390 "Sample measured volume": 391 null, 392 "Text on vial": 393 "T58.14619508995611", 394 "material": 395 "blood plasma", 396 "name": 397 "11_A", 398 "token": 399 "6763cff4-8113-4614-85b9-ef98fb34beba" 400 }, 401 { 402 "Remarks": 403 null, 404 "Sample measured volume": 405 null, 406 "Text on vial": 407 "T71.86067212685215", 408 "material": 409 "blood plasma", 410 "name": 411 "6_A", 412 "token": 413 "5a339aaa-9bb6-4a0a-9ce7-4c42ceaf5771" 414 }, 415 { 416 "Remarks": 417 null, 418 "Sample measured volume": 419 null, 420 "Text on vial": 421 "T2.395117860298579", 422 "material": 423 "blood plasma", 424 "name": 425 "3_A", 426 "token": 427 "a9e73abe-aed3-4c43-8fe7-a6b3dfe6e2ed" 428 }, 429 { 430 "Remarks": 431 null, 432 "Sample measured volume": 433 null, 434 "Text on vial": 435 "T98.99437236833568", 436 "material": 437 "blood plasma", 438 "name": 439 "10_A", 440 "token": 441 "3e63a493-c69d-4cd4-ba23-eeafe962b17f" 442 }, 443 { 444 "Remarks": 445 null, 446 "Sample measured volume": 447 null, 448 "Text on vial": 449 "T25.420102086098005", 450 "material": 451 "blood plasma", 452 "name": 453 "4_A", 454 "token": 455 "34d5611b-7407-489a-b25a-00ad2b0d8789" 456 }, 457 { 458 "Remarks": 459 null, 460 "Sample measured volume": 461 null, 462 "Text on vial": 463 "T69.55369597806298", 464 "material": 465 "blood plasma", 466 "name": 467 "1_A", 468 "token": 469 "5c9dce07-ca4d-4bcb-8ac3-c8488bd7247a" 470 }, 471 { 472 "Remarks": 473 null, 474 "Sample measured volume": 475 null, 476 "Text on vial": 477 "T50.41146383561054", 478 "material": 479 "blood plasma", 480 "name": 481 "5_A", 482 "token": 483 "21a07d33-6d95-46f9-a80d-cd58d7e140d0" 484 } 485 ] 486 } 487 </blockquote> 488 </p> 489 490 <a name="getAssayData"></a> 491 <h1>getAssayData</h1> 492 <h3>url: <g:createLink controller="api" action="getAssayData" absolute="true" /></h3> 493 <p> 494 Returns the measurement data for a particular assay 495 496 <h2>Request parameters</h2> 497 <table> 498 <thead> 499 <th>argument</th> 500 <th>type</th> 501 <th>length</th> 502 <th>description</th> 503 <th>example</th> 504 <th>required</th> 505 </thead> 506 <tr> 507 <td>deviceID</td> 508 <td>string</td> 509 <td>36 (max)</td> 510 <td>a unique ID of the client device / application performing the call</td> 511 <td>9ae87836-d38d-4b86-be6a-eff93f2b049a</td> 512 <td>yes</td> 513 </tr> 514 <tr> 515 <td>validation</td> 516 <td>string</td> 517 <td>-</td> 518 <td><a href="http://www.miraclesalad.com/webtools/md5.php" target="_new">md5sum</a>( token + sequence + shared secret )</td> 519 <td>9ae87836d38d4b86be6aeff93f2b049a</td> 520 <td>yes</td> 521 </tr> 522 <tr> 523 <td>assayToken</td> 524 <td>string</td> 525 <td>255</td> 526 <td>assay token (see getAssays)</td> 527 <td>b6e0c6f4-d8db-4a43-91fa-a157d2d492f0</td> 528 <td>yes</td> 529 </tr> 530 </table> 531 532 <h2>example reply</h2> 533 <blockquote> 325 534 ...todo... 326 535 </blockquote>
Note: See TracChangeset
for help on using the changeset viewer.