Crnk is an implementation of the JSON API specification and recommendations in Java. It allows you to rapidly build REST APIs without having to worry about lower protocol details and lets you instead focus on your application. JSON API and Crnk come with support for:

  • client and server implementation (with a focus on the later).

  • standardized url handling such as /api/persons?filter[title]=John and /api/persons/{id}

  • sorting, filtering, paging of resources

  • attaching link and meta information to data.

  • inserting, updating and deleting of resources.

  • support to request complex object graphs in a single request with JSON API inclusions.

  • support for partial objects with sparse field sets.

  • atomically create, update and delete multiple with jsonpatch.com.

  • a flexible module API to choose and extend the feature set of Crnk.

  • eased testing with the client implementation providing type-safe stubs to access server repositories.

  • repositories providing runtime/meta information about Crnk to implement, for example, documentation and UI automation.

  • generation of type-safe client stubs (currently Typescript as target language implemented)

Next to that Crnk supports many popular frameworks and APIs:

  • CDI: resolve repositories and extensions with CDI.

  • Spring: run Crnk with Spring, including support for Spring Boot, Spring ORM and Spring Security.

  • Servlet API: run Crnk as servlet.

  • JAXRS: run Crnk as feature.

  • JPA: expose entities as JSON API resources.

  • bean validation: properly marshal validation and constraints exceptions.

  • Zipkin: trace all your calls.

1. Architecture

Resources and repositories are the main object types in use by Crnk. Those types closely reflect the JSON API specification. Resources hold the data; while repositories implement the access to those resources. A running Crnk application can have any number of those repositories which may or may not be related. To establish such relations, there is a further distinction between resource repositories and relationship repositories.

The crnk-core project hosts a engine package that provides an implementation of the JSON API specification. It processes incoming requests and makes the appropriate calls to repositories. A single request may make one or multiple calls to repositories. Multiple repository are called when the request asks for inclusions of related resoures where the main repository chooses to not directly handle those inclusions by itself (explained in detail in later chapters). In the background the engine takes care of details like serialization, repository lookup and error handling. For this to work it offers a module API to customize and extend the Crnk feature set. The engine does not dictate how resources and repositories are implemented. It makes use of a more abstract information model and adapters to forward call to a particular implementation. This allows for a lot of flexibility in the repository design. In many cases users will make use of annotations and interfaces provided by crnk-core which follow the specification and recommendations of JSON API. But there is also the possibility to implement repositories in other fashions like it has been done in crnk-jpa.

The subsequent chapters explain how to setup and use Crnk.

2. Setup

There are two main, orthogonal aspects of Crnk that need configuration:

  1. The integration into a web framework like JAXRS or the Servlet API to be able to process requests.

  2. The discovery of repositories, modules, exception mappers, etc.

The subsequent sections explain various possiblities resp. how to implement an own one.

2.1. Requirements

Crnk library requires minimum Java 7 to build and run.

2.2. Integration with JAX-RS

Crnk allows integration with JAX-RS environments through the usage of JAX-RS specification. JAX-RS 2.0 is required for this integration. Under the hood there is a @PreMatching filter which checks each request for JSON API processing. The Setup can look as simple as:

2.2.1. CrnkFeature

@ApplicationPath("/")
	public class MyApplication extends Application {

		@Override
		public Set<Object> getSingletons() {
			CrnkFeature crnkFeature = new CrnkFeature();
			return Collections.singleton((Object)crnkFeature);
		}
	}

CrnkFeature provides various accessors to customize the behavior of Crnk. A more advanced setup may look like:

	public class MyAdvancedCrnkFeature implements Feature {

		@Inject
		private EntityManager em;

		@Inject
		private EntityManagerFactory emFactory;

		...

		@Override
		public boolean configure(FeatureContext featureContext) {
			// also map entities to JSON API resources (see further below)
			JpaModule jpaModule = new JpaModule(emFactory, em, transactionRunner);
			jpaModule.setRepositoryFactory(new ValidatedJpaRepositoryFactory());

			// JSON API compliant URL handling with QuerySpec
			DefaultQuerySpecDeserializer querySpecDeserializer = new DefaultQuerySpecDeserializer();

			// limit all incoming requests to 20 resources if not specified otherwise
			querySpecDeserializer.setDefaultLimit(20L);

			ServiceLocator serviceLocator = ...
			CrnkFeature feature = new CrnkFeature(new ObjectMapper(), querySpecDeserializer, serviceLocator);
			feature.addModule(jpaModule);

			featureContext.register(feature);
			return true;
		}
	}

Note that depending on the discovery mechanism in use (like Spring or CDI), modules like this JpaModule can be picked up automatically.

2.2.2. Exception mapping for JAX-RS services

In many cases Crnk repositories are used along regular JAX-RS services. In such scenarios it can be worthwhile if Crnk repositories and JAX-RS services make use of the same exception handling and response format. To make use of the JSON API resp. Crnk exception handling in JAX-RS services, one can add the JsonapiExceptionMapperBridge to the JAX-RS application. The constructor of JsonapiExceptionMapperBridge takes CrnkFeature as parameter.

For an example have a look at the next section which make use of it together with JsonApiResponseFilter.

2.2.3. Use JSON API format with JAX-RS services

Similar to JsonapiExceptionMapperBridge in the previous section, it is possible for JAX-RS services to return resources in JSON API format with JsonApiResponseFilter. JsonApiResponseFilter wraps primitive responses with a data object; resource objects with data and included objects. The constructor of JsonApiResponseFilter takes CrnkFeature as parameter.

To determine which JAX-RS services should be wrapped, JsonApiResponseFilter checks whether the @Produce annotation delivers JSON API. The produce annotation can be added, for example, to the class:

ScheduleRepository.java
@Path("schedules")
@Produces(HttpHeaders.JSONAPI_CONTENT_TYPE)

And the JAX-RS application setup looks like:

JsonApiResponseFilterTestBase.java
	@ApplicationPath("/")
	class TestApplication extends ResourceConfig {

		TestApplication(JsonApiResponseFilterTestBase instance, boolean enableNullResponse) {
			instance.setEnableNullResponse(enableNullResponse);

			property(CrnkProperties.RESOURCE_SEARCH_PACKAGE, "io.crnk.rs.resource");
			property(CrnkProperties.NULL_DATA_RESPONSE_ENABLED, Boolean.toString(enableNullResponse));

			CrnkFeature feature = new CrnkFeature();
			feature.addModule(new TestModule());

			register(new JsonApiResponseFilter(feature));
			register(new JsonapiExceptionMapperBridge(feature));
			register(new JacksonFeature());

			register(feature);
		}
	}

Note that:

  • CrnkProperties.NULL_DATA_RESPONSE_ENABLED determines whether null responses should be wrapped as JSON API responses.

  • Make use of proper service discovery instead of CrnkProperties.RESOURCE_SEARCH_PACKAGE in real applications.

2.2.4. JAX-RS interoperability

Note that it is possible to implement repositories that host both JAX-RS and JSON-API methods to complement JSON API repositories with non-resource based services. Have a look at the Crnk Client chapter for an example.

2.3. Integration with Servlet API

There are two ways of integrating crnk using Servlets:

  • Adding an instance of AbstractCrnkServlet

  • Adding an instance of AbstractCrnkFilter

2.3.1. Integrating using a Servlet

There is a CrnkServlet implementation allowing to integrate Crnk into a Servlet environment. It can be configured with all the parameters outlined in the subsequent sections. Many times application will desire to do more advanced customizations, in this case one can extends CrnkServlet and get access to CrnkBoot. The code below shows a sample implementation:

SampleCrnkServlet.java
public class SampleCrnkServlet extends CrnkServlet {

	@Override
	protected void initCrnk(CrnkBoot boot) {
		// do your configuration here
	}
}

The newly created servlet must be added to the web.xml file or to another deployment descriptor. The code below shows a sample web.xml file with a properly defined and configured servlet:

  <web-app>
    <servlet>
      <servlet-name>SampleCrnkServlet</servlet-name>
      <servlet-class>io.crnk.servlet.SampleCrnkServlet</servlet-class>
      <init-param>
        <param-name>crnk.config.core.resource.package</param-name>
        <param-value>io.crnk.servlet.resource</param-value>
      </init-param>
    </servlet>
    <servlet-mapping>
      <servlet-name>SampleCrnkServlet</servlet-name>
      <url-pattern>/api/v1/ *</url-pattern>
    </servlet-mapping>
  </web-app>

You may omit the resourceSearchPath depending on which discovery mechanism is in place (see below).

2.3.2. Integrating using a filter

Integrating Crnk as a Servlet filter works in a very similar fashion as for servlets:

SampleCrnkFilter.java
public class SampleCrnkFilter extends CrnkFilter {

	@Override
	protected void initCrnk(CrnkBoot boot) {
		// do your configuration here
	}
}

The newly created filter must be added to web.xml file or other deployment descriptor. A code below shows a sample web.xml file with properly defined and configured filter

  <web-app>
    <filter>
      <filter-name>SampleCrnkFilter</filter-name>
      <filter-class>io.crnk.servlet.SampleCrnkFilter</filter-class>
      <init-param>
        <param-name>crnk.config.core.resource.package</param-name>
        <param-value>io.crnk.servlet.resource</param-value>
      </init-param>
    </filter>
  </web-app>

2.4. Integration with Spring

Crnk provides a simple Spring Boot integration using the @Configuration annotated class CrnkConfigV3. Using this class, the only thing needed to allow Crnk process requests is parameter configuration. An example application.properties file is presented below.

  crnk.domainName=http://localhost:8080
  crnk.pathPrefix=/api

Spring integration uses crnk-servlet AbstractCrnkFilter to fetch the requests. Similar to CDI, repositories and modules are picked up from the Spring ApplicationContext with SpringServiceDiscovery.

2.5. Discovery with CDI

To enable CDI support, add io.crnk:crnk-cdi to your classpath. Crnk will then pickup the CdiServiceDiscovery implementation and use it to discover its modules and repositories. Modules, repositories, etc. will then be picked up if they are registered as CDI beans.

By default Cdi.current() is used to obtain a BeanManager. The application may also make use of CdiServiceDiscovery.setBeanManager(…​) to set a custom one. The various integrations like CrnkFeature provide a setServiceDiscovery method to set a customized instance.

WARN: Cdi.current() has shown to be unreliable in some cases when doing EAR deployment. In such cases it is highly recommended to set the BeanManager manually.

2.6. Discovery with Guice

