Changeset 2199


Ignore:
Timestamp:
Mar 30, 2012, 3:35:57 PM (7 years ago)
Author:
work@…
Message:
  • implemented getSamplesForAssay api call
Location:
trunk/grails-app
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/grails-app/controllers/api/ApiController.groovy

    r2198 r2199  
    204204    }
    205205
    206     def getAssayData = {
    207         println "api:getAssayData: ${params}"
     206    def getSamplesForAssay = {
     207        println "api::getSamplesForAssay: ${params}"
    208208
    209209        String deviceID     = (params.containsKey('deviceID')) ? params.deviceID : ''
     
    223223            response.sendError(401, 'Unauthorized')
    224224        } 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 {
    225265            // define result
    226266            def result = [
  • trunk/grails-app/domain/dbnp/studycapturing/Sample.groovy

    r2153 r2199  
    11package dbnp.studycapturing
     2
    23import java.util.ArrayList;
    34
     
    1314 */
    1415class Sample extends TemplateEntity {
    15         static belongsTo = [
    16                 // A Sample always belongs to one study.
    17                 parent                  : Study,
    18 
    19                 // A Sample optionally has a parent Subject from which it was taken, this Subject should be in the same parent study.
    20                 parentSubject   : Subject,
    21 
    22                 // Also, it has a parent SamplingEvent describing the actual sampling, also within the same parent study.
    23                 parentEvent             : SamplingEvent,
    24 
    25                 // And it has a parent EventGroup which tied it to its parent subject and parent event
    26                 parentEventGroup: EventGroup
    27 
    28                 // We can't have parentAssay since a Sample can belong to multiple Assays
    29         ]
    30 
    31         String name             // should be unique with respect to the parent study (which can be inferred)
    32         Term material           // material of the sample (should normally be bound to the BRENDA ontology)
    33        
    34         /**
    35         * UUID of this sample
    36         */
    37         String sampleUUID
    38        
    39         /**
    40         * return the domain fields for this domain class
    41         * @return List
    42         */
    43         static List<TemplateField> giveDomainFields() { return Sample.domainFields }
    44 
    45         // We have to specify an ontology list for the material property. However, at compile time, this ontology does of course not exist.
    46         // Therefore, the ontology is added at runtime in the bootstrap, possibly downloading the ontology properties if it is not present in the database yet.
    47         static List<TemplateField> domainFields = [
    48                 new TemplateField(
    49                         name: 'name',
    50                         type: TemplateFieldType.STRING,
    51                         preferredIdentifier: true,
    52                         required: true
    53                 ),
    54                 new TemplateField(
    55                         name: 'material',
    56                         type: TemplateFieldType.ONTOLOGYTERM,
    57                         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'"
    58                 )
    59         ]
    60 
    61         static constraints = {
    62                 // The parent subject is optional, e.g. in a biobank of samples the subject could be unknown or non-existing.
    63                 parentSubject(nullable:true)
    64 
    65                 // The same holds for parentEvent
    66                 parentEvent(nullable:true)
    67 
    68                 // and for parentEventGroup
    69                 parentEventGroup(nullable:true)
    70 
    71                 // The material domain field is optional
    72                 material(nullable: true)
    73 
    74                 sampleUUID(nullable: true, unique: true)
    75 
    76                 // Check if the externalSampleId (currently defined as name) is really unique within each parent study of this sample.
    77                 // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraint
    78                 name(unique:['parent'])
    79 
    80                 // Same, but also when the other sample is not even in the database
    81                 // This feature is tested by integration test SampleTests.testSampleUniqueNameConstraintAtValidate
    82                 name(validator: { field, obj, errors ->
    83                         // 'obj' refers to the actual Sample object
    84 
    85                         // define a boolean
    86                         def error = false
    87 
    88                         // check whether obj.parent.samples is not null at this stage to avoid null pointer exception
    89                         if (obj.parent) {
    90 
    91                                 if (obj.parent.samples) {
    92 
    93                                         // check if there is exactly one sample with this name in the study (this one)
    94                                         if (obj.parent.samples.findAll{ it.name == obj.name}.size() > 1) {
    95                                                 error = true
    96                                                 errors.rejectValue(
    97                                                         'name',
    98                                                         'sample.UniqueNameViolation',
    99                                                         [obj.name, obj.parent] as Object[],
    100                                                         'Sample name {0} appears multiple times in study {1}'
    101                                                         )
    102                                         }
    103                                 }
    104                         }
    105                         else {
    106                                 // if there is no parent study defined, fail immediately
    107                                 error = true
    108                         }
    109 
    110                         // got an error, or not?
    111                         return (!error)
    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    }
    114115
    115116    static mapping = {
     
    117118
    118119        // Workaround for bug http://jira.codehaus.org/browse/GRAILS-6754
    119         templateTextFields type: 'text'
    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          * @return      List of assays
    129         */
    130         public List getAssays() {
    131                 return Assay.executeQuery( 'select distinct a from Assay a inner join a.samples s where s = :sample', ['sample': this] )
    132         }
    133        
    134         def String toString() {
    135                 return name
    136         }
    137 
    138         /**
    139         * Basic equals method to check whether objects are equals, by comparing the ids
    140         * @param o              Object to compare with
    141         * @return               True iff the id of the given Sample is equal to the id of this Sample
    142         */
    143    public boolean equals( Object o ) {
    144            if( o == null )
    145                    return false;
    146                    
    147            if( !( o instanceof Sample ) )
    148                    return false
    149            
    150            Sample s = (Sample) o;
    151            
    152            return this.is(s) || this.id == s.id
    153    }
    154        
    155         /**
    156         * Returns the UUID of this sample and generates one if needed
    157         */
    158         public String giveUUID() {
    159                 if( !this.sampleUUID ) {
    160                         this.sampleUUID = UUID.randomUUID().toString();
    161                         if( !this.save(flush:true) ) {
    162                                 //println "Couldn't save sample UUID: " + this.getErrors();
    163                         }
    164                 }
    165                
    166                 return this.sampleUUID;
    167         }
    168        
    169         /**
    170         * Returns a human readable string of a list of samples, with a maximum number
    171         * of characters
    172         *
    173         * @param sampleList List with Sample objects
    174         * @param maxChars maximum number of characters returned
    175         * @return human readble string with at most maxChars characters, representing the samples given.
    176         */
    177    public static String trimSampleNames(ArrayList sampleList, Integer maxChars) {
    178            def simpleSamples = sampleList.name.join(', ');
    179            def showSamples
    180 
    181            // If the subjects will fit, show them all
    182            if (!maxChars || simpleSamples.size() < maxChars) {
    183                    showSamples = simpleSamples;
    184            } else {
    185                    // Always add the first name
    186                    def sampleNames = sampleList[0]?.name;
    187 
    188                    // Continue adding names until the length is to long
    189                    def id = 0;
    190                    sampleList.each { sample ->
    191                            if (id > 0) {
    192                                    if (sampleNames?.size() + sample.name?.size() < maxChars - 15) {
    193                                            sampleNames += ", " + sample.name;
    194                                    } else {
    195                                            return;
    196                                    }
    197                            }
    198                            id++;
    199                    }
    200 
    201                    // Add a postfix
    202                    sampleNames += " and " + (sampleList?.size() - id) + " more";
    203 
    204                    showSamples = sampleNames;
    205            }
    206 
    207            return showSamples
    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    }
    209210
    210211    public static String generateSampleName(flow, subject, eventGroup, samplingEvent) {
     
    228229
    229230    /**
    230         * groovy / java equivalent of php's ucwords function
    231         *
    232         * Capitalize all first letters of separate words
    233         *
    234         * @param String
    235         * @return String
    236         */
    237         public static ucwords(String text) {
    238                 def newText = ''
    239 
    240                 // change case to lowercase
    241                 text = text.toLowerCase()
    242 
    243                 // iterate through words
    244                 text.split(" ").each() {
    245                         newText += it[0].toUpperCase() + it.substring(1) + " "
    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    }
    250251}
  • trunk/grails-app/services/api/ApiService.groovy

    r2198 r2199  
    6161     * call and not to expose domain internals
    6262     *
    63      * @param elements
     63     * @param elements (List or Set)
    6464     * @return
    6565     */
    66     def flattenDomainData(List elements) {
     66    def flattenDomainData(elements) {
    6767        def items = []
    6868
     
    7474            // add token
    7575            if (it.respondsTo('getToken')) {
     76                // some domain methods implement getToken...
    7677                item['token'] = it.getToken()
     78            } else if (it.respondsTo('giveUUID')) {
     79                // ...while other implement giveUUID
     80                item['token'] = it.giveUUID()
    7781            } else {
     82                // and others don't at all... :S
    7883                item['id'] = it.id
    7984            }
  • trunk/grails-app/views/api/index.gsp

    r2197 r2199  
    6363    <li><a href="#getSubjectsForStudy">getSubjectsForStudy</a> - fetch all subjects in a given study</li>
    6464    <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>
    6566    <li><a href="#getAssayData">getAssayData</a> - fetch all measurement data for a given assay</li>
    6667
     
    279280</p>
    280281
    281 <a name="getAssayData"></a>
    282 <h1>getAssayData</h1>
    283 <h3>url: <g:createLink controller="api" action="getAssayData" absolute="true" /></h3>
    284 <p>
    285     Returns the measurement data for a particular assay
     282<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
    286287
    287288<h2>Request parameters</h2>
     
    323324<h2>example reply</h2>
    324325<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>
    325534...todo...
    326535</blockquote>
Note: See TracChangeset for help on using the changeset viewer.