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

openmrs / openmrs-module-webservices.rest / 20369290211

19 Dec 2025 11:56AM UTC coverage: 68.555% (-0.02%) from 68.577%
20369290211

push

github

rkorytkowski
RESTWS-1011: Init resources asynchronously on startup

6 of 13 new or added lines in 3 files covered. (46.15%)

1 existing line in 1 file now uncovered.

9693 of 14139 relevant lines covered (68.56%)

0.69 hits per line

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

15.79
/omod-common/src/main/java/org/openmrs/module/webservices/rest/web/api/impl/RestServiceImpl.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.api.impl;
11

12
import java.io.IOException;
13
import java.util.ArrayList;
14
import java.util.HashMap;
15
import java.util.HashSet;
16
import java.util.Iterator;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Map.Entry;
20
import java.util.Set;
21
import java.util.concurrent.ExecutorService;
22

23
import org.apache.commons.lang.StringUtils;
24
import org.hibernate.proxy.HibernateProxy;
25
import org.openmrs.api.APIException;
26
import org.openmrs.module.ModuleUtil;
27
import org.openmrs.module.webservices.rest.web.OpenmrsClassScanner;
28
import org.openmrs.module.webservices.rest.web.RestConstants;
29
import org.openmrs.module.webservices.rest.web.annotation.SubResource;
30
import org.openmrs.module.webservices.rest.web.api.RestHelperService;
31
import org.openmrs.module.webservices.rest.web.api.RestService;
32
import org.openmrs.module.webservices.rest.web.representation.CustomRepresentation;
33
import org.openmrs.module.webservices.rest.web.representation.NamedRepresentation;
34
import org.openmrs.module.webservices.rest.web.representation.Representation;
35
import org.openmrs.module.webservices.rest.web.resource.api.Resource;
36
import org.openmrs.module.webservices.rest.web.resource.api.SearchConfig;
37
import org.openmrs.module.webservices.rest.web.resource.api.SearchHandler;
38
import org.openmrs.module.webservices.rest.web.resource.api.SearchParameter;
39
import org.openmrs.module.webservices.rest.web.resource.api.SearchQuery;
40
import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingResourceHandler;
41
import org.openmrs.module.webservices.rest.web.resource.impl.DelegatingSubclassHandler;
42
import org.openmrs.module.webservices.rest.web.response.InvalidSearchException;
43
import org.openmrs.module.webservices.rest.web.response.UnknownResourceException;
44
import org.openmrs.util.OpenmrsConstants;
45

46
/**
47
 * Default implementation of the {@link RestService}
48
 */
