docs: private <T> SearchResultsBean<T> find(SearchCriteriaBean criteria, String type,
              IUnmarshaller<T> unmarshaller) throws StorageException {
          try {
              SearchResultsBean<T> rval = new SearchResultsBean<>();
  
              // Set some default in the case that paging information was not included in the request.
              PagingBean paging = criteria.getPaging();
              if (paging == null) {
                  paging = new PagingBean();
                  paging.setPage(1);
                  paging.setPageSize(20);
              }
              int page = paging.getPage();
              int pageSize = paging.getPageSize();
              int start = (page - 1) * pageSize;
  
              SearchSourceBuilder builder = new SearchSourceBuilder().size(pageSize).from(start).fetchSource(true);
  
              // Sort order
              OrderByBean orderBy = criteria.getOrderBy();
              if (orderBy != null) {
                  String name = orderBy.getName();
                  if (name.equals("name") || name.equals("fullName")) { //$NON-NLS-1$ //$NON-NLS-2$
                      name += ".raw"; //$NON-NLS-1$
                  }
                  if (orderBy.isAscending()) {
                      builder.sort(name, SortOrder.ASC);
                  } else {
                      builder.sort(name, SortOrder.DESC);
                  }
              }
  
              // Now process the filter criteria
              List<SearchCriteriaFilterBean> filters = criteria.getFilters();
  
              QueryBuilder q = null;
  
              if (filters != null && !filters.isEmpty()) {
                  FilterBuilder andFilter = FilterBuilders.filter();
                  int filterCount = 0;
                  for (SearchCriteriaFilterBean filter : filters) {
                      String propertyName = filter.getName();
                      if (filter.getOperator() == SearchCriteriaFilterOperator.eq) {
                          andFilter.add(FilterBuilders.termFilter(propertyName, filter.getValue()));
                          filterCount++;
                      } else if (filter.getOperator() == SearchCriteriaFilterOperator.like) {
                          q = QueryBuilders.wildcardQuery(propertyName, filter.getValue().toLowerCase().replace('%', '*'));
                      } else if (filter.getOperator() == SearchCriteriaFilterOperator.bool_eq) {
                          andFilter.add(FilterBuilders.termFilter(propertyName, "true".equals(filter.getValue()))); //$NON-NLS-1$
                          filterCount++;
                      }
                      // TODO implement the other filter operators here!
                  }
  
                  if (filterCount > 0) {
                      q = FilterBuilders.boolFilter(andFilter);
                  }
              }
              builder.query(q);
  
              String query = builder.string();
              Search search = new Search.Builder(query)
                      .addIndex(getIndexName())
                      .addType(type)
                      .build();
              SearchResult response = esClient.execute(search);
              @SuppressWarnings({ "unchecked", "rawtypes" })
              List<Hit<Map<String, Object>, Void>> thehits = (List) response.getHits(Map.class);
  
              rval.setTotalSize((int)(long)response.getTotal());
              for (Hit<Map<String,Object>,Void> hit : thehits) {
                  Map<String, Object> sourceAsMap = hit.source;
                  T bean = unmarshaller.unmarshal(sourceAsMap);
                  rval.getBeans().add(bean);
              }
              return rval;
          } catch (Exception e) {
              throw new StorageException(e);
          }
      }