root/src/groovy/dbnp/rest/common/CommunicationManager.groovy @ 136

Revision 136, 10.9 KB (checked in by jahn, 4 years ago)

Cleaned up some code.

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