49
public class RestServiceImpl implements RestService {
50
        
51
        volatile Map<String, ResourceDefinition> resourceDefinitionsByNames;
52
        
53
        volatile Map<Class<?>, Resource> resourcesBySupportedClasses;
54
        
55
        private volatile Map<CompositeSearchHandlerKeyValue, Set<SearchHandler>> searchHandlersByParameter;
56
        
57
        private volatile Map<CompositeSearchHandlerKeyValue, SearchHandler> searchHandlersByIds;
58
        
59
        private volatile Map<String, Set<SearchHandler>> searchHandlersByResource;
60
        
61
        private volatile List<SearchHandler> allSearchHandlers;
62
        
63
        private RestHelperService restHelperService;
64
        
65
        private OpenmrsClassScanner openmrsClassScanner;
66

67
        private ExecutorService executorService;
68
        
69
        public RestHelperService getRestHelperService() {
70
                return restHelperService;
×
71
        }
72
        
73
        public void setRestHelperService(RestHelperService restHelperService) {
74
                this.restHelperService = restHelperService;
1✔
75
        }
1✔
76
        
77
        public OpenmrsClassScanner getOpenmrsClassScanner() {
78
                return openmrsClassScanner;
×
79
        }
80
        
81
        public void setOpenmrsClassScanner(OpenmrsClassScanner openmrsClassScanner) {
82
                this.openmrsClassScanner = openmrsClassScanner;
1✔
83
        }
1✔
84

85
        public ExecutorService getExecutorService() {
NEW
86
                return executorService;
×
87
        }
88

89
        public void setExecutorService(ExecutorService executorService) {
90
                this.executorService = executorService;
1✔
91
        }
1✔
92

93
        public RestServiceImpl() {
1✔
94
        }
1✔
95
        
96
        static class ResourceDefinition {
97
                
98
                public Resource resource;
99
                
100
                public int order;
101
                
102
                public ResourceDefinition(Resource resource, int order) {
×
103
                        this.resource = resource;
×
104
                        this.order = order;
×
105
                }
×
106
                
107
        }
108
        
109
        /**
110
         * Wraps {@code Resource} name and an additional string-based key into a composite key.
111
         */
112
        private static class CompositeSearchHandlerKeyValue {
113
                
114
                public final String supportedResource;
115
                
116
                public final String secondKey;
117
                
118
                public final String secondKeyValue;
119
                
120
                public CompositeSearchHandlerKeyValue(String supportedResource, String additionalKeyProperty) {
×
121
                        this.supportedResource = supportedResource;
×
122
                        this.secondKey = additionalKeyProperty;
×
123
                        this.secondKeyValue = null;
×
124
                }
×
125
                
126
                public CompositeSearchHandlerKeyValue(String supportedResource, String additionalKeyProperty,
127
                    String additionalKeyPropertyValue) {
×
128
                        this.supportedResource = supportedResource;
×
129
                        this.secondKey = additionalKeyProperty;
×
130
                        this.secondKeyValue = additionalKeyPropertyValue;
×
131
                }
×
132
                
133
                @Override
134
                public boolean equals(Object o) {
135
                        if (this == o)
×
136
                                return true;
×
137
                        if (o == null || getClass() != o.getClass())
×
138
                                return false;
×
139
                        
140
                        CompositeSearchHandlerKeyValue that = (CompositeSearchHandlerKeyValue) o;
×
141
                        
142
                        if (!supportedResource.equals(that.supportedResource))
×
143
                                return false;
×
144
                        if (!secondKey.equals(that.secondKey))
×
145
                                return false;
×
146
                        return secondKeyValue != null ? secondKeyValue.equals(that.secondKeyValue) : that.secondKeyValue == null;
×
147
                        
148
                }
149
                
150
                @Override
151
                public int hashCode() {
152
                        int result = supportedResource.hashCode();
×
153
                        result = 31 * result + secondKey.hashCode();
×
154
                        result = 31 * result + (secondKeyValue != null ? secondKeyValue.hashCode() : 0);
×
155
                        return result;
×
156
                }
157
        }
158
        
159
        private void initializeResources() {
160
                if (resourceDefinitionsByNames != null) {
1✔
161
                        return;
1✔
162
                }
163
                
164
                Map<String, ResourceDefinition> tempResourceDefinitionsByNames = new HashMap<String, ResourceDefinition>();
1✔
165
                Map<Class<?>, Resource> tempResourcesBySupportedClasses = new HashMap<Class<?>, Resource>();
1✔
166
                
167
                List<Class<? extends Resource>> resources;
168
                try {
169
                        resources = openmrsClassScanner.getClasses(Resource.class, true);
1✔
170
                }
171
                catch (IOException e) {
×
172
                        throw new APIException("Cannot access REST resources", e);
×
173
                }
1✔
174
                
175
                for (Class<? extends Resource> resource : resources) {
1✔
176
                        ResourceMetadata resourceMetadata = getResourceMetadata(resource);
1✔
177
                        if (resourceMetadata == null)
1✔
178
                                continue;
1✔
179
                        
180
                        if (isResourceToBeAdded(resourceMetadata, tempResourceDefinitionsByNames.get(resourceMetadata.getName()))) {
×
181
                                Resource newResource = newResource(resource);
×
182
                                
183
                                tempResourceDefinitionsByNames.put(resourceMetadata.getName(), new ResourceDefinition(newResource,
×
184
                                        resourceMetadata.getOrder()));
×
185
                                tempResourcesBySupportedClasses.put(resourceMetadata.getSupportedClass(), newResource);
×
186
                        }
187
                }
×
188
                
189
                resourcesBySupportedClasses = tempResourcesBySupportedClasses;
1✔
190
                resourceDefinitionsByNames = tempResourceDefinitionsByNames;
1✔
191
        }
1✔
192
        
193
        /**
194
         * Determines whether a {@code Resource} should be added to the cache.
195
         * 
196
         * @param resourceMetadata the resource metadata of the resource to be added
197
         * @param existingResourceDefinition the resource definition of resource
198
         * @return true if the resource should be added and false otherwise
199
         */
200
        private boolean isResourceToBeAdded(ResourceMetadata resourceMetadata, ResourceDefinition existingResourceDefinition) {
201
                
202
                if (existingResourceDefinition == null) {
×
203
                        return true;
×
204
                }
205
                if (existingResourceDefinition.order == resourceMetadata.getOrder()) {
×
206
                        throw new IllegalStateException("Two resources with the same name (" + resourceMetadata.getName()
×
207
                                + ") must not have the same order");
208
                }
209
                
210
                return existingResourceDefinition.order >= resourceMetadata.getOrder();
×
211
        }
212
        
213
        /**
214
         * Gets {@code ResourceMetadata} from a {@code Resource} classes annotations.
215
         * 
216
         * @param resource the resource to get the metadata from
217
         * @return the metadata of a resource
218
         */
219
        private ResourceMetadata getResourceMetadata(Class<? extends Resource> resource) {
220
                ResourceMetadata resourceMetadata;
221
                
222
                org.openmrs.module.webservices.rest.web.annotation.Resource resourceAnnotation = resource
1✔
223
                        .getAnnotation(org.openmrs.module.webservices.rest.web.annotation.Resource.class);
1✔
224
                if (resourceAnnotation == null) {
1✔
225
                        SubResource subresourceAnnotation = resource.getAnnotation(SubResource.class);
1✔
226
                        if (subresourceAnnotation == null
1✔
227
                                || !isOpenmrsVersionInVersions(subresourceAnnotation.supportedOpenmrsVersions())) {
×
228
                                return null;
1✔
229
                        }
230
                        org.openmrs.module.webservices.rest.web.annotation.Resource parentResourceAnnotation = subresourceAnnotation
×
231
                                .parent().getAnnotation(org.openmrs.module.webservices.rest.web.annotation.Resource.class);
×
232
                        if (parentResourceAnnotation == null) {
×
233
                                return null;
×
234
                        }
235
                        resourceMetadata = new ResourceMetadata(parentResourceAnnotation.name() + "/" + subresourceAnnotation.path(),
×
236
                                subresourceAnnotation.supportedClass(), subresourceAnnotation.order());
×
237
                } else {
×
238
                        if (!isOpenmrsVersionInVersions(resourceAnnotation.supportedOpenmrsVersions())) {
×
239
                                return null;
×
240
                        }
241
                        resourceMetadata = new ResourceMetadata(resourceAnnotation.name(), resourceAnnotation.supportedClass(),
×
242
                                resourceAnnotation.order());
×
243
                }
244
                return resourceMetadata;
×
245
        }
246
        
247
        private static class ResourceMetadata {
248
                
249
                private final String name;
250
                
251
                private final Class<?> supportedClass;
252
                
253
                private final int order;
254
                
255
                public ResourceMetadata(String name, Class<?> supportedClass, int order) {
×
256
                        this.name = name;
×
257
                        this.supportedClass = supportedClass;
×
258
                        this.order = order;
×
259
                }
×
260
                
261
                public String getName() {
262
                        return name;
×
263
                }
264
                
265
                public Class<?> getSupportedClass() {
266
                        return supportedClass;
×
267
                }
268
                
269
                public int getOrder() {
270
                        return order;
×
271
                }
272
        }
273
        
274
        /**
275
         * Checks if OpenMRS version is in given array of versions.
276
         * 
277
         * @param versions the array of versions to be checked for the openmrs version
278
         * @return true if the openmrs version is in versions and false otherwise
279
         */
280
        private boolean isOpenmrsVersionInVersions(String[] versions) {
281
                
282
                if (versions.length == 0) {
×
283
                        return false;
×
284
                }
285
                
286
                boolean result = false;
×
287
                for (String version : versions) {
×
288
                        if (ModuleUtil.matchRequiredVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, version)) {
×
289
                                result = true;
×
290
                                break;
×
291
                        }
292
                }
293
                return result;
×
294
        }
