001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.ws.server.provider; 015 016import org.gbif.api.model.checklistbank.search.NameUsageSearchRequest.NameUsageQueryField; 017import org.gbif.api.model.common.search.SearchParameter; 018import org.gbif.api.model.common.search.SearchRequest; 019import org.gbif.api.model.common.search.SearchRequest.QueryField; 020import org.gbif.api.util.SearchTypeValidator; 021import org.gbif.api.util.VocabularyUtils; 022import org.gbif.ws.CommonRuntimeException; 023 024import java.lang.reflect.InvocationTargetException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Objects; 032import java.util.stream.Collectors; 033 034import org.apache.commons.lang3.StringUtils; 035import org.springframework.web.context.request.WebRequest; 036 037import static org.gbif.ws.util.CommonWsUtils.getFirst; 038import static org.gbif.ws.util.WebserviceParameter.PARAM_HIGHLIGHT; 039import static org.gbif.ws.util.WebserviceParameter.PARAM_QUERY_FIELD; 040import static org.gbif.ws.util.WebserviceParameter.PARAM_QUERY_STRING; 041import static org.gbif.ws.util.WebserviceParameter.PARAM_SPELLCHECK; 042import static org.gbif.ws.util.WebserviceParameter.PARAM_SPELLCHECK_COUNT; 043 044/** 045 * Provider class that transforms a set of HTTP parameters into a SearchRequest class instance. 046 * This assumes the existence of the following parameters in the HTTP request: 047 * 'page_size', 'offset', 'q' and any of the search parameter enum member names case insensitively. 048 */ 049public class SearchRequestProvider<RT extends SearchRequest<P>, P extends Enum<?> & SearchParameter> 050 implements ContextProvider<RT> { 051 052 private static final int MAX_PAGE_SIZE = 1000; 053 private static final int NON_SPELL_CHECK_COUNT = -1; 054 055 private final Class<P> searchParameterClass; 056 private final Class<RT> requestType; 057 private final Integer maxPageSize; 058 059 public SearchRequestProvider(Class<RT> requestType, Class<P> searchParameterClass) { 060 this.requestType = requestType; 061 this.searchParameterClass = searchParameterClass; 062 this.maxPageSize = MAX_PAGE_SIZE; 063 } 064 065 public SearchRequestProvider( 066 Class<RT> requestType, Class<P> searchParameterClass, Integer maxPageSize) { 067 this.requestType = requestType; 068 this.searchParameterClass = searchParameterClass; 069 this.maxPageSize = maxPageSize; 070 } 071 072 @Override 073 public RT getValue(WebRequest webRequest) { 074 try { 075 RT req = requestType.getDeclaredConstructor().newInstance(); 076 return getSearchRequest(webRequest, req); 077 } catch (InstantiationException 078 | IllegalAccessException 079 | NoSuchMethodException 080 | InvocationTargetException e) { 081 // should never happen 082 throw new CommonRuntimeException(e); 083 } 084 } 085 086 protected P findSearchParam(String name) { 087 try { 088 return VocabularyUtils.lookupEnum(name, searchParameterClass); 089 } catch (IllegalArgumentException e) { 090 // we have all params here, not only the enum ones, so this is ok to end up here a few times 091 } 092 return null; 093 } 094 095 protected RT getSearchRequest(WebRequest webRequest, RT searchRequest) { 096 searchRequest.copyPagingValues(PageableProvider.getPagingRequest(webRequest, maxPageSize)); 097 098 final Map<String, String[]> params = webRequest.getParameterMap(); 099 100 getSearchRequestFromQueryParams(searchRequest, params); 101 102 return searchRequest; 103 } 104 105 /** 106 * Override this method for populating specific search/suggest requests 107 */ 108 protected void getSearchRequestFromQueryParams( 109 RT searchRequest, final Map<String, String[]> params) { 110 final String q = getFirst(params, PARAM_QUERY_STRING); 111 final String highlightValue = getFirst(params, PARAM_HIGHLIGHT); 112 final String spellCheck = getFirst(params, PARAM_SPELLCHECK); 113 final String spellCheckCount = getFirst(params, PARAM_SPELLCHECK_COUNT); 114 115 if (StringUtils.isNotEmpty(q)) { 116 searchRequest.setQ(q); 117 } 118 119 if (StringUtils.isNotEmpty(highlightValue)) { 120 searchRequest.setHighlight(Boolean.parseBoolean(highlightValue)); 121 } 122 123 if (StringUtils.isNotEmpty(spellCheck)) { 124 searchRequest.setSpellCheck(Boolean.parseBoolean(spellCheck)); 125 } 126 127 if (StringUtils.isNotEmpty(spellCheckCount)) { 128 searchRequest.setSpellCheckCount(Integer.parseInt(spellCheckCount)); 129 } else { 130 searchRequest.setSpellCheckCount(NON_SPELL_CHECK_COUNT); 131 } 132 133 if (params.get(PARAM_QUERY_FIELD) != null) { 134 searchRequest.setQFields( 135 Arrays.stream(params.get(PARAM_QUERY_FIELD)) 136 .map(SearchRequestProvider::parseQField) 137 .filter(Objects::nonNull) 138 .collect(Collectors.toSet())); 139 } 140 141 // find search parameter enum based filters 142 setSearchParams(searchRequest, params); 143 } 144 145 /** 146 * Removes all empty and null parameters from the list. 147 * Each value is trimmed(String.trim()) in order to remove all sizes of empty parameters. 148 */ 149 private static List<String> removeEmptyParameters(List<String> parameters) { 150 List<String> cleanParameters = new ArrayList<>(parameters.size()); 151 for (String param : parameters) { 152 String cleanParam = StringUtils.trimToEmpty(param); 153 if (!cleanParam.isEmpty()) { 154 cleanParameters.add(cleanParam); 155 } 156 } 157 return cleanParameters; 158 } 159 160 /** 161 * Iterates over the params map and adds to the search request the recognized parameters (i.e.: those that have a 162 * correspondent value in the P generic parameter). 163 * Empty (of all size) and null parameters are discarded. 164 */ 165 private void setSearchParams(RT searchRequest, Map<String, String[]> params) { 166 for (Entry<String, String[]> entry : params.entrySet()) { 167 P p = findSearchParam(entry.getKey()); 168 if (p != null) { 169 final List<String> list = 170 entry.getValue() != null ? Arrays.asList(entry.getValue()) : Collections.emptyList(); 171 for (String val : removeEmptyParameters(list)) { 172 // validate value for certain types 173 SearchTypeValidator.validate(p, val); 174 searchRequest.addParameter(p, val); 175 } 176 } 177 } 178 } 179 180 private static QueryField parseQField(String qField) { 181 try { 182 return NameUsageQueryField.valueOf(qField); 183 } catch (Exception ex) { 184 // do nothing 185 } 186 return null; 187 } 188}