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 2022/12/01 10:37:58 UTC
[isis] branch master updated: ISIS-3275: re-implement OutboxClient on top of RestClient (4)
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 b7076bfafa ISIS-3275: re-implement OutboxClient on top of RestClient (4)
b7076bfafa is described below
commit b7076bfafaa8709670aa06de8be23e2a481d10c6
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Dec 1 11:37:49 2022 +0100
ISIS-3275: re-implement OutboxClient on top of RestClient (4)
- code de-duplication
---
.../causeway/applib/client/SuppressionType.java | 8 +-
.../restclient/api/OutboxClient.java | 101 +++++-----------
.../secondary/fetch/CommandFetcher.java | 7 +-
.../testdomain/util/rest/RestEndpointService.java | 4 +-
.../restfulobjects/client/ResponseDigest.java | 66 ++---------
.../restfulobjects/client/ResponseDigester.java | 127 +++++++++++++++++++++
.../restfulobjects/client/RestfulClient.java | 64 +++++------
.../client/RestfulClientMediaType.java | 71 ++++++++++++
8 files changed, 276 insertions(+), 172 deletions(-)
diff --git a/api/applib/src/main/java/org/apache/causeway/applib/client/SuppressionType.java b/api/applib/src/main/java/org/apache/causeway/applib/client/SuppressionType.java
index 8dcb94359a..889ded6a97 100644
--- a/api/applib/src/main/java/org/apache/causeway/applib/client/SuppressionType.java
+++ b/api/applib/src/main/java/org/apache/causeway/applib/client/SuppressionType.java
@@ -62,7 +62,9 @@ public enum SuppressionType {
;
- public static EnumSet<SuppressionType> setOf(SuppressionType ... types){
+ public static EnumSet<SuppressionType> all() { return EnumSet.of(ALL); };
+
+ public static EnumSet<SuppressionType> setOf(final SuppressionType ... types){
final EnumSet<SuppressionType> set = EnumSet.noneOf(SuppressionType.class);
stream(types).forEach(set::add);
return set;
@@ -70,7 +72,7 @@ public enum SuppressionType {
public static class ParseUtil {
- public static EnumSet<SuppressionType> parse(List<String> parameterList) {
+ public static EnumSet<SuppressionType> parse(final List<String> parameterList) {
final EnumSet<SuppressionType> set = EnumSet.noneOf(SuppressionType.class);
parameterList.stream()
.map(SuppressionType.ParseUtil::parseOrElseNull)
@@ -82,7 +84,7 @@ public enum SuppressionType {
return set;
}
- private static SuppressionType parseOrElseNull(String literal) {
+ private static SuppressionType parseOrElseNull(final String literal) {
// honor pre v2 behavior
if("true".equalsIgnoreCase(literal)) {
diff --git a/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java b/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
index 12b0715db7..53d8c926a6 100644
--- a/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
+++ b/extensions/core/executionoutbox/restclient/src/main/java/org/apache/causeway/extensions/executionoutbox/restclient/api/OutboxClient.java
@@ -21,15 +21,14 @@
package org.apache.causeway.extensions.executionoutbox.restclient.api;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.apache.causeway.applib.util.schema.InteractionsDtoUtils;
+import org.apache.causeway.commons.functional.Try;
import org.apache.causeway.commons.internal.resources._Json;
import org.apache.causeway.extensions.executionoutbox.restclient.api.delete.DeleteMessage;
import org.apache.causeway.extensions.executionoutbox.restclient.api.deleteMany.DeleteManyMessage;
@@ -41,6 +40,7 @@ import org.apache.causeway.schema.ixn.v2.MemberExecutionDto;
import org.apache.causeway.schema.ixn.v2.PropertyEditDto;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClientConfig;
+import org.apache.causeway.viewer.restfulobjects.client.RestfulClientMediaType;
import lombok.Setter;
import lombok.val;
@@ -89,7 +89,6 @@ public class OutboxClient {
return this;
}
- private UriBuilder pendingUriBuilder;
private UriBuilder deleteUriBuilder;
private UriBuilder deleteManyUriBuilder;
@@ -103,7 +102,6 @@ public class OutboxClient {
* Should be called once all properties have been injected.
*/
public void init() {
- this.pendingUriBuilder = UriBuilder.fromUri(base + "services/causeway.ext.executionOutbox.OutboxRestApi/actions/pending/invoke");
this.deleteUriBuilder = UriBuilder.fromUri(base + "services/causeway.ext.executionOutbox.OutboxRestApi/actions/delete/invoke");
this.deleteManyUriBuilder = UriBuilder.fromUri(base + "services/causeway.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke");
@@ -112,7 +110,8 @@ public class OutboxClient {
restfulClientConfig.setRestfulAuthUser(username);
restfulClientConfig.setRestfulAuthPassword(password);
restfulClientConfig.setConnectTimeoutInMillis(1000L * connectTimeoutInSecs);
- restfulClientConfig.setReadTimeoutInMillis(1000L * connectTimeoutInSecs);
+ restfulClientConfig.setReadTimeoutInMillis(1000L * readTimeoutInSecs);
+ //restfulClientConfig.setUseRequestDebugLogging(true); //for debugging
}
private void ensureInitialized() {
@@ -125,65 +124,47 @@ public class OutboxClient {
ensureInitialized();
- val uri = pendingUriBuilder.build();
+ try(val client = RestfulClient.ofConfig(restfulClientConfig)) {
- Client client = null;
- try {
- client = RestfulClient.ofConfig(restfulClientConfig).getJaxRsClient();
+ var response = client.request(PENDING_URI)
+ .accept(RestfulClientMediaType.RO_XML.mediaTypeFor(InteractionsDto.class))
+ .get();
- val webTarget = client.target(uri);
+ final Try<InteractionsDto> digest = client.digest(response, InteractionsDto.class);
- val invocationBuilder = webTarget.request()
- .header("Authorization", "Basic " + encode(username, password))
- .accept(mediaTypeFor(InteractionsDto.class))
- ;
-
- val invocation = invocationBuilder.buildGet();
- val response = invocation.invoke();
-
- val responseStatus = response.getStatus();
- if (responseStatus != 200) {
- log.warn(invocation.toString());
+ if(digest.isSuccess()) {
+ return digest.getValue()
+ .map(InteractionsDto::getInteractionDto)
+ .orElseGet(Collections::emptyList);
+ } else {
+ log.error("Failed to GET from {}: {}", client.uri(PENDING_URI), digest.getFailure().get());
+ return Collections.emptyList();
}
-
- final InteractionsDto interactionsDto = response.readEntity(InteractionsDto.class);
- return interactionsDto.getInteractionDto();
-
- } catch(Exception ex) {
- log.error(String.format("Failed to GET from %s", uri.toString()), ex);
- } finally {
- closeQuietly(client);
}
- return Collections.emptyList();
- }
-
- // -- HELPER
- private static MediaType mediaTypeFor(final Class<?> dtoClass) {
-
- val headers = new HashMap<String,String>();
- headers.put("profile", "urn:org.restfulobjects:repr-types/action-result");
- headers.put("x-ro-domain-type", dtoClass.getName());
- return new MediaType("application", "xml", headers);
}
-
public void delete(final String interactionId, final int sequence) {
val entity = new DeleteMessage(interactionId, sequence);
- invoke(entity, deleteUriBuilder);
+ invoke(entity, DELETE_URI);
}
public void deleteMany(final List<InteractionDto> interactionDtos) {
-
val interactionsDto = new InteractionsDto();
interactionDtos.forEach(interactionDto -> {
addTo(interactionsDto, interactionDto);
});
val entity = new DeleteManyMessage(InteractionsDtoUtils.toXml(interactionsDto));
- invoke(entity, deleteManyUriBuilder);
+ invoke(entity, DELETE_MANY_URI);
}
+ // -- HELPER
+
+ private static String PENDING_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/pending/invoke";
+ private static String DELETE_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/delete/invoke";
+ private static String DELETE_MANY_URI = "services/causeway.ext.executionOutbox.OutboxRestApi/actions/deleteMany/invoke";
+
private void addTo(final InteractionsDto interactionsDto, final InteractionDto orig) {
val copy = new InteractionDto();
copy.setInteractionId(orig.getInteractionId());
@@ -204,20 +185,15 @@ public class OutboxClient {
: new PropertyEditDto();
}
- private void invoke(final Object entity, final UriBuilder uriBuilder) {
+ private void invoke(final Object entity, final String path) {
ensureInitialized();
val json = _Json.toString(entity);
- Client client = null;
- try {
- client = RestfulClient.ofConfig(restfulClientConfig).getJaxRsClient();
+ try(val client = RestfulClient.ofConfig(restfulClientConfig)) {
- val webTarget = client.target(uriBuilder.build());
-
- val invocationBuilder = webTarget.request();
- invocationBuilder.header("Authorization", "Basic " + encode(username, password));
+ var invocationBuilder = client.request(path);
val invocation = invocationBuilder.buildPut(
Entity.entity(json, MediaType.APPLICATION_JSON_TYPE));
@@ -229,31 +205,8 @@ public class OutboxClient {
// if failed to log message via REST service, then fallback by logging to slf4j
log.warn(entity.toString());
}
- } catch(Exception ex) {
- log.error(entity.toString(), ex);
- } finally {
- closeQuietly(client);
}
- }
-
- private static String encode(final String username, final String password) {
- return java.util.Base64.getEncoder().encodeToString(asBytes(username, password));
- }
-
- private static byte[] asBytes(final String username, final String password) {
- return String.format("%s:%s", username, password).getBytes();
- }
- private static void closeQuietly(final Client client) {
- if (client == null) {
- return;
- }
- try {
- client.close();
- } catch (Exception ex) {
- // ignore so as to avoid overriding any pending exceptions in calling 'finally' block.
- }
}
-
}
diff --git a/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/fetch/CommandFetcher.java b/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/fetch/CommandFetcher.java
index 5c9817651d..05cbe3cd47 100644
--- a/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/fetch/CommandFetcher.java
+++ b/incubator/extensions/core/commandreplay/secondary/src/main/java/org/apache/causeway/extensions/commandreplay/secondary/fetch/CommandFetcher.java
@@ -18,6 +18,7 @@
*/
package org.apache.causeway.extensions.commandreplay.secondary.fetch;
+import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
@@ -40,6 +41,7 @@ import org.apache.causeway.extensions.commandreplay.secondary.status.StatusExcep
import org.apache.causeway.schema.cmd.v2.CommandDto;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClientConfig;
+import org.apache.causeway.viewer.restfulobjects.client.RestfulClientMediaType;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@@ -107,9 +109,8 @@ public class CommandFetcher {
Can<CommandDto> callPrimary(final @Nullable UUID interactionId) throws StatusException {
val client = newClient(secondaryConfig, useRequestDebugLogging);
- val request = client.request(
- URL_SUFFIX,
- SuppressionType.RO);
+ val request = client.request(URL_SUFFIX)
+ .accept(RestfulClientMediaType.SIMPLE_JSON.mediaTypeFor(CommandDto.class, EnumSet.of(SuppressionType.RO)));
val args = client.arguments()
.addActionParameter("interactionId", interactionId!=null ? interactionId.toString() : null)
diff --git a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
index aa295008bb..f8d2ca8ae0 100644
--- a/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
+++ b/regressiontests/stable/src/main/java/org/apache/causeway/testdomain/util/rest/RestEndpointService.java
@@ -41,6 +41,7 @@ import org.apache.causeway.testdomain.ldap.LdapConstants;
import org.apache.causeway.testdomain.util.dto.BookDto;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClient;
import org.apache.causeway.viewer.restfulobjects.client.RestfulClientConfig;
+import org.apache.causeway.viewer.restfulobjects.client.RestfulClientMediaType;
import org.apache.causeway.viewer.restfulobjects.client.log.ClientConversationFilter;
import lombok.NonNull;
@@ -107,7 +108,8 @@ public class RestEndpointService {
// -- NEW REQUEST BUILDER
public Invocation.Builder newInvocationBuilder(final RestfulClient client, final String endpointPath) {
- return client.request(endpointPath, SuppressionType.ALL);
+ return client.request(endpointPath)
+ .accept(RestfulClientMediaType.SIMPLE_JSON.mediaTypeFor(Object.class, SuppressionType.all()));
}
// -- ENDPOINTS
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
index 2a681cd1bf..5c4ef3c978 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigest.java
@@ -18,10 +18,8 @@
*/
package org.apache.causeway.viewer.restfulobjects.client;
-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.Optional;
@@ -30,18 +28,11 @@ import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import org.springframework.lang.Nullable;
-import org.apache.causeway.applib.client.RepresentationTypeSimplifiedV2;
import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.viewer.restfulobjects.applib.dtos.ScalarValueDtoV2;
import lombok.NonNull;
import lombok.val;
@@ -158,9 +149,9 @@ class ResponseDigest<T> {
// see if we can extract the returned representation type (repr-type) from the header
val contentTypeHeaderString = response.getHeaderString("Content-Type");
- val reprType = RepresentationTypeSimplifiedV2.parseContentTypeHeaderString(contentTypeHeaderString)
- .orElse(null);
- if(reprType==null) {
+
+ val digester = ResponseDigester.forContentTypeHeaderString(contentTypeHeaderString).orElse(null);
+ if(digester==null) {
entities = Can.empty();
failureCause = _Exceptions.unrecoverable(String.format(
"Invalid REST response, cannot parse header's Content-Type '%s' for the repr-type to use",
@@ -172,13 +163,15 @@ class ResponseDigest<T> {
if(genericType==null) {
// when response is a singleton
- val singleton = readSingle(reprType);
+ log.debug("readSingle({})", digester);
+ val singleton = digester.readSingle(entityType, response);
entities = singleton==null
? Can.empty()
: Can.ofSingleton(singleton);
} else {
// when response is a list
- entities = Can.ofCollection(readList(reprType));
+ log.debug("readList({})", digester);
+ entities = Can.ofCollection(digester.readList(entityType, genericType, response));
}
} catch (Exception e) {
@@ -189,46 +182,6 @@ class ResponseDigest<T> {
return this;
}
- private T readSingle(final RepresentationTypeSimplifiedV2 reprType)
- throws JsonParseException, JsonMappingException, IOException {
-
- log.debug("readSingle({})", reprType);
-
- if(reprType.isValue()
- || reprType.isValues()) {
- val mapper = new ObjectMapper();
- val jsonInput = response.readEntity(String.class);
- val scalarValueDto = mapper.readValue(jsonInput, ScalarValueDtoV2.class);
- return extractValue(scalarValueDto);
- }
- return response.<T>readEntity(entityType);
- }
-
- private List<T> readList(final RepresentationTypeSimplifiedV2 reprType)
- throws JsonParseException, JsonMappingException, IOException {
-
- log.debug("readList({})", reprType);
-
- if(reprType.isValues()
- || reprType.isValue()) {
- val mapper = new ObjectMapper();
- val jsonInput = response.readEntity(String.class);
- final List<ScalarValueDtoV2> scalarValueDtoList =
- mapper.readValue(
- jsonInput,
- mapper.getTypeFactory().constructCollectionType(List.class, ScalarValueDtoV2.class));
-
- final List<T> resultList = new ArrayList<>(scalarValueDtoList.size());
- for(val valueBody : scalarValueDtoList) {
- // explicit loop, for simpler exception propagation
- resultList.add(extractValue(valueBody));
- }
- return resultList;
-
- }
- return response.readEntity(genericType);
- }
-
private String defaultFailureMessage(final Response response) {
String failureMessage = "non-successful JAX-RS response: " +
String.format("%s (Http-Status-Code: %d)",
@@ -247,12 +200,7 @@ class ResponseDigest<T> {
return failureMessage;
}
- // -- VALUE TYPE HANDLING
- private T extractValue(final ScalarValueDtoV2 scalarValueDto)
- throws JsonParseException, JsonMappingException, IOException {
- return _Casts.uncheckedCast(scalarValueDto.getValue());
- }
}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigester.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigester.java
new file mode 100644
index 0000000000..ed4c52651d
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/ResponseDigester.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.viewer.restfulobjects.client;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.applib.client.RepresentationTypeSimplifiedV2;
+import org.apache.causeway.commons.internal.base._Casts;
+import org.apache.causeway.commons.internal.base._Strings;
+import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.viewer.restfulobjects.applib.dtos.ScalarValueDtoV2;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.val;
+
+interface ResponseDigester {
+
+ <T> T readSingle(Class<T> entityType, Response response);
+ <T> List<T> readList(Class<T> entityType, GenericType<List<T>> genericType, Response response);
+
+ // -- FACTORIES
+
+ static Optional<ResponseDigester> forContentTypeHeaderString(final @Nullable String contentTypeHeaderString) {
+ if(_Strings.isEmpty(contentTypeHeaderString)) {
+ return Optional.empty();
+ }
+ if(contentTypeHeaderString.startsWith("application/xml;profile=\"urn:org.restfulobjects:repr-types/action-result\"")
+ && contentTypeHeaderString.contains("x-ro-domain-type")) {
+ return Optional.of(new ResponseDigesterXmlStandard());
+ }
+ return RepresentationTypeSimplifiedV2.parseContentTypeHeaderString(contentTypeHeaderString)
+ .map(ResponseDigesterJsonSimple::new);
+ }
+
+ // -- IMPLEMENTATIONS
+
+ @RequiredArgsConstructor
+ static class ResponseDigesterXmlStandard implements ResponseDigester {
+
+ @Override
+ public <T> T readSingle(final Class<T> entityType, final Response response) {
+ return response.readEntity(entityType);
+ }
+
+ @Override
+ public <T> List<T> readList(final Class<T> entityType, final GenericType<List<T>> genericType, final Response response) {
+ throw _Exceptions.notImplemented();
+ }
+ }
+
+ @RequiredArgsConstructor
+ static class ResponseDigesterJsonSimple implements ResponseDigester {
+
+ private final RepresentationTypeSimplifiedV2 reprType;
+
+ @SneakyThrows
+ @Override
+ public <T> T readSingle(final Class<T> entityType, final Response response) {
+ if(reprType.isValue()
+ || reprType.isValues()) {
+ val mapper = new ObjectMapper();
+ val jsonInput = response.readEntity(String.class);
+ val scalarValueDto = mapper.readValue(jsonInput, ScalarValueDtoV2.class);
+ return extractValue(scalarValueDto);
+ }
+ return response.<T>readEntity(entityType);
+ }
+
+ @SneakyThrows
+ @Override
+ public <T> List<T> readList(final Class<T> entityType, final GenericType<List<T>> genericType, final Response response) {
+ if(reprType.isValues()
+ || reprType.isValue()) {
+ val mapper = new ObjectMapper();
+ val jsonInput = response.readEntity(String.class);
+ final List<ScalarValueDtoV2> scalarValueDtoList =
+ mapper.readValue(
+ jsonInput,
+ mapper.getTypeFactory().constructCollectionType(List.class, ScalarValueDtoV2.class));
+
+ final List<T> resultList = new ArrayList<>(scalarValueDtoList.size());
+ for(val valueBody : scalarValueDtoList) {
+ // explicit loop, for simpler exception propagation
+ resultList.add(extractValue(valueBody));
+ }
+ return resultList;
+
+ }
+ return response.readEntity(genericType);
+ }
+
+ private <T> T extractValue(final ScalarValueDtoV2 scalarValueDto)
+ throws JsonParseException, JsonMappingException, IOException {
+ return _Casts.uncheckedCast(scalarValueDto.getValue());
+ }
+ }
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
index d5b5815016..66a1bd38f0 100644
--- a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClient.java
@@ -18,22 +18,20 @@
*/
package org.apache.causeway.viewer.restfulobjects.client;
-import java.util.EnumSet;
+import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.function.UnaryOperator;
-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 javax.ws.rs.core.UriBuilder;
-import org.apache.causeway.applib.client.SuppressionType;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.Try;
-import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.internal.context._Context;
import org.apache.causeway.viewer.restfulobjects.client.auth.BasicAuthFilter;
@@ -90,9 +88,7 @@ if(digest.isSuccess()) {
* @since 2.0 {@index}
*/
@Log4j2
-public class RestfulClient {
-
- private static final String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=\"urn:org.apache.causeway/v2\"";
+public class RestfulClient implements AutoCloseable {
private RestfulClientConfig clientConfig;
private Client client;
@@ -129,17 +125,24 @@ public class RestfulClient {
return client;
}
- // -- REQUEST BUILDER
-
- public Builder request(final String path, final SuppressionType ... suppressionTypes) {
- return request(path, SuppressionType.setOf(suppressionTypes));
+ @Override
+ public void close() {
+ if (client == null) {
+ return;
+ }
+ try {
+ client.close();
+ } catch (Throwable ex) {
+ // just ignore
+ }
}
- public Builder request(final String path, final EnumSet<SuppressionType> suppressionTypes) {
- final String responseContentType = DEFAULT_RESPONSE_CONTENT_TYPE
- + toSuppressionLiteral(suppressionTypes);
+ // -- REQUEST BUILDER
- return client.target(relativePathToUri(path)).request(responseContentType);
+ public Builder request(final String path) {
+ return client
+ .target(relativePathToUri(path))
+ .request();
}
// -- ARGUMENT BUILDER
@@ -166,7 +169,17 @@ public class RestfulClient {
return Try.failure(listDigest.getFailureCause());
}
- // -- FILTER
+ // -- UTILITY
+
+ /**
+ * Returns an {@link URI} constructed from this client's base path plus given relative {@code path}.
+ * @param path relative to this client's base
+ */
+ public URI uri(final String path) {
+ return relativePathToUri(path).build();
+ }
+
+ // -- HELPER FILTER
private void registerDefaultJsonProvider() {
try {
@@ -197,27 +210,14 @@ public class RestfulClient {
.forEach(client::register);
}
- // -- HELPER
+ // -- HELPER OTHER
- private String relativePathToUri(String path) {
+ private UriBuilder relativePathToUri(String path) {
final String baseUri = _Strings.suffix(clientConfig.getRestfulBase(), "/");
while(path.startsWith("/")) {
path = path.substring(1);
}
- return baseUri + path;
- }
-
- private String toSuppressionLiteral(final EnumSet<SuppressionType> suppressionTypes) {
- final String suppressionSetLiteral = _NullSafe.stream(suppressionTypes)
- .map(SuppressionType::name)
- .collect(Collectors.joining(","));
-
- if(_Strings.isNotEmpty(suppressionSetLiteral)) {
- return ";suppress=" + suppressionSetLiteral;
- }
-
- return "";
+ return UriBuilder.fromUri(baseUri + path);
}
-
}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClientMediaType.java b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClientMediaType.java
new file mode 100644
index 0000000000..30f6eefd3c
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/causeway/viewer/restfulobjects/client/RestfulClientMediaType.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.causeway.viewer.restfulobjects.client;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.causeway.applib.client.SuppressionType;
+import org.apache.causeway.commons.internal.base._NullSafe;
+import org.apache.causeway.commons.internal.base._Strings;
+
+public enum RestfulClientMediaType {
+ RO_XML{
+ @Override
+ public MediaType mediaTypeFor(final Class<?> dtoClass, final EnumSet<SuppressionType> suppressionTypes) {
+ return new MediaType("application", "xml",
+ Map.<String, String>of(
+ "profile", "urn:org.restfulobjects:repr-types/action-result"
+ + toSuppressionLiteral(suppressionTypes),
+ "x-ro-domain-type", dtoClass.getName()));
+ }
+ },
+ SIMPLE_JSON {
+ @Override
+ public MediaType mediaTypeFor(final Class<?> dtoClass, final EnumSet<SuppressionType> suppressionTypes) {
+ return new MediaType("application", "json",
+ Map.<String, String>of(
+ "profile", "urn:org.apache.causeway/v2"
+ + toSuppressionLiteral(suppressionTypes),
+ "x-ro-domain-type", dtoClass.getName()));
+ }
+ }
+ ;
+
+ public final MediaType mediaTypeFor(final Class<?> dtoClass) {
+ return mediaTypeFor(dtoClass, EnumSet.noneOf(SuppressionType.class));
+ }
+
+ public abstract MediaType mediaTypeFor(final Class<?> dtoClass, EnumSet<SuppressionType> suppressionTypes);
+
+ private static String toSuppressionLiteral(final EnumSet<SuppressionType> suppressionTypes) {
+ final String suppressionSetLiteral = _NullSafe.stream(suppressionTypes)
+ .map(SuppressionType::name)
+ .collect(Collectors.joining(","));
+ if(_Strings.isNotEmpty(suppressionSetLiteral)) {
+ return ";suppress=" + suppressionSetLiteral;
+ }
+ return "";
+ }
+
+}
+