295
        
296
        private void initializeSearchHandlers() {
297
                if (searchHandlersByIds != null) {
×
298
                        return;
×
299
                }
300
                
301
                Map<CompositeSearchHandlerKeyValue, SearchHandler> tempSearchHandlersByIds = new HashMap<CompositeSearchHandlerKeyValue, SearchHandler>();
×
302
                Map<CompositeSearchHandlerKeyValue, Set<SearchHandler>> tempSearchHandlersByParameters = new HashMap<CompositeSearchHandlerKeyValue, Set<SearchHandler>>();
×
303
                Map<String, Set<SearchHandler>> tempSearchHandlersByResource = new HashMap<String, Set<SearchHandler>>();
×
304
                
305
                List<SearchHandler> allSearchHandlers = restHelperService.getRegisteredSearchHandlers();
×
306
                for (SearchHandler searchHandler : allSearchHandlers) {
×
307
                        addSearchHandler(tempSearchHandlersByIds, tempSearchHandlersByParameters, tempSearchHandlersByResource,
×
308
                            searchHandler);
309
                }
×
310
                this.allSearchHandlers = allSearchHandlers;
×
311
                searchHandlersByParameter = tempSearchHandlersByParameters;
×
312
                searchHandlersByIds = tempSearchHandlersByIds;
×
313
                searchHandlersByResource = tempSearchHandlersByResource;
×
314
        }
