• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-module-webservices.rest / 17263834497

27 Aug 2025 10:12AM UTC coverage: 68.44% (-1.5%) from 69.946%
17263834497

push

github

web-flow
RESTWS-988: Update to OpenMRS Platform 2.7.x and Support Java 21 (#670)

* Update to Platform 2.7.x

* Merge 2.5 and 2.7

* Merge 2.4 and 2.7

* Merge omod and omod-2.7

* Update ChangePasswordController1_8

* Move InitPathMatcher2_4 to the correct package

* Address review comments

* Add the licence header

* Revert AnimalResource_1_11

* Update tests

* Update tests

* Update AnimalClassResourceLegacy

* Remove setCurrentOpenMRSVersion method

* Revert formatting changes

* Revert formatting changes

* Revert formatting changes

* Merge omod and commons

* Revert "Merge omod and commons"

This reverts commit c68ef20d3.

* Move RestServiceImplTest to omod

* Remove AnimalClassResourceLegacy.java

* fix doSearch_shouldProcessIncludeAll

* update doSearch_shouldProcessIncludeAll

* update doSearch_shouldProcessIncludeAll

* Use hamcrest in ProviderController2_0Test

10 of 15 new or added lines in 5 files covered. (66.67%)

239 existing lines in 7 files now uncovered.

9572 of 13986 relevant lines covered (68.44%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.17
/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/RestUtil.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.module.webservices.rest.web;
11

12
import org.apache.commons.beanutils.PropertyUtils;
13
import org.apache.commons.lang.StringUtils;
14
import org.apache.commons.lang.exception.ExceptionUtils;
15
import org.apache.commons.logging.Log;
16
import org.apache.commons.logging.LogFactory;
17
import org.openmrs.GlobalProperty;
18
import org.openmrs.OpenmrsData;
19
import org.openmrs.OpenmrsMetadata;
20
import org.openmrs.api.GlobalPropertyListener;
21
import org.openmrs.api.context.Context;
22
import org.openmrs.messagesource.MessageSourceService;
23
import org.openmrs.module.webservices.rest.SimpleObject;
24
import org.openmrs.module.webservices.rest.web.api.RestService;
25
import org.openmrs.module.webservices.rest.web.representation.Representation;
26
import org.openmrs.module.webservices.rest.web.resource.api.Resource;
27
import org.openmrs.module.webservices.rest.web.resource.api.SubResource;
28
import org.openmrs.module.webservices.validation.ValidationException;
29
import org.openmrs.util.OpenmrsClassLoader;
30
import org.openmrs.util.PrivilegeConstants;
31
import org.springframework.validation.FieldError;
32
import org.springframework.validation.ObjectError;
33
import org.springframework.web.bind.ServletRequestBindingException;
34
import org.springframework.web.bind.ServletRequestUtils;
35
import org.springframework.web.bind.annotation.ResponseStatus;
36

37
import javax.servlet.http.HttpServletRequest;
38
import javax.servlet.http.HttpServletResponse;
39
import java.io.File;
40
import java.io.IOException;
41
import java.net.InetAddress;
42
import java.net.URISyntaxException;
43
import java.net.URL;
44
import java.net.UnknownHostException;
45
import java.util.ArrayList;
46
import java.util.Arrays;
47
import java.util.Collection;
48
import java.util.Collections;
49
import java.util.Enumeration;
50
import java.util.LinkedHashMap;
51
import java.util.LinkedHashSet;
52
import java.util.List;
53
import java.util.Set;
54
import java.util.jar.JarEntry;
55
import java.util.jar.JarFile;
56

57
/**
58
 * Convenient helper methods for the Rest Web Services module.
59
 */
60
public class RestUtil implements GlobalPropertyListener {
1✔
61
        
62
        private static Log log = LogFactory.getLog(RestUtil.class);
1✔
63
        
64
        private static boolean contextEnabled = true;
1✔
65

66
        /**
67
         * Returns the global property value with the given name
68
         * @param propertyName the global property to retrieve
69
         * @return the global property value with the given name
70
         */
71
        private static String getGlobalProperty(String propertyName, String defaultValue) {
72
                try {
73
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
74
                        return Context.getAdministrationService().getGlobalProperty(propertyName, defaultValue);
1✔
75
                }
76
                finally {
77
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
78
                }
79
        }
80
        
81
        /**
82
         * Looks up the admin defined global property for the system limit
83
         * 
84
         * @return Integer limit
85
         * @see RestConstants#MAX_RESULTS_DEFAULT_GLOBAL_PROPERTY_NAME
86
         */
87
        public static Integer getDefaultLimit() {
88
                String limit = getGlobalProperty(RestConstants.MAX_RESULTS_DEFAULT_GLOBAL_PROPERTY_NAME, null);
1✔
89
                if (StringUtils.isNotEmpty(limit)) {
1✔
90
                        try {
91
                                return Integer.parseInt(limit);
×
92
                        }
93
                        catch (NumberFormatException nfex) {
×
94
                                log.error(
×
95
                                    RestConstants.MAX_RESULTS_DEFAULT_GLOBAL_PROPERTY_NAME + " must be an integer. " + nfex.getMessage());
×
96
                                return RestConstants.MAX_RESULTS_DEFAULT;
×
97
                        }
98
                } else {
99
                        return RestConstants.MAX_RESULTS_DEFAULT;
1✔
100
                }
101
        }
102
        
103
        /**
104
         * Looks up the admin defined global property for the absolute limit to results of REST calls
105
         * 
106
         * @return Integer limit
107
         * @see RestConstants#MAX_RESULTS_ABSOLUTE_GLOBAL_PROPERTY_NAME
108
         */
109
        public static Integer getAbsoluteLimit() {
110
                String limit = getGlobalProperty(RestConstants.MAX_RESULTS_ABSOLUTE_GLOBAL_PROPERTY_NAME, null);
1✔
111
                if (StringUtils.isNotEmpty(limit)) {
1✔
112
                        try {
113
                                return Integer.parseInt(limit);
×
114
                        }
115
                        catch (NumberFormatException nfex) {
×
116
                                log.error(
×
117
                                    RestConstants.MAX_RESULTS_ABSOLUTE_GLOBAL_PROPERTY_NAME + " must be an integer. " + nfex.getMessage());
×
118
                                return RestConstants.MAX_RESULTS_ABSOLUTE;
×
119
                        }
120
                } else {
121
                        return RestConstants.MAX_RESULTS_ABSOLUTE;
1✔
122
                }
123
        }
124
        
125
        /**
126
         * Tests whether or not a client's IP address is allowed to have access to the REST API (based on a
127
         * admin-settable global property).
128
         * 
129
         * @param ip address of the client
130
         * @return <code>true</code> if client should be allowed access
131
         * @see RestConstants#ALLOWED_IPS_GLOBAL_PROPERTY_NAME
132
         */
133
        public static boolean isIpAllowed(String ip) {
134
                return ipMatches(ip, getAllowedIps());
×
135
        }
136
        
137
        /**
138
         * Tests whether or not there is a match between the given IP address and the candidates.
139
         * 
140
         * @param ip
141
         * @param candidateIps
142
         * @return <code>true</code> if there is a match <strong>Should</strong> return true if list is
143
         *         empty <strong>Should</strong> return false if there is no match <strong>Should</strong>
144
         *         return true for exact match <strong>Should</strong> return true for match with submask
145
         *         <strong>Should</strong> return false if there is no match with submask
146
         *         <strong>Should</strong> return true for exact ipv6 match <strong>Should</strong> throw
147
         *         IllegalArgumentException for invalid mask
148
         */
149
        public static boolean ipMatches(String ip, List<String> candidateIps) {
150
                if (candidateIps.isEmpty()) {
1✔
151
                        return true;
1✔
152
                }
153
                
154
                InetAddress address;
155
                try {
156
                        address = InetAddress.getByName(ip);
1✔
157
                }
158
                catch (UnknownHostException e) {
×
159
                        throw new IllegalArgumentException("Invalid IP in the ip parameter" + ip, e);
×
160
                }
1✔
161
                
162
                for (String candidateIp : candidateIps) {
1✔
163
                        // split IP and mask
164
                        String[] candidateIpPattern = candidateIp.split("/");
1✔
165
                        
166
                        InetAddress candidateAddress;
167
                        try {
168
                                candidateAddress = InetAddress.getByName(candidateIpPattern[0]);
1✔
169
                        }
170
                        catch (UnknownHostException e) {
×
171
                                throw new IllegalArgumentException("Invalid IP in the candidateIps parameter", e);
×
172
                        }
1✔
173
                        
174
                        if (candidateIpPattern.length == 1) { // there's no mask
1✔
175
                                if (address.equals(candidateAddress)) {
1✔
176
                                        return true;
1✔
177
                                }
178
                        } else {
179
                                if (address.getAddress().length != candidateAddress.getAddress().length) {
1✔
180
                                        continue;
×
181
                                }
182
                                
183
                                int bits = Integer.parseInt(candidateIpPattern[1]);
1✔
184
                                if (candidateAddress.getAddress().length < Math.ceil((double) bits / 8)) {
1✔
185
                                        throw new IllegalArgumentException(
1✔
186
                                                "Invalid mask " + bits + " for IP " + candidateIp + " in the candidateIps parameter");
187
                                }
188
                                
189
                                // compare bytes based on the given mask
190
                                boolean matched = true;
1✔
191
                                for (int bytes = 0; bits > 0; bytes++, bits -= 8) {
1✔
192
                                        int mask = 0x000000FF; // mask the entire byte
1✔
193
                                        if (bits < 8) {
1✔
194
                                                // mask only some first bits of a byte
195
                                                mask = (mask << (8 - bits));
1✔
196
                                        }
197
                                        if ((address.getAddress()[bytes] & mask) != (candidateAddress.getAddress()[bytes] & mask)) {
1✔
198
                                                matched = false;
1✔
199
                                                break;
1✔
200
                                        }
201
                                }
202
                                if (matched) {
1✔
203
                                        return true;
1✔
204
                                }
205
                        }
206
                        
207
                }
1✔
208
                return false;
1✔
209
        }
210
        
211
        /**
212
         * Returns a list of IPs which can access the REST API based on a global property. In case the
213
         * property is empty, returns an empty list.
214
         * <p>
215
         * IPs should be separated by a whitespace or a comma. IPs can be declared with bit masks e.g.
216
         * <code>10.0.0.0/30</code> matches <code>10.0.0.0 - 10.0.0.3</code> and <code>10.0.0.0/24</code>
217
         * matches <code>10.0.0.0 - 10.0.0.255</code>.
218
         * 
219
         * @see RestConstants#ALLOWED_IPS_GLOBAL_PROPERTY_NAME
220
         * @return the list of IPs
221
         */
222
        public static List<String> getAllowedIps() {
223
                String allowedIpsProperty = "";
×
224
                try {
225
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
226
                        allowedIpsProperty = getGlobalProperty(RestConstants.ALLOWED_IPS_GLOBAL_PROPERTY_NAME, allowedIpsProperty);
×
227
                }
228
                finally {
229
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
230
                }
231

232
                
233
                if (allowedIpsProperty.isEmpty()) {
×
234
                        return Collections.emptyList();
×
235
                } else {
236
                        String[] allowedIps = allowedIpsProperty.split("[\\s,]+");
×
237
                        return Arrays.asList(allowedIps);
×
238
                }
239
        }
240
        
241
        /*
242
         * TODO - move logic from here to a method to deal with custom
243
         * representations Converts the given <code>openmrsObject</code> into a
244
         * {@link SimpleObject} to be returned to the REST user. <br/>
245
         *
246
         * TODO catch each possible exception in this method and log helpful error
247
         * msgs instead of just having the method throw the generic exception
248
         *
249
         * TODO: change this to use a list of strings for the rep?
250
         *
251
         * @param resource the OpenmrsResource to convert to. If null, looks up the
252
         * resource from the given OpenmrsObject
253
         *
254
         * @param openmrsObject the OpenmrsObject to convert
255
         *
256
         * @param representation the default/full/small/custom (if null, uses
257
         * "default")
258
         *
259
         * @return a SimpleObject (key/value pair mapping) of the object properties
260
         * requested
261
         *
262
         * @throws Exception
263
         *
264
         * public SimpleObject convert(OpenmrsResource resource, OpenmrsObject
265
         * openmrsObject, String representation) throws Exception {
266
         *
267
         * if (representation == null) representation =
268
         * RestConstants.REPRESENTATION_DEFAULT;
269
         *
270
         * if (resource == null) resource =
271
         * HandlerUtil.getPreferredHandler(OpenmrsResource.class,
272
         * openmrsObject.getClass());
273
         *
274
         * // the object to return. adds the default link/display/uuid properties
275
         * SimpleObject simpleObject = new SimpleObject(resource, openmrsObject);
276
         *
277
         * // if they asked for a simple rep, we're done, just return that if
278
         * (RestConstants.REPRESENTATION_REF.equals(representation)) return
279
         * simpleObject;
280
         *
281
         * // get the properties to show on this object String[] propsToInclude =
282
         * getPropsToInclude(resource, representation);
283
         *
284
         * // loop over each prop defined and put it on the simpleObject for (String
285
         * prop : propsToInclude) {
286
         *
287
         * // cut out potential white space around commas prop = prop.trim();
288
         *
289
         * // the property field on the resource of what we're converting Field
290
         * propertyOnResource; try { propertyOnResource =
291
         * resource.getClass().getDeclaredField(prop); } catch (NoSuchFieldException
292
         * e) { // the user requested a field that does not exist on the //
293
         * resource, // so silently skip this log.debug("Skipping field: " + prop +
294
         * " because it does not exist on the " + resource + " resource"); continue;
295
         * }
296
         *
297
         * // the name of the getter methods for this property String getterName =
298
         * "get" + StringUtils.capitalize(prop);
299
         *
300
         * // first check to see if there is a getter defined on the resource, //
301
         * maybe its a custom translation to a string or OpenmrsObject and // we can
302
         * then end early Method getterOnResource = getMethod(resource.getClass(),
303
         * getterName, openmrsObject.getClass());
304
         *
305
         * if (getterOnResource != null) { // e.g. if prop is "name" and a dev
306
         * defined // "personResource.getName(Person)" then we can stop here and //
307
         * just use that method's return value Object returnValue =
308
         * getterOnResource.invoke(resource, openmrsObject);
309
         *
310
         * // turn OpenmrsObjects into Refs if (OpenmrsObject.class
311
         * .isAssignableFrom(returnValue.getClass())) {
312
         *
313
         * String cascadeRep = getCascadeRep(propertyOnResource, representation);
314
         *
315
         * SimpleObject so = convert(openmrsObject, cascadeRep);
316
         * simpleObject.put(prop, so); } else //
317
         * if(String.class.isAssignableFrom(returnValue.getClass())) // everything
318
         * else /should be/ strings. // (what special about Dates, etc?)
319
         * simpleObject.put(prop, returnValue); continue; }
320
         *
321
         * // the user didn't define a getProperty(OpenmrsObject), so we // need to
322
         * find openmrsObject.getProperty() magically by reflection
323
         *
324
         * // get the actual value we'll need to convert on the OpenmrsObject Method
325
         * getterOnObject = openmrsObject.getClass().getMethod( getterName,
326
         * (Class[]) null); Object propValue = getterOnObject.invoke(openmrsObject,
327
         * (Object[]) null);
328
         *
329
         * Class propertyClass = propertyOnResource.getType();
330
         *
331
         * // now convert from OpenmrsObject into this type on the resource if
332
         * (propertyClass.equals(SimpleObject.class)) {
333
         *
334
         * String cascadeRep = getCascadeRep(propertyOnResource, representation);
335
         * SimpleObject subSimpleObject = convert(resource, openmrsObject,
336
         * cascadeRep); simpleObject.put(prop, subSimpleObject); } else if
337
         * (OpenmrsResource.class.isAssignableFrom(propertyClass)) { // the resource
338
         * has a resource property (like AuditInfo) OpenmrsResource openmrsResource
339
         * = (OpenmrsResource) propertyClass .newInstance();
340
         *
341
         * // TODO: if representation just has "prop", assume that means // all
342
         * default properties on the resource
343
         *
344
         * // TODO: if representation has "prop.x, prop.y", assume that // means
345
         * only those properties from the resource // see isCollection else if
346
         * statement for implementation of it // and possibly creating a common
347
         * method for getting the // strippedDownRep
348
         *
349
         * // TODO: else if cascade is one of the standard ones, find the // rep //
350
         * to cascade to String cascadeRep = getCascadeRep(propertyOnResource,
351
         * representation);
352
         *
353
         * SimpleObject subSimpleObject = convert(openmrsResource, openmrsObject,
354
         * cascadeRep); simpleObject.put(prop, subSimpleObject); } else if
355
         * (Reflect.isCollection(propertyClass)) {
356
         *
357
         * // the list put onto the "simpleObject" as a list List<Object>
358
         * listofSimpleObjects = new ArrayList<Object>();
359
         *
360
         * OpenmrsObject collectionContains = isOpenmrsObjectCollection(propValue);
361
         * if (collectionContains != null) { // we have an OpenmrsObject collection
362
         *
363
         * OpenmrsResource collectionResource = HandlerUtil
364
         * .getPreferredHandler(OpenmrsResource.class,
365
         * collectionContains.getClass());
366
         *
367
         * if (representation.contains(prop + ".")) { // recurse on this convert
368
         * method, because the user // asked for something complex by putting in //
369
         * "names.givenName, names.familyName" in the // representation
370
         *
371
         * // TODO: look through the representation and take out // everything but
372
         * "prop.*" strings String strippedDownRep = null; // new String[] { //
373
         * "givenName", // "familyName", // "creator"};
374
         *
375
         * // recurse on this current "convert" method. for (OpenmrsObject o :
376
         * (Collection<OpenmrsObject>) propValue) { convert(collectionResource, o,
377
         * strippedDownRep); } } else if (RestConstants.REPRESENTATION_FULL
378
         * .equals(representation) || RestConstants.REPRESENTATION_MEDIUM
379
         * .equals(representation)) {
380
         *
381
         * String cascadeRep = getCascadeRep(propertyOnResource, representation);
382
         *
383
         * for (OpenmrsObject o : (Collection<OpenmrsObject>) propValue) {
384
         * convert(collectionResource, o, cascadeRep); } } else { // the user didn't
385
         * ask for anything special in the rep, // so they get back lists of ref
386
         * simple objects by // default for (OpenmrsObject o :
387
         * (Collection<OpenmrsObject>) propValue) { // sets uuid/link/display
388
         * SimpleObject listMemberSimpleObject = new SimpleObject(
389
         * collectionResource, o); listofSimpleObjects.add(listMemberSimpleObject);
390
         * } } } else { // we just have a list of java objects, simply put their //
391
         * string values in // TODO how to use conversionservice here? for (Object o
392
         * : (Collection<Object>) propValue) { listofSimpleObjects.add(o); } }
393
         * simpleObject.put(prop, listofSimpleObjects);
394
         *
395
         * } else { // we just have some of java object, put in its toString value
396
         * // TODO use conversionservice? simpleObject.put(prop, propValue); }
397
         *
398
         * }
399
         *
400
         * return simpleObject; }
401
         */
402
        
403
        /*
404
         * Used by code commented out above. Ready for possible deletion.
405
         *
406
         * TODO: look into whether this can use PropertyUtils instead
407
         *
408
         * /** Helper method to use the superclass of param class as well
409
         *
410
         * @param c
411
         *
412
         * @param name
413
         *
414
         * @param param
415
         *
416
         * @return
417
         *
418
         * public Method getMethod(Class<?> c, String name, Class<?> param) {
419
         *
420
         * Method m = null; try { m = c.getMethod(name, param); } catch
421
         * (NoSuchMethodException ex) { // do nothing }
422
         *
423
         * if (m != null) return m;
424
         *
425
         * if (param.getSuperclass() != null) { return getMethod(c, name,
426
         * param.getSuperclass()); }
427
         *
428
         * return null; // throw new NoSuchMethodException("No method on class " + c
429
         * + // " with name " + name + " with param " + param); }
430
         */
431
        
432
        /**
433
         * Determines the request representation, if not provided, uses default. <br/>
434
         * Determines number of results to limit to, if not provided, uses default set by admin. <br/>
435
         * Determines how far into a list to start with given the startIndex param. <br/>
436
         * 
437
         * @param request the current http web request
438
         * @param response the current http web response
439
         * @param defaultView the representation to use if none specified
440
         * @return a {@link RequestContext} object filled with all the necessary values
441
         * @see RestConstants#REQUEST_PROPERTY_FOR_LIMIT
442
         * @see RestConstants#REQUEST_PROPERTY_FOR_REPRESENTATION
443
         * @see RestConstants#REQUEST_PROPERTY_FOR_START_INDEX
444
         * @see RestConstants#REQUEST_PROPERTY_FOR_INCLUDE_ALL
445
         */
446
        public static RequestContext getRequestContext(HttpServletRequest request, HttpServletResponse response,
447
                                                       Representation defaultView) {
448
                if (defaultView == null)
×
449
                        defaultView = Representation.DEFAULT;
×
450
                
451
                RequestContext ret = new RequestContext();
×
452
                ret.setRequest(request);
×
453
                ret.setResponse(response);
×
454
                
455
                // get the "v" param for the representations
456
                String temp = request.getParameter(RestConstants.REQUEST_PROPERTY_FOR_REPRESENTATION);
×
457
                if ("".equals(temp)) {
×
458
                        throw new IllegalArgumentException("?v=(empty string) is not allowed");
×
459
                } else if (temp == null) {
×
460
                        ret.setRepresentation(defaultView);
×
461
                } else if (temp.equals(defaultView.getRepresentation())) {
×
462
                        throw new IllegalArgumentException(
×
463
                                "Do not specify ?v=" + temp + " because it is the default behavior for this request");
464
                } else {
465
                        ret.setRepresentation(Context.getService(RestService.class).getRepresentation(temp));
×
466
                }
467
                
468
                // get the "t" param for subclass-specific requests
469
                temp = request.getParameter(RestConstants.REQUEST_PROPERTY_FOR_TYPE);
×
470
                if ("".equals(temp)) {
×
471
                        throw new IllegalArgumentException(
×
472
                                "?" + RestConstants.REQUEST_PROPERTY_FOR_TYPE + "=(empty string) is not allowed");
473
                } else {
474
                        ret.setType(temp);
×
475
                }
476
                
477
                // fetch the "limit" param
478
                Integer limit = getIntegerParam(request, RestConstants.REQUEST_PROPERTY_FOR_LIMIT);
×
479
                if (limit != null) {
×
480
                        ret.setLimit(limit);
×
481
                }
482
                
483
                // fetch the startIndex param
484
                Integer startIndex = getIntegerParam(request, RestConstants.REQUEST_PROPERTY_FOR_START_INDEX);
×
485
                if (startIndex != null) {
×
486
                        ret.setStartIndex(startIndex);
×
487
                }
488
                
489
                Boolean includeAll = getBooleanParam(request, RestConstants.REQUEST_PROPERTY_FOR_INCLUDE_ALL);
×
490
                if (includeAll != null) {
×
491
                        ret.setIncludeAll(includeAll);
×
492
                }
493
                return ret;
×
494
        }
495
        
496
        /**
497
         * Determines the request representation with Representation.DEFAULT as the default view.
498
         * 
499
         * @param request the current http web request
500
         * @param response the current http web response
501
         * @return a {@link RequestContext} object filled with all the necessary values
502
         * @see getRequestContext(javax.servlet.http.HttpServletRequest,
503
         *      org.openmrs.module.webservices.rest.web.representation.Representation)
504
         */
505
        public static RequestContext getRequestContext(HttpServletRequest request, HttpServletResponse response) {
506
                return getRequestContext(request, response, Representation.DEFAULT);
×
507
        }
508
        
509
        /**
510
         * Convenience method to get the given param out of the given request.
511
         * 
512
         * @param request the WebRequest to look in
513
         * @param param the string name to fetch
514
         * @return null if the param doesn't exist or is not a valid integer
515
         */
516
        private static Integer getIntegerParam(HttpServletRequest request, String param) {
517
                String paramString = request.getParameter(param);
×
518
                
519
                if (paramString != null) {
×
520
                        try {
521
                                return new Integer(paramString);// return the valid value
×
522
                        }
523
                        catch (NumberFormatException e) {
×
524
                                log.debug("unable to parse '" + param + "' parameter into a valid integer: " + paramString);
×
525
                        }
526
                }
527
                
528
                return null;
×
529
        }
530
        
531
        /**
532
         * Convenience method to get the given param out of the given request as a boolean.
533
         * 
534
         * @param request the WebRequest to look in
535
         * @param param the string name to fetch
536
         * @return <code>true</code> if the param is equal to 'true', <code>false</code> for any empty
537
         *         value, null value, or not equal to 'true', or missing param. <strong>Should</strong>
538
         *         return true only if request param is 'true'
539
         */
540
        public static Boolean getBooleanParam(HttpServletRequest request, String param) {
541
                try {
542
                        return ServletRequestUtils.getBooleanParameter(request, param);
1✔
543
                }
544
                catch (ServletRequestBindingException e) {
×
545
                        return false;
×
546
                }
547
        }
548
        
549
        /**
550
         * Sets the HTTP status on the response according to the exception
551
         * 
552
         * @param ex
553
         * @param response
554
         */
555
        public static void setResponseStatus(Throwable ex, HttpServletResponse response) {
556
                ResponseStatus ann = ex.getClass().getAnnotation(ResponseStatus.class);
×
557
                if (ann != null) {
×
558
                        if (StringUtils.isNotBlank(ann.reason())) {
×
559
                                response.setStatus(ann.value().value(), ann.reason());
×
560
                        } else {
561
                                response.setStatus(ann.value().value());
×
562
                        }
563
                } else {
564
                        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
×
565
                }
566
        }
×
567
        
568
        /**
569
         * Sets the HTTP status on the response to no content, and returns an empty value, suitable for
570
         * returning from a @ResponseBody annotated Spring controller method.
571
         * 
572
         * @param response
573
         * @return
574
         */
575
        public static Object noContent(HttpServletResponse response) {
576
                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
×
577
                return "";
×
578
        }
579
        
580
        /**
581
         * Sets the HTTP status for CREATED and (if 'created' has a uri) the Location header attribute
582
         * 
583
         * @param response
584
         * @param created
585
         * @return the object passed in
586
         */
587
        public static Object created(HttpServletResponse response, Object created) {
588
                response.setStatus(HttpServletResponse.SC_CREATED);
×
589
                try {
590
                        String uri = (String) PropertyUtils.getProperty(created, "uri");
×
591
                        response.addHeader("Location", uri);
×
592
                }
593
                catch (Exception ex) {}
×
594
                return created;
×
595
        }
596
        
597
        /**
598
         * Sets the HTTP status for UPDATED and (if 'updated' has a uri) the Location header attribute
599
         * 
600
         * @param response
601
         * @param updated
602
         * @return the object passed in
603
         */
604
        public static Object updated(HttpServletResponse response, Object updated) {
605
                response.setStatus(HttpServletResponse.SC_OK);
×
606
                try {
607
                        String uri = (String) PropertyUtils.getProperty(updated, "uri");
×
608
                        response.addHeader("Location", uri);
×
609
                }
610
                catch (Exception ex) {}
×
611
                return updated;
×
612
        }
613
        
614
        /**
615
         * Updates the Uri prefix through which clients consuming web services will connect to the web app
616
         * 
617
         * @return the webapp's Url prefix
618
         */
619
        public static void setUriPrefix() {
620
                if (contextEnabled) {
1✔
621
                        RestConstants.URI_PREFIX = getGlobalProperty(RestConstants.URI_PREFIX_GLOBAL_PROPERTY_NAME, null);
1✔
622
                }
623
                
624
                if (StringUtils.isBlank(RestConstants.URI_PREFIX)) {
1✔
625
                        RestConstants.URI_PREFIX = "";
1✔
626
                }
627
                
628
                // append the trailing slash in case the user forgot it
629
                if (!RestConstants.URI_PREFIX.endsWith("/")) {
1✔
630
                        RestConstants.URI_PREFIX += "/";
1✔
631
                }
632
                
633
                RestConstants.URI_PREFIX = RestConstants.URI_PREFIX + "ws/rest/";
1✔
634
        }
1✔
635
        
636
        /**
637
         * It allows to disable calls to Context. It should be used in TESTS ONLY.
638
         */
639
        public static void disableContext() {
UNCOV
640
                contextEnabled = false;
×
UNCOV
641
        }
×
642
        
643
        /**
644
         * A Set is returned by removing voided data from passed Collection. The Collection passed as
645
         * parameter is not modified
646
         * 
647
         * @param input collection of OpenmrsData
648
         * @return non-voided OpenmrsData
649
         */
650
        public static <D extends OpenmrsData, C extends Collection<D>> Set<D> removeVoidedData(C input) {
651
                Set<D> data = new LinkedHashSet<D>();
×
652
                for (D d : input) {
×
653
                        if (!d.isVoided()) {
×
654
                                data.add(d);
×
655
                        }
656
                }
×
657
                return data;
×
658
        }
659
        
660
        /**
661
         * A Set is returned by removing retired data from passed Collection. The Collection passed as
662
         * parameter is not modified
663
         * 
664
         * @param input collection of OpenmrsMetadata
665
         * @return non-retired OpenmrsMetaData
666
         */
667
        public static <M extends OpenmrsMetadata, C extends Collection<M>> Set<M> removeRetiredData(C input) {
668
                Set<M> data = new LinkedHashSet<M>();
×
669
                for (M m : input) {
×
670
                        if (!m.isRetired()) {
×
671
                                data.add(m);
×
672
                        }
673
                }
×
674
                return data;
×
675
        }
676
        
677
        /**
678
         * @see org.openmrs.api.GlobalPropertyListener#supportsPropertyName(java.lang.String)
679
         */
680
        @Override
681
        public boolean supportsPropertyName(String propertyName) {
682
                return propertyName.equals(RestConstants.URI_PREFIX_GLOBAL_PROPERTY_NAME);
1✔
683
        }
684
        
685
        /**
686
         * @see org.openmrs.api.GlobalPropertyListener#globalPropertyChanged(org.openmrs.GlobalProperty)
687
         */
688
        @Override
689
        public void globalPropertyChanged(GlobalProperty newValue) {
690
                setUriPrefix();
×
691
        }
×
692
        
693
        /**
694
         * @see org.openmrs.api.GlobalPropertyListener#globalPropertyDeleted(java.lang.String)
695
         */
696
        @Override
697
        public void globalPropertyDeleted(String propertyName) {
698
                setUriPrefix();
×
699
        }
×
700
        
701
        /**
702
         * Inspects the cause chain for the given throwable, looking for an exception of the given class
703
         * (e.g. to find an APIAuthenticationException wrapped in an InvocationTargetException)
704
         * 
705
         * @param throwable
706
         * @param causeClassToLookFor
707
         * @return whether any exception in the cause chain of throwable is an instance of
708
         *         causeClassToLookFor
709
         */
710
        public static boolean hasCause(Throwable throwable, Class<? extends Throwable> causeClassToLookFor) {
711
                return ExceptionUtils.indexOfType(throwable, causeClassToLookFor) >= 0;
×
712
        }
713
        
714
        /**
715
         * Gets a list of classes in a given package. Note that interfaces are not returned.
716
         * 
717
         * @param pkgname the package name.
718
         * @param suffix the ending text on name. eg "Resource.class"
719
         * @return the list of classes.
720
         */
721
        public static ArrayList<Class<?>> getClassesForPackage(String pkgname, String suffix) throws IOException {
722
                ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
×
723
                
724
                //Get a File object for the package
725
                File directory = null;
×
726
                String relPath = pkgname.replace('.', '/');
×
727
                Enumeration<URL> resources = OpenmrsClassLoader.getInstance().getResources(relPath);
×
728
                while (resources.hasMoreElements()) {
×
729
                        
730
                        URL resource = resources.nextElement();
×
731
                        if (resource == null) {
×
732
                                throw new RuntimeException("No resource for " + relPath);
×
733
                        }
734
                        
735
                        try {
736
                                directory = new File(resource.toURI());
×
737
                        }
738
                        catch (URISyntaxException e) {
×
739
                                throw new RuntimeException(
×
740
                                        pkgname + " (" + resource
741
                                                + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...",
742
                                        e);
743
                        }
744
                        catch (IllegalArgumentException ex) {}
×
745
                        
746
                        //If folder exists, look for all resource class files in it.
747
                        if (directory != null && directory.exists()) {
×
748
                                
749
                                //Get the list of the files contained in the package
750
                                String[] files = directory.list();
×
751
                                
752
                                for (int i = 0; i < files.length; i++) {
×
753
                                        
754
                                        //We are only interested in Resource.class files
755
                                        if (files[i].endsWith(suffix)) {
×
756
                                                
757
                                                //Remove the .class extension
758
                                                String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
×
759
                                                
760
                                                try {
761
                                                        Class<?> cls = Class.forName(className);
×
762
                                                        if (!cls.isInterface())
×
763
                                                                classes.add(cls);
×
764
                                                }
765
                                                catch (ClassNotFoundException e) {
×
766
                                                        throw new RuntimeException("ClassNotFoundException loading " + className);
×
767
                                                }
×
768
                                        }
769
                                }
770
                        } else {
×
771
                                
772
                                //Directory does not exist, look in jar file.
773
                                JarFile jarFile = null;
×
774
                                try {
775
                                        String fullPath = resource.getFile();
×
776
                                        String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", "");
×
777
                                        jarFile = new JarFile(jarPath);
×
778
                                        
779
                                        Enumeration<JarEntry> entries = jarFile.entries();
×
780
                                        while (entries.hasMoreElements()) {
×
781
                                                JarEntry entry = entries.nextElement();
×
782
                                                
783
                                                String entryName = entry.getName();
×
784
                                                
785
                                                if (!entryName.endsWith(suffix))
×
786
                                                        continue;
×
787
                                                
788
                                                if (entryName.startsWith(relPath) && entryName.length() > (relPath.length() + "/".length())) {
×
789
                                                        String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", "");
×
790
                                                        
791
                                                        try {
792
                                                                Class<?> cls = Class.forName(className);
×
793
                                                                if (!cls.isInterface())
×
794
                                                                        classes.add(cls);
×
795
                                                        }
796
                                                        catch (ClassNotFoundException e) {
×
797
                                                                throw new RuntimeException("ClassNotFoundException loading " + className);
×
798
                                                        }
×
799
                                                }
800
                                        }
×
801
                                }
802
                                catch (IOException e) {
×
803
                                        throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
×
804
                                }
805
                                finally {
806
                                        if (jarFile != null) {
×
807
                                                jarFile.close();
×
808
                                        }
809
                                }
810
                        }
811
                }
×
812
                
813
                return classes;
×
814
        }
815
        
816
        /**
817
         * Wraps the exception message as a SimpleObject to be sent to client
818
         * 
819
         * @param ex
820
         * @param reason
821
         * @return
822
         */
823
        public static SimpleObject wrapErrorResponse(Exception ex, String reason) {
824
                
825
                String message = ex.getMessage();
1✔
826
                Throwable cause = ex.getCause();
1✔
827
                while (cause != null) {
1✔
828
                        String msg = cause.getMessage();
×
829
                        if (StringUtils.isNotBlank(msg)) {
×
830
                                if (StringUtils.isNotBlank(message)) {
×
831
                                        message += " => ";
×
832
                                }
833
                                else {
834
                                        message = "";
×
835
                                }
836
                                message += msg;
×
837
                        }
838
                        cause = cause.getCause();
×
839
                }
×
840
                
841
                LinkedHashMap map = new LinkedHashMap();
1✔
842
                if (reason != null && !reason.isEmpty()) {
1✔
843
                        map.put("message", reason + " [" + message + "]");
1✔
844
                } else {
845
                        map.put("message", "[" + message + "]");
1✔
846
                }
847
                StackTraceElement[] stackTraceElements = ex.getStackTrace();
1✔
848
                if (stackTraceElements.length > 0) {
1✔
849
                        StackTraceElement stackTraceElement = ex.getStackTrace()[0];
1✔
850
                        String stackTraceDetailsEnabledGp = null;
1✔
851
                        try {
852
                                Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
853
                                stackTraceDetailsEnabledGp = getGlobalProperty(RestConstants.ENABLE_STACK_TRACE_DETAILS_GLOBAL_PROPERTY_NAME, "false");
1✔
854
                        }
855
                        finally {
856
                                Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
857
                        }
858
                        map.put("code", stackTraceElement.getClassName() + ":" + stackTraceElement.getLineNumber());
1✔
859
                        if ("true".equalsIgnoreCase(stackTraceDetailsEnabledGp)) {
1✔
860
                                map.put("detail", ExceptionUtils.getStackTrace(ex));
1✔
861
                        } else {
862
                                map.put("detail", "");
1✔
863
                        }
864
                } else {
1✔
865
                        map.put("code", "");
1✔
866
                        map.put("detail", "");
1✔
867
                }
868
                return new SimpleObject().add("error", map);
1✔
869
        }
870
        
871
        /**
872
         * Creates a SimpleObject to sent to the client with all validation errors (with message codes
873
         * resolved)
874
         * 
875
         * @param ex
876
         * @return
877
         */
878
        public static SimpleObject wrapValidationErrorResponse(ValidationException ex) {
879
                
880
                MessageSourceService messageSourceService = Context.getMessageSourceService();
1✔
881
                
882
                SimpleObject errors = new SimpleObject();
1✔
883
                errors.add("message", messageSourceService.getMessage("webservices.rest.error.invalid.submission"));
1✔
884
                errors.add("code", "webservices.rest.error.invalid.submission");
1✔
885
                
886
                List<SimpleObject> globalErrors = new ArrayList<SimpleObject>();
1✔
887
                SimpleObject fieldErrors = new SimpleObject();
1✔
888
                
889
                if (ex.getErrors().hasGlobalErrors()) {
1✔
890
                        
891
                        for (Object errObj : ex.getErrors().getGlobalErrors()) {
1✔
892
                                
893
                                ObjectError err = (ObjectError) errObj;
1✔
894
                                String message = messageSourceService.getMessage(err.getCode());
1✔
895
                                
896
                                SimpleObject globalError = new SimpleObject();
1✔
897
                                globalError.put("code", err.getCode());
1✔
898
                                globalError.put("message", message);
1✔
899
                                globalErrors.add(globalError);
1✔
900
                        }
1✔
901
                        
902
                }
903
                
904
                if (ex.getErrors().hasFieldErrors()) {
1✔
905
                        
906
                        for (Object errObj : ex.getErrors().getFieldErrors()) {
1✔
907
                                FieldError err = (FieldError) errObj;
1✔
908
                                String message = messageSourceService.getMessage(err.getCode());
1✔
909
                                
910
                                SimpleObject fieldError = new SimpleObject();
1✔
911
                                fieldError.put("code", err.getCode());
1✔
912
                                fieldError.put("message", message);
1✔
913
                                
914
                                if (!fieldErrors.containsKey(err.getField())) {
1✔
915
                                        fieldErrors.put(err.getField(), new ArrayList<SimpleObject>());
1✔
916
                                }
917
                                
918
                                ((List<SimpleObject>) fieldErrors.get(err.getField())).add(fieldError);
1✔
919
                        }
1✔
920
                        
921
                }
922
                
923
                errors.put("globalErrors", globalErrors);
1✔
924
                errors.put("fieldErrors", fieldErrors);
1✔
925
                
926
                return new SimpleObject().add("error", errors);
1✔
927
        }
928
        
929
        /**
930
         * Gets the supported type for the specified resource object
931
         *
932
         * @param resource the resource object whose supported type to look up
933
         * @return the supported class object
934
         */
935
        public static Class<?> getSupportedClass(Resource resource) {
936
                Class<? extends Resource> resourceClass = resource.getClass();
×
937
                if (resource instanceof SubResource) {
×
938
                        return resourceClass.getAnnotation(org.openmrs.module.webservices.rest.web.annotation.SubResource.class)
×
939
                                .supportedClass();
×
940
                } else {
941
                        return resourceClass.getAnnotation(org.openmrs.module.webservices.rest.web.annotation.Resource.class)
×
942
                                .supportedClass();
×
943
                }
944
        }
945

946
        public static boolean isValidUuid(String uuid) {
947
                return uuid != null && (uuid.length() == 36 || uuid.length() == 38 || uuid.indexOf(' ') < 0 || uuid.indexOf('.') < 0);
×
948
        }
949
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc