source: trunk/grails-app/services/dbnp/modules/ModuleCommunicationService.groovy @ 1852

Last change on this file since 1852 was 1852, checked in by robert@…, 12 years ago

Improved module communication when no parameters are given

  • Property svn:keywords set to Rev Author Date
File size: 7.6 KB
Line 
1/**
2 * SynchronizationService Service
3 *
4 * Description of my service
5 *
6 * @author  your email (+name?)
7 * @since       2010mmdd
8 * @package     ???
9 *
10 * Revision information:
11 * $Rev: 1852 $
12 * $Author: robert@isdat.nl $
13 * $Date: 2011-05-19 14:02:01 +0000 (do, 19 mei 2011) $
14 */
15package dbnp.modules
16
17import dbnp.studycapturing.*
18import dbnp.authentication.*
19import grails.converters.*
20import javax.servlet.http.HttpServletResponse
21import org.codehaus.groovy.grails.commons.ConfigurationHolder
22import org.hibernate.*;
23
24class ModuleCommunicationService implements Serializable {
25        static transactional = false
26        def authenticationService
27        def moduleNotificationService
28        SessionFactory sessionFactory
29       
30        /**
31         * Cache containing the contents of different URLs. These urls are
32         * saved per user, since the data could be different for different users.
33         */
34        def cache = [:]
35
36        /**
37         * Number of seconds to save the data in cache
38         */
39        def numberOfSecondsInCache = ConfigurationHolder.config.modules.cacheDuration ? Integer.valueOf( ConfigurationHolder.config.modules.cacheDuration.toString() ) : 300;
40
41        /**
42         * Sends a notification to assay modules that some part of a study has changed.
43         *
44         * Only modules that have the notify flag set to true will be notified. They will be notified on the URL
45         *
46         * [moduleUrl]/rest/notifyStudyChange?studyToken=abc
47         *
48         * Errors that occur when calling this URL are ignored. The module itself is responsible of
49         * maintaining a synchronized state.
50         *
51         * @param study
52         * @return
53         */
54        def invalidateStudy( def study ) {
55                moduleNotificationService.invalidateStudy( study );
56        }
57
58        /**
59         * Checks whether a specific method on a module is reachable and returns a SC_OK response code.
60         *
61         * This method will return false if a method returns an error (including 403 and 401 errors) or
62         * a redirect
63         *
64         * @param moduleUrl             URL of the module
65         * @param path                  Path of the rest method on that module. If omitted, the module reachablility itself is tested
66         * @return                              True if the module is reachable, false otherwise
67         */
68        def isModuleReachable(moduleUrl, path = "") {
69                def connection = ( moduleUrl + path ).toURL().openConnection()
70                try {
71                        return connection.responseCode == HttpServletResponse.SC_OK
72                } catch(e) {
73                        return false
74                }
75        }
76
77        /**
78         * Calls a rest method on a module
79         *
80         * @param consumer      Consumer of that specific module
81         * @param restUrl       Full URL for the method to call
82         * @return                      JSON    JSON object of the parsed text
83         * @deprecated          Use callModuleMethod instead
84         */
85        def callModuleRestMethodJSON( consumer, restUrl ) throws Exception {
86                def parts = restUrl.split( /\?/ );
87                def url = "";
88                def query = "";
89               
90                if( parts.size() > 1 ) {
91                        url = parts[ 0 ]
92                        query = parts[ 1 ]
93                } else if( parts.size() > 0 ) { 
94                        url = parts[ 0 ];
95                }
96               
97                return callModuleMethod( consumer, url, query );
98        }
99       
100        /**
101        * Calls a rest method on a module
102        *
103        * @param consumer       Consumer of that specific module
104        * @param restUrl        Full URL for the method to call, without query string
105        * @param args           Query string for the url to call (e.q. token=abc&field=xyz)
106        * @param requestMethod  GET or POST - HTTP request method to use
107        * @return                       JSON    JSON object of the parsed text
108        */
109        def callModuleMethod( String consumer, String restUrl, String args = null, String requestMethod = "GET" ) {
110                if (!authenticationService.isLoggedIn()) {
111                        // should not happen because we can only get here when a user is
112                        // logged in...
113                        throw new Exception('User is not logged in.')
114                }
115
116                // Check whether the url is present in cache
117                def cacheData = retrieveFromCache( restUrl, args );
118                if( cacheData && cacheData[ "success" ] )
119                        return cacheData[ "contents" ];
120                else if( cacheData && !cacheData[ "success" ] )
121                        throw new Exception( "Error while fetching data from " + restUrl + " (from cache): " + cacheData[ "error" ] )
122
123                // create a random session token that will be used to allow to module to
124                // sync with gscf prior to presenting the measurement data
125                def sessionToken = UUID.randomUUID().toString()
126
127                // put the session token to work
128                authenticationService.logInRemotely( consumer, sessionToken, authenticationService.getLoggedInUser() )
129               
130                // Append the sessionToken to the parameters
131                if( !args ) {
132                        args = ""
133                } else {
134                        args += "&"
135                }
136               
137                args += "sessionToken=" + sessionToken
138               
139                // Perform a call to the url
140                def restResponse
141                try {
142                        log.trace "GSCF call (" + requestMethod + ") to " + consumer + " URL: " + restUrl + " (args: " + args + ")"
143
144                        def textResponse
145                        switch( requestMethod.toUpperCase() ) {
146                                case "GET":
147                                        def url = restUrl + "?" + args;
148                                        def connection = url.toURL().openConnection();
149               
150                                        textResponse = url.toURL().getText()
151                               
152                                        break
153                                case "POST":
154                                        def connection = restUrl.toURL().openConnection()
155                                        connection.setRequestMethod( "POST" );
156                                        connection.doOutput = true
157                                       
158                                        def writer = new OutputStreamWriter( connection.outputStream )
159                                        writer.write( args );
160                                        writer.flush()
161                                        writer.close()
162                                       
163                                        connection.connect();
164                                       
165                                        textResponse = connection.content.text
166
167                                        break
168                                default:
169                                        throw new Exception( "Unknown request method given. Use GET or POST" )
170                        }
171
172                        log.trace "GSCF response: " + textResponse
173                        restResponse = JSON.parse( textResponse )
174                } catch (Exception e) {
175                        storeErrorInCache( restUrl, e.getMessage(), args );
176                        throw new Exception( "An error occurred while fetching " + restUrl + ".", e )
177                } finally {
178                        // Dispose of the ephemeral session token
179                        authenticationService.logOffRemotely(consumer, sessionToken)
180                }
181
182                // Store the response in cache
183                storeInCache( restUrl, restResponse, args );
184               
185                return restResponse
186
187        }
188       
189        /**
190         * Checks whether a specific url exists in cache
191         * @param url   URL to call
192         * @return              true if the url is present in cache
193         */
194        def existsInCache( url, args = null ) {
195                return retrieveFromCache( url, args ) == null;
196        }
197
198        /**
199         * Retrieves the contents of a specific URL from cache
200         * @param url   URL to call
201         * @return              JSON object with the contents of the URL or null if the url doesn't exist in cache
202         */
203        def retrieveFromCache( url, args = null ) {
204                def user = authenticationService.getLoggedInUser();
205                def userId = user ? user.id : -1;
206               
207                url = cacheUrl( url, args )
208               
209                if( cache[ userId ] && cache[ userId ][ url ] && ( System.currentTimeMillis() - cache[ userId ][ url ][ "timestamp" ] ) < numberOfSecondsInCache * 1000 ) {
210                        return cache[ userId ][ url ];
211                } else {
212                        return null;
213                }
214        }
215
216        /**
217         * Store the retrieved contents from a url in cache
218         * @param url           URL that has been called
219         * @param contents      Contents of the URL
220         */
221        def storeInCache( url, contents, args = null ) {
222                def user = authenticationService.getLoggedInUser();
223                def userId = user ? user.id : -1;
224
225                if( !cache[ userId ] )
226                        cache[ userId ] = [:]
227
228                cache[ userId ][ cacheUrl( url, args ) ] = [
229                        "timestamp": System.currentTimeMillis(),
230                        "success": true,
231                        "contents": contents
232                ];
233        }
234       
235        /**
236        * Store the retrieved error from a url in cache
237        * @param url            URL that has been called
238        * @param contents       Contents of the URL
239        */
240   def storeErrorInCache( url, error, args = null ) {
241           def user = authenticationService.getLoggedInUser();
242           def userId = user ? user.id : -1;
243
244           if( !cache[ userId ] )
245                   cache[ userId ] = [:]
246
247           cache[ userId ][ cacheUrl( url, args ) ] = [
248                   "timestamp": System.currentTimeMillis(),
249                   "success": false,
250                   "error": error
251           ];
252   }
253   
254   /**
255    * Url used to save data in cache
256    */
257   def cacheUrl( url, args = null ) {
258                if( args ) {
259                        // Remove sessionToken from args
260                        args = args;
261                        def sessionFound = ( args =~ /sessionToken=[^&]/ );
262                        args = sessionFound.replaceAll( "sessionToken=" );
263                       
264                        url += '?' + args
265                }
266                       
267                return url;
268   }
269
270}
Note: See TracBrowser for help on using the repository browser.