×
315
        
316
        private void addSearchHandler(Map<CompositeSearchHandlerKeyValue, SearchHandler> tempSearchHandlersByIds,
317
                Map<CompositeSearchHandlerKeyValue, Set<SearchHandler>> tempSearchHandlersByParameters,
318
                Map<String, Set<SearchHandler>> tempSearchHandlersByResource, SearchHandler searchHandler) {
319
                for (String supportedVersion : searchHandler.getSearchConfig().getSupportedOpenmrsVersions()) {
×
320
                        if (ModuleUtil.matchRequiredVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, supportedVersion)) {
×
321
                                addSupportedSearchHandler(tempSearchHandlersByIds, tempSearchHandlersByParameters, searchHandler);
×
322
                                addSearchHandlerToResourceMap(tempSearchHandlersByResource, searchHandler);
×
323
                        }
324
                }
×
325
        }
×
326
        
327
        private void addSupportedSearchHandler(Map<CompositeSearchHandlerKeyValue, SearchHandler> tempSearchHandlersByIds,
328
                Map<CompositeSearchHandlerKeyValue, Set<SearchHandler>> tempSearchHandlersByParameters,
329
                SearchHandler searchHandler) {
330
                CompositeSearchHandlerKeyValue searchHanlderIdKey = new CompositeSearchHandlerKeyValue(searchHandler
×
331
                        .getSearchConfig().getSupportedResource(), searchHandler.getSearchConfig().getId());
×
332
                SearchHandler previousSearchHandler = tempSearchHandlersByIds.put(searchHanlderIdKey, searchHandler);
×
333
                if (previousSearchHandler != null) {
×
334
                        SearchConfig config = searchHandler.getSearchConfig();
×
335
                        throw new IllegalStateException("Two search handlers (" + searchHandler.getClass() + ", "
×
336
                                + previousSearchHandler.getClass() + ") for the same resource (" + config.getSupportedResource()
×
337
                                + ") must not have the same ID (" + config.getId() + ")");
×
338
                }
339
                
340
                addSearchHandlerToParametersMap(tempSearchHandlersByParameters, searchHandler);
×
341
        }
×
342
        
343
        private void addSearchHandlerToParametersMap(
344
                Map<CompositeSearchHandlerKeyValue, Set<SearchHandler>> tempSearchHandlersByParameters,
345
                SearchHandler searchHandler) {
346
                
347
                for (SearchQuery searchQueries : searchHandler.getSearchConfig().getSearchQueries()) {
×
348
                        Set<SearchParameter> parameters = new HashSet<SearchParameter>(searchQueries.getRequiredParameters());
×
349
                        parameters.addAll(searchQueries.getOptionalParameters());
×
350
                        
351
                        for (SearchParameter parameter : parameters) {
×
352
                                CompositeSearchHandlerKeyValue parameterKey = new CompositeSearchHandlerKeyValue(searchHandler
×
353
                                        .getSearchConfig().getSupportedResource(), parameter.getName(), parameter.getValue());
×
354
                                Set<SearchHandler> list = tempSearchHandlersByParameters.get(parameterKey);
×
355
                                if (list == null) {
×
356
                                        list = new HashSet<SearchHandler>();
×
357
                                        tempSearchHandlersByParameters.put(parameterKey, list);
×
358
                                }
359
                                list.add(searchHandler);
×
360
                        }
×
361
                }
×
362
        }
×
363
        
364
        private void addSearchHandlerToResourceMap(Map<String, Set<SearchHandler>> tempSearchHandlersByResource,
365
                SearchHandler searchHandler) {
366
                SearchConfig config = searchHandler.getSearchConfig();
×
367
                Set<SearchHandler> handlers = tempSearchHandlersByResource.get(config.getSupportedResource());
×
368
                if (handlers == null) {
×
369
                        handlers = new HashSet<SearchHandler>();
×
370
                        tempSearchHandlersByResource.put(config.getSupportedResource(), handlers);
×
371
                }
372
                handlers.add(searchHandler);
×
373
        }
