source: trunk/grails-app/services/api/ApiService.groovy @ 2224

Last change on this file since 2224 was 2224, checked in by work@…, 10 years ago
  • removed debug method
  • removed obsolete imports
File size: 8.9 KB
Line 
1/**
2 * ApiService Service
3 *
4 * Description of my service
5 *
6 * @author  Jeroen Wesbeek <work@osx.eu>
7 * @since       20120328
8 * @package     api
9 *
10 * Revision information:
11 * $Rev: 1430 $
12 * $Author: work@osx.eu $
13 * $Date: 2011-01-21 21:05:36 +0100 (Fri, 21 Jan 2011) $
14 */
15package api
16
17import java.security.MessageDigest
18import dbnp.studycapturing.Assay
19import dbnp.authentication.SecUser
20import org.springframework.context.ApplicationContextAware
21import org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib
22import org.springframework.context.ApplicationContext
23
24class ApiService implements Serializable, ApplicationContextAware {
25    // inject the module communication service
26    def moduleCommunicationService
27
28    // the shared secret used to validate api calls
29    static final String API_SECRET = "th!s_sH0uld^Pr0bab7y_m0v3_t%_th3_uSeR_d0Ma!n_ins7ead!"
30
31    // transactional
32    static transactional = false
33
34    // characters to split on when converting a string to camelCased format
35    static camelCaseSeparators = " |-|_"
36
37    // hasMany keys to ignore when flattening domain data
38    static ignoreHasManyKeys = [
39            "systemFields",
40            "templateBooleanFields",
41            "templateDateFields",
42            "templateDoubleFields",
43            "templateExtendableStringListFields",
44            "templateFileFields",
45            "templateLongFields",
46            "templateModuleFields",
47            "templateTermFields",
48            "templateRelTimeFields",
49            "templateStringFields",
50            "templateStringListFields",
51            "templateTemplateFields",
52            "templateTextFields"
53    ]
54
55    private ApplicationTagLib g
56
57    void setApplicationContext(ApplicationContext applicationContext) {
58        g = applicationContext.getBean(ApplicationTagLib)
59
60        // now you have a reference to g that you can call render() on
61    }
62
63    /**
64     * validate a client request by checking the validation checksum
65     * @param deviceID
66     * @param validation
67     * @return
68     */
69    def validateRequest(String deviceID, String validation) {
70        def validated = false
71
72        // disable validation check on development and ci
73        if (['development', 'ci'].contains(grails.util.GrailsUtil.environment)) {
74            return true
75        }
76
77        // get token for this device ID
78        Token token = Token.findByDeviceID(deviceID)
79
80        // increase sequence
81        if (token) {
82            token.sequence = token.sequence+1
83            token.merge(flush: true)
84
85            // generate the validation checksum
86            MessageDigest digest = MessageDigest.getInstance("MD5")
87            String validationSum = new BigInteger(1,digest.digest("${token.deviceToken}${token.sequence}${API_SECRET}".getBytes())).toString(16).padLeft(32,"0")
88
89            // check if the validation confirms
90            validated = (validation == validationSum)
91        }
92
93        return validated
94    }
95
96    /**
97     * flatten domain data to relevant data to return in an api
98     * call and not to expose domain internals
99     *
100     * @param elements (List or Set)
101     * @return
102     */
103    def flattenDomainData(elements) {
104        def items = []
105
106        // iterate through elements
107        elements.each {
108            def fields  = (it.respondsTo('giveFields')) ? it.giveFields(): []
109            def item    = [:]
110
111            // check if element has a name
112            ['name','description'].each { checkName ->
113                if (it.hasProperty(checkName)) item[checkName] = it[checkName]
114            }
115
116            // add token
117            if (it.respondsTo('getToken')) {
118                // some domain methods implement getToken...
119                item['token'] = it.getToken()
120            } else if (it.respondsTo('giveUUID')) {
121                // ...while others implement giveUUID
122                item['token'] = it.giveUUID()
123            } else {
124                // and others don't at all, so far
125                // the consistency...
126                item['id'] = it.id
127            }
128
129            // add subject field values
130            fields.each { field ->
131                // get a camelCased version of the field name
132                def name = field.name.split(camelCaseSeparators).collect {it[0].toUpperCase() + it.substring(1)}.join('')
133                    name = name[0].toLowerCase() + name.substring(1)
134
135                // get the value for this field
136                def value = it.getFieldValue( field.name )
137
138                // add value
139                if (value.hasProperty('name')) {
140                    item[ name ] = value.name
141                } else {
142                    item[ name ] = value
143                }
144            }
145
146            // list hasMany sizes
147            it.properties.hasMany.each { hasManyItem ->
148                if (!ignoreHasManyKeys.contains(hasManyItem.key)) {
149                    // add count for this hasMany item
150                    item[ hasManyItem.key ] = it[ hasManyItem.key ].size()
151                }
152            }
153
154            // add item to resultset
155            items[ items.size() ] = item
156        }
157
158        return items
159    }
160
161    /**
162     * wrapper for performing api calls
163     *
164     * validates if the user may call this api
165     *
166     * @param params
167     * @param response
168     * @param itemName
169     * @param item
170     * @param block
171     */
172    def executeApiCall(params,response,itemName,item,block) {
173        // get variables from parameters
174        String deviceID     = (params.containsKey('deviceID')) ? params.deviceID : ''
175        String validation   = (params.containsKey('validation')) ? params.validation : ''
176
177        // fetch user based on deviceID
178        def user = Token.findByDeviceID(deviceID)?.user
179
180        // check if api call may be performed
181        if (!validateRequest(deviceID,validation)) {
182            // validation md5sum does not match predicted hash
183            response.sendError(401, "Unauthorized")
184        } else if (!item) {
185            // no results, invalid 'item'
186            response.sendError(400, "No such ${itemName}")
187        } else if (item.respondsTo('canRead') && !item.canRead(user)) {
188            // the user cannot read this data
189            response.sendError(401, "Unauthorized")
190        } else if (item.hasProperty('parent') && item.parent.respondsTo('canRead') && !item.parent.canRead(user)) {
191            // the user cannot read this data
192            response.sendError(401, "Unauthorized")
193        } else {
194            // allowed api call, execute block / closure
195            block()
196        }
197    }
198
199    /**
200     * get the measurement tokens from the remote module
201     *
202     * @param assay
203     * @param user
204     * @return
205     */
206    def getMeasurements(Assay assay, SecUser user) {
207        def serviceURL = "${assay.module.url}/rest/getMeasurements"
208        def serviceArguments = "assayToken=${assay.assayUUID}"
209        def json
210
211        // call module method
212        try {
213            json = moduleCommunicationService.callModuleMethod(
214                    assay.module.url,
215                    serviceURL,
216                    serviceArguments,
217                    "POST",
218                    user
219            );
220        } catch (Exception e) {
221            log.error "api.getMeasurements failed :: ${e.getMessage()}"
222            json = new org.codehaus.groovy.grails.web.json.JSONArray()
223        }
224
225        return json
226    }
227
228    /**
229     * get measurement data from the remote module in verbose format
230     *
231     * @param assay
232     * @param user
233     * @return
234     */
235    def getMeasurementData(Assay assay, SecUser user) {
236        def serviceURL = "${assay.module.url}/rest/getMeasurementData"
237        def serviceArguments = "assayToken=${assay.assayUUID}&verbose=true"
238        def json
239
240        // call module method
241        try {
242            json = moduleCommunicationService.callModuleMethod(
243                    assay.module.url,
244                    serviceURL,
245                    serviceArguments,
246                    "POST",
247                    user
248            );
249        } catch (Exception e) {
250            log.error "api.getMeasurementData failed :: ${e.getMessage()}"
251            json = new org.codehaus.groovy.grails.web.json.JSONArray()
252        }
253
254        return json
255    }
256
257    /**
258     * get the measurement meta data from the remote module
259     *
260     * @param assay
261     * @param user
262     * @return
263     */
264    def getMeasurementMetaData(Assay assay, SecUser user) {
265        def serviceURL = "${assay.module.url}/rest/getMeasurementMetaData"
266        def serviceArguments = "assayToken=${assay.assayUUID}"
267        def json
268
269        // call module method
270        try {
271            json = moduleCommunicationService.callModuleMethod(
272                    assay.module.url,
273                    serviceURL,
274                    serviceArguments,
275                    "POST",
276                    user
277            );
278        } catch (Exception e) {
279            log.error "api.getMeasurementMetaData failed :: ${e.getMessage()}"
280            json = new org.codehaus.groovy.grails.web.json.JSONArray()
281        }
282
283        return json
284    }
285}
Note: See TracBrowser for help on using the repository browser.