/*******************************************************************************
* Copyright (c) 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.ibm.ws.lars.rest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import com.ibm.ws.lars.rest.Condition.Operation;
import com.ibm.ws.lars.rest.SortOptions.SortOrder;
import com.ibm.ws.lars.rest.exceptions.InvalidParameterException;
import com.ibm.ws.lars.rest.exceptions.RepositoryException;
/**
* Parses query parameters for REST calls which query for assets.
*/
public class AssetQueryParameters {
private final Map<String, String> params;
private static final String LIMIT_PARAM = "limit";
private static final String OFFSET_PARAM = "offset";
private static final String FIELDS_PARAM = "fields";
private static final String APIKEY_PARAM = "apiKey";
private static final String SEARCH_PARAM = "q";
private static final String SORT_ORDER_PARAM = "sortOrder";
private static final String SORT_BY_PARAM = "sortBy";
// Permitted values for the SORT_BY parameter
private static final String SORT_BY_ASC = "ASC";
private static final String SORT_BY_DESC = "DESC";
private static final Set<String> NON_QUERY_PARAMS = new HashSet<>(
Arrays.asList(LIMIT_PARAM, OFFSET_PARAM, FIELDS_PARAM, APIKEY_PARAM, SEARCH_PARAM, SORT_ORDER_PARAM, SORT_BY_PARAM));
private AssetQueryParameters(Map<String, String> params) {
this.params = params;
}
/**
* Parse the asset query parameters from a UriInfo
*
* @param uriInfo the UriInfo
* @return the asset query parameters
*/
public static AssetQueryParameters create(UriInfo uriInfo) {
MultivaluedMap<String, String> params = uriInfo.getQueryParameters(false);
// NOTE: we're putting a multi-valued map into a regular map, only the last value for each key will be preserved
// If the query parameters includes foo=bar&foo=baz, the decodedParams map will only contain foo=baz
// This is the desired behaviour for parsing the parameters for an asset query
Map<String, String> decodedParams = new HashMap<String, String>();
try {
for (Map.Entry<String, List<String>> entry : params.entrySet()) {
String decodedKey = URLDecoder.decode(entry.getKey(), StandardCharsets.UTF_8.name());
for (String value : entry.getValue()) {
decodedParams.put(decodedKey, URLDecoder.decode(value, StandardCharsets.UTF_8.name()));
}
}
} catch (UnsupportedEncodingException e) {
throw new RepositoryException("UTF-8 is unexpectedly missing.", e);
}
return new AssetQueryParameters(decodedParams);
}
/**
* Returns the filters parsed from the request as a list of AssetFilter.
* <p>
* If there were no filter parameters passed with the request then the returned list will be
* empty
*
* If a single field name appeared twice or more in the query string, the returned list will
* only contain one filter for that field, in a single AssetFilter instance. The filter in the
* returned list will represent the last filter from the query string.
*
* @return a list of AssetFilter
*/
public Collection<AssetFilter> getFilters() {
// process parameters as filters
// Filters have the following syntax
// field=value[|value]...
// To ensure there is only one filter per field, add
// them to a keyed map. Convert to a list later
Map<String, AssetFilter> filterMap = new HashMap<>();
for (Entry<String, String> entry : params.entrySet()) {
// Skip any parameters which have a special meaning
if (NON_QUERY_PARAMS.contains(entry.getKey())) {
continue;
}
String value = entry.getValue();
// Pipe (|) separates values which should be ORed together
// split(<regex>, -1) is used to retain any trailing empty strings, ensuring that we always have a non-empty list
List<String> orParts = new ArrayList<String>(Arrays.asList(value.split("\\|", -1)));
List<Condition> conditions = new ArrayList<>();
// The first value can begin with ! to indicate that a filter for NOT that value
if (orParts.get(0).startsWith("!")) {
conditions.add(new Condition(Operation.NOT_EQUALS, orParts.get(0).substring(1)));
orParts.remove(0);
}
// Any later values beginning with ! are ignored
for (Iterator<String> iterator = orParts.iterator(); iterator.hasNext();) {
if (iterator.next().startsWith("!")) {
iterator.remove();
}
}
// Finally all remaining values represent an equals condition
if (!orParts.isEmpty()) {
for (String part : orParts) {
conditions.add(new Condition(Operation.EQUALS, part));
}
}
filterMap.put(entry.getKey(), new AssetFilter(entry.getKey(), conditions));
}
List<AssetFilter> assetFilters = new ArrayList<>();
assetFilters.addAll(filterMap.values());
return assetFilters;
}
/**
* Parses the limit and offset parameters to create and return a PaginationOptions.
* <p>
* If both parameters are present and are integers, a PaginationOptions object will be returned.
* <p>
* If only one or neither parameters are present, null will be returned. Pagination is only
* enabled if both options are provided.
* <p>
* If both parameters are present but are not both integers, an InvalidParameterException is
* thrown
*
* @return a PaginationOptions if both limit and offset parameters are provided, otherwise null
* @throws InvalidParameterException if limit and offset parameters are provided but are not
* integers
*/
public PaginationOptions getPagination() throws InvalidParameterException {
String limitString = params.get(LIMIT_PARAM);
String offsetString = params.get(OFFSET_PARAM);
if (limitString == null && offsetString == null) {
return null;
}
if (limitString == null || offsetString == null) {
throw new InvalidParameterException("If either " + LIMIT_PARAM + " or " + OFFSET_PARAM + " is provided then both must be provided");
}
int limit;
int offset;
try {
limit = Integer.parseInt(limitString);
} catch (NumberFormatException e) {
throw new InvalidParameterException(LIMIT_PARAM + " must be an integer");
}
try {
offset = Integer.parseInt(offsetString);
} catch (NumberFormatException e) {
throw new InvalidParameterException(OFFSET_PARAM + " must be an integer");
}
return new PaginationOptions(offset, limit);
}
/**
* @return the search term parameter, or null if it was not set or is blank
*/
public String getSearchTerm() {
String searchTerm = params.get(SEARCH_PARAM);
if (searchTerm == null || searchTerm.isEmpty()) {
return null;
} else {
return searchTerm;
}
}
/**
* @return the fields param
*/
public String getFields() {
return params.get(FIELDS_PARAM);
}
/**
* @return SortOptions describing how the results should be sorted or null if the results should
* not be sorted
*/
public SortOptions getSortOptions() throws InvalidParameterException {
String sortByField = params.get(SORT_BY_PARAM);
String sortOrderParam = params.get(SORT_ORDER_PARAM);
SortOrder sortOrder;
if (sortByField != null && sortByField.isEmpty()) {
throw new InvalidParameterException(SORT_BY_PARAM + " must not be blank");
}
if (sortOrderParam != null && sortByField == null) {
throw new InvalidParameterException(SORT_ORDER_PARAM + " may only be provided if " + SORT_BY_PARAM + " is also provided");
}
if (sortOrderParam == null) {
sortOrder = SortOrder.ASCENDING;
} else if (sortOrderParam.equalsIgnoreCase(SORT_BY_ASC)) {
sortOrder = SortOrder.ASCENDING;
} else if (sortOrderParam.equalsIgnoreCase(SORT_BY_DESC)) {
sortOrder = SortOrder.DESCENDING;
} else {
throw new InvalidParameterException(SORT_ORDER_PARAM + " must be either \"" + SORT_BY_ASC + "\" or \"" + SORT_BY_DESC + "\"");
}
if (sortByField != null) {
return new SortOptions(sortByField, sortOrder);
} else {
return null;
}
}
}