×
374
        
375
        /**
376
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getRepresentation(java.lang.String)
377
         * <strong>Should</strong> return default representation if given null
378
         * <strong>Should</strong> return default representation if given string is empty
379
         * <strong>Should</strong> return reference representation if given string matches the ref representation
380
         *         constant
381
         * <strong>Should</strong> return default representation if given string matches the default representation
382
         *         constant
383
         * <strong>Should</strong> return full representation if given string matches the full representation constant
384
         * <strong>Should</strong> return an instance of custom representation if given string starts with the custom
385
         *         representation prefix
386
         * <strong>Should</strong> return an instance of named representation for given string if it is not empty and
387
         *         does not match any other case
388
         */
389
        @Override
390
        public Representation getRepresentation(String requested) {
391
                if (StringUtils.isEmpty(requested)) {
1✔
392
                        return Representation.DEFAULT;
×
393
                }
394
                
395
                if (RestConstants.REPRESENTATION_REF.equals(requested)) {
1✔
396
                        return Representation.REF;
1✔
397
                } else if (RestConstants.REPRESENTATION_DEFAULT.equals(requested)) {
1✔
398
                        return Representation.DEFAULT;
1✔
399
                } else if (RestConstants.REPRESENTATION_FULL.equals(requested)) {
1✔
400
                        return Representation.FULL;
1✔
401
                } else if (requested.startsWith(RestConstants.REPRESENTATION_CUSTOM_PREFIX)) {
1✔
402
                        return new CustomRepresentation(requested.replace(RestConstants.REPRESENTATION_CUSTOM_PREFIX, ""));
×
403
                }
404
                
405
                return new NamedRepresentation(requested);
1✔
406
        }
407
        
408
        /**
409
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getResourceByName(String)
410
         * <strong>Should</strong> return resource for given name
411
         * <strong>Should</strong> return resource for given name and ignore unannotated resources
412
         * <strong>Should</strong> fail if failed to get resource classes
413
         * <strong>Should</strong> fail if resource for given name cannot be found
414
         * <strong>Should</strong> fail if resource for given name does not support the current openmrs version
415
         * <strong>Should</strong> return subresource for given name
416
         * <strong>Should</strong> fail if subresource for given name does not support the current openmrs version
417
         * <strong>Should</strong> fail if two resources with same name and order are found for given name
418
         * <strong>Should</strong> return resource with lower order value if two resources with the same name are found
419
         *         for given name
420
         */
421
        @Override
422
        public Resource getResourceByName(String name) throws APIException {
423
                initializeResources();
×
424
                
425
                ResourceDefinition resourceDefinition = resourceDefinitionsByNames.get(name);
×
426
                if (resourceDefinition == null) {
×
427
                        throw new UnknownResourceException("Unknown resource: " + name);
×
428
                } else {
429
                        return resourceDefinition.resource;
×
430
                }
431
        }
432
        
433
        /**
434
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getResourceBySupportedClass(Class)
435
         * <strong>Should</strong> return resource supporting given class and current openmrs version
436
         * <strong>Should</strong> fail if no resource supporting given class and current openmrs version was found
437
         * <strong>Should</strong> fail if no resource supporting given class was found
438
         * <strong>Should</strong> return resource supporting superclass of given class if given class is a hibernate
439
         *         proxy
440
         * <strong>Should</strong> return resource supporting superclass of given class if no resource supporting given
441
         *         class was found
442
         * <strong>Should</strong> return resource supporting direct superclass of given class if no resource supporting
443
         *         given class was found but multiple resources supporting multiple superclasses exist
444
         * <strong>Should</strong> fail if failed to get resource classes
445
         * <strong>Should</strong> fail if two resources with same name and order are found for given class
446
         * <strong>Should</strong> return resource with lower order value if two resources with the same name are found
447
         *         for given class
448
         */
449
        @Override
