Skip to content

Commit

Permalink
Introduced support for custom paging behavior (#232)
Browse files Browse the repository at this point in the history
new @JsonApiResource.pagingBehavior attribute to specify a paging other than the typicall offset/limit pagin.
  • Loading branch information
vicmosin authored and remmeier committed Feb 26, 2018
1 parent 4b4d38c commit 71a9f4c
Show file tree
Hide file tree
Showing 72 changed files with 1,569 additions and 589 deletions.
40 changes: 24 additions & 16 deletions crnk-client/src/main/java/io/crnk/client/CrnkClient.java
@@ -1,17 +1,8 @@
package io.crnk.client;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import io.crnk.client.action.ActionStubFactory;
import io.crnk.client.action.ActionStubFactoryContext;
import io.crnk.client.http.HttpAdapter;
Expand Down Expand Up @@ -64,15 +55,26 @@
import io.crnk.core.module.ModuleRegistry;
import io.crnk.core.module.discovery.ResourceLookup;
import io.crnk.core.module.internal.DefaultRepositoryInformationProviderContext;
import io.crnk.core.queryspec.pagingspec.OffsetLimitPagingBehavior;
import io.crnk.core.repository.RelationshipRepositoryV2;
import io.crnk.core.repository.ResourceRepositoryV2;
import io.crnk.core.resource.list.DefaultResourceList;
import io.crnk.core.utils.Optional;
import io.crnk.legacy.internal.DirectResponseRelationshipEntry;
import io.crnk.legacy.internal.DirectResponseResourceEntry;
import io.crnk.legacy.registry.RepositoryInstanceBuilder;
import io.crnk.legacy.repository.RelationshipRepository;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

/**
* Client implementation giving access to JSON API repositories using stubs.
*/
Expand Down Expand Up @@ -129,7 +131,6 @@ public CrnkClient(ServiceUrlProvider serviceUrlProvider, ClientType clientType)
moduleRegistry.getHttpRequestContextProvider().setServiceUrlProvider(serviceUrlProvider);
moduleRegistry.addModule(new ClientModule());


resourceRegistry = new ClientResourceRegistry(moduleRegistry);
urlBuilder = new JsonApiUrlBuilder(resourceRegistry);

Expand All @@ -138,7 +139,7 @@ public CrnkClient(ServiceUrlProvider serviceUrlProvider, ClientType clientType)

switch (clientType) {
case OBJECT_LINKS:
moduleRegistry.addModule(new JacksonModule(objectMapper, true));
initJacksonModule(true);
documentMapper = new ClientDocumentMapper(moduleRegistry, objectMapper, new PropertiesProvider() {
@Override
public String getProperty(String key) {
Expand All @@ -150,13 +151,18 @@ public String getProperty(String key) {
});
break;
default:
moduleRegistry.addModule(new JacksonModule(objectMapper));
initJacksonModule(false);
documentMapper = new ClientDocumentMapper(moduleRegistry, objectMapper, new NullPropertiesProvider());
}

setProxyFactory(new BasicProxyFactory());
}

private void initJacksonModule(final boolean serializeLinksAsObjects) {
moduleRegistry.addModule(new JacksonModule(objectMapper, serializeLinksAsObjects,
Collections.singletonList(new OffsetLimitPagingBehavior())));
}

/**
* Finds and registers modules on the classpath trough the use of java.util.ServiceLoader.
* Each module can register itself for lookup by registering a ClientModuleFactory.
Expand Down Expand Up @@ -418,7 +424,8 @@ public ResourceRepositoryV2<Resource, String> getRepositoryForPath(String resour
init();

ResourceInformation resourceInformation =
new ResourceInformation(moduleRegistry.getTypeParser(), Resource.class, resourceType, null, null);
new ResourceInformation(moduleRegistry.getTypeParser(), Resource.class, resourceType, null, null,
new OffsetLimitPagingBehavior());
return new ResourceRepositoryStubImpl<>(this, Resource.class, resourceInformation, urlBuilder);
}

Expand All @@ -430,7 +437,8 @@ public RelationshipRepositoryV2<Resource, String, Resource, String> getRepositor
init();

ResourceInformation sourceResourceInformation =
new ResourceInformation(moduleRegistry.getTypeParser(), Resource.class, sourceResourceType, null, null);
new ResourceInformation(moduleRegistry.getTypeParser(), Resource.class, sourceResourceType, null, null,
new OffsetLimitPagingBehavior());
return new RelationshipRepositoryStubImpl<>(this, Resource.class, Resource.class, sourceResourceInformation, urlBuilder);
}

Expand Down
75 changes: 61 additions & 14 deletions crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java
@@ -1,11 +1,8 @@
package io.crnk.core.boot;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import io.crnk.core.engine.error.JsonApiExceptionMapper;
import io.crnk.core.engine.filter.DocumentFilter;
import io.crnk.core.engine.filter.ResourceFilterDirectory;
Expand Down Expand Up @@ -43,6 +40,8 @@
import io.crnk.core.queryspec.DefaultQuerySpecDeserializer;
import io.crnk.core.queryspec.QuerySpecDeserializer;
import io.crnk.core.queryspec.internal.QuerySpecAdapterBuilder;
import io.crnk.core.queryspec.pagingspec.OffsetLimitPagingBehavior;
import io.crnk.core.queryspec.pagingspec.PagingBehavior;
import io.crnk.core.repository.Repository;
import io.crnk.legacy.internal.QueryParamsAdapterBuilder;
import io.crnk.legacy.locator.JsonServiceLocator;
Expand All @@ -51,6 +50,10 @@
import io.crnk.legacy.repository.annotations.JsonApiRelationshipRepository;
import io.crnk.legacy.repository.annotations.JsonApiResourceRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* Facilitates the startup of Crnk in various environments (Spring, CDI,
* JAX-RS, etc.).
Expand Down Expand Up @@ -90,6 +93,9 @@ public class CrnkBoot {

private Boolean allowUnknownAttributes;

private Boolean allowUnknownParameters;

private List<PagingBehavior> pagingBehaviors;

private static String buildServiceUrl(String resourceDefaultDomain, String webPathPrefix) {
return resourceDefaultDomain + (webPathPrefix != null ? webPathPrefix : "");
Expand Down Expand Up @@ -161,6 +167,7 @@ public void boot() {
setupServiceUrlProvider();
setupServiceDiscovery();
setupQuerySpecDeserializer();
setupPagingBehavior();
bootDiscovery();
}

Expand Down Expand Up @@ -325,7 +332,7 @@ private void addModules() {

boolean serializeLinksAsObjects =
Boolean.parseBoolean(propertiesProvider.getProperty(CrnkProperties.SERIALIZE_LINKS_AS_OBJECTS));
moduleRegistry.addModule(new JacksonModule(objectMapper, serializeLinksAsObjects));
moduleRegistry.addModule(new JacksonModule(objectMapper, serializeLinksAsObjects, pagingBehaviors));

List<Module> discoveredModules = getInstancesByType(Module.class);
for (Module module : discoveredModules) {
Expand Down Expand Up @@ -444,6 +451,17 @@ public void setAllowUnknownAttributes() {
this.allowUnknownAttributes = true;
}

/**
* Sets the allow unknown query parameters for API requests.
* <p>
*/
public void setAllowUnknownParameters() {
PreconditionUtil.assertNull("Allow unknown parameters requires using the QuerySpecDeserializer, but " +
"it is null.", this.queryParamsBuilder);

this.allowUnknownParameters = true;
}

public ModuleRegistry getModuleRegistry() {
return moduleRegistry;
}
Expand All @@ -466,23 +484,48 @@ private void setupQuerySpecDeserializer() {
}
}


if (querySpecDeserializer instanceof DefaultQuerySpecDeserializer) {
if (defaultPageLimit != null) {
((DefaultQuerySpecDeserializer) this.querySpecDeserializer).setDefaultLimit(defaultPageLimit);
}
if (maxPageLimit != null) {
((DefaultQuerySpecDeserializer) this.querySpecDeserializer).setMaxPageLimit(maxPageLimit);
}
if (allowUnknownAttributes == null) {
String strAllow = propertiesProvider.getProperty(CrnkProperties.ALLOW_UNKNOWN_ATTRIBUTES);
if (strAllow != null) {
allowUnknownAttributes = Boolean.parseBoolean(strAllow);
}
}
if (allowUnknownAttributes != null) {
((DefaultQuerySpecDeserializer) this.querySpecDeserializer)
.setAllowUnknownAttributes(allowUnknownAttributes);
((DefaultQuerySpecDeserializer) this.querySpecDeserializer).setAllowUnknownAttributes(allowUnknownAttributes);
}
if (allowUnknownParameters == null) {
String strAllow = propertiesProvider.getProperty(CrnkProperties.ALLOW_UNKNOWN_PARAMETERS);
if (strAllow != null) {
allowUnknownParameters = Boolean.parseBoolean(strAllow);
}
}
if (allowUnknownParameters != null) {
((DefaultQuerySpecDeserializer) this.querySpecDeserializer).setAllowUnknownParameters(allowUnknownParameters);
}
}
}

