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

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

Solved #344 - retrieving module data is now done using POST

  • Property svn:keywords set to Rev Author Date
File size: 7.5 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: 1596 $
12 * $Author: robert@isdat.nl $
13 * $Date: 2011-03-08 10:20:31 +0000 (di, 08 mrt 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 URL
131                if( !args )
132                        args = ""
133               
134                args += "&sessionToken=" + sessionToken
135               
136                // Perform a call to the url
137                def restResponse
138                try {
139                        log.trace "GSCF call (" + requestMethod + ") to " + consumer + " URL: " + restUrl + " (args: " + args + ")"
140
141                        def textResponse
142                        switch( requestMethod.toUpperCase() ) {
143                                case "GET":
144                                        def url = restUrl + "?" + args;
145                                        def connection = url.toURL().openConnection();
146               
147                                        textResponse = url.toURL().getText()
148                               
149                                        break
150                                case "POST":
151                                        def connection = restUrl.toURL().openConnection()
152                                        connection.setRequestMethod( "POST" );
153                                        connection.doOutput = true
154                                       
155                                        def writer = new OutputStreamWriter( connection.outputStream )
156                                        writer.write( args );
157                                        writer.flush()
158                                        writer.close()
159                                       
160                                        connection.connect();
161                                       
162                                        textResponse = connection.content.text
163
164                                        break
165                                default:
166                                        throw new Exception( "Unknown request method given. Use GET or POST" )
167                        }
168
169                        log.trace "GSCF response: " + textResponse
170                        restResponse = JSON.parse( textResponse )
171                } catch (Exception e) {
172                        storeErrorInCache( restUrl, e.getMessage(), args );
173                        throw new Exception( "An error occurred while fetching " + restUrl + ".", e )
174                } finally {
175                        // Dispose of the ephemeral session token
176                        authenticationService.logOffRemotely(consumer, sessionToken)
177                }
178
179                // Store the response in cache
180                storeInCache( restUrl, restResponse, args );
181               
182                return restResponse
183
184        }
185       
186        /**
187         * Checks whether a specific url exists in cache
188         * @param url   URL to call
189         * @return              true if the url is present in cache
190         */
191        def existsInCache( url, args = null ) {
192                return retrieveFromCache( url, args ) == null;
193        }
194
195        /**
196         * Retrieves the contents of a specific URL from cache
197         * @param url   URL to call
198         * @return              JSON object with the contents of the URL or null if the url doesn't exist in cache
199         */
200        def retrieveFromCache( url, args = null ) {
201                def user = authenticationService.getLoggedInUser();
202                def userId = user ? user.id : -1;
203               
204                url = cacheUrl( url, args )
205               
206                if( cache[ userId ] && cache[ userId ][ url ] && ( System.currentTimeMillis() - cache[ userId ][ url ][ "timestamp" ] ) < numberOfSecondsInCache * 1000 ) {
207                        return cache[ userId ][ url ];
208                } else {
209                        return null;
210                }
211        }
212
213        /**
214         * Store the retrieved contents from a url in cache
215         * @param url           URL that has been called
216         * @param contents      Contents of the URL
217         */
218        def storeInCache( url, contents, args = null ) {
219                def user = authenticationService.getLoggedInUser();
220                def userId = user ? user.id : -1;
221
222                if( !cache[ userId ] )
223                        cache[ userId ] = [:]
224
225                cache[ userId ][ cacheUrl( url, args ) ] = [
226                        "timestamp": System.currentTimeMillis(),
227                        "success": true,
228                        "contents": contents
229                ];
230        }
231       
232        /**
233        * Store the retrieved error from a url in cache
234        * @param url            URL that has been called
235        * @param contents       Contents of the URL
236        */
237   def storeErrorInCache( url, error, args = null ) {
238           def user = authenticationService.getLoggedInUser();
239           def userId = user ? user.id : -1;
240
241           if( !cache[ userId ] )
242                   cache[ userId ] = [:]
243
244           cache[ userId ][ cacheUrl( url, args ) ] = [
245                   "timestamp": System.currentTimeMillis(),
246                   "success": false,
247                   "error": error
248           ];
249   }
250   
251   /**
252    * Url used to save data in cache
253    */
254   def cacheUrl( url, args = null ) {
255                if( args ) {
256                        // Remove sessionToken from args
257                        args = args;
258                        def sessionFound = ( args =~ /sessionToken=[^&]/ );
259                        args = sessionFound.replaceAll( "sessionToken=" );
260                       
261                        url += '?' + args
262                }
263                       
264                return url;
265   }
266
267}
Note: See TracBrowser for help on using the repository browser.