450
        public Resource getResourceBySupportedClass(Class<?> resourceClass) throws APIException {
451
                initializeResources();
1✔
452
                
453
                if (HibernateProxy.class.isAssignableFrom(resourceClass)) {
1✔
454
                        resourceClass = resourceClass.getSuperclass();
×
455
                }
456
                
457
                Resource resource = resourcesBySupportedClasses.get(resourceClass);
1✔
458
                
459
                if (resource == null) {
1✔
460
                        Entry<Class<?>, Resource> bestResourceEntry = null;
1✔
461
                        
462
                        for (Entry<Class<?>, Resource> resourceEntry : resourcesBySupportedClasses.entrySet()) {
1✔
463
                                if (resourceEntry.getKey().isAssignableFrom(resourceClass) && (bestResourceEntry == null
×
464
                                        || bestResourceEntry.getKey().isAssignableFrom(resourceEntry.getKey()))) {
×
465
                                        bestResourceEntry = resourceEntry;
×
466
                                }
467
                        }
×
468
                        
469
                        if (bestResourceEntry != null) {
1✔
470
                                resource = bestResourceEntry.getValue();
×
471
                        }
472
                }
473
                
474
                if (resource == null) {
1✔
475
                        throw new APIException("Unknown resource: " + resourceClass);
1✔
476
                } else {
477
                        return resource;
×
478
                }
479
        }
480
        
481
        /**
482
         * @throws InstantiationException
483
         */
484
        private Resource newResource(Class<? extends Resource> resourceClass) {
485
                try {
486
                        return resourceClass.newInstance();
×
487
                }
488
                catch (Exception ex) {
×
489
                        throw new APIException("Failed to instantiate " + resourceClass, ex);
×
490
                }
491
        }
492
        
493
        /**
494
         * Returns a search handler, which supports the given resource and the map of parameters and
495
         * values.
496
         * <p>
497
         * A {@code SearchHandler} is selected according to following steps (in this order):
498
         * <ul>
499
         * <li>Lookup a {@code SearchHandler} based on its {@code id} ({@code SearchConfig#id}) if
500
         * specified in given {@code parameters}. This lookup can fail if no or two
501
         * {@code SearchHandler}'s is/are found for given {@code id} and {@code resourceName}.</li>
502
         * <li>Lookup a {@code SearchHandler} based on given {@code parameters} if no {@code id} is
503
         * specified. The lookup returns the {@code SearcHandler} supporting all requested
504
         * {@code parameters} and with {@code parameters} satisfying the {@code SearchHandler}'s
505
         * {@code SearchConfig}'s required parameters. This lookup can fail if more than 1
506
         * {@code SearchHandler} satisfies the requirements mentioned before.</li>
507
         * </ul>
508
         * If no {@code SearchHandler} is found, {@code NULL} is returned.
509
         * </p>
510
         * 
511
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getSearchHandler(java.lang.String,
512
         *      java.util.Map)
513
         * <strong>Should</strong> return search handler matching id set in given parameters
514
         * <strong>Should</strong> fail if parameters contain a search handler id which cannot be found
515
         * <strong>Should</strong> fail if two search handlers for the same resource have the same id
516
         * <strong>Should</strong> return null if parameters do not contain a search handler id and no other non special
517
         *         request parameters
518
         * <strong>Should</strong> return search handler providing all request parameters and parameters satisfying its
519
         *         required parameters
520
         * <strong>Should</strong> return null if given parameters are missing a parameter required by search handlers
521
         *         eligible for given resource name and parameters
522
         * <strong>Should</strong> return default search handler if two search handlers match given resource and parameters and no search handler
523
         *                id is specified and one of the matching search handlers is the default
524
         * <strong>Should</strong> fail if two search handlers match given resource and parameters and no search handler
525
         *         id is specified and neither matching search hander is the default
526
         * <strong>Should</strong> return null if a non special request parameter in given parameters cannot be found in
527
         *         any search handler
528
         * <strong>Should</strong> return null if no search handler is found for given resource name
529
         * <strong>Should</strong> return null if no search handler is found for current openmrs version
530
         */
531
        @Override
