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

openmrs / openmrs-module-webservices.rest / 15182232555

22 May 2025 08:51AM UTC coverage: 48.281% (-0.01%) from 48.295%
15182232555

push

github

web-flow
Testing release notes

6965 of 14426 relevant lines covered (48.28%)

0.48 hits per line

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

38.2
/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 java.io.File;
13
import java.io.IOException;
14
import java.net.InetAddress;
15
import java.net.URISyntaxException;
16
import java.net.URL;
17
import java.net.UnknownHostException;
18
import java.util.ArrayList;
19
import java.util.Arrays;
20
import java.util.Collection;
21
import java.util.Collections;
22
import java.util.Enumeration;
23
import java.util.LinkedHashMap;
24
import java.util.LinkedHashSet;
25
import java.util.List;
26
import java.util.Set;
27
import java.util.jar.JarEntry;
28
import java.util.jar.JarFile;
29
import java.util.regex.Pattern;
30

31
import javax.servlet.http.HttpServletRequest;
32
import javax.servlet.http.HttpServletResponse;
33

34
import org.apache.commons.beanutils.PropertyUtils;
35
import org.apache.commons.lang.StringUtils;
36
import org.apache.commons.lang.exception.ExceptionUtils;
37
import org.apache.commons.logging.Log;
38
import org.apache.commons.logging.LogFactory;
39
import org.openmrs.GlobalProperty;
40
import org.openmrs.OpenmrsData;
41
import org.openmrs.OpenmrsMetadata;
42
import org.openmrs.api.GlobalPropertyListener;
43
import org.openmrs.api.context.Context;
44
import org.openmrs.messagesource.MessageSourceService;
45
import org.openmrs.module.webservices.rest.SimpleObject;
46
import org.openmrs.module.webservices.rest.web.api.RestService;
47
import org.openmrs.module.webservices.rest.web.representation.Representation;
48
import org.openmrs.module.webservices.rest.web.resource.api.Resource;
49
import org.openmrs.module.webservices.rest.web.resource.api.SubResource;
50
import org.openmrs.module.webservices.validation.ValidationException;
51
import org.openmrs.util.OpenmrsClassLoader;
52
import org.openmrs.util.PrivilegeConstants;
53
import org.springframework.validation.FieldError;
54
import org.springframework.validation.ObjectError;
55
import org.springframework.web.bind.ServletRequestBindingException;
56
import org.springframework.web.bind.ServletRequestUtils;
57
import org.springframework.web.bind.annotation.ResponseStatus;
58
import org.springframework.web.context.request.WebRequest;
59

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

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

941
        public static boolean isValidUuid(String uuid) {
942
                return uuid != null && (uuid.length() == 36 || uuid.length() == 38 || uuid.indexOf(' ') < 0 || uuid.indexOf('.') < 0);
×
943
        }
944
}
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