A GuiceServiceDiscovery implementation is provided. The various integrations like CrnkFeature provide a setServiceDiscovery method to set the instance. For an example have a look at the dropwizard example application (https://github.com/crnk-project/crnk-framework/tree/master/crnk-examples/dropwizard-simple-example).

2.7. Discovery with Spring

The Spring integration comes with a SpringServiceDiscovery that makes use of the Spring ApplicationContext to discover beans.

2.8. Discovery without a dependency injection framework

If no dependency injection framework is used, Crnk can also discover beans on its own. For this purpose, the org.reflections:reflections library has to be added to the classpath and the CrnkProperties.RESOURCE_SEARCH_PACKAGE be defined. In JAX-RS this may look like:

@ApplicationPath("/")
	public class MyApplication extends Application {

		@Override
        public Set<Object> getSingletons() {
            CrnkFeature crnkFeature = new CrnkFeature();
            crnkFeature.getBoot().setServiceLocator(...);
            return Collections.singleton((Object)crnkFeature);
        }

		@Override
        public Map<String, Object> getProperties() {
            Map<String, Object> map = new HashMap<>();
            map.put(CrnkProperties.RESOURCE_SEARCH_PACKAGE, "com.myapplication.model")
            return map;
        }
	}

A JsonServiceLocator service locator can be provided to control the instatiation of object. By default the default constructor will be used. The CrnkProperties.RESOURCE_SEARCH_PACKAGE property is passed to define which package should be searched for beans. Multiple packages can be passed by specifying a comma separated string of packages i.e. com.company.service.dto,com.company.service.repository. It will pick up any public non-abstract class that makes use of Crnk interfaces, like repositories, exception mappers and modules.

2.9. No Discovery

It is also possible to make use of no discovery mechanism at all. In this case it is still possible to add repositories and other features through modules. Have a look at the various module related chapters.

2.10. CrnkBoot

CrnkBoot is a class shared among all the different integrations that takes care of setting up and starting Crnk. Every integration will provide access to it, which in turn allows for virtually any kind of customization.

Some possiblities:

  • getObjectMapper allows access to the used Jackson instance.

  • addModule allows to add a module.

  • setServiceDiscovery sets a custom service discovery mechanism.

  • setPropertiesProvider allows to set how properties are resolved.

  • getQuerySpecDeserializer and setQuerySpecDeserializer allows to reconfigure how parameters are parsed. Note that in some areas JSON API only provides reocmmendations and Crnk follows those recommendations by default. So depending on your use cases, you may want to configure or implement some aspects differently.

  • setMaxPageLimit allows to set the maximum number of allowed resources that can be fetched with a request by limiting pagination.

  • setDefaultPageLimit allows to set a default page limit if none is specified by the request. Highly recommended to be used as people frequently browse repositories on there own with a web browser and fail to provide pagination. As a result, your entire database may get downloaded and may bring down your servers depending on the datasize.

2.11. Parameters

Any of the integrations allows API access to customize Crnk. There are also a number of configuration flags provided by CrnkProperties:

  • crnk.config.core.resource.domain Domain name as well as protocol and optionally port number used when building links objects in responses i.e. http://crnk.io. The value must not end with /. If the property is omitted, then they are extracted from the incoming request, which should work well for most use cases.

  • crnk.config.web.path.prefix Default prefix of a URL path used in two cases:

    • When building links objects in responses

    • When performing method matching An example of a prefix /api/v1.

  • crnk.config.include.paging.packagingEnabled enables pagination for inclusions. Disabled by default. Be aware this may inadvertently enable pagination for included resources when doing paging on root resources if data structures are cyclic. See CrnkProperties.INCLUDE_PAGING_ENABLED fore mor information.

  • crnk.config.include.behavior with possible values BY_TYPE (default) and BY_ROOT_PATH. BY_ROOT_PATH specifies that an inclusion can only requested as path from the root resource such as include[tasks]=project.schedule. While BY_TYPE can further request inclusions by type directly such as include[tasks]=project&include[projects]=schedule. For simple object structures they are semantically the same, but they do differ for more complex ones, like when multiple attributes lead to the same type or for cycle structures. In the later case BY_TYPE inclusions become recursive, while BY_ROOT_PATH do not. Note that the use of BY_TYPE outmatches BY_ROOT_PATH, so BY_TYPE includes everything BY_ROOT_PATH does and potentially more. For more information see CrnkProperties.INCLUDE_BEHAVIOR.

  • crnk.config.resource.immutableWrite with values IGNORE (default) or FAIL. Determines how to deal with field that cannot be changed upon a PATCH or POST request. For more information see CrnkProperties.RESOURCE_FIELD_IMMUTABLE_WRITE_BEHAVIOR.

  • crnk.config.resource.response.return_404 with values true and false (default). Enforces a 404 response should a repository return a null value. This is common practice, but not strictly mandated by the JSON API specification. In general it is recommended for repository to throw ResourceNotFoundException.

3. Resource

A resource as defined by JSON API holds the actual data. The engine part of crnk-core is agnostic to how such resources are actually implemented (see the architecture and modules chapters). This chapter gives information of the most common way using annotations provided by crnk-core.

3.1. JsonApiResource

It is the most important annotation which defines a resource. It requires type parameter to be defined that is used to form a URLs and type field in passed JSONs. According to JSON API standard, the name defined in type can be either plural or singular

The example below shows a sample class which contains a definition of a resource.

  @JsonApiResource(type = "tasks")
  public class Task {
    // fields, getters and setters
  }

3.2. JsonApiId

Defines a field which will be used as an identifier of a resource. Each resource requires this annotation to be present on a field which type implements Serializable or is of primitive type.

The example below shows a sample class which contains a definition of a field which contains an identifier.

  @JsonApiResource(type = "tasks")
  public class Task {
    @JsonApiId
    private Long id;

    // fields, getters and setters
  }

3.3. JsonApiRelation

Indicates an association to either a single value or collection of resources. The type of such fields must be a valid resource.

The example below shows a sample class which contains this kind of relationship.

  @JsonApiResource(type = "tasks")
  public class Task {

    // ID field

    @JsonApiRelation(lookUp=LookupIncludeBehavior.AUTOMATICALLY_WHEN_NULL,serialize=SerializeType.ONLY_ID)
    private Project project;

    // fields, getters and setters
  }

The optional serialize parameter specifies how the association should be serialized when making a request. There are two things to consider. Whether related resources should be added to the include section of the response document. And whether the id of related resources should be serialized along with the resource in the corresponding relationships.[name].data section. Either LAZY, ONLY_ID or EAGER can be specified:

  • LAZY only serializes the ID and does the inclusion if explicitly requested by the include URL parameter. This is the default.

  • ONLY_ID always serializes the ID, but does only to an inclusion if explicitly requested by the include URL parameter.

  • EAGER always both serializes the ID and does an inclusion.

There are two possibilities of how related resources are fetched. Either the requested repository directly returns related resources with the returned resources. Or Crnk can take-over that work by doing nested calls to the corresponding RelationshipRepositoryV2 implementations. The behavior is controlled by the optional lookUp parameter. There are three options:

  • 'NONE' makes the requested repository responsible for returning related resources. This is the default.

  • 'AUTOMATICALLY_WHEN_NULL' will let Crnk lookup related resources if not already done by the requested repository.

  • 'AUTOMATICALLY_ALWAYS' will force Crnk to always lookup related resource regardless whether it is already done by the requested repository.

3.4. JsonApiMetaInformation

Field or getter annotated with JsonApiMetaInformation are marked to carry a MetaInformation implementation. See http://jsonapi.org/format/#document-meta for more information about meta data. Example:

	@JsonApiResource(type = "projects")
	public class Project {

		...

		@JsonApiMetaInformation
		private ProjectMeta meta;

		public static class ProjectMeta implements MetaInformation {

			private String value;

			public String getValue() {
				return value;
			}

			public void setValue(String value) {
				this.value = value;
			}
		}
	}

3.5. JsonApiLinksInformation

Field or getter annotated with JsonApiLinksInformation are marked to carry a LinksInformation implementation. See http://jsonapi.org/format/#document-links for more information about linking. Example:

	@JsonApiResource(type = "projects")
	public class Project {

		...

		@JsonApiLinksInformation
		private ProjectLinks links;

		public static class ProjectLinks implements MetaInformation {

			private String value;

			public String getValue() {
				return value;
			}

			public void setValue(String value) {
				this.value = value;
			}
		}
	}

3.6. Jackson annotations

Crnk comes with (partial) support for Jackson annotations. Currently supported are:

Annotation Description

@JsonIgnore

Excludes a given attribute from serialization.

@JsonProperty.value

Renames an attribute during serialization.

@JsonProperty.access

Specifies whether an object can be read and/or written. WRITE_ONLY is not yet supported.

Support for more annotations will be added in the future. PRs welcomed.

4. Repositories

The modelled resources must be complemented by a corresponding repository implementation. This is achieved by implementing one of those two repository interfaces:

  • ResourceRepositoryV2 for a resource

  • RelationshipRepositoryV2 resp. BulkRelationshipRepositoryV2 for resource relationships

4.1. ResourceRepositoryV2

Base repository which is used to operate on resources. Each resource should have a corresponding repository implementation. It consist of five basic methods which provide a CRUD for a resource and two parameters: the first is a type of a resource and the second is a type of the resource’s identifier.

The methods are as follows:

  • findOne(ID id, QuerySpec querySpec) Search one resource with a given ID. If a resource cannot be found, a ResourceNotFoundException exception should be thrown. It should return an entity with associated relationships.

  • findAll(QuerySpec querySpec) Search for all of the resources. An instance of QuerySpec can be used if necessary. If no resources can be found an empty Iterable or null must be returned. It should return entities with associated relationships.

  • findAll(Iterable<ID>ids, QuerySpec querySpec) Search for resources constrained by a list of identifiers. An instance of QuerySpec can be used if necessary. If no resources can be found an empty Iterable or null must be returned. It should return entities with associated relationships.

  • save(S entity) Saves a resource. It should not save relating relationships. A Returning resource must include assigned identifier created for the instance of resource. This method should be able to both create a new resource and update existing one.

  • delete(ID id) Removes a resource identified by id parameter.

The ResourceRepositoryBase is a base class that takes care of some boiler-plate, like implementing findOne with findAll. An implementation can then look as simple as:

	public class ProjectRepository extends ResourceRepositoryBase<Project, String> {

		private Map<Long, Project> projects = new HashMap<>();

		public ProjectRepository() {
			super(Project.class);
			save(new Project(1L, "Project A"));
			save(new Project(2L, "Project B"));
			save(new Project(3L, "Project C"));
		}

		@Override
		public synchronized void delete(String id) {
			projects.remove(id);
		}

		@Override
		public synchronized <S extends Project> S save(S project) {
			projects.put(project.getId(), project);
			return project;
		}

		@Override
		public synchronized ResourceList<Project> findAll(QuerySpec querySpec) {
			return querySpec.apply(projects.values());
		}
	}

There is further a ReadOnlyResourceRepositoryBase base class that does not allow to override the create, delete and update methods. crnk-meta accordingly reports insertable, updateable, deltable for such repositories as false.

4.2. RelationshipRepositoryV2

Each relationship defined in Crnk (annotation @JsonApiToOne and @JsonApiToMany) must have a relationship repository defined.

Base unidirectional repository responsible for operations on relations. All of the methods in this interface have fieldName field as their last parameter to solve the problem of many relationships between the same resources.

  • setRelation(T source, D_ID targetId, String fieldName) Sets a resource defined by targetId to a field fieldName in an instance source. If no value is to be set, null value is passed.

  • setRelations(T source, Iterable<D_ID> targetIds, String fieldName) Sets resources defined by targetIds to a field fieldName in an instance source. This is a all-or-nothing operation, that is no partial relationship updates are passed. If no values are to be set, empty Iterable is passed.

  • addRelations(T source, Iterable<D_ID> targetIds, String fieldName) Adds relationships to a list of relationships.

  • removeRelations(T source, Iterable<D_ID> targetIds, String fieldName) Removes relationships from a list of relationships.

  • findOneTarget(T_ID sourceId, String fieldName, QuerySpec querySpec) Finds one field’s value defined by fieldName in a source defined by sourceId.

  • findManyTargets(T_ID sourceId, String fieldName, QuerySpec querySpec) Finds an Iterable of field’s values defined by fieldName in a source defined by sourceId .

This interface must be implemented to let Crnk work correctly, some of the requests are processed using only this kind of repository. As it can be seen above, there are two kinds of methods: for multiple and single relationships and it is possible to implement only one type of methods, e.g. singular methods. Nevertheless, it should be avoided because of potential future problems when adding new fields of other sizes.

In many cases, relationship operations can be mapped back to resource repository operations. Making the need for a custom relationship repository implementation redundant. A findManyTargets request might can be served by filtering the target repository. Or a relationship can be set by invoking the save operation on either the source or target resource repository (usually you want to save on the single-valued side). The ResourceRepositoryBase is a base class that takes care of exactly this. A repository implementation then looks as simple as:

	public class ProjectToTaskRepository extends RelationshipRepositoryBase<Project, Long, Task, Long> {

		public ScheduleToTaskRepository() {
			super(Project.class, Task.class);
		}
	}

For this to work, relations must be set up bidirectionally with the opposite attribute:

	@JsonApiResource(type = "tasks")
	public class Task {

		@JsonApiToOne(opposite = "tasks")
		@JsonApiIncludeByDefault
		private Project project;

	    ...
	}

4.3. BulkRelationshipRepositoryV2

BulkRelationshipRepositoryV2 extends RelationshipRepositoryV2 and provides an additional findTargets method. It allows to fetch a relation for multiple resources at once. It is recommended to make use of this implementation if a relationship is loaded frequently (either by a eager declaration or trough the include parameter) and it is costly to fetch that relation. RelationshipRepositoryBase provides a default implementation where findOneTarget and findManyTargets forward calls to the bulk findTargets.

4.4. ResourceList

ResourceRepositoryV2 and RelationshipRepositoryV2 return lists of type ResourceList. The ResourceList can carry, next to the actual resources, also meta and links information:

  • getLinks() Gets the links information attached to this lists.

  • getMeta() Gets the meta information attached to this lists.

  • getLinks(Class<L> linksClass) Gets the links information of the given type attached to this lists. If the given type is not found, null is returned.

  • getMeta(Class<M> metaClass) Gets the meta information of the given type attached to this lists. If the given type is not found, null is returned.

There is a default implementation named DefaultResourceList. To gain type-safety, improved readability and crnk-client support, application may provide a custom implementation extending ResourceListBase:

	class ScheduleList extends ResourceListBase<Schedule, ScheduleListMeta, ScheduleListLinks> {

	}

	class ScheduleListLinks implements LinksInformation {

		public String name = "value";

		...
	}

	class ScheduleListMeta implements MetaInformation {

		public String name = "value";

		...
	}

This implementation can then be added to a repository interface declaration and used by both servers and clients:

	public interface ScheduleRepository extends ResourceRepositoryV2<Schedule, Long> {

		@Override
		public ScheduleList findAll(QuerySpec querySpec);

	}

4.5. Query parameters with QuerySpec

Crnk passes JSON API query parameters to repositories trough a QuerySpec parameter. It holds request parameters like sorting and filtering specified by JSON API. The subsequent sections will provide a number of example.

Note
Not everything is specified by JSON API. For some request parameters only recommendations are provided as different applications are likely to be in need of different semantics and implementations. For this reason the engine part in crnk-core makes use of QueryAdapter and allows implementations other than QuerySpec (like the legacy QueryParams).

4.5.1. Filtering

Note
The JSON API specification does not a mandate a specific filtering semantic. Instead it provides a recommendation that comes by default with Crnk. Depending on the data store in use, application may choose to extend or replace that default implementation.

Resource filtering can be achieved by providing parameters which start with filter. The format for filters: filter[ResourceType][property|operator]([property|operator])* = "value"

  • GET /tasks/?filter[name]=Super task

  • GET /tasks/?filter[name][EQ]=Super task

  • GET /tasks/?filter[tasks][name]=Super task

  • GET /tasks/?filter[tasks][name]=Super task&filter[tasks][dueDate]=2015-10-01

QuerySpec uses the EQ operator if no operator was provided.

Operators are represented by the FilterOperator class. Crnk comes with a set of default filters:

Name Descriptor

EQ

equals operator where values match exactly.

NEQ

not equals where values do not match.

LIKE

where the value matches the specified pattern. It is usually not case-sensitive and makes use of % as wildclard, but may different depending on the underlying implementation.

LT

lower than the specified value

LE

lower than or equals the specified value

GT

greater than the specified value

GE

greater than or equals the specified value

The application is free to implements its own FilterOperator. Next to the name a matches method can be implemented to support in-memory filtering with QuerySpec.apply. Otherwise, it is up to the repository implementation to handle the various filter operators; usually by translating them to datastore-native query expressions. Custom operators can be registered with DefaultQuerySpecDeserializer.addSupportedOperator(..). The default operator can be overridden by setting DefaultQuerySpecDeserializer.setDefaultOperator(…​).

4.5.2. Sorting

Sorting information for the resources can be achieved by providing sort parameter.

  • GET /tasks/?sort=name,-shortName

  • GET /tasks/?sort[projects]=name,-shortName&include=projects

4.5.3. Pagination

Pagination for the repositories can be achieved by providing page parameter. The format for pagination: page[offset|limit] = "value", where value is an integer

Example:

  • GET /tasks/?page[offset]=0&page[limit]=10

The JSON API specifies first, previous, next and last links (see http://jsonapi.org/format/#fetching-pagination). The PagedLinksInformation interface provides a Java representation of those links that can be implemented and returned by repositories along with the result data. There is a default implementation named DefaultPagedLinksInformation.

There are two ways to let Crnk compute pagination links automatically:

  1. The repository returns meta information implementing PagedMetaInformation. With this interface the total number of (potentially filtered) resources is passed to Crnk, which in turn allows the computation of the links.

  2. The repository returns meta information implementing HasMoreResourcesMetaInformation. This interface only specifies whether further resources are available after the currently requested resources. This lets Crnk compute all except the last link.

Note that for both approaches the repository has to return either no links or links information implementing PagedLinksInformation. If the links are already set, then the computation will be skipped.

The potential benefit of the second over the first approach is that it might be easier to just determine whether more resources are available rather than counting all resources. This is typically achieved by querying limit + 1 resources.

4.5.4. Sparse Fieldsets

Information about fields to include in the response can be achieved by providing fields parameter.

  • GET /tasks/?fields=name

  • GET /tasks/?fields[projects]=name,description&include=projects

Information about relationships to include in the response can be achieved by providing include parameter. The format for fields: include[ResourceType] = "property(.property)*"

Examples:

  • GET /tasks/?include[tasks]=project

  • GET /tasks/1/?include[tasks]=project

  • GET /tasks/?include[tasks]=author

  • GET /tasks/?include[tasks][]=author&include[tasks][]=comments

  • GET /tasks/?include[projects]=task&include[tasks]=comments

  • GET /tasks/?include[projects]=task&include=comments (QuerySpec example)

4.5.6. API

The QuerySpec API looks like (further setters available as well):

	public class QuerySpec {
		public <T> List<T> apply(Iterable<T> resources){...}

		public Long getLimit() {...}

		public long getOffset() {...}

		public List<FilterSpec> getFilters() {...}

		public List<SortSpec> getSort() {...}

		public List<IncludeFieldSpec> getIncludedFields() {...}

		public List<IncludeRelationSpec> getIncludedRelations() {...}

		public QuerySpec getQuerySpec(Class<?> resourceClass) {...}

		...
	}

Note that single QuerySpec holds the parameters for a single resource type and, in more complex scenarios, request can lead to multiple QuerySpec instances (namely when related resources are also filtered, sorted, etc). A repository is invoked with the QuerySpec for the requested root type. If related resources are included in the request, their QuerySpecs can be obtained by calling QuerySpec.getRelatedSpec(Class) on the root QuerySpec.

FilterSpec holds a value of type object. Since URL parameters are passed as String, they get converted to the proper types by the DefaultQuerySpecDeserializer. The type is determined based on the type of the filtered attribute.

QuerySpec provides a method apply that allows in-memory sorting, filtering and paging on any java.util.Collection. It is useful for testing and on smaller datasets to keep the implementation of a repository as simple as possible. It returns a ResourceList that carries a PagedMetaInformation that lets Crnk automatically compute pagination links.

4.5.7. DefaultQuerySpecDeserializer

Crnk make use of DefaultQuerySpecDeserializer to map URL parameters to a QuerySpec instance. This instance is accessible from the various integrations, such as from the CrnkFeature. It provides a number of customization options:

  • setDefaultLimit(Long) Sets the page limit if none is specified by the request.

  • setMaxPageLimit(Long) Sets the maximum page limit allowed to be requested.

  • setAllowUnknownAttributes(boolean) DefaultQuerySpecDeserializer validates all passed parameters against the domain model and fails if one of the attributes is unknown. This flag allows to disable that check in case the should be necessary.

  • setIgnoreParseExceptions(boolean) DefaultQuerySpecDeserializer attempts to convert all filter parameters to their proper type based on the attribute type to be filtered. In some scenarios like dates this behavior may be undesirable as applications introduce expressions like 'now`. Enabling this flag will let DefaultQuerySpecDeserializer ignore such values and provide them as String within FilterSpec.

  • setEnforceDotPathSeparator(boolean) DefaultQuerySpecDeserializer by default supports by default two URL conventions: task[project][name]=myProject and task[project.name]=myProject. The later is recommended. The former still supported for historic reasons. By default the flag is still disabled, but it is recommended to be packagingEnabled and will become the default at some point in the future. Note that without the enforcement, there is danger of introducing ambiguity with resources and attributes are named equally.

Note that appropriate page limits are vital to protect against denial-of-service attacks when working with large data sets! Such attacks may not be of malicious nature, but normals users using a browser and omitting to specify pagination parameters.

DefaultQuerySpecDeserializer implements QuerySpecDeserializer and you may also provide your own implementation to further customize its behavior. The various integrations like CrnkFeature will allow to replace the implementation.

One can get access to DefaultQuerySpecDeserializer trough the various integrations, e.g. CrnkFeature.getQuerySpecDeserializer(). For more information have a look at the setup chapter.

4.6. Error Handling

Processing errors in Crnk can be handled by throwing an exception and providing a corresponding exception mapper which defines mapping to a proper JSON API error response.

4.6.1. Throwing an exception…​

Here is an example of throwing an Exception in the code:

  if (somethingWentWrong()) {
    throw new SampleException("errorId", "Oops! Something went wrong.")
  }

Sample exception is nothing more than a simple runtime exception:

  public class SampleException extends RuntimeException {

    private final String id;
    private final String title;

    public ExampleException(String id, String title) {
      this.id = id;
      this.title = title;
    }

    public String getId() {
      return id;
    }

    public String getTitle() {
      return title;
    }
  }

4.6.2. …​and mapping it to JSON API response

Class responsible for mapping the exception should:

  • implement ExceptionMapper interface

  • available trough the used discovery mechanism or added trough a module.

Sample exception mapper:

TestExceptionMapper.java
package io.crnk.test.mock;

import java.util.List;

import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.error.ErrorResponse;
import io.crnk.core.engine.error.ExceptionMapper;
import io.crnk.core.repository.response.JsonApiResponse;

public class TestExceptionMapper implements ExceptionMapper<TestException> {

	public static final int HTTP_ERROR_CODE = 499;

	@Override
	public ErrorResponse toErrorResponse(TestException cve) {
		ErrorData error = ErrorData.builder().setDetail(cve.getMessage()).build();
		return ErrorResponse.builder().setStatus(HTTP_ERROR_CODE).setSingleErrorData(error).build();
	}

	@Override
	public TestException fromErrorResponse(ErrorResponse errorResponse) {
		JsonApiResponse response = errorResponse.getResponse();
		List<ErrorData> errors = (List<ErrorData>) response.getEntity();
		StringBuilder message = new StringBuilder();
		for (ErrorData error : errors) {
			String title = error.getDetail();
			message.append(title);
		}
		return new TestException(message.toString());
	}

	@Override
	public boolean accepts(ErrorResponse errorResponse) {
		return errorResponse.getHttpStatus() == HTTP_ERROR_CODE;
	}
}

On the server-side an exception should be mapped to an ErrorResponse object with toErrorResponse. It consists of an HTTP status and ErrorData (which is consistent with JSON API error structure). On the client-side an ExceptionMapper returning true upon accept(…​) is used to map an ErrorResponse back to an exception with fromErrorResponse.

Note that the exception mapper is reponsible for providing the logging of exceptions with the appropriate log levels. Also have a look at the subsequent section about the validation module that takes care of JSR-303 bean validation exception mapping.

4.7. Meta Information

Note
With ResourceList and @JsonApiMetaInformation meta information can be returned directly. A MetaRepository implementation is no longer necessary.

There is a special interface which can be added to resource repositories to provide meta information: io.crnk.core.repository.MetaRepository. It contains a single method MetaInformation getMetaInformation(Iterable<T> resources) which return meta information object that implements the marker interface io.crnk.response.MetaInformation.

If you want to add meta information along with the responses, all repositories (those that implement ResourceRepository and RelationshipRepository) must implement MetaRepository.

When using annotated versions of repositories, a method that returns a MetaInformation object should be annotated with JsonApiMeta and the first parameter of the method must be a list of resources.

Note
With ResourceList and @JsonApiLinksInformation links information can be returned directly. A LinksRepository implementation is usually not necessary.

There is a special interface which can be added to resource repositories to provide links information: io.crnk.core.repository.LinksRepository. It contains a single method LinksInformation getLinksInformation(Iterable<T> resources) which return links information object that implements the marker interface io.crnk.response.LinksInformation.

If you want to add meta information along with the responses, all repositories (those that implement ResourceRepository and RelationshipRepository), must implement LinksRepository.

When using annotated versions of repositories, a method that returns a LinksInformation object should be annotated with JsonApiLinks and the first parameter of the method has to be a list of resources. :basedir: ../../../.. :clientdir: {basedir}/crnk-client

5. Client

There is a client implementation for Java and Android projects to allow communicating with JSON-API compliant servers. Two http client libraries are supported:

Add one of those library to the classpath and Crnk will pick it up automatically.

To start using the client just create an instance of CrnkClient and pass the service URL as parameter.

5.1. Usage

The client has three main methods:

  • CrnkClient#getRepositoryForInterface(Class) to obtain a resource repository stub from an existing repository interface.

  • CrnkClient#getRepositoryForType(Class) to obtain a generic resource repository stub from the provided resource type.

  • CrnkClient#getRepositoryForType(Class, Class) to obtain a generic relationship repository stub from the provided source and target resource types.

The interface of the repositories is as same as defined in `Repositories`_ section.

An example of the usage:

  CrnkClient client = new CrnkClient("http://localhost:8080/api");
  ResourceRepositoryV2<Task, Long> taskRepo = client.getRepositoryForType(Task.class);
  List<Task> tasks = taskRepo.findAll(new QuerySpec(Task.class));

Have a look at, for example, the QuerySpecClientTest to see more examples of how it is used.

5.2. Modules

CrnkClient can be extended by modules:

  CrnkClient client = new CrnkClient("http://localhost:8080/api");
  client.addModule(ValidationModule.create());

Typical use cases include:

  • adding exception mappers

  • registering new types of resources (like JPA entities by the JpaModule)

  • intercepting requests for monitoring

  • adding security tokens to requests

Many modules allow a registration both on server and client side. The client part then typically makes use of a subset of the server features, like exception mappers and resource registrations.

There is a mechanism to discover and register client modules automatically:

  CrnkClient client = new CrnkClient("http://localhost:8080/api");
  client.findModules();

findModules makes use of java.util.ServiceLoader and looks up for ClientModuleFactory. JpaModule, ValidationModule, MetaModule, SecurityModule implement such a service registration. In contrast, BraveModule needs a Brave instance and does not yet allow a fully automated setup.

5.3. Type-Safety

It is possible to work with CrnkClient in a fully type-safe manner.

In a first step an interface for a repository is defined:

ScheduleRepository.java
public interface ScheduleRepository extends ResourceRepositoryV2<Schedule, Long> {
	@Override
	ScheduleList findAll(QuerySpec querySpec);

	class ScheduleList extends ResourceListBase<Schedule, ScheduleListMeta, ScheduleListLinks> {

	}

	class ScheduleListLinks extends DefaultPagedLinksInformation implements LinksInformation {

		public String name = "value";
	}

	class ScheduleListMeta implements MetaInformation {

		public String name = "value";

	}
}

And then it can be used like:

QuerySpecClientTest.java
		ScheduleRepository scheduleRepository = client.getResourceRepository(ScheduleRepository.class);

		Schedule schedule = new Schedule();
		schedule.setId(13L);
		schedule.setName("mySchedule");
		scheduleRepository.create(schedule);

		QuerySpec querySpec = new QuerySpec(Schedule.class);
		ScheduleList list = scheduleRepository.findAll(querySpec);
		Assert.assertEquals(1, list.size());
		ScheduleListMeta meta = list.getMeta();
		ScheduleListLinks links = list.getLinks();
		Assert.assertNotNull(meta);
		Assert.assertNotNull(links);

5.4. JAX-RS interoperability

The interface stubs from the previous section can also be used to make calls to JAX-RS. For example, the ScheduleRepository can be complemented with a JAX-RS annotation:

ScheduleRepository.java
@Path("schedules")
@Produces(HttpHeaders.JSONAPI_CONTENT_TYPE)

and further JAX-RS services can be added:

ScheduleRepository.java
	@GET
	@Path("repositoryAction")
	@Produces(MediaType.TEXT_HTML)
	String repositoryAction(@QueryParam(value = "msg") String msg);

	@GET
	@Path("repositoryActionJsonApi")
	String repositoryActionJsonApi(@QueryParam(value = "msg") String msg);

	@GET
	@Path("repositoryActionWithJsonApiResponse")
	String repositoryActionWithJsonApiResponse(@QueryParam(value = "msg") String msg);

	@GET
	@Path("repositoryActionWithResourceResult")
	Schedule repositoryActionWithResourceResult(@QueryParam(value = "msg") String msg);

	@GET
	@Path("repositoryActionWithException")
	Schedule repositoryActionWithException(@QueryParam(value = "msg") String msg);

	@GET
	@Path("repositoryActionWithNullResponse")
	@Produces(MediaType.TEXT_HTML)
	String repositoryActionWithNullResponse();

	@GET
	@Path("repositoryActionWithNullResponseJsonApi")
	String repositoryActionWithNullResponseJsonApi();

	@GET
	@Path("{id}/resourceAction")
	String resourceAction(@PathParam("id") long id, @QueryParam(value = "msg") String msg);

To make this work a dependency to org.glassfish.jersey.ext:jersey-proxy-client must be added and JerseyActionStubFactory registered with CrnkClient:

AbstractClientTest.java
		client.setActionStubFactory(JerseyActionStubFactory.newInstance());

Then a client can make use the Crnk stubs and it will transparently switch between JSON-API and JAX-RS calls:

JsonApiActionResponseTest.java
		String result = scheduleRepository.repositoryAction("hello");
		Assert.assertEquals("repository action: hello", result);
Warning
Due to limited configurability of the Jersey Proxies it is currently not possible to reuse the same HTTP connections for both types of calls. We attempt to address that in the future. Be aware of this when you, for example, add further request headers (like security), as it has to be done in two places (unfortunately).

5.5. HTTP customization

It is possible to hook into the HTTP implementation used by Crnk (OkHttp or Apache). Make use of CrnkClient#getHttpAdapter() and cast it to either HttpClientAdapter or OkHttpAdapter. Both implementations provide a addListener method, which in turn gives access to the native builder used to construct the respective HTTP client implementation. This allows to cover various use cases:

  • add custom request headers (security, tracing, etc.)

  • collect statistics

  • …​

You may have a look at crnk-brave for an advanced example. :basedir: ../../../..

6. Modules

6.1. JPA Module

The JPA module allows to automatically expose JPA entities as JSON API repositories. No implementation or Crnk-specific annotations are necessary.

The feature set includes:

  • expose JPA entities to JSON API repositories

  • expose JPA relations as JSON API repositories

  • decide which entities to expose as endpoints

  • sorting, filtering, paging, inclusion of related resources.

  • all default operators of crnk are supported: EQ, NEQ, LIKE, LT, LE, GT, GE.

  • filter, sort and include parameters can make use of the dot notation to join to related entities. For example, sort=-project.name,project.id, filter[project.name][NEQ]=someValue or include=project.tasks.

  • support for entity inheritance by allowing sorting, filtering and inclusions to refer to attributes on subtypes.

  • support for Jackson annotations to customize entity attributes on the JSON API layer, see here.

  • DTO mapping support to map entities to DTOs before sending them to clients.

  • JPA Criteria API and QueryDSL support to issue queries.

  • filter API to intercept and modify issued queries.

  • support for computed attributes behaving like regular, persisted attributes.

  • automatic transaction handling spanning requests and doing a rollback in case of an exception.

  • OptimisticLockExceptionMapper mapped to JSON API errors with 409 status code.

  • PersistenceException and RollbackException are unwrapped to the usually more interesting exceptions like ValidationException and then translated to JSON API errors.

Have a look at the Spring Boot example application which makes use of the JPA module, DTO mapping and computed attributes.

Not yet supported are:

  • sparse field sets.

6.1.1. JPA Setup

To use the module, add a dependency to io.crnk:crnk-jpa and register the JpaModule to Crnk. For example in the case of JAX-RS:

	TransactionRunner transactionRunner = ...;
	JpaModule jpaModule = JpaModule.newServerModule(entityManagerFactory, entityManager, transactionRunner);
	jpaModule.setRepositoryFactory(new ValidatedJpaRepositoryFactory());

	CrnkFeature feature = new CrnkFeature(...);
	feature.addModule(jpaModule);

The JPA modules by default looks up the entityManagerFactory and obtains a list of registered JPA entities. For each entity a instance of JpaEntityRepository is registered to Crnk using the module API. Accordingly, every relation is registered as JpaRelationshipRepository. JpaModule.setRepositoryFactory allows to provide a factory to change or customized the used repositories. To manually select the entities exposed to Crnk use JpaModule.addEntityClass(…​) and JpaModule.removeEntityClass(…​). If no entityManagerFactory is provided to newServerModule, then the registration of entities is omitted and can be done manually.

The transactionRunner needs to be implemented by the application to hook into the transaction processing of the used environment (Spring, JEE, etc.). This might be as simple as a Spring bean implementing TransactionRunner and adding a @Transactional annotation. The JPA module makes sure that every call to a repository happens within such a transaction boundary.

To setup a Crnk client with the JPA module use:

	client = new CrnkClient(getBaseUri().toString());

	JpaModule module = JpaModule.newClientModule();
	setupModule(module, false);
	client.addModule(module);

Have a look at https://github.com/crnk-project/crnk-framework/blob/develop/crnk-jpa/src/test/java/io/crnk/jpa/JpaQuerySpecEndToEndTest.java within the crnk-jpa test cases to see how everything is used together with crnk-client. The JPA modules further has a number of more advanced customization options that are discussed in the subsequent sections.

6.1.2. Pagination

The JPA module implements both pagination approaches supported by Crnk. Setting JpaModule.setTotalResourceCountUsed(true|false) allows to decide whether the total number of resources should be counted or whether just the presence of a subsequent resource is checked (by querying limit + 1 entities). By default the total resources are counted. Have a look at the [pagination] section for more information.

6.1.3. Criteria API and QueryDSL

The JPA module can work with two different query APIs, the default Criteria API and QueryDSL. JpaModule.setQueryFactory allows to choose between those two implementation. There is the JpaCriteriaQueryFactory and the QuerydslQueryFactory. By default the Criteria API is used. QueryDSL sits on top of JPQL and has to advantage of being easier to use.

6.1.4. Customizing the JPA repository

The setup page outlined the JpaRepositoryFactory that can be used to hook a custom JPA repository implementations into the JPA module. The JPA module further provides a more lightweight filter API to perform various changes to JPA repository requests:

JpaModule.addFilter(new MyRepositoryFilter())

A filter looks like:

	public class MyRepositoryFilter extends JpaRepositoryFilterBase {

		boolean accept(Class<?> resourceType){...}

		<T, I extends Serializable> JpaEntityRepository<T, I> filterCreation(JpaEntityRepository<T, I> repository){...}

		QuerySpec filterQuerySpec(Object repository, QuerySpec querySpec){...}

		...
	}

The various filter methods allow a wide variety of customizations or also to replace the passed object in question.

6.1.5. DTO Mapping

Mapping to DTO objects is supported with JpaModule.registerMappedEntityClass(…​). A mapper then can be provided that translates the Entity to a DTO class. Such a mapper might be implemented manually or generated (mostly) automatically with tools like MapStruct. If two mapped entities are registered, there respective mapped relationships will be automatically registered as well.

The mechanism is not limited to simple mappings, but can also introduce computed attributes like in the example depicted here:

	JpaModule module = JpaModule.newServerModule(emFactory, em, transactionRunner);
				module.setQueryFactory(QuerydslQueryFactory.newInstance());
	QuerydslExpressionFactory<QTestEntity> basicComputedValueFactory = new QuerydslExpressionFactory<QTestEntity>() {

		@Override
		public Expression<String> getExpression(QTestEntity parent, JPAQuery<?> jpaQuery) {
			return parent.stringValue.upper();
		}
	};

	QuerydslQueryFactory queryFactory = (QuerydslQueryFactory) module.getQueryFactory();
	queryFactory.registerComputedAttribute(TestEntity.class, TestDTO.ATTR_COMPUTED_UPPER_STRING_VALUE,
		 String.class, basicComputedValueFactory);
	module.addMappedEntityClass(TestEntity.class, TestDTO.class, new TestDTOMapper(entityManager));

and

	public class TestDTOMapper implements JpaMapper<TestEntity, TestDTO> {

		@Override
		public TestDTO map(Tuple tuple) {
			TestDTO dto = new TestDTO();
			TestEntity entity = tuple.get(0, TestEntity.class);
			dto.setId(entity.getId());
			dto.setStringValue(entity.getStringValue());
			dto.setComputedUpperStringValue(tuple.get("computedUpperStringValue", String.class));
			...
			return dto;
		}

		...

	}

Some of the regular entity attributes are mapped to the DTO. But there is also a computedUpperStringValue attribute that is computed with an expression. The expression can be written with the Criteria API or QueryDSL depending on which query backend is in use.

Computed attributes are indistinguishable from regular, persisted entity attributes. They can be used for selection, sorting and filtering. Both JpaCriteriaQueryFactory and QuerydslQueryFactory provide a registerComputedAttribute method to register an expression factory to create such computed attributes. The registration requires the target entity and a name. To make the computed attribute available to consumers, the mapper class has access to it trough the provided tuple class. Have a look at https://github.com/crnk-project/crnk-framework/blob/develop/crnk-jpa/src/test/java/io/crnk/jpa/mapping/DtoMappingTest.java to see everything in use.

There is currently not yet any support for renaming of attribute. If attributes are renamed on DTOs, the incoming QuerySpec has to be modified accordingly to match again the entity attribute naming.

6.2. JSR 303 Validation Module

A ValidationModule provided by io.crnk:crnk-validation implements exception mappers for javax.validation.ValidationException and javax.validation.ConstraintViolationException. Among others, it properly translates 'javax.validation.ConstraintViolation' instances to JSON API errors. A JSON API error can, among others, contain a source pointer. This source pointer allows a clients/UI to display the validation errors next to the corresponding input fields.

A translated exception can look like:

{
	"errors": [
		{
			"status": "422",
			"code": "javax.validation.constraints.NotNull",
			"title": "may not be null",
			"source": {
				"pointer": "data/attributes/name"
			},
			"meta": {
				"resourceId": "1",
				"type": "ConstraintViolation",
				"messageTemplate": "{javax.validation.constraints.NotNull.message}",
				"resourceType": "projects"
			}
		}
	]
}

Notice the 422 status code used for such errors.

6.3. Tracing with Zipkin/Brave

A BraveClientModule and BraveServletModule provided by io.crnk:crnk-brave4 provides integration into Zipkin/Brave to implement tracing for your repositories. The module is applicable to both a Crnk client or server.

The Crnk client can make use of either HttpClient or OkHttp to issue HTTP requests. Accordingly, a matching brave integration must be added to the classpath:

  • io.zipkin.brave:brave-instrumentation-okhttp3

  • io.zipkin.brave:brave-instrumentation-httpclient

The BraveClientModule then takes care of the integration and will create a client span for each request.

On the server-side, BraveServletModule creates a local span for each accessed repository. Every request triggers one or more repository accesses (depending on whether relations are included). Note however that BraveServletModule does not setup tracing for incoming requests. That is the purpose of the JAX-RS/servlet integration of Brave.

Have a look at the Spring boot example application to see the BraveServletModule in use together with a log reporter writing the output to console.

Note
io.crnk:crnk-brave is deprecated and makes use of the Brave 3.x API.

6.4. Security Module

This is an module that intercepts all repository requests and performs role-based access control. Have a look at the SecurityModule and the related SecurityConfig class. A setup can looks as follows:

SecurityModuleIntTest.java
			Builder builder = SecurityConfig.builder();
			builder.permitRole("allRole", ResourcePermission.ALL);
			builder.permitRole("getRole", ResourcePermission.GET);
			builder.permitRole("patchRole", ResourcePermission.PATCH);
			builder.permitRole("postRole", ResourcePermission.POST);
			builder.permitRole("deleteRole", ResourcePermission.DELETE);
			builder.permitRole("taskRole", Task.class, ResourcePermission.ALL);
			builder.permitRole("taskReadRole", Task.class, ResourcePermission.GET);
			builder.permitRole("projectRole", Project.class, ResourcePermission.ALL);
			builder.permitAll(ResourcePermission.GET);
			builder.permitAll(Project.class, ResourcePermission.POST);
			module = SecurityModule.newServerModule(builder.build());

			CrnkFeature feature = new CrnkFeature();
			feature.addModule(module);

The security module is a bit more advanced than the more typical @RolesAllowed annotation. As such the configuration is not done trough annotations, but with the SecurityConfig object where rules specify which resources and methods are available to users. A rule can grant access for role to a single or all resources for a given set of methods. Those rules are then applied in various contexts:

  • Accesses to repositories for checked (for GET, POST, PATCH and DELETE requests).

  • Both resources repository and relationship repositories are checked. For the later access to the return type is checked (TODO consider checking the source as well).

  • A request may span multiple repository accesses in case of inclusions with the include parameter. In this case every access is checked individually.

  • Relationship fields to resources the user is not authorized to see are omitted from results and can also not be modified.

  • HomeModule and MetaModule show only resources the user is authorized to see. In case of the MetaModule the MetaAttribute and MetaResource show also information about what can be read, inserted, updated and deleted.

  • ResourcePermission is added to the response as meta-data to inform the client about the authorized methods.

  • (soon) Query parameters like filters, sort and inclusions are checked against unauthorized access to related resources.

Internally the security module makes use of ResourceFilter to perform this task. It is quite simple to add further custom behavior. Have a look at the module development chapter for this.

The security module further serializes javax.security authorization and authentication exceptions. As such it is also recommended to be used by CrnkClient.

Future work:

  • Authorized access to fields.

  • Check query parameters.

6.5. Meta Module

This is a module that exposes the internal workings of Crnk as JSON API repositories. It lets you browse the set of available resources, their types, their attributes, etc. For example, Crnk UI make use of the meta module to implement auto-completing of input fields.

Note
There is currently no JSON API standard for meta data. There are more general formats like Swagger and ALPS. At some point those might be supported as well (probably rather the later than the former). One can view them to be complementary to the MetaModule as the later is exactly tailored towards JSON API, such as the accessability as regular JSON API (meta) repository and data structures matching the standard. Most likely, any future standard implementation will built up on the information from the MetaModule.

6.5.1. Setup

A setup can look as follows:

		MetaModule metaModule = MetaModule.create();
		metaModule.addMetaProvider(new ResourceMetaProvider());

ResourceMetaProvider exposes all JSON API resources and repositories as meta data. You may add further provides to expose more meta data, such as the JpaMetaProvider.

6.5.2. Examples

To learn more about the set of available resources, have a look at the MetaElement class and all its subclasses. Some of the most important classes are:

Path

Implementation

Description

/meta/element

MetaElement

Base class implemented by any meta element.

/meta/type

MetaType

Base class implemented by any meta type element.

/meta/primitiveType

MetaPrimitiveType

Represents primitive types like Strings and Integers.

/meta/arrayType

MetaArrayType

Represents an array type.

/meta/listType

MetaListType

Represents an list type.

/meta/setType

MetaSetType

Represents an set type.

/meta/mapType

MetaMapType

Represents an map type.

/meta/dataObject

MetaDataObject

Base type for any object holding data, like JPA entities or JSON API resources.

/meta/attribute

MetaAttribute

Represents an attribute of a MetaDataObject.

/meta/resource

MetaResource

JSON API resource representation extending MetaDataObject.

/meta/resourceRepository

MetaResourceRepository

JSON API repository representation holding resources.

A MetaResource looks like:

{
    "id" : "resources.project",
    "type" : "meta/resource",
    "attributes" : {
      "name" : "Project",
      "resourceType" : "projects"
    },
    "relationships" : {
      "parent" : {
        ...
      },
      "interfaces" : {
        ...
      },
      "declaredKeys" : {
        ...
      },
      "children" : {
        ...
      },
      "declaredAttributes" : {
        ...
      },
      "subTypes" : {
        ...
      },
      "attributes" : {
        ...
      },
      "superType" : {
        ...
      },
      "elementType" : {
        ...
      },
      "primaryKey" : {
        ...
      }
    }
  }

A MetaAttribute looks like:

{
    "id" : "resources.project.name",
    "type" : "meta/resourceField",
    "attributes" : {
      "filterable" : true,
      "nullable" : true,
      "lazy" : false,
      "association" : false,
      "primaryKeyAttribute" : false,
      "sortable" : true,
      "version" : false,
      "insertable" : true,
      "meta" : false,
      "name" : "name",
      "updatable" : true,
      "links" : false,
      "derived" : false,
      "lob" : false,
      "cascaded" : false
    },
    "relationships" : {
      "parent" : {
        ...
      },
      "children" : {
        ...
      },
      "oppositeAttribute" : {
        ...
      },
      "type" : {
        ...
      }
    }
  }

6.5.3. Identifiers for Meta Elements

Of importance is the assignment of IDs to meta elements. For resources the resource type is used to compute the meta id and a resources prefix is added. In the example above, person gets a resources.person meta id. Related objects (DTOs, links/meta info) located in the same or a subpackage of a resource gets the same meta id prefix. A ProjectData sitting in a dto subpackage would get a resources.dto.projectdata meta id.

The meta ids are used, for example, by the Typescript generator to determine the file structure and dependencies of generated source files.

Applications are enabled to adapt the id generator process with:

new ResourceMetaProvider(idPrefix)

and

ResourceMetaProvider.putIdMapping(String packageName, String idPrefix)

to override the default resources prefix and assign a specific prefix for a package.

6.5.4. Extending the Meta Module

There is a MetaModuleExtension extension that allows other Crnk modules contribute MetaProvider implementation. This allows to:

  • add MetaFilter implementations to intercept and modify meta elements upon initialization and request.

  • add MetaPartition implementations to introduce new, isolated areas in the meta model, like a JPA meta model next to the JSON API one (like for documentation purposes).

For more detailed information have a look at the current ResourceMetaProvider.

6.6. Home Module

The HomeModule provides an implementation for JSON Home. It allows you to obtain a list of available repositories when accessing the parent page of your repositories (typically /api).

		HomeModule metaModule = HomeModule.create();
		...

The module is currently in an incubator stage. In the Spring Boot example applications it looks like:

{
  "resources" : {
    "tag:schedule" : {
      "href" : "/schedule/"
    },
    "tag:tasks" : {
      "href" : "/tasks/"
    },
    "tag:scheduleDto" : {
      "href" : "/scheduleDto/"
    },
    "tag:meta/collectionType" : {
      "href" : "/meta/collectionType/"
    },
    "tag:projects" : {
      "href" : "/projects/"
    },
     "tag:meta/resource" : {
	  "href" : "/meta/resource/"
	},
	"tag:meta/attribute" : {
	  "href" : "/meta/attribute/"
	},
	...
  }
}

6.7. Operations Module

By its nature RESTful applications are limited to the insertion, update and deletion of single resources. As such, developers have to design resources accordingly while having to consider aspects like transaction handling and atomicity. It is not uncommon to combine multiple data objects on the server-side and expose it as single resource to clients. It is a simple approach, but can also mean quite a substantial overhead when having to implement potentially redudant repositories. Furthermore, things like validation handling, relationships and supporting complex object graphs can get tricky when a single resource starts holding complex object graphs again.

For all the before mentioned reason support for jsonpatch.com is provided. It allows to send multiple insertions, updates and deletions with a single request and provides the results for each such executed operation. Note that numerous attempts and discussions have taken place and are still ongoing to establish a common JSON API standard, but that does not seem to make much progress. With jsonpatch.com there is already an estabilished standard that fits well for many use cases.

The implementation is provided as OperationsModule and the setup looks like:

		OperationsModule operationsModule = OperationsModule.create();
		...

Further filters can be applied to intercept incoming requests. Typically applications will make use of that to start a new transaction spanning all requests. This looks as follows:

AbstractOperationsTest.java
			operationsModule.addFilter(new TransactionOperationFilter());

There is further an operations client implementation that works along the regular JSON API client implementation:

OperationsPostTest.java
		OperationsClient operationsClient = new OperationsClient(client);
		OperationsCall call = operationsClient.createCall();
		call.add(HttpMethod.POST, movie);
		call.add(HttpMethod.POST, person1);
		call.add(HttpMethod.POST, person2);
		call.execute();

The current limitations of the implementation are:

  • So far does not support bulk GET operations.

  • Does so far not support bulk update of relationships.

With support for POST, PATCH and DELETE operations the most important building blocks should be in place. The limitations are expected to be addressed at some point as well, contributions welcomed.

6.8. UI Module

The UI module makes crnk-ui accessible trough the module system. It allows to browse and edit all the repositories and resources. The setup looks like:

		UIModule operationsModule = UIModule.create(new UIModuleConfig());
		...

By default the user interface is accessible from the /browse/ directory next to all the repositories. Have a look at the Spring Boot example application to see a working example.

This module is currently in incubation. Please provide feedback.

An example from the Spring Boot example application looks like:

crnk ui

6.9. Activiti Module

Note
This module is in new and in incubation. Feedback and improvements welcomed.

There is an ActivitiModule for the Activiti workflow engine that offers an alternative REST API. The motivation of ActivitiModule is to:

  • have a JSON API compliant REST API to benefit from the resource-oriented architecture, linking, sorting, filtering, paging, and client-side tooling of JSON API.

  • have a type-safe, non-generic REST API that is tailored towards the use cases at hand. This means that for each process and task definition, there is a dedicated repository and resource type for it. The resource is comprised of both the static fields provided by Activiti (like name, startTime and priority) and the dynamic fields stored by the application as process/task/form variables. Mapping to static resp. dynamic fields is done automatically by the ActivitiModule and hidden from consumers. The repository implementations ensure a proper isolation of different types. And the application is enabled, for example, to introduce custom security policies for each resource with the SecurityModule or a ResourceFilter.

This setup differs substantially from the API provided by Activiti that is implemented in generic fashion.

6.9.1. Setup

The ActivitiModule comes within a small example application within the src/main/test directory that showcases its use. It sets up an approval flow where changes to the Schedule resource must be approved by a user.

The ActivitiModule implements four resource base classes that match the equivalent Activiti classes:

  • ExecutionResource

  • FormResource

  • ProcessInstanceResource

  • TaskResource

To setup a JSON API repository for a process or task, the corresponding resource class can be subclassed and extended with the application specific fields. For example:

ApprovalProcessInstance.java
public abstract class ApprovalProcessInstance extends ProcessInstanceResource {

	private String resourceId;

	private String resourceType;

	public String getResourceId() {
		return resourceId;
	}

	...
}

and

ScheduleApprovalProcessInstance.java
@JsonApiResource(type = "approval/schedule")
public class ScheduleApprovalProcessInstance extends ApprovalProcessInstance {

	private ScheduleApprovalValues newValues;

	private ScheduleApprovalValues previousValues;

	...
}

The example application makes use of an intermediate ApprovalProcessInstance base class to potentially share the approval logic among multiple entities in the future (if it would be real-world use case). ScheduleApprovalProcessInstance has the static fields of Activiti and a number of custom, dynamic fields like resourceType, resourceId and newValues. The dynamic fields will be mapped to to process, task resp. form variables.

Notice the relation to ApproveTask, which is a task counter part extending from TaskResource. If a process has multiple tasks, you may introduce multiple such relationships.

Finally, the setup of the ActiviModule looks like:

ApprovalTestApplication.java
	public static ActivitiModule createActivitiModule(ProcessEngine processEngine) {
		ActivitiModuleConfig config = new ActivitiModuleConfig();
		ProcessInstanceConfig processConfig = config.addProcessInstance(ScheduleApprovalProcessInstance.class);
		processConfig.filterByProcessDefinitionKey("scheduleChange");
		processConfig.addTaskRelationship(
				"approveTask", ApproveTask.class, "approveScheduleTask"
		);
		TaskRepositoryConfig taskConfig = config.addTask(ApproveTask.class);
		taskConfig.filterByTaskDefinitionKey("approveScheduleTask");
		taskConfig.setForm(ApproveForm.class);
		return ActivitiModule.create(processEngine, config);
	}
  • ActivitiModuleConfig allows to register processes and tasks that then will be exposed as repositories.

  • ScheduleApprovalProcessInstance, ApproveTask and the approveTask relationship are registered.

  • ApproveTask is user task that is handled by submitting an ApproveForm.

  • filterByProcessDefinitionKey and filterByTaskDefinitionKey ensure that the two repositories are isolated from other repositories for GET, POST, PATCH and DELETE operations.

One could imagine to make this configuration also available through an annotation-based API in the future as it is closely related to the resource classes and fields.

6.9.2. Example application

The example application goes a few steps further in the setup. The patterns of those steps might be of interest of consumers of the ActivitiModule as well.

The workflow looks as follows:

approval.bpmn20.xml
<?xml version="1.0" encoding="UTF-8"?>

<definitions id="approvalDefinitions"
			 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			 targetNamespace="http://activiti.org/bpmn20"
			 xmlns:activiti="http://activiti.org/bpmn"
			 xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">


	<process id="scheduleChange" name="Approve schedule change" isExecutable="true">
		<documentation>
			This process is initiated when a user modifies a scheduleEntity trough the JSON API endpoint.
		</documentation>

		<startEvent id="startScheduleChange" name="Start" activiti:initiator="initiator"></startEvent>

		<userTask id="approveScheduleTask" name="Approve new Schedule">
			<extensionElements>
				<activiti:formProperty id="approved" name="Do you approve this change" type="boolean" required="true" />
			</extensionElements>
		</userTask>

		<sequenceFlow id="startFlow" sourceRef="startScheduleChange" targetRef="approveScheduleTask"></sequenceFlow>

		<sequenceFlow id="decideFlow" sourceRef="approveScheduleTask" targetRef="approvalExclusiveGateway"></sequenceFlow>

		<serviceTask id="scheduleChangeApproved" name="Create schedule Account, send Alerts"
					 activiti:expression="${approvalManager.approved(execution)}"></serviceTask>
		<serviceTask id="scheduleChangeDenied" name="send alert"
					 activiti:expression="${approvalManager.denied(execution)}"></serviceTask>

		<endEvent id="endEvent" name="End"></endEvent>

		<exclusiveGateway id="approvalExclusiveGateway" name="Exclusive Gateway"></exclusiveGateway>

		<sequenceFlow id="approveFlow" sourceRef="approvalExclusiveGateway" targetRef="scheduleChangeApproved">
			<conditionExpression xsi:type="tFormalExpression">
				<![CDATA[
                ${approved == true}
            ]]>
			</conditionExpression>
		</sequenceFlow>

		<sequenceFlow id="denyFlow" sourceRef="approvalExclusiveGateway" targetRef="scheduleChangeDenied">
			<conditionExpression xsi:type="tFormalExpression">
				<![CDATA[
                ${approved == false}
            ]]>
			</conditionExpression>
		</sequenceFlow>
		<sequenceFlow id="flow5" sourceRef="scheduleChangeDenied" targetRef="endEvent"></sequenceFlow>
		<sequenceFlow id="flow6" sourceRef="scheduleChangeApproved" targetRef="endEvent"></sequenceFlow>
	</process>
</definitions>

There is a:

  • approveScheduleTask task requires a form submission by a user.

  • approvalExclusiveGateway checks whether the change was accepted.

  • scheduleChangeApproved invokes ${approvalManager.approved(execution)} whereas approvalManager is a Java object taking care of the approval handling and registered to activiti.cfg.xml.

  • approvalManager.approved(…​) reconstructs the original request and forwards it to Crnk again to save the approved changes. This means the regular ScheduleRepository implementation will be called in the same fashion as for a typical request. Real world use cases may also need to save and reconstruct the security context.

For the approval-related functionality a second module is registered:

ApprovalTestApplication.java
	public static SimpleModule createApprovalModule(ApprovalManager approvalManager) {
		FilterSpec approvalFilter = new FilterSpec(
				Arrays.asList("definitionKey"), FilterOperator.EQ, "scheduleChange"
		);
		List<FilterSpec> approvalFilters = Arrays.asList(approvalFilter);

		SimpleModule module = new SimpleModule("approval");
		module.addRepositoryDecoratorFactory(
				ApprovalRepositoryDecorator.createFactory(approvalManager)
		);
		module.addRepository(new ApprovalRelationshipRepository(Schedule.class,
				ScheduleApprovalProcessInstance.class, "approval",
				"approval/schedule", approvalFilters)
		);
		return module;
	}
  • ApprovalRepositoryDecorator hooks into the request processing of the Crnk engine and intercepts all PATCH and POST requests for the Schedule resource. The decorator then may chooses to abort the request and start an approval flow instead with the help of ApprovalManager.

  • ApprovalRelationshipRepository introduces an additional relationship between the actual resources and approval resources. It can be used, for example, by user interfaces to show the current status of an open approval workflow. ApprovalRelationshipRepository.getResourceFields declares the relationship field, meaning that the original application resource does not have to declare the relationship. This may or may not be useful depending on how much control there is over the original resource (for example there is no control over JPA entities).

The chosen setup leads to an approval system that is fully transparent to the actual repository implementations and can be added to any kind of repository.

ApprovalIntTest showcases the example workflow by doing a change, starting the approval process, submitting a form and then verifying the changes have been saved.

6.9.3. Limitations

  • Currently the main entities of Activiti have been exposed. History and configuration-related repositories could be exposed as well in the future.

  • Activiti has a limited query API that is inherited by the application. Potentially crnk-jpa could help out a bit in this area.

  • Multi-tenancy is not yet done out-of-the-box.

7. Module Development

Crnk has a module API that allows to extend the core functionality by third-party contributions. The mentioned JPA module in the next section is an example for that. The API is similar in spirit to the one of the https://github.com/FasterXML/jackson. The main interface is Module with a default implementation provided by SimpleModule. A module has access to a ModuleContext that allows to register all kinds of extensions like new ResourceInformationBuilder, ResourceLookup, Filter, ExceptionMapper and Jackson modules. It also gives access to the ResourceRegistry holding information about all the repositories registered to crnk. The JpaModule in crnk-jpa provides a good, more advanced example of using the module API.

7.1. Request Filtering

Crnk provides three different, complementing mechanisms to hook into the request processing.

The DocumentFilter interface allows to intercept incoming requests and do any kind of validation, changes, monitoring, transaction handling, etc. DocumentFilter can be hooked into Crnk by setting up a module and registering the filter to the ModuleContext. Not that for every request, this interface is called exactly once.

A request may span multiple repository accesses. To intercept the actual repository requests, implement the RepositoryFilter interface. RepositoryFilter has a number of methods that allow two intercept the repository request at different stages. Like Filter it can be hooked into Crnk by setting up a module and registering the filter to the ModuleContext.

Similar to RepositoryFilter it is possible to decorate a repository with another repository implementing the same Crnk repository interfaces. The decorated repository instead of the actual repository will get called and it is up to the decorated repository of how to proceed with the request, usually by calling the actual repository. RepositoryDecoratorFactory can be registered with ModuleContext.addRepositoryDecoratorFactory. The factory gets notified about every repository registration and is then free do decorate it or not.

7.2. Resource Filtering

ResourceFilter allows to restrict access to resources and fields. To methods filterResource and filterField can be implemented for this purpose. Both return a FilterBehavior which allows to distinguish between NONE, IGNORE and FORBIDDEN. For example, a field like a lock count can make use of IGNORE in order to be ignored for POST and PATCH requests (the current value on the server is left untouched). While access to an unauthorized resource or field results in a forbidden error with FORBIDDEN.

The SecurityModule makes use of ResourceFilter to perform access control. SecurityResourceFilter in 'crnk-security` gives an example how it is used. The MetaModule and HomeModule make use of ResourceFilterDirectory obtained with ModuleContext.getResourceFilterDirectory(…​) to query those ResourceFilter and only display information about resources and fields accessible in the context of the current re quest. The ResourceFilterDirectory makes use of per-request caching as the information may be accessed repeatedly for a request.

7.3. Filter Modifications

Changes to attributes and relationships can be tracked by implementing ResourceModificationFilter. The filter is invoked upon an incoming request while setting up the resource objects; before the actual repository is called. Such filters are useful, for example, to implement auditing functionality.

7.4. Filter Priority

DocumentFilter, RepositoryFilter and ResourceModificationFilter can implement Prioritizable to introduce a priority among multiple filters.

7.5. Access to HTTP layer

HttpRequestContext resp. HttpRequestContextProvider provides access to the HTTP requests. Most notably to get and set HTTP request and response headers. In many cases, the underlying implementation like JAXRS or Servlet provides that access as well. With HttpRequestContext there is an implementation that is independent of that implementation. As such it is well suited for module development, in particular for request filtering. A typical use case is to set and access security headers.

HttpRequestContextProvider.getRequestContext returns the request context for the currently active request. Modules have access to HttpRequestContextProvider trough the ModuleContext. Repositories, filters and modules can implement HttpRequestContextAware to get access to HttpRequestContextProvider.

7.6. Module Extensions and dependencies

ModuleExtension is an interface can can be implemented by modules to specify a contract how others can extend it. The interface has two mandator properties: targetModule and optional. targetModule specifies the module consuming those extensions (and providing the implementation for it). optional specifies whether the target module must be registered or not. In case of an optional extension without the module being registered, the extension is simply ignored. The implementing module is free to add any further, custom methods to provide extension hooks to other modules. To get access to this extensions, the module can implement ModuleExtensionAware. Extensions must be registered during Module.setupModule(…​) and will be available to the target module when Module.init() is called.

For an example have a look at MetaModuleExtension and the JpaModule making use of it. The ModuleExtension was introduced with Crnk 2.0 and its use is expected to grow heavily over time.

7.7. Integrate third-party data stores

The core of Crnk is quite flexible when it comes to implementing repositories. As such, it is not mandatory to make use of the Crnk annotations and conventions. Instead, it is also (likely) possible to integrate an existing data store setup like JPA, JDBC, ElasticSearch, etc. into Crnk. For this purpose a module can provide custom implementations of ResourceInformationBuilder and RepositoryInformationBuilder trough ModuleContext.addResourceInformationBuilder and ModuleContext.addRepositoryInformationBuilder. For example, the JpaModule of crnk-jpa makes use of that to read JPA instead of Crnk annotations. Such a module can then register additional (usually dynamic) repositories with ModuleContext.addRepository.

7.8. Implement a custom discovery mechanism

Crnk comes with out-of-the-box support for Spring and CDI. Both of them implement ServiceDiscovery. You may provide your own implementation which can be hooked into the various Crnk integrations, like the CrnkFeature. Modules have access to that ServiceDiscovery trough the ModuleContext.

7.9. Let a module hook into the Crnk HTTP client implementation

Modules for the Crnk client can additionally implement HttpAdapterAware. It gives the module access to the underlying HTTP client implementation and allows arbitrary customizations of it. Have a look at the Crnk client documentation for more information.

7.10. Implement a custom integration

Adding a new integration has become quite simple in recent times. Have a look at crnk-servlet and crnk-rs. Most functionality necessary is already be provided by crnk-core. The steps include:

  • implement HttpRequestContextBase.

  • instantiate CrnkBoot to setup crnk.

  • get the RequestDispatcher from CrnkBoot.

  • invoke the RequestDispatcher for each incoming request with the implemented HttpRequestContextBase.

  • you may want to further implement SecurityProvider, TransactionRunner and PropertiesProvider to interface with that particular systems.

7.11. Create repositories at runtime

Repositories are usually created at compile-time, either by making use of the various annotations or a module such as the ´JpaModule´. However, the module API also allows the creation of repositories at runtime. There are two complementary mechanisms in place to achieve this and outlined in the next two sections.

Note
this feature is in incubation, more refinements are expected in upcoming releases.

7.11.1. Implementing repositories dynamically at runtime

There are different possibilities to implement a repository at runtime:

  • Create a matching resource class at runtime with a library like http://bytebuddy.net/#/ to follow the same pattern as for any compile-time repository.

  • Make use of the Resource class. It is the generic JSON API resource presentation within the Crnk engine.

  • Make use of an arbitrary dynamic object like a java.util.Map and provide a ResourceFieldAccessor for each ResourceField to specify how to read and write attributes (see below for ResourceField examples).

In the following example we make use of the second option:

DynamicResourceRepository.java
public class DynamicResourceRepository extends ResourceRepositoryBase<Resource, String> implements UntypedResourceRepository<Resource, String> {

	private static Map<String, Resource> RESOURCES = new HashMap<>();

	private final String resourceType;

	public DynamicResourceRepository(String resourceType) {
		super(Resource.class);
		this.resourceType = resourceType;
	}

	@Override
	public String getResourceType() {
		return resourceType;
	}

	@Override
	public Class<Resource> getResourceClass() {
		return Resource.class;
	}

	@Override
	public DefaultResourceList<Resource> findAll(QuerySpec querySpec) {
		return querySpec.apply(RESOURCES.values());
	}
...
}

This new repository can be registered to Crnk with a module:

DynamicModule
public class DynamicModule implements Module {

	@Override
	public String getModuleName() {
		return "dynamic";
	}

	@Override
	public void setupModule(ModuleContext context) {

		for (int i = 0; i < 2; i++) {
			RegistryEntryBuilder builder = context.newRegistryEntryBuilder();

			String resourceType = "dynamic" + i;
			RegistryEntryBuilder.ResourceRepository resourceRepository = builder.resourceRepository();
			resourceRepository.instance(new DynamicResourceRepository(resourceType));

			RegistryEntryBuilder.RelationshipRepository relationshipRepository = builder.relationshipRepository(resourceType);
			relationshipRepository.instance(new DynamicRelationshipRepository(resourceType));

			InformationBuilder.Resource resource = builder.resource();
			resource.resourceType(resourceType);
			resource.resourceClass(Resource.class);
			resource.addField("id", ResourceFieldType.ID, String.class);
			resource.addField("value", ResourceFieldType.ATTRIBUTE, String.class);
			resource.addField("parent", ResourceFieldType.RELATIONSHIP, Resource.class).oppositeResourceType(resourceType).oppositeName("children");
			resource.addField("children", ResourceFieldType.RELATIONSHIP, List.class).oppositeResourceType(resourceType).oppositeName("parent");

			context.addRegistryEntry(builder.build());
		}
	}
}

A new RegistryEntry is created and registered with Crnk. It provides information about:

  • the resource and all its fields.

  • the repositories and instances thereof.

Have a look at the complete example in crnk-client and crnk-test There is a further example test case and relationship repository.

7.11.2. Registering repositories at runtime

There are two possibilities to register a new repository at runtime:

  • by using a Module and invoking ModuleContext.addRegistryEntry as done in the previous section.

  • by implementing a ResourceRegistryPart and invoking ModuleContext.addResourceRegistry.

The first is well suited if there is a predefined set of repositories that need to be registered (like a fixed set of JPA entities in the JpaModule). The later is suited for fully dynamic use cases where the set of repositories can change over time (like tables in a database or tasks in an activiti instance). In this case the repositories no longer need registration. Instead the custom ResourceRegistryPart implementation always provides an up-to-date set of repositories that is used by the Crnk engine.

An example can be found at CustomResourceRegistryTest.java

7.12. Discovery of Modules by CrnkClient

If a module does not need configuration, it can provide a ClientModuleFactory implementation and register it to the java.util.ServiceLoader by adding a 'META-INF/services/io.crnk.client.module.ClientModuleFactory` file with the implementation class name. This lets CrnkClient discover the module automatically when calling CrnkClient.findModules(). An example looks like:

ValidationClientModuleFactory
package io.crnk.validation.internal;

import io.crnk.client.module.ClientModuleFactory;
import io.crnk.validation.ValidationModule;

public class ValidationClientModuleFactory implements ClientModuleFactory {

	@Override
	public ValidationModule create() {
		return ValidationModule.create();
	}
}

and

META-INF/services/io.crnk.client.module.ClientModuleFactory
io.crnk.validation.internal.ValidationClientModuleFactory

8. Generation

Note
this feature is still in incurbation, feedback and contributions welcomed. In particular further frameworks like Spring must be supported.

Crnk allows the generation of Typescript stubs for type-safe, client-side web development. Contributions for other languages like iOS would be very welcomed.

8.1. Typescript

The Typescript generator allows the generation of:

  • interfaces for resources and related objects (like nested objects and enumeration types).

  • interfaces for result documents (i.e. resources and any linking and meta information).

  • interfaces for links information.

  • interfaces for meta information.

  • methods to create empty object instances.

  • QueryDSL-like expression classes (see <expressions>)

Currently the generator targets the ngrx-json-api library. Support for other libraries/formats would be straightforward to add, contributions welcomed. A generated resource looks like:

import {DefaultPagedLinksInformation} from './information/default.paged.links.information';
import {Tasks} from './tasks';
import {CrnkStoreResource} from '@crnk/angular-ngrx/stub';
import {
	ManyQueryResult,
	OneQueryResult,
	ResourceRelationship,
	TypedManyResourceRelationship,
	TypedOneResourceRelationship
} from 'ngrx-json-api/src/interfaces';

export module Schedules {
	export interface Relationships {
		[key: string]: ResourceRelationship;
		task?: TypedOneResourceRelationship<Tasks>;
		lazyTask?: TypedOneResourceRelationship<Tasks>;
		tasks?: TypedManyResourceRelationship<Tasks>;
		tasksList?: TypedManyResourceRelationship<Tasks>;
	}
	export interface Attributes {
		name?: string;
		delayed?: boolean;
	}
}
export interface Schedules extends CrnkStoreResource {
	relationships?: Schedules.Relationships;
	attributes?: Schedules.Attributes;
}
export interface SchedulesResult extends OneQueryResult {
	data?: Schedules;
}
export module SchedulesListResult {
	export interface ScheduleListLinks extends DefaultPagedLinksInformation {
	}
	export interface ScheduleListMeta {
	}
}
export interface SchedulesListResult extends ManyQueryResult {
	data?: Array<Schedules>;
	links?: SchedulesListResult.ScheduleListLinks;
	meta?: SchedulesListResult.ScheduleListMeta;
}
export let createEmptySchedules = function(id: string): Schedules {
	return {
		id: id,
		type: 'schedules',
		attributes: {
		},
		relationships: {
			task: {data: null},
			lazyTask: {data: null},
			tasks: {data: []},
			tasksList: {data: []},
		},
	};
};

Internally the generator has to make use of the running application to gather the necessary information for generation. This approach not only supports the typical, manual implement resources and repositories manually, but also the ones obtained through third-party modules such the JPA entities exposed by the JPA module. There are different possibilities to do that. https://github.com/crnk-project/crnk-framework/blob/master/crnk-client-angular-ngrx/build .gradle[crnk-client-angular-ngrx] does such a setup manually in Gradle. Alternatively, there is a Gradle plugin taking care of the generator setup. It makes use of the JUnit to get the application to a running state at built-time. So far a JUnit setup with Deltaspike for JEE application. Other non-CDI integrations like Spring will soon. Such a setup may look like:

buildscript {
	dependencies {
		classpath "io.crnk:crnk-gen-typescript:${version}"
		classpath "com.moowork.gradle:gradle-node-plugin:1.1.1"
	}
}

node {
	version = '6.9.1'
	download = true
	distBaseUrl = "${ADN_NODEJS_MIRROR_BASE_URL}/dist"
}

apply plugin: 'crnk-gen-typescript'

configurations {
	typescriptGenRuntime
}

dependencies {
	typescriptGenRuntime project(':project-to-generate-from')
}

typescriptGen{

	runtime {
		configuration = 'typescriptGenRuntime'
	}

	npm {
		packageVersion = '0.0.1'
		packageName = '@adnovum/moap-movie-management-api'
		description = 'movie management API to access backend'
		gitRepository = 'https://.../bitbucket/scm/moap/moap-movie.git'
		license = 'MIT'
		// packagingEnabled = false
		// outputDir = ...
	}

	includes = ['resources.task']
	excludes = ['resources.project']

	packageMapping['resources.something'] = '@crnk/some-demo-library'
	peerDependencies['ngrx-json-api'] = '>=2.0.0-beta.6'
	generateExpressions = true

	// genDir = ...

}
typescriptGen.init()
  • the moowork plugin is used to to gain a node setup.

  • packageName, description, license, gitRepository are added to the generated package.json.

  • by default the package version matches the npm version.

  • crnk-meta is used to gather a meta model of the underlying resources (or a any other type of object like JPA entities). Important to know is that every object is assigned a string-based meta id. By default the meta id matches resources.<resourceType>. For example a Task resource with resource type task has a resources.task meta id.

  • applying crnk-gen-typescript results in a new assembleTypescript task. Consumers may want to add that task to assemble as dependency.

  • runtime.configuration sets the Gradle configuration to use to obtain a classpath. In the given example typescriptGenRuntime is used. You may also use compile or anything else.

  • packagingEnabled triggers the generation of a package.json and tsconfig.json file (true as default). Useful to publish the stubs to an NPM repository for use by other projects. Setting it to false will result in the generation of the Typescript files only. This may be used to generate files directly into an existing frontend project. In this case only the generateTypescript task is available, there is no assembly taking place.

  • genDir specifies where source files are generated to.

  • npm.outputDirectory specifies where the compiled NPM package should be placed (build/npm as default).

  • generateExpressions specifies whether QueryDSL like classes should be generated (false as default).

  • packageMapping allows to specify into which libraries resources belong to. By default all resources have a resources. meta id prefix and go into the currently generated package. In the example above, all resources with a something/* resource type resp. resources.something meta id prefix are included from a @crnk/some-demo-library library and no longer get generated manually.

  • includes and excludes allow to include and exclude resources from generation based on their meta id.

Note
the generator plugin is not yet included in any example applications. It is not possible to include a plugin from within the same project. At some point an external example application will be setup to showcase the use of the plugin.

9. Angular Development with ngrx

Note
this feature is still in incurbation, feedback and contributions welcomed.

This chapter is dedicated to Angular development with Crnk, ngrx and https://github.com/abdulhaq-e/ngrx-json-api[ngrx-json-api]. ngrx brings the redux-style application development from React to Angular. Its motivation is to separate the presentation layer from application state for a clean, mockable, debug-friendly, performant and scalable design.

We believe that JSON API and redux can complement each other well. The resource-based nature of JSON API and its normalized response document format (trough relationships and inclusions) are well suited to be put into an ngrx-based store. ngrx-json-api is a project that does exactly that. The missing piece is how to integrate Angular components like forms and tables with ngrx-json-api. Tables need to display JSON API resources and do sorting, filtering, paging. Forms need to display JSON API resources and trigger POST, PATCH and DELETE requests. Errors should be displayed to the user in a dialog, header section or next to input component causing the issue (based on JSON API source pointers).

Crnk provides two tools: crnk-gen-typescript and @crnk/angular-ngrx. crnk-gen-typescript generates type-safe Typescript stubs from any Crnk backend. @crnk/angular-ngrx takes care of the binding of Angular forms and tables (and a few other things) to ngrx-json-api. crnk-gen-typescript and @crnk/angular-ngrx can be used together or individually. For more information about Typescript generation have a look at the [generation] chapter.

9.1. Feature overview

@crnk/angular-ngrx provides a number of different components:

Import

Description

@crnk/angular-ngrx/operations

CrnkOperationsModule implements JSON PATCH as Angular module. The module hooks into ngrx-json-api and enhances it with bulk insert, update, delete capabilities.

@crnk/angular-ngrx/expression

A simple QueryDSL-like expression model for Typescript.

@crnk/angular-ngrx/expression/forms

Binding of the expression model to Angular form components (a JSON API specific flavor of ngModel).

@crnk/angular-ngrx/binding

Helper classes that take care of binding tables or forms to JSON API. Makes use of @crnk/angular-ngrx/expression.

@crnk/angular-ngrx/meta

Typescript API for Meta Module generated with crnk-gen-typescript.

@crnk/angular-ngrx/stub

Some minor base classes used by Typescript generator. Not of direct interest.

All of those components are fairly lightweight and can also be used independently (if not specified otherwise above).

9.2. Bulk support with JSON Patch

CrnkOperationsModule imported from @crnk/angular-ngrx/operations provides client side support for JSON PATCH. This enables clients to issue bulk requests. See Operations module for more information about how it is implemented in Crnk.

CrnkOperationsModule integrates into NgrxJsonApiModule by replacing the implementation of ApiApplyInitAction in ngrx-json-api. Instead of issuing multiple requests, it will then issue a single bulk JSON Patch request. The bulk response triggers the usual ApiApplySuccessAction resp. ApiApplyFailAction.

Have a look at crnk.operations.effects.spec.ts for a test case demonstrating its use.

9.3. Expressions

@crnk/angular-ngrx/expression provides a QueryDSL-like expression model for Typescript. It is used to address boiler-plate when working with the Angular FormModule resp. ngModel directly. For example, when an input field needs to be bound to a JSON API resource field, a number of things must happen:

  • The input field should display the current store value.

  • The input field must have a unique form name.

  • The input field must sent changes back to the store.

  • The FormControl backing the input field must be properly validated. JSON API errors may may contain a source pointer. If the source pointer points to a field that is bound to a FormControl, it must be accounted for in its valid state.

  • The input field is usually accompanied by a message field displaying validation errors.

  • Errors that cannot be mapped to a FormControl must be displayed in a editor header or error dialog.

ngModel is limited to holding a simple value. In contrast, the use cases here require an understanding of the entire resource. It is necessary to have full JSON API resource and the path to the field to determine the field value and errors. This is achieved with @crnk/angular-ngrx/expression:

  • Expression interface represents any kind of value that can be obtained in some fashion.

  • Path<T> implements Expression and refers to a property of type <T> in an object.

  • For nested paths like attribute.name two Path objects are nested.

  • StringPath, NumberPath, BooleanPath and BeanPath<T> are type-safe implementations of path to account for primitive and Object types.

  • BeanBinding implements Path and represents the root, usually a resource. The root has an empty path.

Such expressions and paths can be constructed manually. Or, in most cases, crnk-gen-typescript can take care of that. In this case usage looks like:

crnk.expression.spec.ts
	let bean: MetaAttribute;
	let qbean: QMetaAttribute;

	beforeEach(() => {
		bean = {
			id: 'someBean.title',
			type: 'meta/attribute',
			attributes: {
				name: 'someName'
			}
		};
		qbean = new QMetaAttribute(new BeanBinding(bean));
	});


	it('should bind to bean', () => {
		expect(qbean.id.getValue()).toEqual('someBean.title');
		expect(qbean.attributes.name.getValue()).toEqual('someName');
		expect(qbean.attributes.name.toString()).toEqual('attributes.name');
		expect(qbean.id.getResource()).toBe(bean);
		expect(qbean.attributes.name.getResource()).toBe(bean);
	});

	it('should update bean', () => {
		qbean.attributes.name.setValue("updatedName");
		expect(bean.attributes.name).toEqual('updatedName');
	});

	it('should provide form name', () => {
		expect(qbean.attributes.name.toFormName()).toEqual('//meta/attribute//someBean.title//attributes.name');
	});

Note that:

  • QMetaAttribute from the meta model is used as example resource. At some point a dedicated test model will be setup.

  • it is fully type-safe

  • getValue fetches the value of the given path.

  • setValue sets the value of the given path.

  • toString returns the string representation of the path separated by dots.

  • getResource returns the object resp. resource backing the path.

  • toFormName computes a default (unique) form name for that path. The name is composed of the resource type, resource id and path to allow editing of multiple resources on the same screen.

  • QMetaAttribute can also be constructed without a bean binding. In this case it can still be used to construct type-safe paths and call toString. This can be used, for example, to specify a field for a table column where only later multiple records will then be loaded and shown.

The CrnkBindingFormModule provides two directives crnkExpression and crnkFormExpression that represent the ngModel counter-parts for expressions. While the former can be used standalone, the later is used for forms and registers itself to ngForm with the name provided by toFormName. Usage can look like:

<input id="nameInput" [crnkExpression]="resource.attributes.name"/>

or

<input id="nameInput" required [crnkFormExpression]="resource.attributes.name"/>

Notice the required validation directive. crnkExpression and crnkFormExpression support validation and ControlValueAccessor exactly like ngModel.

The use of expressions provides an (optional) foundation for the form and table binding discussed in the next sections.

9.4. Form Binding

Working with forms and JSON API is the same for many use cases:

  • components are bound to store values

  • components have to update store values by dispatching appropriate actions

  • components may perform basic local validation. For example with the Angular required directive.

  • components may get server-side validation errors using the JSON API error format.

  • components may perform client-side validation within the store with effects. The JSON API error mechanism can reused for this purpose. The ModifyStoreResourceErrorsAction action of ngrx-json-api can be triggered by a (validation) effect listing to value changes and makes arbitrarily complex, client-side validation logic possible.

There is a FormBinding class provided by CrnkExpressionFormModule that takes care of exactly this:

crnk.test.editor.component.ts
import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {FormBinding} from "../binding/crnk.binding.form";
import {QMetaAttribute} from "../meta/meta.attribute";
import {Subscription} from "rxjs/Subscription";
import {CrnkBindingService} from "../binding/crnk.binding.service";
import {BeanBinding} from "../expression/crnk.expression";

@Component({
	selector: 'test-editor',
	templateUrl: "crnk.test.editor.component.html"
})
export class TestEditorComponent implements OnInit, OnDestroy {

	@ViewChild('formRef') form;

	public binding: FormBinding;

	public resource: QMetaAttribute;

	private subscription: Subscription;

	constructor(private bindingService: CrnkBindingService) {
	}

	ngOnInit() {
		this.binding = this.bindingService.bindForm({
			form: this.form,
			queryId: 'editorQuery'
		});

		// note that one could use the "async" pipe and "as" operator, but so
		// far code completion does not seem to work in Intellij. For this reason
		// the example sticks to slightly more verbose subscriptions.
		this.subscription = this.binding.resource$.subscribe(
			person => {
				this.resource = new QMetaAttribute(new BeanBinding(person), null);
			}
		);
	}

	ngOnDestroy() {
		this.subscription.unsubscribe();
	}
}

A template then looks like:

crnk.test.editor.component.html
<form #formRef="ngForm">
	<div *ngIf="resource != null">

		<input id="nameInput" required [crnkFormExpression]="resource.attributes.name"/>

		<div id="valid">{{binding.valid | async}}</div>
		<div id="dirty">{{binding.dirty | async}}</div>

		<crnk-control-errors [expression]="resource.attributes.name">
			<ng-template let-errorCode="errorCode">
				<span id="controlError">{{errorCode}}</span>
			</ng-template>
		</crnk-control-errors>

		<crnk-resource-errors [expression]="resource.attributes.name">
			<ng-template let-errorData="errorData">
				<span id="resourceError">{{errorData.detail}}</span>
			</ng-template>
		</crnk-resource-errors>
	</div>
</form>

Note that:

  • It is fully type-safe.

  • It is compact.

  • There are two flavors to display errors. Only one is needed for a real application. Usually crnk-control-errors is used and allows to display any FormControl validation issue, either from a local validator or from the JSON API resource. crnk-resource-errors is a standalone flavor that is not bound to any FormControl and displays JSON API errors only. In both case a template must be specified how the error is rendered. In case of multiple errors, the template is rendered multiple times. errorCode and errorData are available as variable. errorData contains the full JSON API error object in case of a JSON API error.

  • FormBinding does not push changes to the store as long as local validation (required, min-length, etc.) do not pass.

  • FormBinding makes use of the form name to update the store. Therefore, the use of the expression model is optional and applications can also continue making use of ngModel and formControlName where appropriate. The name of form controls can follow to patterns: //<type>//<id>//path or just path`. The form allows to modify multiple resources, while the later assumes the primary resource loaded by the query is being modified.

  • FormBinding.dirty notifies whether bound resource(s) were modified.

  • FormBinding.valid notifies whether bound resource(s) are invalid.

The Angular FormModule gives a number of restrictions. In the future we expect to also support the use FormBinding without a NgForm instance (for some performance and simplicity benefits). Please provide feedback in this area of what is most helpful.

9.5. Table Binding

Similar to FormBinding there is a TableBinding class that looks like:

crnk.test.table.component.ts
import {Component, OnDestroy, OnInit} from '@angular/core';
import {MetaAttributeListResult} from '../meta/meta.attribute';
import {Subscription} from 'rxjs/Subscription';
import {CrnkBindingService} from '../binding/crnk.binding.service';
import {DataTableBinding} from '../binding/crnk.binding.table';

@Component({
	selector: 'test-table',
	templateUrl: 'crnk.test.table.component.html'
})
export class TestTableComponent implements OnInit, OnDestroy {

	binding: DataTableBinding;

	private subscription: Subscription;

	public result: MetaAttributeListResult;

	constructor(private bindingService: CrnkBindingService) {
	}

	ngOnInit() {
		this.binding = this.bindingService.bindDataTable({
			queryId: 'tableQuery',
			fromServer: false
		});
		this.subscription = this.binding.result$.subscribe(
			it => this.result = it as MetaAttributeListResult
		);
	}

	ngOnDestroy() {
		this.subscription.unsubscribe();
	}
}

and

crnk.test.table.component.html
<div *ngIf="result != null">
	<p-dataTable [value]="result.data"
			     selectionMode="single"
				 [(selection)]="binding.selectedResource"
				 [lazy]="true" [rows]="10" [paginator]="true"
				 (onLazyLoad)="binding.onLazyLoad($event)"
				 (onRowDblclick)="open($event.data)"
	>
		<p-column field="attributes.name" [header]="name" sortable="true"
				  [filter]="true" filterMatchMode="exact">
		</p-column>
	</p-dataTable>
</div>

Note that:

  • Also type-safe with the generated MetaAttributeListResult.

  • It currently is limited to the PrimeNG DataTable, but PRs for other implementations are welcomed.

  • TableBinding.onLazyLoad translates PrimeNG query, sort and page parameters to JSON API parameters.

9.6. Meta Model

@crnk/angular-ngrx/meta hosts a Typescript API for Meta Module generated by crnk-gen-typescript.

10. FAQ

  1. How to do Cors with Crnk?

    In most (if not all) cases Cors should be setup in the underlying integration, like with the Servlet-API or as JAX-RS filter and not within Crnk itself. This allows to make use of the native Cors mechanisms of an integration and to share Cors handling with the other parts of the application.

  2. Is Swagger supported by Crnk?

    Have a look at http://www.crnk.io/related/.