source: trunk/src/groovy/dbnp/rest/common/CommunicationManager.groovy @ 1010

Last change on this file since 1010 was 1010, checked in by j.saito@…, 10 years ago

Ticket #57 - Unified getAssays and getAssay.

Also, added convenience method for checking the params object in RestControllers? in CommunicationManager?.

File size: 11.6 KB
Line 
1package dbnp.rest.common
2
3import grails.converters.JSON
4import java.net.URLEncoder
5import org.codehaus.groovy.grails.web.json.*
6
7
8/**  CommunicationManager
9 *
10 *   @author Jahn
11 *
12 *   This class manages communication between dbNP modules such as GSCF and SAM.
13 *   By communication we mean two ways of exchanging information: (1) via Rest resources,
14 *   and (2) via Grails views that a module can make available to another module.
15 *
16 *   For Rest communication this class implements a Rest client that fetches data
17 *   from other modules' Rest resources. The Rest implementation transfers data in JSON.
18 *
19 *   Note: Do not use this class directly to fetch data. Instead use your module's
20 *   rest wrapper methods. Use this module, to create these rest wrapper methods.
21 *   For instance, use dbnp.rest.sam.registerRestWrapperMethodsGSCFtoSAM to register new methods
22 *   for accessing GSCF's Rest service in SAM; your new method shoud then use this class.
23 */
24
25
26class CommunicationManager {
27
28    def        static Encoding      = "UTF-8" 
29    def public static SAMServerURL  = "http://localhost:8182/sam"
30    def public static GSCFServerURL = "http://localhost:8080/gscf"
31    def public static DSPServerURL  = "http://localhost:8080/gscf"
32
33
34
35    /**
36     * Get the results of provided by a rest Rest resource.
37     *
38     * @params String resource The name of the resource, e.g. importer/pages
39     * @params Map params      A Map of parmater names and values., e.g. ['externalAssayID':12]
40     * @return String url   
41     */
42    public static Object getRestResource( RestServerURL, resource, params ) {
43                def url = getRestURL( RestServerURL, resource, params )
44                return  JSON.parse( url.newReader() )
45    }
46
47
48    /**
49     * Convenience method for constructing URLs for SAM that need parameters.
50     * Note that parameters are first convereted to strings by calling their toString() method
51     * and then Encoded to protect special characters.
52     *
53     * @params String resource The name of the resource, e.g. importer/pages
54     * @params Map params      A Map of parmater names and values., e.g. ['externalAssayID':12]
55     * @return String url   
56     */
57    public static URL getRestURL( RestServerURL, resource, params ) {
58        def url = RestServerURL + '/' + resource
59                def first = true
60                params.each { name, value ->
61                        if(first) {
62                                first = false
63                                url += '/nil?' + name + "=" + URLEncoder.encode( value.toString(), Encoding )
64                        }
65                        else { 
66                                url += '&' + name + "=" + URLEncoder.encode( value.toString(), Encoding  )
67                        }
68                }
69                return new URL( url )
70    }
71
72
73
74    /**
75     * This method dynamically adds a static method to the CommunicationManager.
76     * 
77     * @params String serverURL         A rest server URL.
78     * @params String restName          The name of a rest resource on the server.     
79     * @params Map params               A list of parameter names to be passed to this resource.
80     * @return String url   
81     * 
82     * Given a rest resource at serverURL called resourceName, we register a static method
83     * for the CommunicationManager. The new method has the same name and arity as the resource.
84     * 
85     * Example: Suppopse http://localhost:8080/gscf/rest/getSamples is a Rest resource.
86     * 
87     * In our grails app, we would like to connect to this service. We want to have a
88     * method getSamples() that fetches the result from the service. We do this by calling
89     * 
90     *          CommunicationManager.addRestWrapper( 'http://localhost:8080/gscf/rest', 'getSamples', ['externalStudyID'] )
91     * 
92     * This registers a new method:
93     * 
94         *               public static Object CommunicationManager.getSamples( Object arg )
95     * 
96     * This method has arrity 1 and expects to be given a map. The map is the parameter map
97     * of the rest service getSamples. It maps parameter called "externalStudyID" to some object
98     * that is passed. So, it can be called like as follows:
99     * 
100     *      def sampleList = CommunicationManager.getSamples( [externalStudyID:4711] )
101     * 
102     *  The call will deliver the results of the parameterized rest resource given at:
103     * 
104     *          http://localhost:8080/gscf/rest/nil?externalStudyID=4711
105     *
106     */
107
108    public static addRestWrapper( serverURL, restName, params = [], closure = { return it } ) {
109                CommunicationManager.metaClass.registerStaticMethod( restName ) { Object [] strangeGroovyArgs ->
110                        def map = [:]
111                    def args = strangeGroovyArgs[0]        // groovy nests the parameters of the methods in some other array
112
113                        if(params.size > 0 )
114                        {
115                                for( i in 0..(params.size-1) ) {
116                                        def param = params[i]
117                                        map[param] = args[i]
118                                }
119                        }
120
121                        return closure( getRestResource( serverURL, restName, map ) )
122                }
123        }
124
125
126
127
128
129    /**
130     * This method dynamically registers a static method to the CommunicationManager. The new method
131     * gives url for a Grails view on some server and takes as arguments the arguments required
132     * as params by the view.
133     * 
134     * @params String methodname        The name for method to be registered.
135     * @params String serverURL         The server's URL.
136     * @params String viewName          The view's name, e.g., '/Assay/show'
137     * @params Map params               The parameter list required by this view.
138     * @return String URL
139     * 
140     */ 
141    public static addViewWrapper( methodName, serverURL, viewName, params = [] ) {
142
143                CommunicationManager.metaClass.registerStaticMethod( methodName ) { Object [] strangeGroovyArgs ->
144                        def map = [:]
145                    def args = strangeGroovyArgs[0]        // groovy nests the parameters of the methods in some other array
146                        for( i in 0..(params.size-1) ) {
147                                def param = params[i]
148                            map[param] = args[i]
149                        }
150                        return getRestURL( serverURL, viewName, map )
151                }
152    }
153
154
155    /**
156     *  This creates on run time new methods for accessing Rest resources that GSCF provides for SAM.
157     *  This method should be called in grails-app/conf/BootStrap.groovy in the SAM module.
158     */ 
159    public static registerRestWrapperMethodsGSCFtoSAM() {
160        def url = GSCFServerURL + '/rest'
161                addRestWrapper( url , 'getStudies' )
162        addRestWrapper( url , 'getSubjects', ['studyToken'] )
163                addRestWrapper( url , 'getAssays',   ['studyToken','moduleURL'] )
164                addRestWrapper( url , 'getSamples',  ['assayToken'] )
165        }
166
167
168    /**
169     *  This method creates on run time new methods for accessing Grails views that SAM provides for GSCF.
170     *  This method should be called in grails-app/conf/BootStrap.groovy in the GSCF module.
171     */ 
172    public static registerRestWrapperMethodsSAMtoGSCF() {
173                def url = SAMServerURL
174
175                // register method that links to the SAM view for importing a SimpleAssay.
176        // parameters: externalAssayID, an externalAssayID
177                addViewWrapper( 'getAssayImportURL', url, 'importer/pages', ['externalAssayID', 'externalStudyID'] )
178
179                // register method that links to the SAM view for showing a SimpleAssay
180        // parameters: externalAssayID
181                addViewWrapper( 'getAssayShowURL', url, 'simpleAssay/show', ['externalAssayID'] )
182
183                // register method that links to the SAM view for editing a SimpleAssay
184        // parameters: externalAssayID
185                addViewWrapper( 'getAssayEditURL', url, 'simpleAssay/show', ['externalAssayID'] )
186
187                // register method that links to the SAM view for editing a SimpleAssay
188        // parameters: externalAssayID
189                addViewWrapper( 'getMeasurementTypesURL', url, 'simpleAssayMeasurementType/list', ['externalStudyID'] )
190
191                // register rest resource that returns the results of a full text query on SAM
192        // parameters:   query. A string for fulltext search on SAM
193        // return value: results map. It contains two keys 'studyIds', and 'samples'. 'studyIds'
194                //               key maps to a list of Study domain objects of GSCF. 'samples' maps to a
195                //               list of pairs. Each pair consists of a Sample domain object of GSCF and
196                //               additional sample information from SAM provided as a map.
197                // Example of a returned map:
198                //                               ["studies":[NuGO PPS human study],
199                //               "samples":[[ [...], dbnp.studycapturing.Sample: 1]]]
200                def closure = { map -> 
201                    def studies = []   
202                    def assays  = []   
203                        def studiesHQ = "from dbnp.studycapturing.Study as s where s.code=?"
204                        map['studyIds'].each { studies.add( dbnp.studycapturing.Study.find(studiesHQ,[it]) ) }
205                        map['assays'].each { samAssay ->
206                                def assayID = samAssay['externalAssayID']
207                            def assayHQ = "from dbnp.studycapturing.Assay as a where a.externalAssayID='${assayID}'"
208                                def assay = dbnp.studycapturing.Assay.find(assayHQ)
209                                assays.add( [samAssay,assay] )
210                        } 
211                        return [studies:studies, assays:assays] 
212                }
213
214                addRestWrapper( url+'/rest', 'getQueryResult',  ['query'], closure )
215
216
217                // Rest resource: getQueryResultWithOperator
218                //
219                // register rest resource that returns the results of a simple query with measurement value on SAM
220        // parameters:   query. A keyword to match a measurement type on SAM.
221        //               operator. One of '=','<', or '>' that serves for selecting measurements.
222        //               value. A double value for the measurement.
223                //
224        // return value: results list of maps. each map contains an assay, a sample, the value found,
225        //               a unit (as String), and a type (as String).
226                //
227                // Example of a returned list of maps:
228                //                               [["type":"Glucose", "unit":"g", "value":"201.0", "assay":Lipid profiling,
229                //                                 "sample":A10_B], ["type":"Glucose", "unit":"g", "value":"101.0",
230                //                                 "assay":Lipid profiling, "sample":A1_B], ["type":"Insulin", "unit":"g", "value":"202.0",
231                //                                 "assay":Lipid profiling, "sample":A10_B], ["type":"Insulin", "unit":"g", "value":"102.0",
232                //                                 "assay":Lipid profiling, "sample":A1_B]]
233                //             
234                def closure2 = { listOfMaps ->
235                        def results = []
236                        listOfMaps.each{ map -> 
237                                def result = [ type:map['type'], unit:map['unit'], value:map['value'] ]
238                                def assayId = map['externalAssayId'].toLong()
239                                def assay  = dbnp.studycapturing.Assay.find( "from dbnp.studycapturing.Assay as a where a.externalAssayID= ?", [assayId] ) 
240                                def sample = dbnp.studycapturing.Sample.find( "from dbnp.studycapturing.Sample as s where s.name = ?", [map['externalSampleId']] ) 
241                                result['assay']=assay
242                                result['sample']=sample
243                                results.add( result )
244                        }
245                        return results
246                }
247
248                addRestWrapper( url+'/rest', 'getQueryResultWithOperator',  ['query','operator','value'], closure2 )
249    }
250
251
252    /**
253     *  This method creates on run time new methods for accessing Grails views that SAM provides for GSCF.
254     *  This method should be called in grails-app/conf/BootStrap.groovy in the GSCF module.
255     */ 
256    public static registerRestWrapperMethodsGSCFtoDSP() {
257                def url = DSPServerURL
258                addRestWrapper( url, 'isUser',  ['username','password'] )
259                addRestWrapper( url, 'listStudies',  ['username','password'] )
260                addRestWrapper( url, 'listStudySamples',  ['username','password','study_token'] )
261                addRestWrapper( url, 'getStudy',  ['username','password','study_token'] )
262                addRestWrapper( url, 'getStudySample',  ['username','password','study_token','sample_token'] )
263                addRestWrapper( url, 'isUser',  ['username','password'] )
264    }
265
266
267    /**
268     * Give list of missing parameters for a parameter call in a RestController.
269     * 
270     * @params params Map params        The parameter list required by this view.
271     * @params requiredParamers                 List of parameter names that must be provided
272     * @return true, if params has all required parameters, false otherwise
273     */ 
274        static String hasValidParams( params, Object [] requiredParams ) {
275                def list = []
276                requiredParams.each { p ->
277                        if( !params[p] ) list.push p
278                }
279                if(list.size()>0) { return true }
280                return false
281        }
282
283
284
285}
Note: See TracBrowser for help on using the repository browser.