Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
remmeier committed Jul 17, 2018
1 parent 48c791b commit d6c7369
Show file tree
Hide file tree
Showing 23 changed files with 522 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ResourceRepositoryStubImpl<T, I extends Serializable> extends Clien


public ResourceRepositoryStubImpl(CrnkClient client, Class<T> resourceClass, ResourceInformation resourceInformation,
JsonApiUrlBuilder urlBuilder) {
JsonApiUrlBuilder urlBuilder) {
super(client, urlBuilder, resourceClass);
this.resourceInformation = resourceInformation;
}
Expand Down Expand Up @@ -63,7 +63,19 @@ public <S extends T> S save(S entity) {
@SuppressWarnings("unchecked")
private <S extends T> S modify(S entity, boolean create) {
Object id = getId(entity, create);
String url = urlBuilder.buildUrl(resourceInformation, id, (QuerySpec) null);

String url;
if (create && resourceInformation.isNested()) {
// TODO align with other cases, consider adding Method/complete object to url computation
// workaround to get parentId out of id
ResourceField idField = resourceInformation.getIdField();
id = idField.getAccessor().getValue(entity);
url = urlBuilder.buildUrl(resourceInformation, id, (QuerySpec) null);
url = url.substring(0, url.lastIndexOf('/'));
} else {
url = urlBuilder.buildUrl(resourceInformation, id, (QuerySpec) null);
}

return (S) executeUpdate(url, entity, create);
}

Expand All @@ -78,11 +90,9 @@ private <S extends T> Object getId(S entity, boolean create) {
}
if (create) {
return null;
}
else if (entity instanceof Resource) {
} else if (entity instanceof Resource) {
return ((Resource) entity).getId();
}
else {
} else {
ResourceField idField = resourceInformation.getIdField();
return idField.getAccessor().getValue(entity);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.crnk.client.suite;

import io.crnk.test.suite.NestedRepositoryAccessTestBase;

public class NestedRepositoryClientTest extends NestedRepositoryAccessTestBase {

public NestedRepositoryClientTest() {
ClientTestContainer testContainer = new ClientTestContainer();
this.testContainer = testContainer;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,12 @@ private void setupNesting() {
ResourceFieldAccessor parentIdAccessor = new ResourceFieldAccessor(){

@Override
public Object getValue(Object resource) {
Object id = getIdField().getAccessor().getValue(resource);
public Object getValue(Object object) {
if(idField.getType().isInstance(object)){
return finalParentAttribute.getValue(object);
}

Object id = getIdField().getAccessor().getValue(object);
return finalParentAttribute.getValue(id);
}

Expand All @@ -208,8 +212,11 @@ public void setValue(Object resource, Object fieldValue) {
this.nestedIdAccessor = new ResourceFieldAccessor() {

@Override
public Object getValue(Object resource) {
Object id = getIdField().getAccessor().getValue(resource);
public Object getValue(Object object) {
if(idField.getType().isInstance(object)){
return finalIdAttribute.getValue(object);
}
Object id = getIdField().getAccessor().getValue(object);
return finalIdAttribute.getValue(id);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,8 @@ protected Optional<Result> setRelationFieldAsync(Object newResource, RegistryEnt
if (relationship.getData().isPresent()) {
ResourceIdentifier relationshipId = (ResourceIdentifier) relationship.getData().get();

ResourceField field = registryEntry.getResourceInformation()
ResourceInformation resourceInformation = registryEntry.getResourceInformation();
ResourceField field = resourceInformation
.findRelationshipFieldByName(relationshipName);

if (field == null) {
Expand All @@ -373,7 +374,7 @@ protected Optional<Result> setRelationFieldAsync(Object newResource, RegistryEnt
.getType();
Serializable typedRelationshipId = parseId(relationshipId, idFieldType);

if (field.hasIdField()) {
if (field.hasIdField() && (!resourceInformation.isNested() || resourceInformation.getParentField() != field)) {
field.getIdAccessor().setValue(newResource, typedRelationshipId);
}
if (decideSetRelationObjectField(entry, typedRelationshipId, field)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,6 @@ public String getResourceUrl(QueryContext queryContext, ResourceInformation reso
String baseUrl = queryContext != null ? queryContext.getBaseUrl() : getServiceUrlProvider().getUrl();
String url = UrlUtils.removeTrailingSlash(baseUrl);
String resourcePath = resourceInformation.getResourcePath();
if (resourceInformation.isNested()) {
throw new UnsupportedOperationException("method not available for nested resources since id of parent needed");
}
return url != null ? String.format("%s/%s", url, resourcePath) : null;
}

Expand All @@ -149,12 +146,17 @@ public String getResourceUrl(QueryContext queryContext, final Object resource) {
throw new InvalidResourceException("Not registered resource found: " + resource);
}
ResourceInformation resourceInformation = findEntry(type.get()).getResourceInformation();
Object id = resourceInformation.getId(resource);
return getResourceUrl(queryContext, resourceInformation, id);
}

@Override
public String getResourceUrl(QueryContext queryContext, ResourceInformation resourceInformation, Object id) {
if (resourceInformation.isNested()) {
ResourceField parentField = resourceInformation.getParentField();

Object parentId = parentField.getIdAccessor().getValue(resource);
Object nestedId = resourceInformation.getNestedIdAccessor().getValue(resource);
Object parentId = parentField.getIdAccessor().getValue(id);
Object nestedId = resourceInformation.getNestedIdAccessor().getValue(id);
PreconditionUtil.verify(parentId != null, "nested resources must have a parent, got null from " + parentField.getIdName());
PreconditionUtil.verify(nestedId != null, "nested resources must have a non-null identifier");

Expand All @@ -166,11 +168,11 @@ public String getResourceUrl(QueryContext queryContext, final Object resource) {
return parentUrl + "/" + childrenField.getJsonName() + "/" + nestedId;
}

Object id = resourceInformation.getId(resource);
return String.format("%s/%s", getResourceUrl(queryContext, resourceInformation), id);
return String.format("%s/%s", getResourceUrl(queryContext, resourceInformation), resourceInformation.toIdString(id));

}


@Override
public String getResourceUrl(QueryContext queryContext, final Class<?> clazz) {
RegistryEntry registryEntry = findEntry(clazz);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import java.util.Map;
import java.util.concurrent.Callable;

import io.crnk.core.engine.information.resource.ResourceField;
import io.crnk.core.engine.information.resource.ResourceInformation;
import io.crnk.core.engine.query.QueryAdapter;
import io.crnk.core.engine.query.QueryContext;
import io.crnk.core.engine.registry.RegistryEntry;
import io.crnk.core.engine.registry.ResourceRegistry;
import io.crnk.core.module.ModuleRegistry;
import io.crnk.core.queryspec.DefaultQuerySpecDeserializer;
import io.crnk.core.queryspec.QuerySpec;
Expand Down Expand Up @@ -45,11 +48,10 @@ public String buildUrl(ResourceInformation resourceInformation, Object id, Query
}

public String buildUrl(ResourceInformation resourceInformation, Object id, QueryAdapter queryAdapter,
String relationshipName) {
String relationshipName) {
if (queryAdapter instanceof QuerySpecAdapter) {
return buildUrl(resourceInformation, id, ((QuerySpecAdapter) queryAdapter).getQuerySpec(), relationshipName);
}
else {
} else {
return buildUrl(resourceInformation, id, ((QueryParamsAdapter) queryAdapter).getQueryParams(), relationshipName);
}
}
Expand All @@ -64,9 +66,13 @@ public String buildUrl(ResourceInformation resourceInformation, Object id, Query
}

private String buildUrlInternal(ResourceInformation resourceInformation, Object id, Object query, String relationshipName) {
String url = moduleRegistry.getResourceRegistry().getResourceUrl(queryContext, resourceInformation);

String url;
ResourceRegistry resourceRegistry = moduleRegistry.getResourceRegistry();
if (id instanceof Collection) {
if (resourceInformation.isNested()) {
throw new UnsupportedOperationException("not yet implemented");
}
url = resourceRegistry.getResourceUrl(queryContext, resourceInformation);
Collection<?> ids = (Collection<?>) id;
Collection<String> strIds = new ArrayList<>();
for (Object idElem : ids) {
Expand All @@ -75,10 +81,10 @@ private String buildUrlInternal(ResourceInformation resourceInformation, Object
}
url += "/";
url += StringUtils.join(",", strIds);
}
else if (id != null) {
String strId = resourceInformation.toIdString(id);
url += "/" + strId;
} else if (id != null) {
url = resourceRegistry.getResourceUrl(queryContext, resourceInformation, id);
} else {
url = resourceRegistry.getResourceUrl(queryContext, resourceInformation);
}
if (relationshipName != null) {
url += "/relationships/" + relationshipName;
Expand All @@ -89,8 +95,7 @@ else if (id != null) {
QuerySpec querySpec = (QuerySpec) query;
QuerySpecUrlMapper urlMapper = moduleRegistry.getUrlMapper();
urlBuilder.addQueryParameters(urlMapper.serialize(querySpec));
}
else if (query instanceof QueryParams) {
} else if (query instanceof QueryParams) {
QueryParams queryParams = (QueryParams) query;
urlBuilder.addQueryParameters(queryParamsSerializer.serializeFilters(queryParams));
urlBuilder.addQueryParameters(queryParamsSerializer.serializeSorting(queryParams));
Expand Down Expand Up @@ -134,8 +139,7 @@ public void addQueryParameter(String key, final String value) {
if (firstParam) {
builder.append("?");
firstParam = false;
}
else {
} else {
builder.append("&");
}
builder.append(key);
Expand All @@ -154,8 +158,7 @@ private void addQueryParameter(String key, Object value) {
for (Object element : (Collection<?>) value) {
addQueryParameter(key, (String) element);
}
}
else {
} else {
addQueryParameter(key, (String) value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public interface ResourceRegistry extends ResourceRegistryPart {
*/
String getResourceUrl(QueryContext queryContext, ResourceInformation resourceInformation);

String getResourceUrl(QueryContext queryContext, ResourceInformation resourceInformation, Object id);

/**
* Retrieves the url of the resource
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public T findOne(I id, QuerySpec querySpec) {
PreconditionUtil.verify(!iterator.hasNext(), "expected unique result for id=%s, querySpec=%s", id, querySpec);
return resource;
} else {
throw new ResourceNotFoundException("resource not found");
throw new ResourceNotFoundException("resource not found: " + id);
}
}

Expand Down
8 changes: 8 additions & 0 deletions crnk-documentation/src/docs/asciidoc/repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -835,5 +835,13 @@ include::../../../../crnk-examples/spring-boot-example/src/main/java/io/crnk/exa
- `getMatcher` attaches the repository to the historized resources.
- `findManyTarget` implements the lookup of history elements.

anchor:JsonApiExposed[]

## @JsonApiExposed

A repository may be annotated with `@JsonApiExposed(false)`. In this case the repository is only available internally to other repositories, not
externally on the JSON API endpoint. There are different use cases for this:

- Have a look at the https://github.com/crnk-project/crnk-framework/tree/master/crnk-examples/spring-boot-microservice-example[micro-service example application] to see how remote repositories can be linked to local ones.
- <<nested_resources,nested resources>> can be made accessible only through their parent, such as `http://example.com/posts/1/comments/2` and no longer `http://example.com/comments`

40 changes: 39 additions & 1 deletion crnk-documentation/src/docs/asciidoc/resource.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,42 @@ Crnk comes with (partial) support for Jackson annotations. Currently supported a

|===

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


anchor:nested_resources[]

## Nested Resources

WARNING: This feature is experimental and will be refined in subsequent releases.

A resource may be nested and belong to a parent resource. In this case the nested resource is access through its parent resource.
An URL then looks like:

`http://example.com/posts/1/comments/2`

For a resource to become nested, it must make use of a structure identifier:

[source]
.NestedId.java
----
include::../../../../crnk-test/src/main/java/io/crnk/test/mock/models/nested/NestedId.java[]
----

The id is setup of two parts:

- the local identifier of the child resource, annotated with `@JsonApiId`. It must be unique among all nested resources having the same parent.
- the identifier of the parent resource, annotated with a `@JsonApiRelationId`. The nested resource must have matching relationship field (in this case `parent`)`.
`NestedId` further implements `parse`, `toString`, `hashCode` and `equals` to deal with String serialization and equality.

For an example have a look at `NestedRepositoryClientTest` and its parent class. Depending on the use of <<JsonApiExposed>> it
is or is not be possible to also directly access `http://example.com/comments` without going
through the parents.

Current Limitations:

- Nesting is currently limited to a single level (and the possibility to have further relationships on the nested resource).
- When creating new resources with CrnkClient, the nested identifier must be setup in order to let CrnkClient access the parentId.
- Being experimental.
8 changes: 7 additions & 1 deletion crnk-documentation/src/docs/releases/latest/info.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ <h4>@JsonApiExpose annotation</h4>
<h4>InMemoryResourceRepository default implementation</h4>

There is a default implementation for a in-memory resource repository that is backed by a ConcurrentHashMap. It can be useful to get started quickly,
use for mocking and work with small data sets.
use for mocking and work with small data sets.

<h4>Nested Resources</h4>

Work started to bring support for nested resource having url patterns like http://example.com/posts/1/comments/2. For more information
you can check out the <i>nested resource</i> section in the documentation. However, this feature is still considered (very) experimental
and will be refined and more thoroughly tested in subsequent releases. Help to finish it is welcomed.
4 changes: 2 additions & 2 deletions crnk-examples/spring-boot-microservice-example/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# spring-boot-micro-service-example
# spring-boot-microservice-example

Showcases two minimal Spring Boot micro services for projects and
tasks that have a JSON API relationship
Expand All @@ -11,7 +11,7 @@ In order to run this example do:

## How to run with Gradle

gradlew :crnk-examples:spring-boot-micro-service-example:run
gradlew :crnk-examples:spring-boot-microservice-example:run

## The service will be available at

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void enrichTypeIdInformation(List<Operation> operations) {
String path = OperationParameterUtils.parsePath(operation.getPath());
JsonPath jsonPath = (new PathBuilder(resourceRegistry, moduleContext.getTypeParser())).build(path);

RegistryEntry entry = resourceRegistry.getEntryByPath(path);
RegistryEntry entry = jsonPath.getRootEntry();
String idString = entry.getResourceInformation().toIdString(jsonPath.getId());

Resource resource = new Resource();
Expand Down
22 changes: 22 additions & 0 deletions crnk-test/src/main/java/io/crnk/test/mock/TestModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@

import io.crnk.core.module.Module;
import io.crnk.test.mock.repository.*;
import io.crnk.test.mock.repository.nested.ManyNestedRepository;
import io.crnk.test.mock.repository.nested.RelatedRepository;
import io.crnk.test.mock.repository.nested.NestedRelationshipRepository;
import io.crnk.test.mock.repository.nested.ParentRepository;

public class TestModule implements Module {

private static ManyNestedRepository manyNestedRepository = new ManyNestedRepository();

private static RelatedRepository relatedRepository = new RelatedRepository();

private static ParentRepository parentRepository = new ParentRepository();

private static NestedRelationshipRepository nestedRelationshipRepository = new NestedRelationshipRepository();

@Override
public String getModuleName() {
return "test";
Expand All @@ -22,6 +34,12 @@ public void setupModule(ModuleContext context) {
context.addRepository(new TaskToScheduleRepo());
context.addRepository(new PrimitiveAttributeRepository());
context.addRepository(new RelationIdTestRepository());

context.addRepository(manyNestedRepository);
context.addRepository(relatedRepository);
context.addRepository(parentRepository);
context.addRepository(nestedRelationshipRepository);

context.addExceptionMapper(new TestExceptionMapper());
}

Expand All @@ -32,5 +50,9 @@ public static void clear() {
ProjectToTaskRepository.clear();
ScheduleRepositoryImpl.clear();
RelationIdTestRepository.clear();

manyNestedRepository.clear();
relatedRepository.clear();
parentRepository.clear();
}
}

0 comments on commit d6c7369

Please sign in to comment.