diff --git a/build.gradle b/build.gradle
index 10744082d..c9868b469 100644
--- a/build.gradle
+++ b/build.gradle
@@ -116,7 +116,7 @@ gradle.beforeProject { Project project ->
if (!docs && !examples && !testProject && !legacyBraveProject) {
apply plugin: "jacoco"
jacoco {
- toolVersion = "0.7.6.201602180812"
+ toolVersion = "0.8.5"
}
rootProject.tasks.jacocoMerge.executionData tasks.withType(Test)
diff --git a/crnk-client/src/main/java/io/crnk/client/CrnkClient.java b/crnk-client/src/main/java/io/crnk/client/CrnkClient.java
index 9fc43e7ad..768e6539e 100644
--- a/crnk-client/src/main/java/io/crnk/client/CrnkClient.java
+++ b/crnk-client/src/main/java/io/crnk/client/CrnkClient.java
@@ -134,6 +134,8 @@ public class CrnkClient {
private ClientProxyFactory configuredProxyFactory;
+ private boolean filterCriteriaInRequestBody;
+
public enum ClientType {
SIMPLE_lINKS,
@@ -274,13 +276,13 @@ public ModuleRegistry getModuleRegistry() {
}
@Override
- public DefaultResourceList getCollection(Class resourceClass, String url) {
+ public DefaultResourceList getCollection(Class resourceClass, String url, String body) {
RegistryEntry entry = resourceRegistry.findEntry(resourceClass);
ResourceInformation resourceInformation = entry.getResourceInformation();
// TODO add decoration
final ResourceRepositoryStubImpl repositoryStub =
- new ResourceRepositoryStubImpl<>(CrnkClient.this, resourceClass, resourceInformation, urlBuilder);
- return repositoryStub.findAll(url);
+ new ResourceRepositoryStubImpl<>(CrnkClient.this, resourceClass, resourceInformation, urlBuilder, filterCriteriaInRequestBody);
+ return repositoryStub.findAll(url, body);
}
});
@@ -320,6 +322,7 @@ protected void init() {
setupServiceDiscovery();
initHttpAdapter();
+ setupUrlMapper();
setupPagingBehavior();
initObjectMapper();
configureObjectMapper();
@@ -335,6 +338,17 @@ protected void init() {
}
}
+ private void setupUrlMapper() {
+ if (filterCriteriaInRequestBody) {
+ QuerySpecUrlMapper urlMapper = moduleRegistry.getUrlMapper();
+ if (urlMapper instanceof DefaultQuerySpecUrlMapper) {
+ ((DefaultQuerySpecUrlMapper) urlMapper).setFilterCriteriaInRequestBody(filterCriteriaInRequestBody);
+ } else {
+ throw new IllegalStateException("Need DefaultQuerySpecUrlMapper for filterCriteriaInRequestBody option");
+ }
+ }
+ }
+
protected void initObjectMapper() {
if (objectMapper == null) {
objectMapper = createDefaultObjectMapper();
@@ -415,7 +429,7 @@ protected RegistryEntry allocateRepository(Class resourceClass, Regist
ModuleUtils.adaptInformation(resourceInformation, moduleRegistry);
final ResourceRepository repositoryStub = (ResourceRepository) decorate(
- new ResourceRepositoryStubImpl(this, resourceClass, resourceInformation, urlBuilder)
+ new ResourceRepositoryStubImpl(this, resourceClass, resourceInformation, urlBuilder, filterCriteriaInRequestBody)
);
// create interface for it!
@@ -486,7 +500,7 @@ private RelationshipRepositoryAdapter allocateRepositoryRelation(ResourceField f
}
final Object relationshipRepositoryStub = decorate(
- new RelationshipRepositoryStubImpl(this, sourceClass, targetClass, sourceEntry.getResourceInformation(), urlBuilder)
+ new RelationshipRepositoryStubImpl(this, sourceClass, targetClass, sourceEntry.getResourceInformation(), urlBuilder, filterCriteriaInRequestBody)
);
RelationshipRepositoryAdapter adapter = new RelationshipRepositoryAdapterImpl(field, moduleRegistry, relationshipRepositoryStub);
@@ -543,7 +557,7 @@ public ResourceRepository getRepositoryForPath(String resource
, null
, PagingSpec.class
);
- return (ResourceRepository) decorate(new ResourceRepositoryStubImpl<>(this, Resource.class, resourceInformation, urlBuilder));
+ return (ResourceRepository) decorate(new ResourceRepositoryStubImpl<>(this, Resource.class, resourceInformation, urlBuilder, filterCriteriaInRequestBody));
}
/**
@@ -558,7 +572,7 @@ public RelationshipRepository getRepositoryF
PagingSpec.class);
return (RelationshipRepository) decorate(
new RelationshipRepositoryStubImpl<>(this, Resource.class, Resource.class, sourceResourceInformation,
- urlBuilder));
+ urlBuilder, filterCriteriaInRequestBody));
}
@@ -734,4 +748,9 @@ public boolean isInitialized(Class> clazz) {
return super.getEntry(clazz) != null;
}
}
+
+ public void setFilterCriteriaInRequestBody(boolean filterCriteriaInRequestBody) {
+ PreconditionUtil.verify(!initialized, "CrnkClient already initialized, cannot add filters to body");
+ this.filterCriteriaInRequestBody = filterCriteriaInRequestBody;
+ }
}
diff --git a/crnk-client/src/main/java/io/crnk/client/http/apache/HttpClientRequest.java b/crnk-client/src/main/java/io/crnk/client/http/apache/HttpClientRequest.java
index df1e3fdcc..11bf56753 100644
--- a/crnk-client/src/main/java/io/crnk/client/http/apache/HttpClientRequest.java
+++ b/crnk-client/src/main/java/io/crnk/client/http/apache/HttpClientRequest.java
@@ -39,7 +39,11 @@ public HttpClientRequest(CloseableHttpClient impl, String url, HttpMethod method
this.listeners = listeners;
this.requestBody = requestBody;
if (method == HttpMethod.GET) {
- requestBase = new HttpGet(url);
+ if (requestBody == null || requestBody.isEmpty()) {
+ requestBase = new HttpGet(url);
+ } else {
+ requestBase = new HttpGetWithBody(url, requestBody, CONTENT_TYPE);
+ }
} else if (method == HttpMethod.POST) {
HttpPost post = new HttpPost(url);
post.setEntity(new StringEntity(requestBody, CONTENT_TYPE));
diff --git a/crnk-client/src/main/java/io/crnk/client/http/apache/HttpGetWithBody.java b/crnk-client/src/main/java/io/crnk/client/http/apache/HttpGetWithBody.java
new file mode 100644
index 000000000..fe83aed89
--- /dev/null
+++ b/crnk-client/src/main/java/io/crnk/client/http/apache/HttpGetWithBody.java
@@ -0,0 +1,20 @@
+package io.crnk.client.http.apache;
+
+import io.crnk.core.engine.http.HttpMethod;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+
+import java.net.URI;
+
+public class HttpGetWithBody extends HttpEntityEnclosingRequestBase {
+ public HttpGetWithBody(String url, String body, ContentType contentType) {
+ setURI(URI.create(url));
+ setEntity(new StringEntity(body, contentType));
+ }
+
+ @Override
+ public String getMethod() {
+ return HttpMethod.GET.toString();
+ }
+}
diff --git a/crnk-client/src/main/java/io/crnk/client/internal/ClientStubBase.java b/crnk-client/src/main/java/io/crnk/client/internal/ClientStubBase.java
index 7435a49e4..f422c439c 100644
--- a/crnk-client/src/main/java/io/crnk/client/internal/ClientStubBase.java
+++ b/crnk-client/src/main/java/io/crnk/client/internal/ClientStubBase.java
@@ -1,15 +1,9 @@
package io.crnk.client.internal;
-import java.io.IOException;
-import java.util.List;
-import java.util.Optional;
-
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import io.crnk.client.ClientException;
-import io.crnk.client.ClientFormat;
-import io.crnk.client.CrnkClient;
-import io.crnk.client.ResponseBodyException;
-import io.crnk.client.TransportException;
+import io.crnk.client.*;
import io.crnk.client.http.HttpAdapter;
import io.crnk.client.http.HttpAdapterRequest;
import io.crnk.client.http.HttpAdapterResponse;
@@ -19,10 +13,15 @@
import io.crnk.core.engine.error.ExceptionMapper;
import io.crnk.core.engine.http.HttpHeaders;
import io.crnk.core.engine.http.HttpMethod;
+import io.crnk.core.engine.information.resource.ResourceInformation;
import io.crnk.core.engine.internal.exception.ExceptionMapperRegistry;
import io.crnk.core.engine.internal.utils.PreconditionUtil;
import io.crnk.core.engine.query.QueryContext;
import io.crnk.core.exception.CrnkException;
+import io.crnk.core.queryspec.FilterSpec;
+import io.crnk.core.queryspec.QuerySpec;
+import io.crnk.core.queryspec.internal.JsonFilterSpecMapper;
+import io.crnk.core.queryspec.mapper.DefaultQuerySpecUrlMapper;
import io.crnk.core.queryspec.mapper.UrlBuilder;
import io.crnk.core.resource.list.DefaultResourceList;
import io.crnk.core.resource.meta.JsonLinksInformation;
@@ -30,9 +29,17 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
public class ClientStubBase {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientStubBase.class);
+ protected final boolean filterCriteriaInRequestBody;
+ protected final JsonFilterSpecMapper filterSpecMapper;
+ protected final ObjectMapper compactMapper;
protected CrnkClient client;
@@ -41,14 +48,22 @@ public class ClientStubBase {
protected Class> resourceClass;
- public ClientStubBase(CrnkClient client, UrlBuilder urlBuilder, Class> resourceClass) {
+ public ClientStubBase(CrnkClient client, UrlBuilder urlBuilder, Class> resourceClass, boolean filterCriteriaInRequestBody) {
this.client = client;
this.urlBuilder = urlBuilder;
this.resourceClass = resourceClass;
+ this.filterCriteriaInRequestBody = filterCriteriaInRequestBody;
+ if (filterCriteriaInRequestBody) {
+ filterSpecMapper = ((DefaultQuerySpecUrlMapper) client.getUrlMapper()).getJsonParser();
+ compactMapper = client.getObjectMapper();
+ } else {
+ filterSpecMapper = null;
+ compactMapper = null;
+ }
}
- protected Object executeGet(String requestUrl, ResponseType responseType) {
- return execute(requestUrl, responseType, HttpMethod.GET, null);
+ protected Object executeGet(String requestUrl, String body, ResponseType responseType) {
+ return execute(requestUrl, responseType, HttpMethod.GET, body);
}
protected Object executeDelete(String requestUrl) {
@@ -99,8 +114,7 @@ protected Object execute(String url, ResponseType responseType, HttpMethod metho
if (Resource.class.equals(resourceClass)) {
Document document = objectMapper.readValue(body, format.getDocumentClass());
return toResourceResponse(document, objectMapper);
- }
- else {
+ } else {
Document document = objectMapper.readValue(body, format.getDocumentClass());
ClientDocumentMapper documentMapper = client.getDocumentMapper();
@@ -109,8 +123,7 @@ protected Object execute(String url, ResponseType responseType, HttpMethod metho
}
}
return null;
- }
- catch (IOException e) {
+ } catch (IOException e) {
throw new TransportException(e);
}
}
@@ -127,8 +140,7 @@ private static Object toResourceResponse(Document document, ObjectMapper objectM
list.setLinks(new JsonLinksInformation(document.getMeta(), objectMapper));
}
return list;
- }
- else {
+ } else {
return data;
}
}
@@ -165,16 +177,32 @@ public static RuntimeException handleError(CrnkClient client, HttpAdapterRespons
Throwable throwable = mapper.get().fromErrorResponse(errorResponse);
if (throwable instanceof RuntimeException) {
return (RuntimeException) throwable;
- }
- else {
+ } else {
return new ClientException(response.code(), response.message(), throwable);
}
- }
- else {
+ } else {
return new ClientException(response.code(), response.message());
}
}
+ protected String serializeFilter(QuerySpec querySpec, ResourceInformation resourceInformation) {
+ if (querySpec.getFilters() == null || querySpec.getFilters().isEmpty()) {
+ return null;
+ }
+ // filter lists without operator does not work well with JSON serialization -> replace with AND expression
+ List filters = querySpec.getFilters();
+ if (filters.size() > 1) {
+ filters = Collections.singletonList(FilterSpec.and(filters));
+ }
+ JsonNode jsonNode = filterSpecMapper.serialize(resourceInformation, filters, client.getQueryContext());
+ try {
+ return compactMapper.writeValueAsString(jsonNode);
+ } catch (JsonProcessingException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+
public enum ResponseType {
NONE, RESOURCE, RESOURCES
}
diff --git a/crnk-client/src/main/java/io/crnk/client/internal/RelationshipRepositoryStubImpl.java b/crnk-client/src/main/java/io/crnk/client/internal/RelationshipRepositoryStubImpl.java
index 31f59f51d..b852cbf2a 100644
--- a/crnk-client/src/main/java/io/crnk/client/internal/RelationshipRepositoryStubImpl.java
+++ b/crnk-client/src/main/java/io/crnk/client/internal/RelationshipRepositoryStubImpl.java
@@ -21,114 +21,123 @@
import java.util.Collection;
public class RelationshipRepositoryStubImpl extends ClientStubBase
- implements RelationshipRepository {
-
- private Class sourceClass;
-
- private Class targetClass;
-
- private ResourceInformation sourceResourceInformation;
-
- public RelationshipRepositoryStubImpl(CrnkClient client, Class sourceClass, Class targetClass,
- ResourceInformation sourceResourceInformation, UrlBuilder urlBuilder) {
- super(client, urlBuilder, targetClass);
- this.sourceClass = sourceClass;
- this.targetClass = targetClass;
- this.sourceResourceInformation = sourceResourceInformation;
- }
-
- @Override
- public void setRelation(T source, J targetId, String fieldName) {
- Serializable sourceId = getSourceId(source);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
- executeWithId(url, HttpMethod.PATCH, targetId);
- }
-
- @Override
- public void setRelations(T source, Collection targetIds, String fieldName) {
- Serializable sourceId = getSourceId(source);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
- executeWithIds(url, HttpMethod.PATCH, targetIds);
- }
-
- @Override
- public void addRelations(T source, Collection targetIds, String fieldName) {
- Serializable sourceId = getSourceId(source);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
- executeWithIds(url, HttpMethod.POST, targetIds);
- }
-
- @Override
- public void removeRelations(T source, Collection targetIds, String fieldName) {
- Serializable sourceId = getSourceId(source);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
- executeWithIds(url, HttpMethod.DELETE, targetIds);
- }
-
- private Serializable getSourceId(T source) {
- if (source instanceof Resource) {
- return ((Resource) source).getId();
- }
- ResourceField idField = sourceResourceInformation.getIdField();
- return (Serializable) idField.getAccessor().getValue(source);
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public D findOneTarget(I sourceId, String fieldName, QuerySpec querySpec) {
- verifyQuerySpec(querySpec);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, querySpec, fieldName, false);
- return (D) executeGet(url, ResponseType.RESOURCE);
- }
-
- @Override
- public DefaultResourceList findManyTargets(I sourceId, String fieldName, QuerySpec querySpec) {
- verifyQuerySpec(querySpec);
- String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, querySpec, fieldName, false);
- return (DefaultResourceList) executeGet(url, ResponseType.RESOURCES);
- }
-
- protected void verifyQuerySpec(QuerySpec querySpec) {
- Class> resourceClass = querySpec.getResourceClass();
- if (resourceClass != null && !resourceClass.equals(targetClass)) {
- throw new BadRequestException("resourceClass mismatch between repository and QuerySpec argument: "
- + resourceClass + " vs " + targetClass);
- }
- }
-
-
- private void executeWithIds(String requestUrl, HttpMethod method, Collection> targetIds) {
- Document document = new Document();
- ArrayList resourceIdentifiers = new ArrayList<>();
- for (Object targetId : targetIds) {
- resourceIdentifiers.add(sourceResourceInformation.toResourceIdentifier(targetId));
- }
- document.setData(Nullable.of(resourceIdentifiers));
- Document transportDocument = client.getFormat().toTransportDocument(document);
- doExecute(requestUrl, method, transportDocument);
- }
-
- private void executeWithId(String requestUrl, HttpMethod method, Object targetId) {
- Document document = new Document();
- ResourceIdentifier resourceIdentifier = sourceResourceInformation.toResourceIdentifier(targetId);
- document.setData(Nullable.of(resourceIdentifier));
- Document transportDocument = client.getFormat().toTransportDocument(document);
- doExecute(requestUrl, method, transportDocument);
- }
-
- private void doExecute(String requestUrl, HttpMethod method, final Document document) {
- final ObjectMapper objectMapper = client.getObjectMapper();
- String requestBodyValue = ExceptionUtil.wrapCatchedExceptions(() -> objectMapper.writeValueAsString(document));
- execute(requestUrl, ResponseType.NONE, method, requestBodyValue);
- }
-
- @Override
- public Class getSourceResourceClass() {
- return sourceClass;
- }
-
- @Override
- public Class getTargetResourceClass() {
- return targetClass;
- }
+ implements RelationshipRepository {
+
+ private Class sourceClass;
+
+ private Class targetClass;
+
+ private ResourceInformation sourceResourceInformation;
+
+ public RelationshipRepositoryStubImpl(CrnkClient client, Class sourceClass, Class targetClass,
+ ResourceInformation sourceResourceInformation, UrlBuilder urlBuilder,
+ boolean filterCriteriaInRequestBody) {
+ super(client, urlBuilder, targetClass, filterCriteriaInRequestBody);
+ this.sourceClass = sourceClass;
+ this.targetClass = targetClass;
+ this.sourceResourceInformation = sourceResourceInformation;
+ }
+
+ @Override
+ public void setRelation(T source, J targetId, String fieldName) {
+ Serializable sourceId = getSourceId(source);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
+ executeWithId(url, HttpMethod.PATCH, targetId);
+ }
+
+ @Override
+ public void setRelations(T source, Collection targetIds, String fieldName) {
+ Serializable sourceId = getSourceId(source);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
+ executeWithIds(url, HttpMethod.PATCH, targetIds);
+ }
+
+ @Override
+ public void addRelations(T source, Collection targetIds, String fieldName) {
+ Serializable sourceId = getSourceId(source);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
+ executeWithIds(url, HttpMethod.POST, targetIds);
+ }
+
+ @Override
+ public void removeRelations(T source, Collection targetIds, String fieldName) {
+ Serializable sourceId = getSourceId(source);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, (QuerySpec) null, fieldName);
+ executeWithIds(url, HttpMethod.DELETE, targetIds);
+ }
+
+ private Serializable getSourceId(T source) {
+ if (source instanceof Resource) {
+ return ((Resource) source).getId();
+ }
+ ResourceField idField = sourceResourceInformation.getIdField();
+ return (Serializable) idField.getAccessor().getValue(source);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public D findOneTarget(I sourceId, String fieldName, QuerySpec querySpec) {
+ verifyQuerySpec(querySpec);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, querySpec, fieldName, false);
+ String body = null;
+ if (filterCriteriaInRequestBody) {
+ body = serializeFilter(querySpec, sourceResourceInformation);
+ }
+ return (D) executeGet(url, body, ResponseType.RESOURCE);
+ }
+
+ @Override
+ public DefaultResourceList findManyTargets(I sourceId, String fieldName, QuerySpec querySpec) {
+ verifyQuerySpec(querySpec);
+ String url = urlBuilder.buildUrl(client.getQueryContext(), sourceResourceInformation, sourceId, querySpec, fieldName, false);
+ String body = null;
+ if (filterCriteriaInRequestBody) {
+ body = serializeFilter(querySpec, sourceResourceInformation);
+ }
+ return (DefaultResourceList) executeGet(url, body, ResponseType.RESOURCES);
+ }
+
+ protected void verifyQuerySpec(QuerySpec querySpec) {
+ Class> resourceClass = querySpec.getResourceClass();
+ if (resourceClass != null && !resourceClass.equals(targetClass)) {
+ throw new BadRequestException("resourceClass mismatch between repository and QuerySpec argument: "
+ + resourceClass + " vs " + targetClass);
+ }
+ }
+
+
+ private void executeWithIds(String requestUrl, HttpMethod method, Collection> targetIds) {
+ Document document = new Document();
+ ArrayList resourceIdentifiers = new ArrayList<>();
+ for (Object targetId : targetIds) {
+ resourceIdentifiers.add(sourceResourceInformation.toResourceIdentifier(targetId));
+ }
+ document.setData(Nullable.of(resourceIdentifiers));
+ Document transportDocument = client.getFormat().toTransportDocument(document);
+ doExecute(requestUrl, method, transportDocument);
+ }
+
+ private void executeWithId(String requestUrl, HttpMethod method, Object targetId) {
+ Document document = new Document();
+ ResourceIdentifier resourceIdentifier = sourceResourceInformation.toResourceIdentifier(targetId);
+ document.setData(Nullable.of(resourceIdentifier));
+ Document transportDocument = client.getFormat().toTransportDocument(document);
+ doExecute(requestUrl, method, transportDocument);
+ }
+
+ private void doExecute(String requestUrl, HttpMethod method, final Document document) {
+ final ObjectMapper objectMapper = client.getObjectMapper();
+ String requestBodyValue = ExceptionUtil.wrapCatchedExceptions(() -> objectMapper.writeValueAsString(document));
+ execute(requestUrl, ResponseType.NONE, method, requestBodyValue);
+ }
+
+ @Override
+ public Class getSourceResourceClass() {
+ return sourceClass;
+ }
+
+ @Override
+ public Class getTargetResourceClass() {
+ return targetClass;
+ }
}
diff --git a/crnk-client/src/main/java/io/crnk/client/internal/ResourceRepositoryStubImpl.java b/crnk-client/src/main/java/io/crnk/client/internal/ResourceRepositoryStubImpl.java
index 6241d8ec5..692f88282 100644
--- a/crnk-client/src/main/java/io/crnk/client/internal/ResourceRepositoryStubImpl.java
+++ b/crnk-client/src/main/java/io/crnk/client/internal/ResourceRepositoryStubImpl.java
@@ -11,7 +11,6 @@
import io.crnk.core.engine.information.resource.ResourceInformation;
import io.crnk.core.engine.internal.document.mapper.DocumentMappingConfig;
import io.crnk.core.engine.internal.utils.ExceptionUtil;
-import io.crnk.core.engine.internal.utils.JsonApiUrlBuilder;
import io.crnk.core.engine.query.QueryAdapter;
import io.crnk.core.exception.BadRequestException;
import io.crnk.core.queryspec.QuerySpec;
@@ -37,8 +36,8 @@ public class ResourceRepositoryStubImpl extends ClientStubBase
public ResourceRepositoryStubImpl(CrnkClient client, Class resourceClass, ResourceInformation resourceInformation,
- UrlBuilder urlBuilder) {
- super(client, urlBuilder, resourceClass);
+ UrlBuilder urlBuilder, boolean filterCriteriaInRequestBody) {
+ super(client, urlBuilder, resourceClass, filterCriteriaInRequestBody);
this.resourceInformation = resourceInformation;
}
@@ -171,19 +170,27 @@ public T findOne(I id, QuerySpec querySpec) {
public DefaultResourceList findAll(QuerySpec querySpec) {
verifyQuerySpec(querySpec);
String url = urlBuilder.buildUrl(client.getQueryContext(), resourceInformation, null, querySpec);
- return findAll(url);
+ String body = null;
+ if (filterCriteriaInRequestBody) {
+ body = serializeFilter(querySpec, resourceInformation);
+ }
+ return findAll(url, body);
}
@Override
public DefaultResourceList findAll(Collection ids, QuerySpec querySpec) {
verifyQuerySpec(querySpec);
String url = urlBuilder.buildUrl(client.getQueryContext(), resourceInformation, ids, querySpec);
- return findAll(url);
+ String body = null;
+ if (filterCriteriaInRequestBody) {
+ body = serializeFilter(querySpec, resourceInformation);
+ }
+ return findAll(url, body);
}
@SuppressWarnings("unchecked")
- public DefaultResourceList findAll(String url) {
- return (DefaultResourceList) executeGet(url, ResponseType.RESOURCES);
+ public DefaultResourceList findAll(String url, String body) {
+ return (DefaultResourceList) executeGet(url, body, ResponseType.RESOURCES);
}
protected void verifyQuerySpec(QuerySpec querySpec) {
@@ -196,7 +203,7 @@ protected void verifyQuerySpec(QuerySpec querySpec) {
@SuppressWarnings("unchecked")
protected T findOne(String url) {
- return (T) executeGet(url, ResponseType.RESOURCE);
+ return (T) executeGet(url, null, ResponseType.RESOURCE);
}
}
diff --git a/crnk-client/src/main/java/io/crnk/client/internal/proxy/ClientProxyFactoryContext.java b/crnk-client/src/main/java/io/crnk/client/internal/proxy/ClientProxyFactoryContext.java
index 0a3d8fb6d..35b3dcc84 100644
--- a/crnk-client/src/main/java/io/crnk/client/internal/proxy/ClientProxyFactoryContext.java
+++ b/crnk-client/src/main/java/io/crnk/client/internal/proxy/ClientProxyFactoryContext.java
@@ -7,6 +7,9 @@ public interface ClientProxyFactoryContext {
ModuleRegistry getModuleRegistry();
- DefaultResourceList getCollection(Class resourceClass, String url);
+ DefaultResourceList getCollection(Class resourceClass, String url, String body);
+ default DefaultResourceList getCollection(Class resourceClass, String url) {
+ return getCollection(resourceClass, url, null);
+ }
}
diff --git a/crnk-client/src/test/java/io/crnk/client/FilterCriteriaInBodyClientTest.java b/crnk-client/src/test/java/io/crnk/client/FilterCriteriaInBodyClientTest.java
new file mode 100644
index 000000000..3cc07f717
--- /dev/null
+++ b/crnk-client/src/test/java/io/crnk/client/FilterCriteriaInBodyClientTest.java
@@ -0,0 +1,160 @@
+package io.crnk.client;
+
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import io.crnk.client.http.apache.HttpClientAdapter;
+import io.crnk.core.boot.CrnkProperties;
+import io.crnk.core.exception.InternalServerErrorException;
+import io.crnk.core.queryspec.*;
+import io.crnk.core.queryspec.mapper.DefaultQuerySpecUrlMapper;
+import io.crnk.core.repository.ResourceRepository;
+import io.crnk.core.resource.list.ResourceList;
+import io.crnk.test.mock.models.PrimitiveAttributeResource;
+import io.crnk.test.mock.models.Project;
+import io.crnk.test.mock.models.Task;
+import io.crnk.test.mock.repository.ProjectRepository;
+import io.crnk.test.mock.repository.TaskRepository;
+import org.apache.commons.lang3.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.util.*;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.junit.Assert.assertEquals;
+
+public class FilterCriteriaInBodyClientTest extends AbstractClientTest {
+ private static final int PROJECT_COUNT = 10;
+
+ private final ProjectRepository projectRepository = new ProjectRepository();
+ private final TaskRepository taskRepository = new TaskRepository();
+
+ @Before
+ public void setup() {
+ super.setup();
+ createProjects();
+ }
+
+ private void createProjects() {
+ for (int i = 0; i < PROJECT_COUNT; i++) {
+ Task task = new Task();
+ task.setId((long) i);
+ task.setName("Task " + i);
+ taskRepository.create(task);
+ Project project = new Project();
+ project.setId((long) i);
+ project.setName("Project " + (char) ('A' + i));
+ project.getTasks().add(task);
+ if (i == 5) {
+ project.setDescription("Test");
+ }
+ projectRepository.create(project);
+ }
+ }
+
+ @Override
+ protected TestApplication configure() {
+ TestApplication app = super.configure();
+ Map properties = new HashMap<>();
+ properties.put(CrnkProperties.FILTER_CRITERIA_IN_HTTP_BODY, Boolean.TRUE.toString());
+ properties.put(CrnkProperties.ALLOW_UNKNOWN_ATTRIBUTES, Boolean.TRUE.toString());
+ app.setProperties(properties);
+ return app;
+ }
+
+ @Override
+ protected void setupClient(CrnkClient client) {
+ client.setHttpAdapter(new HttpClientAdapter());
+ client.setFilterCriteriaInRequestBody(true);
+ ((DefaultQuerySpecUrlMapper) client.getUrlMapper()).setAllowUnknownAttributes(true);
+ client.getObjectMapper().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ client.getObjectMapper().registerModule(new JavaTimeModule());
+ }
+
+ @Test
+ public void testWithoutFilter() {
+ ResourceRepository projectRepo = client.getRepositoryForType(Project.class);
+ ResourceList projects = projectRepo.findAll(new QuerySpec(Project.class));
+ assertNotNull(projects);
+ assertEquals(PROJECT_COUNT, projects.size());
+ }
+
+ @Test
+ public void testWithSingleFilter() {
+ QuerySpec querySpec = new QuerySpec(Project.class);
+ querySpec.addFilter(PathSpec.of("id").filter(FilterOperator.LT, 5));
+ ResourceRepository projectRepo = client.getRepositoryForType(Project.class);
+ ResourceList projects = projectRepo.findAll(querySpec);
+ assertNotNull(projects);
+ assertEquals(4, projects.size());
+ }
+
+ @Test
+ public void testWithCombinedFilter() {
+ QuerySpec querySpec = new QuerySpec(Project.class);
+ querySpec.addFilter(FilterSpec.or(
+ PathSpec.of("id").filter(FilterOperator.LT, 5),
+ PathSpec.of("description").filter(FilterOperator.EQ, "Test")));
+ ResourceRepository projectRepo = client.getRepositoryForType(Project.class);
+ ResourceList projects = projectRepo.findAll(querySpec);
+ assertNotNull(projects);
+ assertEquals(5, projects.size());
+ }
+
+ @Test(expected = InternalServerErrorException.class)
+ public void testUnknownFilterAttributeIsPassedToServer() {
+ ResourceRepository projectRepo = client.getRepositoryForType(Project.class);
+ QuerySpec querySpec = new QuerySpec(Project.class);
+ String unknownPath = "unknown";
+ querySpec.addFilter(PathSpec.of(unknownPath).filter(FilterOperator.EQ, 1));
+ projectRepo.findAll(querySpec);
+ }
+
+ @Test
+ public void filterByDateWithPlainDate() {
+ Date now = new Date();
+ createResource(now);
+
+ QuerySpec querySpec = new QuerySpec(PrimitiveAttributeResource.class);
+ Date max = DateUtils.addDays(now, 1);
+ querySpec.addFilter(PathSpec.of("dateValue").filter(FilterOperator.LT, max));
+ ResourceRepository primitiveRepo = client.getRepositoryForType(PrimitiveAttributeResource.class);
+ ResourceList resources = primitiveRepo.findAll(querySpec);
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ }
+
+ private void createResource(Date date) {
+ ResourceRepository primitiveRepo = client.getRepositoryForType(PrimitiveAttributeResource.class);
+ PrimitiveAttributeResource resource = new PrimitiveAttributeResource();
+ resource.setId(1L);
+ resource.setDateValue(date);
+ primitiveRepo.create(resource);
+ }
+
+ @Test
+ public void filterByDateWithLocalDate() {
+ Date now = new Date();
+ createResource(now);
+
+ QuerySpec querySpec = new QuerySpec(PrimitiveAttributeResource.class);
+ LocalDate max = LocalDate.now().plusDays(-1);
+ querySpec.addFilter(PathSpec.of("dateValue").filter(FilterOperator.GT, max));
+ ResourceRepository primitiveRepo = client.getRepositoryForType(PrimitiveAttributeResource.class);
+ ResourceList resources = primitiveRepo.findAll(querySpec);
+ assertNotNull(resources);
+ assertEquals(1, resources.size());
+ }
+
+ @Test
+ public void testListOfFiltersIsSerializedCorrectly() {
+ QuerySpec querySpec = new QuerySpec(Project.class);
+ querySpec.addFilter(PathSpec.of("tasks.id").filter(FilterOperator.EQ, Arrays.asList(3L, 4L)));
+ querySpec.addFilter(PathSpec.of("tasks.name").filter(FilterOperator.EQ, Arrays.asList("Task 4", "Task 5")));
+ ResourceRepository projectRepo = client.getRepositoryForType(Project.class);
+ ResourceList projects = projectRepo.findAll(querySpec);
+ assertNotNull(projects);
+ assertEquals(1, projects.size());
+ }
+}
diff --git a/crnk-client/src/test/java/io/crnk/client/internal/ClientStubBaseTest.java b/crnk-client/src/test/java/io/crnk/client/internal/ClientStubBaseTest.java
index 01b887e38..9c940cf37 100644
--- a/crnk-client/src/test/java/io/crnk/client/internal/ClientStubBaseTest.java
+++ b/crnk-client/src/test/java/io/crnk/client/internal/ClientStubBaseTest.java
@@ -42,7 +42,7 @@ public void setup() {
urlBuilder = Mockito.mock(JsonApiUrlBuilder.class);
- stub = new ClientStubBase(client, urlBuilder, Task.class);
+ stub = new ClientStubBase(client, urlBuilder, Task.class, false);
}
@Test
diff --git a/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java b/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java
index 680d45dd8..bc55aa0b9 100644
--- a/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java
+++ b/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java
@@ -543,7 +543,9 @@ private void setupQuerySpecUrlMapper() {
if (moduleRegistry.getUrlMapper() == null) {
List list = serviceDiscovery.getInstancesByType(QuerySpecUrlMapper.class);
if (list.isEmpty()) {
- moduleRegistry.setUrlMapper(new DefaultQuerySpecUrlMapper());
+ DefaultQuerySpecUrlMapper urlMapper = new DefaultQuerySpecUrlMapper();
+ urlMapper.setFilterCriteriaInRequestBody("true".equals(propertiesProvider.getProperty(CrnkProperties.FILTER_CRITERIA_IN_HTTP_BODY)));
+ moduleRegistry.setUrlMapper(urlMapper);
}
else {
moduleRegistry.setUrlMapper(list.get(0));
diff --git a/crnk-core/src/main/java/io/crnk/core/boot/CrnkProperties.java b/crnk-core/src/main/java/io/crnk/core/boot/CrnkProperties.java
index e2e8acc55..4d695ba91 100644
--- a/crnk-core/src/main/java/io/crnk/core/boot/CrnkProperties.java
+++ b/crnk-core/src/main/java/io/crnk/core/boot/CrnkProperties.java
@@ -152,9 +152,17 @@ private CrnkProperties() {
*
* Set a boolean whether Crnk should throw
* {@link io.crnk.core.exception.ResourceNotFoundException ResourceNotFoundException} if related resources are missing.
- *
+ *
crnk.config.resource.request.filterCriteriaInHttp
* Set this to false
to ignore missing related resources.
*/
public static final String EXCEPTION_ON_MISSING_RELATED_RESOURCE = "crnk.config.serialize.relation.exceptionOnMissingRelatedResource";
+ /**
+ * Set a boolean if crnk should send filter criteria in the HTTP GET request body.
+ * By default and in compliance with the JSON API Spec, the filter has to be sent as query param in the URL.
+ * But this might result in errors related to the maximum supported length of URLs, especially with nested filters.
+ * Sending the parameters in the request body will allow for longer, more complex filters.
+ */
+ public static final String FILTER_CRITERIA_IN_HTTP_BODY = "crnk.config.resource.request.filterCriteriaInHttpBody";
+
}
diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/http/JsonApiRequestProcessorBase.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/http/JsonApiRequestProcessorBase.java
index e43934d07..374fd4eaa 100644
--- a/crnk-core/src/main/java/io/crnk/core/engine/internal/http/JsonApiRequestProcessorBase.java
+++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/http/JsonApiRequestProcessorBase.java
@@ -9,10 +9,7 @@
import io.crnk.core.engine.dispatcher.Response;
import io.crnk.core.engine.document.Document;
import io.crnk.core.engine.document.ErrorData;
-import io.crnk.core.engine.http.HttpHeaders;
-import io.crnk.core.engine.http.HttpRequestContext;
-import io.crnk.core.engine.http.HttpResponse;
-import io.crnk.core.engine.http.HttpStatus;
+import io.crnk.core.engine.http.*;
import io.crnk.core.exception.MethodNotAllowedException;
import io.crnk.core.module.Module;
import org.slf4j.Logger;
@@ -70,7 +67,7 @@ protected String getContentType() {
protected Document getRequestDocument(HttpRequestContext requestContext) throws JsonProcessingException {
byte[] requestBody = requestContext.getRequestBody();
- if (requestBody != null && requestBody.length > 0) {
+ if (requestBody != null && requestBody.length > 0 && !HttpMethod.GET.toString().equals(requestContext.getMethod())) {
ObjectMapper objectMapper = moduleContext.getObjectMapper();
try {
return objectMapper.readerFor(Document.class).readValue(requestBody);
diff --git a/crnk-core/src/main/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapper.java b/crnk-core/src/main/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapper.java
index 08c08d6e5..ebfd67dc1 100644
--- a/crnk-core/src/main/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapper.java
+++ b/crnk-core/src/main/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapper.java
@@ -3,6 +3,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.crnk.core.engine.http.HttpMethod;
import io.crnk.core.engine.information.resource.ResourceInformation;
import io.crnk.core.engine.internal.utils.ClassUtils;
import io.crnk.core.engine.internal.utils.PreconditionUtil;
@@ -29,6 +30,7 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -57,6 +59,8 @@ public class DefaultQuerySpecUrlMapper
private boolean allowUnknownParameters = false;
+ private boolean filterCriteriaInRequestBody;
+
protected QuerySpecUrlContext context;
protected QueryPathResolver pathResolver = new DefaultQueryPathResolver();
@@ -91,6 +95,10 @@ public void init(QuerySpecUrlContext ctx) {
jsonParser = new JsonFilterSpecMapper(ctx, supportedOperators, defaultOperator, pathResolver);
}
+ public JsonFilterSpecMapper getJsonParser() {
+ return jsonParser;
+ }
+
/**
* @return true if attribute paths must be separated with ".". Ealier
* Crnk versions did made use of brackets
@@ -178,6 +186,9 @@ public QuerySpec deserialize(ResourceInformation resourceInformation, Map pagingBehavior = entry.getPagingBehavior();
@@ -212,6 +226,20 @@ public QuerySpec deserialize(ResourceInformation resourceInformation, Map 0) {
+ try {
+ String body = new String(requestBody, StandardCharsets.UTF_8);
+ JsonNode jsonNode = context.getObjectMapper().readTree(body);
+ List filterSpecs = jsonParser.deserialize(jsonNode, resourceInformation, queryContext);
+ filterSpecs.forEach(rootQuerySpec::addFilter);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
private static void put(Map> map, String key, String value) {
map.put(key, new HashSet<>(Arrays.asList(value)));
}
@@ -270,7 +298,9 @@ protected void serialize(QuerySpec querySpec, Map> map, Quer
boolean isRoot = querySpec == rootQuerySpec;
- serializeFilters(querySpec, resourceInformation, map, isRoot, queryContext);
+ if (! filterCriteriaInRequestBody) {
+ serializeFilters(querySpec, resourceInformation, map, isRoot, queryContext);
+ }
serializeSorting(querySpec, resourceInformation, map, isRoot, queryContext);
serializeIncludedFields(querySpec, resourceInformation, map, isRoot, queryContext);
serializeIncludedRelations(querySpec, resourceInformation, map, isRoot, queryContext);
@@ -706,4 +736,12 @@ public boolean getAllowUnknownParameters() {
public QueryPathResolver getPathResolver() {
return pathResolver;
}
+
+ public boolean isFilterCriteriaInRequestBody() {
+ return filterCriteriaInRequestBody;
+ }
+
+ public void setFilterCriteriaInRequestBody(boolean filterCriteriaInRequestBody) {
+ this.filterCriteriaInRequestBody = filterCriteriaInRequestBody;
+ }
}
diff --git a/crnk-core/src/test/java/io/crnk/core/mock/TestHttpRequestContext.java b/crnk-core/src/test/java/io/crnk/core/mock/TestHttpRequestContext.java
new file mode 100644
index 000000000..c080dbf2c
--- /dev/null
+++ b/crnk-core/src/test/java/io/crnk/core/mock/TestHttpRequestContext.java
@@ -0,0 +1,108 @@
+package io.crnk.core.mock;
+
+import io.crnk.core.engine.http.HttpMethod;
+import io.crnk.core.engine.http.HttpRequestContext;
+import io.crnk.core.engine.http.HttpResponse;
+import io.crnk.core.engine.query.QueryContext;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+public class TestHttpRequestContext implements HttpRequestContext {
+ private byte[] requestBody = new byte[0];
+
+ @Override
+ public boolean accepts(String contentType) {
+ return false;
+ }
+
+ @Override
+ public boolean acceptsAny() {
+ return false;
+ }
+
+ @Override
+ public T unwrap(Class type) {
+ return null;
+ }
+
+ @Override
+ public Object getRequestAttribute(String name) {
+ return null;
+ }
+
+ @Override
+ public void setRequestAttribute(String name, Object value) {
+
+ }
+
+ @Override
+ public QueryContext getQueryContext() {
+ return null;
+ }
+
+ @Override
+ public boolean hasResponse() {
+ return false;
+ }
+
+ @Override
+ public Set getRequestHeaderNames() {
+ return null;
+ }
+
+ @Override
+ public String getRequestHeader(String name) {
+ return null;
+ }
+
+ @Override
+ public Map> getRequestParameters() {
+ return null;
+ }
+
+ @Override
+ public String getPath() {
+ return null;
+ }
+
+ @Override
+ public String getBaseUrl() {
+ return null;
+ }
+
+ @Override
+ public byte[] getRequestBody() {
+ return requestBody;
+ }
+
+ public void setRequestBody(byte[] requestBody) {
+ this.requestBody = requestBody;
+ }
+
+ public void setRequestBody(String body) {
+ requestBody = body.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public String getMethod() {
+ return HttpMethod.GET.toString();
+ }
+
+ @Override
+ public URI getRequestUri() {
+ return null;
+ }
+
+ @Override
+ public HttpResponse getResponse() {
+ return null;
+ }
+
+ @Override
+ public void setResponse(HttpResponse response) {
+
+ }
+}
diff --git a/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperDeserializerFromBodyTest.java b/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperDeserializerFromBodyTest.java
new file mode 100644
index 000000000..823e96878
--- /dev/null
+++ b/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperDeserializerFromBodyTest.java
@@ -0,0 +1,127 @@
+package io.crnk.core.queryspec.mapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.crnk.core.CoreTestContainer;
+import io.crnk.core.engine.information.resource.ResourceInformation;
+import io.crnk.core.engine.parser.TypeParser;
+import io.crnk.core.engine.registry.RegistryEntry;
+import io.crnk.core.engine.registry.ResourceRegistry;
+import io.crnk.core.mock.TestHttpRequestContext;
+import io.crnk.core.mock.models.Task;
+import io.crnk.core.mock.repository.TaskWithPagingBehavior;
+import io.crnk.core.mock.repository.TaskWithPagingBehaviorRepository;
+import io.crnk.core.mock.repository.TaskWithPagingBehaviorToProjectRepository;
+import io.crnk.core.module.SimpleModule;
+import io.crnk.core.queryspec.*;
+import io.crnk.core.queryspec.pagingspec.CustomOffsetLimitPagingBehavior;
+import io.crnk.core.queryspec.pagingspec.OffsetLimitPagingBehavior;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class DefaultQuerySpecUrlMapperDeserializerFromBodyTest extends AbstractQuerySpecTest {
+ protected DefaultQuerySpecUrlMapper urlMapper;
+
+ protected ResourceInformation taskInformation;
+
+ private ResourceInformation taskWithPagingBehaviorInformation;
+
+ private QuerySpecUrlContext deserializerContext;
+ private TestHttpRequestContext httpRequestContext;
+
+ @Before
+ public void setup() {
+ super.setup();
+
+ deserializerContext = new QuerySpecUrlContext() {
+
+ @Override
+ public ResourceRegistry getResourceRegistry() {
+ return resourceRegistry;
+ }
+
+ @Override
+ public TypeParser getTypeParser() {
+ return moduleRegistry.getTypeParser();
+ }
+
+ @Override
+ public ObjectMapper getObjectMapper() {
+ return container.getObjectMapper();
+ }
+
+ @Override
+ public UrlBuilder getUrlBuilder() {
+ return container.getModuleRegistry().getUrlBuilder();
+ }
+ };
+
+ urlMapper = new DefaultQuerySpecUrlMapper();
+ urlMapper.setFilterCriteriaInRequestBody(true);
+ urlMapper.init(deserializerContext);
+
+ RegistryEntry taskEntry = resourceRegistry.getEntry(Task.class);
+ taskInformation = taskEntry.getResourceInformation();
+ taskWithPagingBehaviorInformation = resourceRegistry.getEntry(TaskWithPagingBehavior.class).getResourceInformation();
+
+ httpRequestContext = new TestHttpRequestContext();
+ queryContext.setRequestContext(httpRequestContext);
+ }
+
+ @Override
+ protected void setup(CoreTestContainer container) {
+ super.setup(container);
+ SimpleModule customPagingModule = new SimpleModule("customPaging");
+ customPagingModule.addRepository(new TaskWithPagingBehaviorRepository());
+ customPagingModule.addRepository(new TaskWithPagingBehaviorToProjectRepository());
+ customPagingModule.addPagingBehavior(new OffsetLimitPagingBehavior());
+ customPagingModule.addPagingBehavior(new CustomOffsetLimitPagingBehavior());
+ container.addModule(customPagingModule);
+ }
+
+ @Test
+ public void testEmptyFilter() {
+ QuerySpec querySpec = urlMapper.deserialize(taskInformation, Collections.emptyMap(), queryContext);
+ assertNotNull(querySpec.getFilters());
+ assertTrue(querySpec.getFilters().isEmpty());
+ }
+
+ @Test
+ public void testSimpleEqualsFilter() {
+ httpRequestContext.setRequestBody("{\"name\": \"test\"}");
+ QuerySpec querySpec = urlMapper.deserialize(taskInformation, Collections.emptyMap(), queryContext);
+ List filters = querySpec.getFilters();
+ assertNotNull(filters);
+ assertEquals(1, filters.size());
+ FilterSpec filter = filters.get(0);
+ assertEquals(FilterOperator.EQ, filter.getOperator());
+ assertEquals(Collections.singletonList("name"), filter.getAttributePath());
+ assertEquals("test", filter.getValue());
+ }
+
+ @Test
+ public void testCompoundFilter() {
+ httpRequestContext.setRequestBody("{ \"OR\": [ {\"id\": [12, 13, 14]}, {\"name\": \"test\"} ] }");
+ QuerySpec querySpec = urlMapper.deserialize(taskInformation, Collections.emptyMap(), queryContext);
+ List filters = querySpec.getFilters();
+ assertNotNull(filters);
+ assertEquals(1, filters.size());
+ FilterSpec filter = filters.get(0);
+ assertEquals(FilterOperator.OR, filter.getOperator());
+ List expression = Arrays.asList(
+ PathSpec.of("id").filter(FilterOperator.EQ, Arrays.asList(12L, 13L, 14L)),
+ PathSpec.of("name").filter(FilterOperator.EQ, "test")
+ );
+ assertEquals(expression, filter.getExpression());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testFilterInUrlResultsInException() {
+ urlMapper.deserialize(taskInformation, Collections.singletonMap("filter[name]", Collections.singleton("test")), queryContext);
+ }
+}
diff --git a/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperSerializerTest.java b/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperSerializerTest.java
index eed20d919..b2b40a086 100644
--- a/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperSerializerTest.java
+++ b/crnk-core/src/test/java/io/crnk/core/queryspec/mapper/DefaultQuerySpecUrlMapperSerializerTest.java
@@ -12,11 +12,7 @@
import io.crnk.core.mock.models.Project;
import io.crnk.core.mock.models.Schedule;
import io.crnk.core.mock.models.Task;
-import io.crnk.core.queryspec.Direction;
-import io.crnk.core.queryspec.FilterOperator;
-import io.crnk.core.queryspec.FilterSpec;
-import io.crnk.core.queryspec.QuerySpec;
-import io.crnk.core.queryspec.SortSpec;
+import io.crnk.core.queryspec.*;
import io.crnk.core.queryspec.pagingspec.NumberSizePagingBehavior;
import io.crnk.core.queryspec.pagingspec.OffsetLimitPagingBehavior;
import org.junit.Assert;
@@ -49,6 +45,7 @@ public void setup() {
container.boot();
urlMapper = (DefaultQuerySpecUrlMapper) container.getBoot().getUrlMapper();
+ urlMapper.setFilterCriteriaInRequestBody(false);
resourceRegistry = container.getResourceRegistry();
urlBuilder = container.getModuleRegistry().getUrlBuilder();
@@ -278,4 +275,12 @@ private void check(String expectedUrl, Object id, QuerySpec querySpec) {
String actualUrl = urlBuilder.buildUrl(queryContext, entry.getResourceInformation(), id, querySpec);
assertEquals(expectedUrl, actualUrl);
}
+
+ @Test
+ public void testFilterNotAddedToUrlWhenInHttpBody() {
+ urlMapper.setFilterCriteriaInRequestBody(true);
+ QuerySpec querySpec = new QuerySpec(Schedule.class);
+ querySpec.addFilter(new FilterSpec(PathSpec.of("desc"), FilterOperator.EQ, "test"));
+ check("http://127.0.0.1/schedules", null, querySpec);
+ }
}
diff --git a/crnk-ui/package-lock.json b/crnk-ui/package-lock.json
index 311af2100..d453d72c5 100644
--- a/crnk-ui/package-lock.json
+++ b/crnk-ui/package-lock.json
@@ -470,15 +470,13 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz",
"integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==",
- "dev": true,
- "optional": true
+ "dev": true
},
"are-we-there-yet": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"dev": true,
- "optional": true,
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.3"
@@ -1522,8 +1520,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "dev": true,
- "optional": true
+ "dev": true
},
"constants-browserify": {
"version": "1.0.0",
@@ -1919,8 +1916,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "dev": true,
- "optional": true
+ "dev": true
},
"denodeify": {
"version": "1.2.1",
@@ -2733,7 +2729,6 @@
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"dev": true,
- "optional": true,
"requires": {
"graceful-fs": "4.1.11",
"inherits": "2.0.3",
@@ -2752,7 +2747,6 @@
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
- "optional": true,
"requires": {
"aproba": "1.1.2",
"console-control-strings": "1.1.0",
@@ -2769,7 +2763,6 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "1.0.1"
}
@@ -2779,7 +2772,6 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
@@ -2808,8 +2800,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
- "dev": true,
- "optional": true
+ "dev": true
},
"getpass": {
"version": "0.1.7",
@@ -3015,8 +3006,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "dev": true,
- "optional": true
+ "dev": true
},
"hash-base": {
"version": "2.0.2",
@@ -4554,8 +4544,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
- "dev": true,
- "optional": true
+ "dev": true
},
"matcher-collection": {
"version": "1.0.4",
@@ -4966,7 +4955,6 @@
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
- "optional": true,
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
@@ -7281,6 +7269,15 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -7308,15 +7305,6 @@
}
}
},
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "5.1.1"
- }
- },
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
@@ -8404,7 +8392,6 @@
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"dev": true,
- "optional": true,
"requires": {
"string-width": "1.0.2"
},
@@ -8414,7 +8401,6 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "1.0.1"
}
@@ -8424,7 +8410,6 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",