You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/01/28 11:20:55 UTC
[isis] branch master updated: ISIS-2158: REST-client: also support
actions, that return collections
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new 51d2ca5 ISIS-2158: REST-client: also support actions, that return collections
51d2ca5 is described below
commit 51d2ca53327c91bc7221f08b2263da30c520cecd
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jan 28 12:20:47 2020 +0100
ISIS-2158: REST-client: also support actions, that return collections
- previous for single entity result only ... T digest.get()
- now unified for any cardinality ... Can<T> digest.getEntites()
---
.../isis/testdomain/jdo/InventoryResource.java | 20 ++++
.../isis/testdomain/rest/RestEndpointService.java | 30 +++++-
.../isis/testdomain/rest/RestServiceTest.java | 35 ++++--
.../shiro/ShiroSecmanLdap_restfulStressTest.java | 2 +-
.../isis/extensions/restclient/ResponseDigest.java | 118 +++++++++++++++------
.../isis/extensions/restclient/RestfulClient.java | 33 +++++-
6 files changed, 189 insertions(+), 49 deletions(-)
diff --git a/examples/smoketests/src/main/java/org/apache/isis/testdomain/jdo/InventoryResource.java b/examples/smoketests/src/main/java/org/apache/isis/testdomain/jdo/InventoryResource.java
index f9fb309..220862b 100644
--- a/examples/smoketests/src/main/java/org/apache/isis/testdomain/jdo/InventoryResource.java
+++ b/examples/smoketests/src/main/java/org/apache/isis/testdomain/jdo/InventoryResource.java
@@ -30,8 +30,10 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import org.apache.isis.applib.annotation.Action;
import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.ParameterLayout;
import org.apache.isis.applib.services.repository.RepositoryService;
import org.apache.isis.core.commons.internal.base._NullSafe;
+import org.apache.isis.core.commons.internal.collections._Lists;
import lombok.val;
@@ -59,6 +61,24 @@ public class InventoryResource {
return repository.persist(book);
}
+ @Action
+ public List<Book> multipleBooks(
+
+ @ParameterLayout(named = "")
+ int nrOfBooks
+
+ ) {
+
+ val books = _Lists.<Book>newArrayList();
+
+ // for this test we do not care if we generate duplicates
+ for(int i=0; i<nrOfBooks; ++i) {
+ val book = Book.of("MultipleBooksTest", "An awesome Book["+i+"]", 12, "Author", "ISBN", "Publisher");
+ books.add(repository.persist(book));
+ }
+ return books;
+ }
+
@Action //TODO improve the REST client such that the param can be of type Book
public Book storeBook(String newBook) throws JAXBException {
Book book = BookDto.decode(newBook).toBook();
diff --git a/examples/smoketests/src/main/java/org/apache/isis/testdomain/rest/RestEndpointService.java b/examples/smoketests/src/main/java/org/apache/isis/testdomain/rest/RestEndpointService.java
index 5fb1a5e..f494412 100644
--- a/examples/smoketests/src/main/java/org/apache/isis/testdomain/rest/RestEndpointService.java
+++ b/examples/smoketests/src/main/java/org/apache/isis/testdomain/rest/RestEndpointService.java
@@ -18,8 +18,11 @@
*/
package org.apache.isis.testdomain.rest;
+import java.util.List;
+
import javax.inject.Inject;
import javax.ws.rs.client.Invocation;
+import javax.ws.rs.core.GenericType;
import javax.xml.bind.JAXBException;
import org.springframework.core.env.Environment;
@@ -67,11 +70,11 @@ public class RestEndpointService {
public RestfulClient newClient(boolean useRequestDebugLogging) {
- if(useRequestDebugLogging) {
- final String msg = "RestfulLoggingFilter seems to fail in 4.4.1, consumes the response's entity (in order to log) so that subsequent attempts to read response fail.";
- log.error(msg);
- throw new UnsupportedOperationException(msg);
- }
+// if(useRequestDebugLogging) {
+// final String msg = "RestfulLoggingFilter seems to fail in 4.4.1, consumes the response's entity (in order to log) so that subsequent attempts to read response fail.";
+// log.error(msg);
+// throw new UnsupportedOperationException(msg);
+// }
val restRootPath =
String.format("http://localhost:%d%s/",
getPort(),
@@ -110,6 +113,23 @@ public class RestEndpointService {
return digest;
}
+ public ResponseDigest<Book> getMultipleBooks(RestfulClient client) throws JAXBException {
+
+ Invocation.Builder request = client.request(
+ "services/testdomain.InventoryResource/actions/multipleBooks/invoke",
+ SuppressionType.ALL);
+
+ val args = client.arguments()
+ .addActionParameter("nrOfBooks", 2)
+ .build();
+
+ val response = request.post(args);
+ val digest = client.digestList(response, Book.class, new GenericType<List<Book>>() {});
+
+ return digest;
+ }
+
+
public ResponseDigest<Book> storeBook(RestfulClient client, Book newBook) throws JAXBException {
val request = client.request(
"services/testdomain.InventoryResource/actions/storeBook/invoke",
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/rest/RestServiceTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/rest/RestServiceTest.java
index a4245e5..57d6012 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/rest/RestServiceTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/rest/RestServiceTest.java
@@ -58,7 +58,7 @@ class RestServiceTest {
assertNotNull(restService.getPort());
assertTrue(restService.getPort()>0);
- val useRequestDebugLogging = false; // not used because seems to fail in 4.4.1, consumes the response's entity and so
+ val useRequestDebugLogging = false;
val restfulClient = restService.newClient(useRequestDebugLogging);
val digest = restService.getRecommendedBookOfTheWeek(restfulClient);
@@ -67,7 +67,7 @@ class RestServiceTest {
fail(digest.getFailureCause());
}
- val bookOfTheWeek = digest.get();
+ val bookOfTheWeek = digest.getEntities().getSingletonOrFail();
assertNotNull(bookOfTheWeek);
assertEquals("Book of the week", bookOfTheWeek.getName());
@@ -80,7 +80,7 @@ class RestServiceTest {
assertNotNull(restService.getPort());
assertTrue(restService.getPort()>0);
- val useRequestDebugLogging = false; // not used because seems to fail in 4.4.1, consumes the response's entity and so
+ val useRequestDebugLogging = false;
val restfulClient = restService.newClient(useRequestDebugLogging);
val newBook = Book.of("REST Book", "A sample REST book for testing.", 77.,
@@ -92,14 +92,37 @@ class RestServiceTest {
fail(digest.getFailureCause());
}
- val storedBook = digest.get();
+ val storedBook = digest.getEntities().getSingletonOrFail();
assertNotNull(storedBook);
assertEquals("REST Book", storedBook.getName());
}
-
+ @Test
+ void multipleBooks_viaRestEndpoint() throws JAXBException {
+
+ assertNotNull(restService.getPort());
+ assertTrue(restService.getPort()>0);
+
+ val useRequestDebugLogging = false;
+ val restfulClient = restService.newClient(useRequestDebugLogging);
+
+ val digest = restService.getMultipleBooks(restfulClient);
+
+ if(!digest.isSuccess()) {
+ fail(digest.getFailureCause());
+ }
+
+ val multipleBooks = digest.getEntities();
+
+ assertEquals(2, multipleBooks.size());
+
+ for(val book : multipleBooks) {
+ assertEquals("MultipleBooksTest", book.getName());
+ }
+
+ }
@Test
void httpSessionInfo() {
@@ -113,7 +136,7 @@ class RestServiceTest {
fail(digest.getFailureCause());
}
- val httpSessionInfo = digest.get();
+ val httpSessionInfo = digest.getEntities().getSingletonOrFail();
assertNotNull(httpSessionInfo);
assertEquals("no http-session", httpSessionInfo);
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
index 3674c78..7899ffd 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/shiro/ShiroSecmanLdap_restfulStressTest.java
@@ -127,7 +127,7 @@ class ShiroSecmanLdap_restfulStressTest extends AbstractShiroTest {
fail(digest.getFailureCause());
}
- val httpSessionInfo = digest.get();
+ val httpSessionInfo = digest.getEntities();
assertNotNull(httpSessionInfo);
assertEquals("no http-session", httpSessionInfo);
diff --git a/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java b/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
index 38b043e..f7dbba8 100644
--- a/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
+++ b/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/ResponseDigest.java
@@ -21,10 +21,13 @@ package org.apache.isis.extensions.restclient;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.Future;
import java.util.function.Function;
+import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
@@ -32,10 +35,12 @@ import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import org.apache.isis.applib.util.schema.CommonDtoUtils;
+import org.apache.isis.core.commons.collections.Can;
import org.apache.isis.core.commons.internal.base._Casts;
import org.apache.isis.core.commons.internal.base._Strings;
import org.apache.isis.core.commons.internal.resources._Json;
+import lombok.NonNull;
import lombok.val;
/**
@@ -44,16 +49,41 @@ import lombok.val;
*/
public class ResponseDigest<T> {
- /** synchronous response processing */
- public static <T> ResponseDigest<T> of(Response response, Class<T> entityType) {
- return new ResponseDigest<>(response, entityType).digest();
+ /**
+ * synchronous response processing (single entity)
+ * @param <T>
+ * @param response
+ * @param entityType
+ * @return
+ */
+ public static <T> ResponseDigest<T> of(
+ @NonNull final Response response,
+ @NonNull final Class<T> entityType) {
+
+ return new ResponseDigest<>(response, entityType, null).digest();
}
-
+
+ /**
+ * synchronous response processing (list of entities)
+ * @param <T>
+ * @param response
+ * @param entityType
+ * @param genericType
+ * @return
+ */
+ public static <T> ResponseDigest<T> ofList(
+ @NonNull final Response response,
+ @NonNull final Class<T> entityType,
+ @NonNull final GenericType<List<T>> genericType) {
+
+ return new ResponseDigest<>(response, entityType, genericType).digest();
+ }
+
/** a-synchronous response failure processing */
public static <T> ResponseDigest<T> ofAsyncFailure(
- Future<Response> asyncResponse,
- Class<T> entityType,
- Exception failure) {
+ final Future<Response> asyncResponse,
+ final Class<T> entityType,
+ final Exception failure) {
Response response;
try {
@@ -62,20 +92,22 @@ public class ResponseDigest<T> {
response = null;
}
- final ResponseDigest<T> failureDigest = new ResponseDigest<>(response, entityType);
+ final ResponseDigest<T> failureDigest = new ResponseDigest<>(response, entityType, null);
return failureDigest.digestAsyncFailure(asyncResponse.isCancelled(), failure);
}
-
+
private final Response response;
private final Class<T> entityType;
+ private final GenericType<List<T>> genericType;
- private T entity;
+ private Can<T> entities;
private Exception failureCause;
- protected ResponseDigest(Response response, Class<T> entityType) {
+ protected ResponseDigest(Response response, Class<T> entityType, GenericType<List<T>> genericType) {
this.response = response;
this.entityType = entityType;
+ this.genericType = genericType;
}
public boolean isSuccess() {
@@ -86,23 +118,23 @@ public class ResponseDigest<T> {
return failureCause!=null;
}
- public T get(){
- return entity;
+ public Can<T> getEntities(){
+ return entities;
}
public Exception getFailureCause(){
return failureCause;
}
- public T ifSuccessGetOrElseMap(Function<Exception, T> failureMapper) {
+ public Can<T> ifSuccessGetOrElseMap(Function<Exception, Can<T>> failureMapper) {
return isSuccess()
- ? get()
+ ? getEntities()
: failureMapper.apply(getFailureCause());
}
- public <X> X ifSuccessMapOrElseMap(Function<T, X> successMapper, Function<Exception, X> failureMapper) {
+ public <X> X ifSuccessMapOrElseMap(Function<Can<T>, X> successMapper, Function<Exception, X> failureMapper) {
return isSuccess()
- ? successMapper.apply(get())
+ ? successMapper.apply(getEntities())
: failureMapper.apply(getFailureCause());
}
@@ -111,47 +143,65 @@ public class ResponseDigest<T> {
private ResponseDigest<T> digest() {
if(response==null) {
- entity = null;
+ entities = Can.empty();
failureCause = new NoSuchElementException();
return this;
}
if(!response.hasEntity()) {
- entity = null;
+ entities = Can.empty();
failureCause = new NoSuchElementException(defaultFailureMessage(response));
return this;
}
if(response.getStatusInfo().getFamily() != Family.SUCCESSFUL) {
- entity = null;
+ entities = Can.empty();
failureCause = new RestfulClientException(defaultFailureMessage(response));
return this;
}
- if(isValueType(entityType)) {
- try {
- val responseBody = response.readEntity(String.class);
- entity = parseValueTypeBody(responseBody);
- } catch (Exception e) {
- entity = null;
- failureCause = new RestfulClientException("failed to read JAX-RS response content", e);
- }
- return this;
- }
-
try {
- entity = response.readEntity(entityType);
+
+ if(genericType==null) {
+ // when response is a singleton
+ entities = Can.ofSingleton(readSingle());
+ } else {
+ // when response is a list
+ entities = Can.ofCollection(readList());
+ }
+
} catch (Exception e) {
- entity = null;
+ entities = Can.empty();
failureCause = new RestfulClientException("failed to read JAX-RS response content", e);
}
return this;
}
+ private T readSingle() throws JsonParseException, JsonMappingException, IOException {
+ if(isValueType(entityType)) {
+ val responseBody = response.readEntity(String.class);
+ return parseValueTypeBody(responseBody);
+ }
+ return response.<T>readEntity(entityType);
+ }
+
+ private List<T> readList() throws JsonParseException, JsonMappingException, IOException {
+ if(isValueType(entityType)) {
+ final List<String> valueBodies = response.readEntity(new GenericType<List<String>>() {});
+ final List<T> resultList = new ArrayList<>(valueBodies.size());
+ for(val valueBody : valueBodies) {
+ // explicit loop, for simpler exception propagation
+ resultList.add(parseValueTypeBody(valueBody));
+ }
+ return resultList;
+ }
+ return response.readEntity(genericType);
+ }
+
private ResponseDigest<T> digestAsyncFailure(boolean isCancelled, Exception failure) {
- entity = null;
+ entities = Can.empty();
if(isCancelled) {
failureCause = new RestfulClientException("Async JAX-RS request was canceled", failure);
diff --git a/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/RestfulClient.java b/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/RestfulClient.java
index 039d020..65c18d1 100644
--- a/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/RestfulClient.java
+++ b/mappings/restclient/api/src/main/java/org/apache/isis/extensions/restclient/RestfulClient.java
@@ -19,6 +19,7 @@
package org.apache.isis.extensions.restclient;
import java.util.EnumSet;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@@ -26,6 +27,7 @@ import java.util.stream.Collectors;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
+import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import org.apache.isis.applib.client.SuppressionType;
@@ -71,7 +73,7 @@ ResponseDigest<MyObject> digest = client.digest(response, MyObject.class);
}
if(digest.isSuccess()) {
- System.out.println("result: "+ digest.get().get$$instanceId());
+ System.out.println("result: "+ digest.getEntities().getSingletonOrFail().get$$instanceId());
} else {
digest.getFailureCause().printStackTrace();
}
@@ -97,7 +99,7 @@ ResponseDigest<MyObject> digest = digestFuture.get(); // blocking
}
if(digest.isSuccess()) {
- System.out.println("result: "+ digest.get().get$$instanceId());
+ System.out.println("result: "+ digest.getEntities().getSingletonOrFail().get$$instanceId());
} else {
digest.getFailureCause().printStackTrace();
}
@@ -185,6 +187,10 @@ public class RestfulClient {
return ResponseDigest.of(response, entityType);
}
+ public <T> ResponseDigest<T> digestList(Response response, Class<T> entityType, GenericType<List<T>> genericType) {
+ return ResponseDigest.ofList(response, entityType, genericType);
+ }
+
// -- RESPONSE PROCESSING (ASYNC)
public <T> CompletableFuture<ResponseDigest<T>> digest(
@@ -205,6 +211,28 @@ public class RestfulClient {
return completableFuture;
}
+
+ public <T> CompletableFuture<ResponseDigest<T>> digestList(
+ final Future<Response> asyncResponse,
+ final Class<T> entityType,
+ GenericType<List<T>> genericType) {
+
+ final CompletableFuture<ResponseDigest<T>> completableFuture = CompletableFuture.supplyAsync(()->{
+ try {
+ Response response = asyncResponse.get();
+ ResponseDigest<T> digest = digestList(response, entityType, genericType);
+
+ return digest;
+
+ } catch (Exception e) {
+
+ return ResponseDigest.ofAsyncFailure(asyncResponse, entityType, e);
+
+ }
+ });
+
+ return completableFuture;
+ }
// -- FILTER
@@ -258,5 +286,4 @@ public class RestfulClient {
-
}