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 <ahu...@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 ""; + } + +} +