private void setupPagingBehavior() {
if (pagingBehaviors == null) {
setupServiceDiscovery();

pagingBehaviors = new ArrayList<>();
pagingBehaviors.addAll(serviceDiscovery.getInstancesByType(PagingBehavior.class));

if (pagingBehaviors.isEmpty()) {
pagingBehaviors.add(new OffsetLimitPagingBehavior());
}
}

for (PagingBehavior pagingBehavior: pagingBehaviors) {
if (pagingBehavior instanceof OffsetLimitPagingBehavior) {
if (defaultPageLimit != null) {
((OffsetLimitPagingBehavior) pagingBehavior).setDefaultLimit(defaultPageLimit);
}
if (maxPageLimit != null) {
((OffsetLimitPagingBehavior) pagingBehavior).setMaxPageLimit(maxPageLimit);
}
}
}
}
Expand All @@ -505,4 +548,8 @@ public boolean isNullDataResponseEnabled() {
public ServiceUrlProvider getServiceUrlProvider() {
return moduleRegistry.getHttpRequestContextProvider().getServiceUrlProvider();
}

public List<PagingBehavior> getPagingBehaviors() {
return pagingBehaviors;
}
}
7 changes: 7 additions & 0 deletions crnk-core/src/main/java/io/crnk/core/boot/CrnkProperties.java
Expand Up @@ -137,6 +137,13 @@ private CrnkProperties() {
*/
public static final String ALLOW_UNKNOWN_ATTRIBUTES = "crnk.config.resource.request.allowUnknownAttributes";

/**
* <p>
* Set a boolean whether Crnk should allow unknown parameters in query parameters.
* </p>
*/
public static final String ALLOW_UNKNOWN_PARAMETERS = "crnk.config.resource.request.allowUnknownParameters";

/**
* <p>
* Set a boolean whether Crnk should links should be serialized as JSON objects.
Expand Down
@@ -1,10 +1,5 @@
package io.crnk.core.engine.information;

import io.crnk.core.engine.internal.information.DefaultInformationBuilder;
import io.crnk.core.repository.RelationshipMatcher;
import io.crnk.core.resource.annotations.RelationshipRepositoryBehavior;
import java.lang.reflect.Type;

import io.crnk.core.engine.information.repository.RelationshipRepositoryInformation;
import io.crnk.core.engine.information.repository.RepositoryMethodAccess;
import io.crnk.core.engine.information.repository.ResourceRepositoryInformation;
Expand All @@ -13,9 +8,14 @@
import io.crnk.core.engine.information.resource.ResourceFieldAccessor;
import io.crnk.core.engine.information.resource.ResourceFieldType;
import io.crnk.core.engine.information.resource.ResourceInformation;
import io.crnk.core.queryspec.pagingspec.PagingBehavior;
import io.crnk.core.repository.RelationshipMatcher;
import io.crnk.core.resource.annotations.LookupIncludeBehavior;
import io.crnk.core.resource.annotations.RelationshipRepositoryBehavior;
import io.crnk.core.resource.annotations.SerializeType;

import java.lang.reflect.Type;

public interface InformationBuilder {

Field createResourceField();
Expand Down Expand Up @@ -52,6 +52,8 @@ interface Resource {

Resource superResourceType(String superResourceType);

Resource pagingBehavior(PagingBehavior pagingBehavior);

ResourceInformation build();

}
Expand Down
Expand Up @@ -14,6 +14,7 @@
import io.crnk.core.exception.MultipleJsonApiMetaInformationException;
import io.crnk.core.exception.ResourceDuplicateIdException;
import io.crnk.core.exception.ResourceException;
import io.crnk.core.queryspec.pagingspec.PagingBehavior;
import io.crnk.core.resource.annotations.JsonApiResource;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -90,6 +91,11 @@ public class ResourceInformation {

private ResourceValidator validator;

/**
* {@link PagingBehavior} instance
*/
private PagingBehavior pagingBehavior;

private StringMapper idStringMapper = new StringMapper() {
@Override
public String toString(Object input) {
Expand All @@ -104,19 +110,20 @@ public Object parse(String input) {
};

public ResourceInformation(TypeParser parser, Class<?> resourceClass, String resourceType, String superResourceType,
List<ResourceField> fields) {
this(parser, resourceClass, resourceType, superResourceType, null, fields);
List<ResourceField> fields, PagingBehavior pagingBehavior) {
this(parser, resourceClass, resourceType, superResourceType, null, fields, pagingBehavior);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public ResourceInformation(TypeParser parser, Class<?> resourceClass, String resourceType, String superResourceType,
ResourceInstanceBuilder<?> instanceBuilder, List<ResourceField> fields) {
ResourceInstanceBuilder<?> instanceBuilder, List<ResourceField> fields, PagingBehavior pagingBehavior) {
this.parser = parser;
this.resourceClass = resourceClass;
this.resourceType = resourceType;
this.superResourceType = superResourceType;
this.instanceBuilder = instanceBuilder;
this.fields = fields;
this.pagingBehavior = pagingBehavior;

initFields();
if (this.instanceBuilder == null) {
Expand Down Expand Up @@ -416,4 +423,7 @@ public List<ResourceField> getFields() {
return Collections.unmodifiableList(fields);
}

public PagingBehavior getPagingBehavior() {
return pagingBehavior;
}
}

0 comments on commit 71a9f4c

Please sign in to comment.