/*
* JBoss, Home of Professional Open Source
* Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
*/
package org.searchisko.api.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import javax.ws.rs.core.MultivaluedMap;
import org.elasticsearch.common.joda.time.format.ISODateTimeFormat;
import org.searchisko.api.model.QuerySettings;
import org.searchisko.api.model.SortByValue;
import org.searchisko.api.service.SearchService;
/**
* Search Query parameters parser component.
*
* @author Libor Krzyzanek
* @author Lukas Vlcek
* @author Vlastimil Elias (velias at redhat dot com)
*/
@Named
@ApplicationScoped
@Singleton
@Lock(LockType.READ)
public class QuerySettingsParser {
private final static Logger log = Logger.getLogger(QuerySettingsParser.class.getName());
/**
* Parse REST parameters, validate them, sanity them, and store into {@link QuerySettings} bean.
*
* @param params REST request params to parse
* @return query settings instance filled with valid search settings (never null)
* @throws IllegalArgumentException if some param has invalid value. Message from exception contains parameter name
* and is used for error handling later!
*/
public QuerySettings parseUriParams(final MultivaluedMap<String, String> params) throws IllegalArgumentException {
QuerySettings settings = new QuerySettings();
if (params == null) {
settings.getFiltersInit();
return settings;
}
// Make copy of all param keys. Remove key from this copy each time a particular param is processed.
// The idea is to process the defined parameters first (and remove relevant keys) and then process
// the rest, where the rest can match configured filters.
Set<String> paramKeys = new TreeSet<>(params.keySet());
// process query
for (String key = QuerySettings.QUERY_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setQuery(SearchUtils.trimToNull(params.getFirst(key)));
}
// process highlighting
for (String key = QuerySettings.QUERY_HIGHLIGHT_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setQueryHighlight(readBooleanParam(params, key));
}
// process fields
for (String key = QuerySettings.FIELDS_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setFields(normalizeListParam(params.get(key)));
}
// process sort
for (String key = QuerySettings.SORT_BY_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setSortBy(SortByValue.parseRequestParameterValue(params.getFirst(key)));
}
// process aggregations
for (String key = QuerySettings.AGGREGATION_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
List<String> aggregationKeys = params.get(key);
if (aggregationKeys != null) {
for (String key_ : aggregationKeys) {
if (key_ != null && !key_.trim().isEmpty()) {
settings.addAggregation(key_);
}
}
}
}
// process from
for (String key = QuerySettings.FROM_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setFrom(readIntegerParam(params, key));
if (settings.getFrom() != null && settings.getFrom() < 0)
throw new IllegalArgumentException(key);
}
// process size
for (String key = QuerySettings.SIZE_KEY; paramKeys.contains(key); paramKeys.remove(key)) {
settings.setSize(readIntegerParam(params, key));
if (settings.getSize() != null
&& (settings.getSize() < 0 || settings.getSize() > SearchService.RESPONSE_MAX_SIZE))
throw new IllegalArgumentException(key);
}
// remaining url parameters can be all search filters
QuerySettings.Filters filters = settings.getFiltersInit();
for (String key : paramKeys) {
List<String> urlValues = SearchUtils.safeList(params.get(key));
if (urlValues != null)
filters.acknowledgeUrlFilterCandidate(key, urlValues);
}
if (log.isLoggable(Level.FINE)) {
log.fine("Requested search settings: " + settings);
}
return settings;
}
/**
* Read request param value as integer.
*
* @param params to get param from
* @param paramKey key of param
* @return param value as integer or null
* @throws IllegalArgumentException if param value is not convertible to Integer
*/
protected Integer readIntegerParam(MultivaluedMap<String, String> params, String paramKey)
throws IllegalArgumentException {
if (params != null && params.containsKey(paramKey)) {
try {
String s = SearchUtils.trimToNull(params.getFirst(paramKey));
if (s == null)
return null;
return new Integer(s);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(paramKey);
}
}
return null;
}
/**
* Read request param value as ISO datetime format and return it as millis.
*
* @param params to get param from
* @param paramKey key of param
* @return param timestamp value as Long or null
* @throws IllegalArgumentException if datetime param value is not parsable due bad format
*/
protected Long readDateParam(MultivaluedMap<String, String> params, String paramKey) throws IllegalArgumentException {
if (params != null && params.containsKey(paramKey)) {
try {
String s = SearchUtils.trimToNull(params.getFirst(paramKey));
if (s == null)
return null;
return ISODateTimeFormat.dateTimeParser().parseMillis(s);
} catch (Exception e) {
throw new IllegalArgumentException(paramKey);
}
}
return null;
}
/**
* Read request param value as boolean.
*
* @param params to get param from
* @param paramKey key of param
* @return param boolean value
*/
protected boolean readBooleanParam(MultivaluedMap<String, String> params, String paramKey) {
if (params != null && params.containsKey(paramKey)) {
return Boolean.parseBoolean(SearchUtils.trimToNull(params.getFirst(paramKey)));
}
return false;
}
/**
* Normalize list with param values. Trim values, remove empty values, return null list if empty etc.
*
* @param paramValue value to normalize
* @return normalized List param value
*/
protected List<String> normalizeListParam(List<String> paramValue) {
if (paramValue == null || paramValue.isEmpty()) {
return null;
}
List<String> ret = new ArrayList<String>();
for (String s : paramValue) {
s = SearchUtils.trimToNull(s);
if (s != null) {
ret.add(s);
}
}
if (ret.isEmpty())
return null;
else
return ret;
}
}