532
        public SearchHandler getSearchHandler(String resourceName, Map<String, String[]> parameters) throws APIException {
533
                initializeSearchHandlers();
×
534
                
535
                Set<SearchParameter> searchParameters = new HashSet<SearchParameter>();
×
536
                
537
                for (Map.Entry<String, String[]> parameter : parameters.entrySet()) {
×
538
                        if (!RestConstants.SPECIAL_REQUEST_PARAMETERS.contains(parameter.getKey())
×
539
                                || RestConstants.REQUEST_PROPERTY_FOR_TYPE.equals(parameter.getKey())) {
×
540
                                searchParameters.add(new SearchParameter(parameter.getKey(), parameter.getValue()[0]));
×
541
                        }
542
                }
×
543
                
544
                String[] searchIds = parameters.get(RestConstants.REQUEST_PROPERTY_FOR_SEARCH_ID);
×
545
                if (searchIds != null && searchIds.length > 0) {
×
546
                        SearchHandler searchHandler = searchHandlersByIds.get(new CompositeSearchHandlerKeyValue(resourceName,
×
547
                                searchIds[0]));
548
                        if (searchHandler == null) {
×
549
                                throw new InvalidSearchException("The search with id '" + searchIds[0] + "' for '" + resourceName
×
550
                                        + "' resource is not recognized");
551
                        } else {
552
                                return searchHandler;
×
553
                        }
554
                }
555
                
556
                Set<SearchHandler> candidateSearchHandlers = null;
×
557
                for (SearchParameter param : searchParameters) {
×
558
                        Set<SearchHandler> searchHandlers = searchHandlersByParameter.get(new CompositeSearchHandlerKeyValue(
×
559
                                resourceName, param.getName(), param.getValue()));
×
560
                        if (searchHandlers == null) {
×
561
                                searchHandlers = searchHandlersByParameter.get(new CompositeSearchHandlerKeyValue(resourceName, param
×
562
                                        .getName()));
×
563
                                if (searchHandlers == null)
×
564
                                        return null; //Missing parameter so there's no handler.
×
565
                        }
566
                        if (candidateSearchHandlers == null) {
×
567
                                candidateSearchHandlers = new HashSet<SearchHandler>();
×
568
                                candidateSearchHandlers.addAll(searchHandlers);
×
569
                        } else {
570
                                //Eliminate candidate search handlers that do not include all parameters
571
                                candidateSearchHandlers.retainAll(searchHandlers);
×
572
                        }
573
                }
×
574
                
575
                if (candidateSearchHandlers == null) {
×
576
                        return null;
×
577
                } else {
578
                        eliminateCandidateSearchHandlersWithMissingRequiredParameters(candidateSearchHandlers, searchParameters);
×
579
                        
580
                        if (candidateSearchHandlers.isEmpty()) {
×
581
                                return null;
×
582
                        } else if (candidateSearchHandlers.size() == 1) {
×
583
                                return candidateSearchHandlers.iterator().next();
×
584
                        }
585
                        // if multiple, return default
586
                        for (SearchHandler candidateSearchHandler : candidateSearchHandlers) {
×
587
                                if ("default".equals(candidateSearchHandler.getSearchConfig().getId())) {
×
588
                                        return candidateSearchHandler;
×
589
                                }
590
                        }
×
591

592
                        // multiple and no default, throw exception
593
                        List<String> candidateSearchHandlerIds = new ArrayList<String>();
×
594
                        for (SearchHandler candidateSearchHandler : candidateSearchHandlers) {
×
595
                                candidateSearchHandlerIds.add(RestConstants.REQUEST_PROPERTY_FOR_SEARCH_ID + "="
×
596
                                                + candidateSearchHandler.getSearchConfig().getId());
×
597
                        }
×
598
                        throw new InvalidSearchException("The search is ambiguous. Please specify "
×
599
                                        + StringUtils.join(candidateSearchHandlerIds, " or "));
×
600

601
                }
602
        }
603
        
604
        /**
605
         * Eliminate search handlers with at least one required parameter that is not provided in
606
         * {@code searchParameters}.
607
         * 
608
         * @param candidateSearchHandlers the search handlers to filter for required parameters
609
         * @param searchParameters the search parameters to be checked against search handlers required
610
         *            parameters
611
         */
612
        private void eliminateCandidateSearchHandlersWithMissingRequiredParameters(Set<SearchHandler> candidateSearchHandlers,
613
                Set<SearchParameter> searchParameters) {
614
                Iterator<SearchHandler> it = candidateSearchHandlers.iterator();
×
615
                while (it.hasNext()) {
×
616
                        SearchHandler candidateSearchHandler = it.next();
×
617
                        boolean remove = true;
×
618
                        
619
                        for (SearchQuery candidateSearchQueries : candidateSearchHandler.getSearchConfig().getSearchQueries()) {
×
620
                                Set<SearchParameter> requiredParameters = new HashSet<SearchParameter>(
×
621
                                        candidateSearchQueries.getRequiredParameters());
×
622
                                
623
                                Iterator<SearchParameter> iterator = requiredParameters.iterator();
×
624
                                while (iterator.hasNext()) {
×
625
                                        SearchParameter requiredParameter = iterator.next();
×
626
                                        for (SearchParameter param : searchParameters) {
×
627
                                                if (requiredParameter.getValue() == null) {
×
628
                                                        if (requiredParameter.getName().equals(param.getName())) {
×
629
                                                                iterator.remove();
×
630
                                                        }
631
                                                } else {
632
                                                        if (requiredParameter.equals(param)) {
×
633
                                                                iterator.remove();
×
634
                                                        }
635
                                                }
636
                                        }
×
637
                                }
×
638
                                if (requiredParameters.isEmpty()) {
×
639
                                        remove = false;
×
640
                                        break;
×
641
                                }
642
                        }
×
643
                        
644
                        if (remove) {
×
645
                                it.remove();
×
646
                        }
647
                }
×
648
        }
