io.katharsis.queryParams.QueryParams Maven / Gradle / Ivy
package io.katharsis.queryParams;
import io.katharsis.jackson.exception.ParametersDeserializationException;
import io.katharsis.queryParams.include.Inclusion;
import io.katharsis.queryParams.params.*;
import io.katharsis.resource.RestrictedQueryParamsMembers;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Contains a set of parameters passed along with the request.
*/
public class QueryParams {
private TypedParams filters;
private TypedParams sorting;
private TypedParams grouping;
private TypedParams includedFields;
private TypedParams includedRelations;
private Map pagination;
/**
* Important! Katharsis implementation differs form JSON API
* definition of filtering
* in order to fit standard query parameter serializing strategy and maximize effective processing of data.
*
* Filter params can be send with following format (Katharsis does not specify or implement any operators):
* filter[ResourceType][property|operator]([property|operator])* = "value"
*
* Examples of accepted filtering of resources:
*
* - {@code GET /tasks/?filter[Task][name]=Super task}
* - {@code GET /tasks/?filter[Task][name]=Super task&[Task][dueDate]=2015-10-01}
* - {@code GET /tasks/?filter[Task][name][$startWith]=Super task}
* - {@code GET /tasks/?filter[Task][name][][$startWith]=Super&[Task][name][][$endWith]=task}
*
*
* @return {@link TypedParams} Map of filtering params passed to a request grouped by type of resource
*/
public TypedParams getFilters() {
return filters;
}
void setFilters(Map> filters) {
Map>> temporaryFiltersMap = new LinkedHashMap<>();
for (Map.Entry> entry : filters.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.filter.name());
String resourceType = propertyList.get(0);
String propertyPath = String.join(".", propertyList.subList(1, propertyList.size()));
if (temporaryFiltersMap.containsKey(resourceType)) {
Map> resourceParams = temporaryFiltersMap.get(resourceType);
resourceParams.put(propertyPath, Collections.unmodifiableSet(entry.getValue()));
} else {
Map> resourceParams = new LinkedHashMap<>();
temporaryFiltersMap.put(resourceType, resourceParams);
resourceParams.put(propertyPath, entry.getValue());
}
}
Map decodedFiltersMap = new LinkedHashMap<>();
for (Map.Entry>> resourceTypesMap : temporaryFiltersMap.entrySet()) {
Map> filtersMap = Collections.unmodifiableMap(resourceTypesMap.getValue());
decodedFiltersMap.put(resourceTypesMap.getKey(), new FilterParams(filtersMap));
}
this.filters = new TypedParams<>(Collections.unmodifiableMap(decodedFiltersMap));
}
/**
* Important! Katharsis implementation differs form JSON API
* definition of sorting
* in order to fit standard query parameter serializing strategy and maximize effective processing of data.
*
* Sort params can be send with following format:
* sort[ResourceType][property]([property])* = "asc|desc"
*
* Examples of accepted sorting of resources:
*
* - {@code GET /tasks/?sort[Task][name]=asc}
* - {@code GET /project/?sort[Project][shortName]=desc&sort[User][name][firstName]=asc}
*
*
* @return {@link TypedParams} Map of sorting params passed to request grouped by type of resource
*/
public TypedParams getSorting() {
return sorting;
}
void setSorting(Map> sorting) {
Map> temporarySortingMap = new LinkedHashMap<>();
for (Map.Entry> entry : sorting.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.sort.name());
String resourceType = propertyList.get(0);
String propertyPath = String.join(".", propertyList.subList(1, propertyList.size()));
if (temporarySortingMap.containsKey(resourceType)) {
Map resourceParams = temporarySortingMap.get(resourceType);
resourceParams.put(propertyPath, RestrictedSortingValues.valueOf(entry.getValue()
.iterator()
.next()));
} else {
Map resourceParams = new HashMap<>();
temporarySortingMap.put(resourceType, resourceParams);
resourceParams.put(propertyPath, RestrictedSortingValues.valueOf(entry.getValue()
.iterator()
.next()));
}
}
Map decodedSortingMap = new LinkedHashMap<>();
for (Map.Entry> resourceTypesMap : temporarySortingMap.entrySet
()) {
Map sortingMap = Collections.unmodifiableMap(resourceTypesMap.getValue());
decodedSortingMap.put(resourceTypesMap.getKey(), new SortingParams(sortingMap));
}
this.sorting = new TypedParams<>(Collections.unmodifiableMap(decodedSortingMap));
}
/**
* Important: Grouping itself is not specified by JSON API itself, but the
* keyword and format it reserved for today and future use in Katharsis.
*
* Group params can be send with following format:
* group[ResourceType] = "property(.property)*"
*
* Examples of accepted grouping of resources:
*
* - {@code GET /tasks/?group[Task]=name}
* - {@code GET /project/?group[User]=name.firstName&include[Project]=team}
*
*
* @return {@link Map} Map of grouping params passed to request grouped by type of resource
*/
public TypedParams getGrouping() {
return grouping;
}
void setGrouping(Map> grouping) {
Map> temporaryGroupingMap = new LinkedHashMap<>();
for (Map.Entry> entry : grouping.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.group.name());
if (propertyList.size() > 1) {
throw new ParametersDeserializationException("Exceeded maximum level of nesting of 'group' parameter " +
"(1) eg. group[Task][name] <-- #2 level and more are not allowed");
}
String resourceType = propertyList.get(0);
if (temporaryGroupingMap.containsKey(resourceType)) {
Set resourceParams = temporaryGroupingMap.get(resourceType);
resourceParams.addAll(entry.getValue());
temporaryGroupingMap.put(resourceType, resourceParams);
} else {
Set resourceParams = new LinkedHashSet<>();
resourceParams.addAll(entry.getValue());
temporaryGroupingMap.put(resourceType, resourceParams);
}
}
Map decodedGroupingMap = new LinkedHashMap<>();
for (Map.Entry> resourceTypesMap : temporaryGroupingMap.entrySet()) {
Set groupingSet = Collections.unmodifiableSet(resourceTypesMap.getValue());
decodedGroupingMap.put(resourceTypesMap.getKey(), new GroupingParams(groupingSet));
}
this.grouping = new TypedParams<>(Collections.unmodifiableMap(decodedGroupingMap));
}
/**
* Important! Katharsis implementation sets on strategy of pagination whereas JSON API
* definition of pagination
* is agnostic about pagination strategies.
*
* Pagination params can be send with following format:
* page[offset|limit] = "value", where value is an integer
*
* Examples of accepted grouping of resources:
*
* - {@code GET /projects/?page[offset]=0&page[limit]=10}
*
*
* @return {@link Map} Map of pagination keys passed to request
*/
public Map getPagination() {
return pagination;
}
void setPagination(Map> pagination) {
Map decodedPagination = new LinkedHashMap<>();
for (Map.Entry> entry : pagination.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.page.name());
if (propertyList.size() > 1) {
throw new ParametersDeserializationException("Exceeded maximum level of nesting of 'page' parameter " +
"(1) eg. page[offset][minimal] <-- #2 level and more are not allowed");
}
String resourceType = propertyList.get(0);
decodedPagination.put(RestrictedPaginationKeys.valueOf(resourceType), Integer.parseInt(entry
.getValue()
.iterator()
.next()));
}
this.pagination = Collections.unmodifiableMap(decodedPagination);
}
/**
* Important! Katharsis implementation differs form JSON API
* definition of sparse field set
* in order to fit standard query parameter serializing strategy and maximize effective processing of data.
*
* Sparse field set params can be send with following format:
* fields[ResourceType] = "property(.property)*"
*
* Examples of accepted sparse field sets of resources:
*
* - {@code GET /tasks/?fields[Task]=name}
* - {@code GET /tasks/?fields[Task][]=name&fields[Task][]=dueDate}
* - {@code GET /tasks/?fields[User]=name.surname&include[Task]=author}
*
*
* @return {@link TypedParams} Map of sparse field set params passed to a request grouped by type of resource
*/
public TypedParams getIncludedFields() {
return includedFields;
}
void setIncludedFields(Map> sparse) {
Map> temporarySparseMap = new LinkedHashMap<>();
for (Map.Entry> entry : sparse.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.fields.name());
if (propertyList.size() > 1) {
throw new ParametersDeserializationException("Exceeded maximum level of nesting of 'fields' " +
"parameter (1) eg. fields[Task][name] <-- #2 level and more are not allowed");
}
String resourceType = propertyList.get(0);
if (temporarySparseMap.containsKey(resourceType)) {
Set resourceParams = temporarySparseMap.get(resourceType);
resourceParams.addAll(entry.getValue());
temporarySparseMap.put(resourceType, resourceParams);
} else {
Set resourceParams = new LinkedHashSet<>();
resourceParams.addAll(entry.getValue());
temporarySparseMap.put(resourceType, resourceParams);
}
}
Map decodedSparseMap = new LinkedHashMap<>();
for (Map.Entry> resourceTypesMap : temporarySparseMap.entrySet()) {
Set sparseSet = Collections.unmodifiableSet(resourceTypesMap.getValue());
decodedSparseMap.put(resourceTypesMap.getKey(), new IncludedFieldsParams(sparseSet));
}
this.includedFields = new TypedParams<>(Collections.unmodifiableMap(decodedSparseMap));
}
/**
* Important! Katharsis implementation differs form JSON API
* definition of includes
* in order to fit standard query parameter serializing strategy and maximize effective processing of data.
*
* Included field set params can be send with following format:
* include[ResourceType] = "property(.property)*"
*
* Examples of accepted sparse field sets of resources:
*
* - {@code GET /tasks/?include[Task]=author}
* - {@code GET /tasks/?include[Task][]=author&include[Task][]=comments}
* - {@code GET /projects/?include[Project]=task&include[Task]=comments}
*
*
* @return {@link TypedParams} Map of sparse field set params passed to a request grouped by type of resource
*/
public TypedParams getIncludedRelations() {
return includedRelations;
}
void setIncludedRelations(Map> inclusions) {
Map> temporaryInclusionsMap = new LinkedHashMap<>();
for (Map.Entry> entry : inclusions.entrySet()) {
List propertyList = buildPropertyListFromEntry(entry, RestrictedQueryParamsMembers.include.name());
if (propertyList.size() > 1) {
throw new ParametersDeserializationException("Exceeded maximum level of nesting of 'include' " +
"parameter (1)");
}
String resourceType = propertyList.get(0);
if (temporaryInclusionsMap.containsKey(resourceType)) {
Set resourceParams = temporaryInclusionsMap.get(resourceType);
resourceParams.add(new Inclusion(entry.getValue()
.iterator()
.next()));
temporaryInclusionsMap.put(resourceType, resourceParams);
} else {
Set resourceParams = new LinkedHashSet<>();
resourceParams.add(new Inclusion(entry.getValue()
.iterator()
.next()));
temporaryInclusionsMap.put(resourceType, resourceParams);
}
}
Map decodedInclusions = new LinkedHashMap<>();
for (Map.Entry> resourceTypesMap : temporaryInclusionsMap.entrySet()) {
Set inclusionSet = Collections.unmodifiableSet(resourceTypesMap.getValue());
decodedInclusions.put(resourceTypesMap.getKey(), new IncludedRelationsParams(inclusionSet));
}
this.includedRelations = new TypedParams<>(Collections.unmodifiableMap(decodedInclusions));
}
private List buildPropertyListFromEntry(Map.Entry> entry, String prefix) {
String entryKey = entry.getKey()
.substring(prefix.length());
String pattern = "\\w+(? matchList = new LinkedList<>();
while (matcher.find()) {
matchList.add(matcher.group());
}
if (matchList.size() < 1) {
throw new ParametersDeserializationException("Malformed filter parameter: " + entryKey);
}
return matchList;
}
}