/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.module.webservices.rest.web.resource.impl; import java.lang.reflect.Method; import java.util.List; import org.openmrs.api.OpenmrsService; import org.openmrs.api.context.Context; import org.openmrs.module.webservices.rest.web.RequestContext; import org.openmrs.module.webservices.rest.web.resource.api.Searchable; /** * Helper for {@link Searchable} implementations, which delegates to named service methods that do * paged searching (by String, Integer, Integer) and the matching count of that search. * * @param <T> the generic type of the {@link List} that will be returned by the search method */ public class ServiceSearcher<T> { private Class<? extends OpenmrsService> serviceClass; private String searchMethod; private String countMethod; public ServiceSearcher(Class<? extends OpenmrsService> serviceClass, String searchMethod, String countMethod) { this.serviceClass = serviceClass; this.searchMethod = searchMethod; this.countMethod = countMethod; } /** * Makes service calls to get the count and search results for the given query, and packages * those up as an AlreadyPaged search result * * @param query * @param context * @return */ public AlreadyPaged<T> search(String query, RequestContext context) { OpenmrsService service = Context.getService(serviceClass); List<T> results; Integer count; results = doPagedSearch(service, query, context); count = doCount(service, query, context); boolean hasMore = count > context.getStartIndex() + context.getLimit(); return new AlreadyPaged<T>(context, results, hasMore); } /** * Finds and invokes a search method whose name is given by searchMethod and whose signature * includes one String, two Integers, and any number of boolean or Booleans * * @param service * @param query * @param context * @return */ private List<T> doPagedSearch(OpenmrsService service, String query, RequestContext context) { try { for (Method candidate : serviceClass.getMethods()) { if (candidate.getName().equals(searchMethod) && hasRightParameterTypes(candidate, 1, 2)) return invokePagedSearchMethod(service, candidate, query, context); } } catch (Exception ex) { throw new RuntimeException(searchMethod + " failed", ex); } throw new RuntimeException("Cannot find suitable method"); } /** * Finds and invokes a count method whose name is given by countMethod and whose signature * includes one String, and any number of boolean or Booleans * * @param service * @param query * @param context * @return */ private int doCount(OpenmrsService service, String query, RequestContext context) { try { for (Method candidate : serviceClass.getMethods()) { if (candidate.getName().equals(countMethod) && hasRightParameterTypes(candidate, 1, 0)) { return invokeCountMethod(service, candidate, query, context); } } } catch (Exception ex) { throw new RuntimeException(countMethod + " failed", ex); } throw new RuntimeException("Cannot find suitable method"); } /** * Tests whether the method has the expected number of String and Integer arguments * * @param method * @param expectedStrings * @param expectedIntegers * @return */ private boolean hasRightParameterTypes(Method method, int expectedStrings, int expectedIntegers) { int strings = 0; int integers = 0; for (Class<?> clazz : method.getParameterTypes()) { if (clazz.equals(String.class)) ++strings; else if (clazz.equals(Integer.class)) ++integers; else if (!clazz.equals(boolean.class) && !clazz.equals(Boolean.class)) return false; } return strings == expectedStrings && integers == expectedIntegers; } /** * Invokes a paged search method, using query as its String argument, and the context's * startIndex and limit to the first two Integer arguments * * @param service * @param method * @param query * @param context * @return * @throws Exception */ @SuppressWarnings("unchecked") private List<T> invokePagedSearchMethod(OpenmrsService service, Method method, String query, RequestContext context) throws Exception { Object[] args = new Object[method.getParameterTypes().length]; boolean firstInteger = true; for (int i = 0; i < method.getParameterTypes().length; ++i) { Class<?> clazz = method.getParameterTypes()[i]; if (clazz.equals(String.class)) { args[i] = query; } else if (clazz.equals(Integer.class)) { if (firstInteger) { args[i] = context.getStartIndex(); firstInteger = false; } else { args[i] = context.getLimit(); } } else if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) { args[i] = context.getIncludeAll(); } else { throw new RuntimeException("Method has argument types that are not allowed"); } } return (List<T>) method.invoke(service, args); } /** * Invokes a count method, using query as its String argument * * @param service * @param method * @param query * @param context * @return * @throws Exception */ private int invokeCountMethod(OpenmrsService service, Method method, String query, RequestContext context) throws Exception { Object[] args = new Object[method.getParameterTypes().length]; for (int i = 0; i < method.getParameterTypes().length; ++i) { Class<?> clazz = method.getParameterTypes()[i]; if (clazz.equals(String.class)) { args[i] = query; } else if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) { args[i] = context.getIncludeAll(); } else { throw new RuntimeException("Method has argument types that are not allowed"); } } return (Integer) method.invoke(service, args); } }