×
649
        
650
        /**
651
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getResourceHandlers()
652
         * <strong>Should</strong> return list of delegating resource handlers including subclass handlers
653
         * <strong>Should</strong> return list with delegating resource with lower order value if two resources with the
654
         *         same name are found for given name
655
         * <strong>Should</strong> fail if failed to get resource classes
656
         * <strong>Should</strong> fail if two resources with same name and order are found for a class
657
         */
658
        @Override
659
        public List<DelegatingResourceHandler<?>> getResourceHandlers() throws APIException {
660
                initializeResources();
×
661
                
662
                List<DelegatingResourceHandler<?>> resourceHandlers = new ArrayList<DelegatingResourceHandler<?>>();
×
663
                
664
                for (Resource resource : resourcesBySupportedClasses.values()) {
×
665
                        if (resource instanceof DelegatingResourceHandler) {
×
666
                                resourceHandlers.add((DelegatingResourceHandler<?>) resource);
×
667
                        }
668
                }
×
669
                
670
                List<DelegatingSubclassHandler> subclassHandlers = restHelperService.getRegisteredRegisteredSubclassHandlers();
×
671
                for (DelegatingSubclassHandler subclassHandler : subclassHandlers) {
×
672
                        resourceHandlers.add(subclassHandler);
×
673
                }
×
674
                
675
                return resourceHandlers;
×
676
        }
677
        
678
        /**
679
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getAllSearchHandlers()
680
         * <strong>Should</strong> return all search handlers if search handlers have been initialized
681
         * <strong>Should</strong> return null if search handlers have not been initialized
682
         */
683
        public List<SearchHandler> getAllSearchHandlers() {
684
                
685
                return allSearchHandlers;
×
686
        }
687
        
688
        /**
689
         * @see org.openmrs.module.webservices.rest.web.api.RestService#getSearchHandlers(java.lang.String)
690
         * <strong>Should</strong> return search handlers for given resource name
691
         * <strong>Should</strong> return null if no search handler is found for given resource name
692
         * <strong>Should</strong> return null if no search handler is found for current openmrs version
693
         * <strong>Should</strong> return null given null
694
         * <strong>Should</strong> fail if two search handlers for the same resource have the same id
695
         */
696
        @Override
697
        public Set<SearchHandler> getSearchHandlers(String resourceName) {
698
                if (searchHandlersByResource == null) {
×
699
                        initializeSearchHandlers();
×
700
                }
701
                return searchHandlersByResource.get(resourceName);
×
702
        }
703
        
704
        /**
705
         * @see RestService#initialize()
706
         * <strong>Should</strong> initialize resources and search handlers
707
         * <strong>Should</strong> clear cached resources and search handlers and reinitialize them
708
         * <strong>Should</strong> fail if failed to get resource classes
709
         * <strong>Should</strong> fail if failed to instantiate a resource
710
         * <strong>Should</strong> fail if two resources with same name and order are found
711
         * <strong>Should</strong> fail if two search handlers for the same resource have the same id
712
         */
713
        @Override
714
        public void initialize() {
715
                // first clear out any existing values
UNCOV
716
                resourceDefinitionsByNames = null;
×
717
                resourcesBySupportedClasses = null;
×
718
                searchHandlersByIds = null;
×
719
                searchHandlersByParameter = null;
×
720
                searchHandlersByResource = null;
×
721

722
                initializeResources();
×
723
                initializeSearchHandlers();
×
724
        }
×
725

726
        @Override
727
        public void initializeAsync() {
NEW
728
                RestServiceImpl restService = this;
×
NEW
729
                executorService.submit(new Runnable() {
×
730

731
                        @Override
732
                        public void run() {
NEW
733
                                restService.initialize();
×
NEW
734
                        }
×
735
                });
NEW
736
        }
×
737
}
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