Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.7.2 backports 2 #38661

Merged
merged 11 commits into from
Feb 8, 2024
Prev Previous commit
Next Next commit
Fixes #38543 - LinksProcessor ID field error for native class HalColl…
…ectionWrapper

(cherry picked from commit 9855040)
  • Loading branch information
Bas Passon authored and gsmet committed Feb 7, 2024
commit 520a61ee135c44b91667533cb27bf08db70c0019
12 changes: 6 additions & 6 deletions docs/src/main/asciidoc/resteasy-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1869,22 +1869,22 @@ When we call a resource `/records/1` that returns only one instance, then the ou
}
----

Finally, you can also provide additional HAL links programmatically in your resource just by returning either `HalCollectionWrapper` (to return a list of entities) or `HalEntityWrapper` (to return a single object) as described in the following example:
Finally, you can also provide additional HAL links programmatically in your resource just by returning either `HalCollectionWrapper<T>` (to return a list of entities) or `HalEntityWrapper<T>` (to return a single object) as described in the following example:

[source,java]
----
@Path("/records")
public class RecordsResource {

@Inject
RestLinksProvider linksProvider;
HalService halService;

@GET
@Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
@RestLink(rel = "list")
public HalCollectionWrapper getAll() {
public HalCollectionWrapper<Record> getAll() {
List<Record> list = // ...
HalCollectionWrapper halCollection = new HalCollectionWrapper(list, "collectionName", linksProvider.getTypeLinks(Record.class));
HalCollectionWrapper<Record> halCollection = halService.toHalCollectionWrapper( list, "collectionName", Record.class);
halCollection.addLinks(Link.fromPath("/records/1").rel("first-record").build());
return halCollection;
}
Expand All @@ -1894,9 +1894,9 @@ public class RecordsResource {
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks(RestLinkType.INSTANCE)
public HalEntityWrapper get(@PathParam("id") int id) {
public HalEntityWrapper<Record> get(@PathParam("id") int id) {
Record entity = // ...
HalEntityWrapper halEntity = new HalEntityWrapper(entity, linksProvider.getInstanceLinks(entity));
HalEntityWrapper<Record> halEntity = halService.toHalWrapper(entity);
halEntity.addLinks(Link.fromPath("/records/1/parent").rel("parent-record").build());
return halEntity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@
* - the JSON-B serializer: {@link HalCollectionWrapperJsonbSerializer}
* - the Jackson serializer: {@link HalCollectionWrapperJacksonSerializer}
*/
public class HalCollectionWrapper extends HalWrapper {
public class HalCollectionWrapper<T> extends HalWrapper {

private final Collection<HalEntityWrapper> collection;
private final Collection<HalEntityWrapper<T>> collection;
private final String collectionName;

public HalCollectionWrapper(Collection<HalEntityWrapper> collection, String collectionName, Link... links) {
public HalCollectionWrapper(Collection<HalEntityWrapper<T>> collection, String collectionName, Link... links) {
this(collection, collectionName, new HashMap<>());

addLinks(links);
}

public HalCollectionWrapper(Collection<HalEntityWrapper> collection, String collectionName, Map<String, HalLink> links) {
public HalCollectionWrapper(Collection<HalEntityWrapper<T>> collection, String collectionName, Map<String, HalLink> links) {
super(links);

this.collection = collection;
this.collectionName = collectionName;
}

public Collection<HalEntityWrapper> getCollection() {
public Collection<HalEntityWrapper<T>> getCollection() {
return collection;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,33 @@
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class HalCollectionWrapperJacksonSerializer extends JsonSerializer<HalCollectionWrapper> {
public class HalCollectionWrapperJacksonSerializer extends JsonSerializer<HalCollectionWrapper<?>> {

@Override
public void serialize(HalCollectionWrapper wrapper, JsonGenerator generator, SerializerProvider serializers)
public void serialize(HalCollectionWrapper<?> wrapper, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeStartObject();
writeEmbedded(wrapper, generator, serializers);
writeLinks(wrapper, generator);
generator.writeEndObject();
}

private void writeEmbedded(HalCollectionWrapper wrapper, JsonGenerator generator, SerializerProvider serializers)
private void writeEmbedded(HalCollectionWrapper<?> wrapper, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
JsonSerializer<Object> entitySerializer = serializers.findValueSerializer(HalEntityWrapper.class);

generator.writeFieldName("_embedded");
generator.writeStartObject();
generator.writeFieldName(wrapper.getCollectionName());
generator.writeStartArray();
for (HalEntityWrapper entity : wrapper.getCollection()) {
for (HalEntityWrapper<?> entity : wrapper.getCollection()) {
entitySerializer.serialize(entity, generator, serializers);
}
generator.writeEndArray();
generator.writeEndObject();
}

private void writeLinks(HalCollectionWrapper wrapper, JsonGenerator generator) throws IOException {
private void writeLinks(HalCollectionWrapper<?> wrapper, JsonGenerator generator) throws IOException {
generator.writeFieldName("_links");
generator.writeObject(wrapper.getLinks());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;

// Using the raw type here as eclipse yasson doesn't like custom serializers for
// generic root types, see https://github.com/eclipse-ee4j/yasson/issues/639
public class HalCollectionWrapperJsonbSerializer implements JsonbSerializer<HalCollectionWrapper> {

@Override
Expand All @@ -14,19 +16,19 @@ public void serialize(HalCollectionWrapper wrapper, JsonGenerator generator, Ser
generator.writeEnd();
}

private void writeEmbedded(HalCollectionWrapper wrapper, JsonGenerator generator, SerializationContext context) {
private void writeEmbedded(HalCollectionWrapper<?> wrapper, JsonGenerator generator, SerializationContext context) {
generator.writeKey("_embedded");
generator.writeStartObject();
generator.writeKey(wrapper.getCollectionName());
generator.writeStartArray();
for (HalEntityWrapper entity : wrapper.getCollection()) {
for (HalEntityWrapper<?> entity : wrapper.getCollection()) {
context.serialize(entity, generator);
}
generator.writeEnd();
generator.writeEnd();
}

private void writeLinks(HalCollectionWrapper wrapper, JsonGenerator generator, SerializationContext context) {
private void writeLinks(HalCollectionWrapper<?> wrapper, JsonGenerator generator, SerializationContext context) {
context.serialize("_links", wrapper.getLinks(), generator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@
* - the JSON-B serializer: {@link HalEntityWrapperJsonbSerializer}
* - the Jackson serializer: {@link HalEntityWrapperJacksonSerializer}
*/
public class HalEntityWrapper extends HalWrapper {
public class HalEntityWrapper<T> extends HalWrapper {

private final Object entity;
private final T entity;

public HalEntityWrapper(Object entity, Link... links) {
public HalEntityWrapper(T entity, Link... links) {
this(entity, new HashMap<>());

addLinks(links);
}

public HalEntityWrapper(Object entity, Map<String, HalLink> links) {
public HalEntityWrapper(T entity, Map<String, HalLink> links) {
super(links);

this.entity = entity;
}

public Object getEntity() {
public T getEntity() {
return entity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;

public class HalEntityWrapperJacksonSerializer extends JsonSerializer<HalEntityWrapper> {
public class HalEntityWrapperJacksonSerializer extends JsonSerializer<HalEntityWrapper<?>> {

@Override
public void serialize(HalEntityWrapper wrapper, JsonGenerator generator, SerializerProvider serializers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.eclipse.yasson.internal.model.ClassModel;
import org.eclipse.yasson.internal.model.PropertyModel;

// Using the raw type here as eclipse yasson doesn't like custom serializers for
// generic root types, see https://github.com/eclipse-ee4j/yasson/issues/639
public class HalEntityWrapperJsonbSerializer implements JsonbSerializer<HalEntityWrapper> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public abstract class HalService {
* @param entityClass The class of the objects in the collection. If null, it will not resolve the links for these objects.
* @return The Hal collection wrapper instance.
*/
public HalCollectionWrapper toHalCollectionWrapper(Collection<Object> collection, String collectionName,
public <T> HalCollectionWrapper<T> toHalCollectionWrapper(Collection<T> collection, String collectionName,
Class<?> entityClass) {
List<HalEntityWrapper> items = new ArrayList<>();
for (Object entity : collection) {
List<HalEntityWrapper<T>> items = new ArrayList<>();
for (T entity : collection) {
items.add(toHalWrapper(entity));
}

Expand All @@ -36,7 +36,7 @@ public HalCollectionWrapper toHalCollectionWrapper(Collection<Object> collection
classLinks = getClassLinks(entityClass);
}

return new HalCollectionWrapper(items, collectionName, classLinks);
return new HalCollectionWrapper<>(items, collectionName, classLinks);
}

/**
Expand All @@ -45,8 +45,8 @@ public HalCollectionWrapper toHalCollectionWrapper(Collection<Object> collection
* @param entity The entity to wrap.
* @return The Hal entity wrapper.
*/
public HalEntityWrapper toHalWrapper(Object entity) {
return new HalEntityWrapper(entity, getInstanceLinks(entity));
public <T> HalEntityWrapper<T> toHalWrapper(T entity) {
return new HalEntityWrapper<>(entity, getInstanceLinks(entity));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hal-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.quarkus.resteasy.reactive.links.deployment;

import java.util.List;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Link;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;

import org.jboss.resteasy.reactive.common.util.RestMediaType;

import io.quarkus.hal.HalCollectionWrapper;
import io.quarkus.hal.HalEntityWrapper;
import io.quarkus.hal.HalService;
import io.quarkus.resteasy.reactive.links.InjectRestLinks;
import io.quarkus.resteasy.reactive.links.RestLink;
import io.quarkus.resteasy.reactive.links.RestLinkType;

@Path("/hal")
public class HalWrapperResource {

@Inject
HalService halService;

@GET
@Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
@RestLink(rel = "list")
@InjectRestLinks
public HalCollectionWrapper<TestRecordWithIdAndPersistenceIdAndRestLinkId> getRecords(@Context UriInfo uriInfo) {
List<TestRecordWithIdAndPersistenceIdAndRestLinkId> items = List.of(
new TestRecordWithIdAndPersistenceIdAndRestLinkId(1, 10, 100, "one"),
new TestRecordWithIdAndPersistenceIdAndRestLinkId(2, 20, 200, "two"));

HalCollectionWrapper<TestRecordWithIdAndPersistenceIdAndRestLinkId> halCollection = halService.toHalCollectionWrapper(
items,
"collectionName", TestRecordWithIdAndPersistenceIdAndRestLinkId.class);
halCollection.addLinks(
Link.fromUriBuilder(uriInfo.getBaseUriBuilder().path(String.format("/hal/%d", 1))).rel("first-record").build());

return halCollection;
}

@GET
@Path("/{id}")
@Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
@RestLink(rel = "self")
@InjectRestLinks(RestLinkType.INSTANCE)
public HalEntityWrapper<TestRecordWithIdAndPersistenceIdAndRestLinkId> getRecord(@PathParam("id") int id,
@Context UriInfo uriInfo) {

HalEntityWrapper<TestRecordWithIdAndPersistenceIdAndRestLinkId> halEntity = halService.toHalWrapper(
new TestRecordWithIdAndPersistenceIdAndRestLinkId(1, 10, 100, "one"));
halEntity.addLinks(Link.fromUriBuilder(uriInfo.getBaseUriBuilder().path(String.format("/hal/%d/parent", id)))
.rel("parent-record").build());

return halEntity;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.quarkus.resteasy.reactive.links.deployment;

import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import jakarta.ws.rs.core.HttpHeaders;

import org.jboss.resteasy.reactive.common.util.RestMediaType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.Version;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;
import io.restassured.response.Response;

public class HalWrapperResourceTest {

@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HalWrapperResource.class, TestRecordWithIdAndPersistenceIdAndRestLinkId.class))
.setForcedDependencies(List.of(
Dependency.of("io.quarkus", "quarkus-resteasy-reactive-jackson", Version.getVersion()),
Dependency.of("io.quarkus", "quarkus-hal", Version.getVersion())));

@TestHTTPResource("hal")
String recordsUrl;

@TestHTTPResource("hal/{id}")
String recordIdUrl;

@Test
void shouldGetAllRecordsWithCustomHalMetadata() {
Response response = given()
.header(HttpHeaders.ACCEPT, RestMediaType.APPLICATION_HAL_JSON)
.get(recordsUrl).thenReturn();

assertThat(response.body().jsonPath().getString("_embedded['collectionName'][0].restLinkId")).isEqualTo("1");
assertThat(response.body().jsonPath().getString("_embedded['collectionName'][0]._links.self.href")).endsWith("/hal/1");
assertThat(response.body().jsonPath().getString("_embedded['collectionName'][0]._links.list.href")).endsWith("/hal");
assertThat(response.body().jsonPath().getString("_embedded['collectionName'][1].restLinkId")).isEqualTo("2");
assertThat(response.body().jsonPath().getString("_embedded['collectionName'][1]._links.self.href")).endsWith("/hal/2");
assertThat(response.body().jsonPath().getString("_embedded['collectionName'][1]._links.list.href")).endsWith("/hal");
assertThat(response.body().jsonPath().getString("_links.first-record.href")).endsWith("/hal/1");
assertThat(response.body().jsonPath().getString("_links.list.href")).endsWith("/hal");
}

@Test
void shouldGetSingleRecordWithCustomHalMetadata() {
Response response = given()
.header(HttpHeaders.ACCEPT, RestMediaType.APPLICATION_HAL_JSON)
.get(recordIdUrl, 1L)
.thenReturn();

assertThat(response.body().jsonPath().getString("restLinkId")).isEqualTo("1");
assertThat(response.body().jsonPath().getString("_links.parent-record.href")).endsWith("/hal/1/parent");
assertThat(response.body().jsonPath().getString("_links.self.href")).endsWith("/hal/1");
assertThat(response.body().jsonPath().getString("_links.list.href")).endsWith("/hal");

}
}