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/04/12 13:57:30 UTC

[isis] branch master updated (5857ada795 -> 39e71bf288)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


    from 5857ada795 ISIS-3000: create isis-viewer-restfulobjects-client (stub)
     new d71ff2b340 ISIS-3000: relocate adoc
     new 39e71bf288 ISIS-3000: relocate sources

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 mappings/restclient/adoc/antora.yml                   | 19 -------------------
 mappings/restclient/adoc/modules/restclient/nav.adoc  |  4 ----
 .../adoc/modules/restclient/partials/module-nav.adoc  |  7 -------
 .../adoc/modules/ROOT/pages/client.adoc               |  0
 .../adoc/modules/ROOT/partials/module-nav.adoc        |  2 ++
 .../client}/ActionParameterListBuilder.java           |  2 +-
 .../client}/IsisModuleExtRestClient.java              |  2 +-
 .../viewer/restfulobjects/client}/ResponseDigest.java |  2 +-
 .../viewer/restfulobjects/client}/RestfulClient.java  |  8 ++++----
 .../restfulobjects/client}/RestfulClientConfig.java   |  4 ++--
 .../client}/RestfulClientException.java               |  2 +-
 .../restfulobjects/client}/auth/BasicAuthFilter.java  |  2 +-
 .../client}/log/ClientConversationFilter.java         |  2 +-
 .../client}/log/ClientConversationLogger.java         |  2 +-
 14 files changed, 15 insertions(+), 43 deletions(-)
 delete mode 100644 mappings/restclient/adoc/antora.yml
 delete mode 100644 mappings/restclient/adoc/modules/restclient/nav.adoc
 delete mode 100644 mappings/restclient/adoc/modules/restclient/partials/module-nav.adoc
 rename mappings/restclient/adoc/modules/restclient/pages/about.adoc => viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc (100%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/ActionParameterListBuilder.java (98%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/IsisModuleExtRestClient.java (94%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/ResponseDigest.java (99%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/RestfulClient.java (96%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/RestfulClientConfig.java (92%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/RestfulClientException.java (96%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/auth/BasicAuthFilter.java (98%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/log/ClientConversationFilter.java (98%)
 copy {mappings/restclient/applib/src/main/java/org/apache/isis/extensions/restclient => viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client}/log/ClientConversationLogger.java (98%)


[isis] 02/02: ISIS-3000: relocate sources

Posted by ah...@apache.org.
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

commit 39e71bf288a5aea0bd6f66e4ed1c5f75636f941f
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Apr 12 15:44:38 2022 +0200

    ISIS-3000: relocate sources
---
 .../client/ActionParameterListBuilder.java         | 119 ++++++++
 .../client/IsisModuleExtRestClient.java            |  28 ++
 .../restfulobjects/client/ResponseDigest.java      | 318 +++++++++++++++++++++
 .../restfulobjects/client/RestfulClient.java       | 297 +++++++++++++++++++
 .../restfulobjects/client/RestfulClientConfig.java |  60 ++++
 .../client/RestfulClientException.java             |  43 +++
 .../client/auth/BasicAuthFilter.java               |  96 +++++++
 .../client/log/ClientConversationFilter.java       |  93 ++++++
 .../client/log/ClientConversationLogger.java       | 102 +++++++
 9 files changed, 1156 insertions(+)

diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ActionParameterListBuilder.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ActionParameterListBuilder.java
new file mode 100644
index 0000000000..6956e0fc49
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ActionParameterListBuilder.java
@@ -0,0 +1,119 @@
+/*
+ *  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.isis.viewer.restfulobjects.client;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.Entity;
+
+import lombok.Getter;
+
+/**
+ * @since 2.0 {@index}
+ */
+public class ActionParameterListBuilder {
+
+    private final Map<String, String> actionParameters = new LinkedHashMap<>();
+
+    @Getter
+    private final Map<String, Class<?>> actionParameterTypes = new LinkedHashMap<>();
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final String parameterValue) {
+        actionParameters.put(parameterName, parameterValue != null
+                ? value("\"" + parameterValue + "\"")
+                        : value(JSON_NULL_LITERAL));
+        actionParameterTypes.put(parameterName, String.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final int parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, int.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final long parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, long.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final byte parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, byte.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final short parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, short.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final double parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, double.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final float parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, float.class);
+        return this;
+    }
+
+    public ActionParameterListBuilder addActionParameter(final String parameterName, final boolean parameterValue) {
+        actionParameters.put(parameterName, value(""+parameterValue));
+        actionParameterTypes.put(parameterName, boolean.class);
+        return this;
+    }
+
+//XXX would be nice to have, but also requires the RO spec to be updated
+//    public ActionParameterListBuilder addActionParameterDto(String parameterName, Object parameterDto) {
+//        actionParameters.put(parameterName, dto(parameterDto));
+//        return this;
+//    }
+
+    public Entity<String> build() {
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("{\n")
+        .append(actionParameters.entrySet().stream()
+                .map(this::toJson)
+                .collect(Collectors.joining(",\n")))
+        .append("\n}");
+
+        return Entity.json(sb.toString());
+    }
+
+    // -- HELPER
+
+    private static final String JSON_NULL_LITERAL = "null";
+
+    private String value(final String valueLiteral) {
+        return "{\"value\" : " + valueLiteral + "}";
+    }
+
+    private String toJson(final Map.Entry<String, String> entry) {
+        return "   \""+entry.getKey()+"\": "+entry.getValue();
+    }
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/IsisModuleExtRestClient.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/IsisModuleExtRestClient.java
new file mode 100644
index 0000000000..2dfe1344e9
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/IsisModuleExtRestClient.java
@@ -0,0 +1,28 @@
+/*
+ *  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.isis.viewer.restfulobjects.client;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Configuration
+public class IsisModuleExtRestClient {
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ResponseDigest.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ResponseDigest.java
new file mode 100644
index 0000000000..53e13b23e7
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/ResponseDigest.java
@@ -0,0 +1,318 @@
+/*
+ *  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.isis.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;
+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;
+
+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.isis.applib.client.RepresentationTypeSimplifiedV2;
+import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.base._Casts;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.viewer.restfulobjects.applib.dtos.ScalarValueDtoV2;
+
+import lombok.NonNull;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Log4j2
+public class ResponseDigest<T> {
+
+    /**
+     * synchronous response processing (single entity)
+     * @param <T>
+     * @param response
+     * @param entityType
+     */
+    public static <T> ResponseDigest<T> of(
+            final @NonNull Response response,
+            final @NonNull Class<T> entityType) {
+
+        return new ResponseDigest<>(response, entityType, null).digest();
+    }
+
+    /**
+     * synchronous response processing (list of entities)
+     * @param <T>
+     * @param response
+     * @param entityType
+     * @param genericType
+     */
+    public static <T> ResponseDigest<T> ofList(
+            final @NonNull Response response,
+            final @NonNull Class<T> entityType,
+            final @NonNull GenericType<List<T>> genericType) {
+
+        return new ResponseDigest<>(response, entityType, genericType).digest();
+    }
+
+    /** a-synchronous response failure processing */
+    public static <T> ResponseDigest<T> ofAsyncFailure(
+            final Future<Response> asyncResponse,
+            final Class<T> entityType,
+            final Exception failure) {
+
+        Response response;
+        try {
+            response = asyncResponse.isDone() ? asyncResponse.get() : null;
+        } catch (Exception e) {
+            response = null;
+        }
+
+        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 Can<T> entities;
+    private Exception failureCause;
+
+
+    protected ResponseDigest(final Response response, final Class<T> entityType, final GenericType<List<T>> genericType) {
+        this.response = response;
+        this.entityType = entityType;
+        this.genericType = genericType;
+    }
+
+    /**
+     * @return whether the REST endpoint replied with a success status code.
+     */
+    public boolean isSuccess() {
+        return !isFailure();
+    }
+
+    /**
+     * @return whether the REST endpoint replied with a failure status code.
+     */
+    public boolean isFailure() {
+        return failureCause!=null;
+    }
+
+    /**
+     * @return (non-null), optionally the result if cardinality is exactly ONE
+     */
+    public Optional<T> getEntity(){
+        return getEntities().getSingleton();
+    }
+
+    /**
+     * @return (non-null), the entities replied by the REST endpoint supporting any cardinality ZERO, ONE or more.
+     */
+    public Can<T> getEntities(){
+        return entities;
+    }
+
+    /**
+     * @return (nullable), the failure case (if any), when the REST endpoint replied with a failure status code
+     */
+    public @Nullable Exception getFailureCause(){
+        return failureCause;
+    }
+
+    /**
+     * @param failureMapper - fallback, to calculate a result from given failure exception
+     * @return the result if cardinality is exactly ONE, otherwise the result of applying the failure to the {@code failureMapper}
+     */
+    public T singletonOrElseMapFailure(final Function<Exception, T> failureMapper) {
+        return isSuccess()
+                ? getEntity().orElseGet(()->failureMapper.apply(new NoSuchElementException()))
+                : failureMapper.apply(getFailureCause());
+    }
+
+    /**
+     * @param failureMapper - fallback, to calculate a result from given failure exception
+     * @return the result of any cardinality, otherwise the result of applying the failure to the {@code failureMapper}
+     */
+    public Can<T> multipleOrElseMapFailure(final Function<Exception, Can<T>> failureMapper) {
+        return isSuccess()
+                ? getEntities()
+                : failureMapper.apply(getFailureCause());
+    }
+
+    // -- HELPER
+
+    private ResponseDigest<T> digest() {
+
+        if(response==null) {
+            entities = Can.empty();
+            failureCause = new NoSuchElementException();
+            return this;
+        }
+
+        // a valid result corresponding to object not found, which is not an error per se
+        if(response.getStatusInfo().getStatusCode() == 404) {
+            entities = Can.empty();
+            return this;
+        }
+
+        if(!response.hasEntity()) {
+            entities = Can.empty();
+            failureCause = new NoSuchElementException(defaultFailureMessage(response));
+            return this;
+        }
+
+        if(response.getStatusInfo().getFamily() != Family.SUCCESSFUL) {
+            entities = Can.empty();
+            failureCause = new RestfulClientException(defaultFailureMessage(response));
+            return this;
+        }
+
+        // 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) {
+            entities = Can.empty();
+            failureCause = new RestfulClientException(String.format(
+                    "Invalid REST response, cannot parse header's Content-Type '%s' for the repr-type to use",
+                    contentTypeHeaderString));
+            return this;
+        }
+
+        try {
+
+            if(genericType==null) {
+                // when response is a singleton
+                val singleton = readSingle(reprType);
+                entities = singleton==null
+                        ? Can.empty()
+                        : Can.ofSingleton(singleton);
+            } else {
+                // when response is a list
+                entities = Can.ofCollection(readList(reprType));
+            }
+
+        } catch (Exception e) {
+            entities = Can.empty();
+            failureCause = new RestfulClientException("failed to read JAX-RS response content", e);
+        }
+
+        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 ResponseDigest<T> digestAsyncFailure(final boolean isCancelled, final Exception failure) {
+
+        entities = Can.empty();
+
+        if(isCancelled) {
+            failureCause = new RestfulClientException("Async JAX-RS request was canceled", failure);
+            return this;
+        }
+
+        if(response==null) {
+            failureCause = new RestfulClientException("Async JAX-RS request failed", failure);
+            return this;
+        }
+
+        failureCause = new RestfulClientException("Async JAX-RS request failed "
+                + defaultFailureMessage(response), failure);
+        return this;
+
+    }
+
+    private String defaultFailureMessage(final Response response) {
+        String failureMessage = "non-successful JAX-RS response: " +
+                String.format("%s (Http-Status-Code: %d)",
+                        response.getStatusInfo().getReasonPhrase(),
+                        response.getStatus());
+
+        if(response.hasEntity()) {
+            try {
+                String jsonContent = _Strings.read((InputStream) response.getEntity(), StandardCharsets.UTF_8);
+                return failureMessage + "\nContent:\n" + jsonContent;
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        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/isis/viewer/restfulobjects/client/RestfulClient.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClient.java
new file mode 100644
index 0000000000..8743b3e5aa
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClient.java
@@ -0,0 +1,297 @@
+/*
+ *  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.isis.viewer.restfulobjects.client;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+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;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.commons.internal.context._Context;
+import org.apache.isis.viewer.restfulobjects.client.auth.BasicAuthFilter;
+import org.apache.isis.viewer.restfulobjects.client.auth.BasicAuthFilter.Credentials;
+import org.apache.isis.viewer.restfulobjects.client.log.ClientConversationLogger;
+
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * Setup the Restful Client with Basic-Auth:
+ *
+ * <p>
+ *     For example:
+ * </p>
+ *
+ * <blockquote><pre>
+RestfulClientConfig clientConfig = new RestfulClientConfig();
+clientConfig.setRestfulBase("http://localhost:8080/helloworld/restful/");
+// setup basic-auth
+clientConfig.setUseBasicAuth(true); // default = false
+clientConfig.setRestfulAuthUser("sven");
+clientConfig.setRestfulAuthPassword("pass");
+// setup request/response debug logging
+clientConfig.setUseRequestDebugLogging(true); // default = false
+
+RestfulClient client = RestfulClient.ofConfig(clientConfig);
+ * </pre></blockquote>
+ *
+ * Synchronous example:
+ * <blockquote><pre>{@code
+
+Builder request = client.request(
+                "services/myService/actions/lookupMyObjectById/invoke",
+                SuppressionType.RO);
+
+Entity<String> args = client.arguments()
+        .addActionParameter("id", "12345")
+        .build();
+
+Response response = request.post(args);
+
+ResponseDigest<MyObject> digest = client.digest(response, MyObject.class);
+}
+
+if(digest.isSuccess()) {
+    System.out.println("result: "+ digest.getEntities().getSingletonOrFail().get$$instanceId());
+} else {
+    digest.getFailureCause().printStackTrace();
+}
+ * </pre></blockquote>
+ * Asynchronous example:
+ * <blockquote><pre>{@code
+Builder request = client.request(
+                "services/myService/actions/lookupMyObjectById/invoke",
+                SuppressionType.RO);
+
+Entity<String> args = client.arguments()
+        .addActionParameter("id", "12345")
+        .build();
+
+Future<Response> asyncResponse = request
+        .async()
+        .post(args);
+
+CompletableFuture<ResponseDigest<MyObject>> digestFuture =
+                client.digest(asyncResponse, MyObject.class);
+
+ResponseDigest<MyObject> digest = digestFuture.get(); // blocking
+}
+
+if(digest.isSuccess()) {
+    System.out.println("result: "+ digest.getEntities().getSingletonOrFail().get$$instanceId());
+} else {
+    digest.getFailureCause().printStackTrace();
+}
+ * </pre></blockquote>
+ *
+ * Maven Setup:
+ * <blockquote><pre>{@code
+<dependency>
+    <groupId>org.apache.isis.core</groupId>
+    <artifactId>isis-core-applib</artifactId>
+    <version>2.0.0-M2-SNAPSHOT</version>
+</dependency>
+<dependency>
+    <groupId>org.glassfish.jersey.ext</groupId>
+    <artifactId>jersey-spring5</artifactId>
+    <version>2.29.1</version>
+</dependency>
+<dependency>
+    <groupId>org.glassfish</groupId>
+    <artifactId>javax.json</artifactId>
+    <version>1.1.4</version>
+</dependency>
+<dependency>
+    <groupId>org.eclipse.persistence</groupId>
+    <artifactId>org.eclipse.persistence.moxy</artifactId>
+    <version>2.7.5</version>
+</dependency>
+ * }</pre></blockquote>
+ *
+ * @since 2.0 {@index}
+ */
+@Log4j2
+public class RestfulClient {
+
+    private static final String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=\"urn:org.apache.isis/v2\"";
+
+    private RestfulClientConfig clientConfig;
+    private Client client;
+
+    public static RestfulClient ofConfig(RestfulClientConfig clientConfig) {
+        RestfulClient restClient = new RestfulClient();
+        restClient.init(clientConfig);
+        return restClient;
+    }
+
+    public void init(RestfulClientConfig clientConfig) {
+        this.clientConfig = clientConfig;
+        client = ClientBuilder.newClient();
+
+        registerDefaultJsonProvider();
+        registerBasicAuthFilter();
+        registerConversationFilters();
+    }
+
+    public RestfulClientConfig getConfig() {
+        return clientConfig;
+    }
+
+    public Client getJaxRsClient() {
+        return client;
+    }
+
+    // -- REQUEST BUILDER
+
+    public Builder request(String path, SuppressionType ... suppressionTypes) {
+        return request(path, SuppressionType.setOf(suppressionTypes));
+    }
+
+    public Builder request(String path, EnumSet<SuppressionType> suppressionTypes) {
+        final String responseContentType = DEFAULT_RESPONSE_CONTENT_TYPE
+                + toSuppressionLiteral(suppressionTypes);
+
+        return client.target(relativePathToUri(path)).request(responseContentType);
+    }
+
+    // -- ARGUMENT BUILDER
+
+    public ActionParameterListBuilder arguments() {
+        return new ActionParameterListBuilder();
+    }
+
+    // -- RESPONSE PROCESSING (SYNC)
+
+    public <T> ResponseDigest<T> digest(Response response, Class<T> entityType) {
+        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(
+            final Future<Response> asyncResponse,
+            final Class<T> entityType) {
+
+        final CompletableFuture<ResponseDigest<T>> completableFuture = CompletableFuture.supplyAsync(()->{
+            try {
+                Response response = asyncResponse.get();
+                ResponseDigest<T> digest = digest(response, entityType);
+
+                return digest;
+
+            } catch (Exception e) {
+                return ResponseDigest.ofAsyncFailure(asyncResponse, entityType, e);
+            }
+        });
+
+        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
+
+    private void registerDefaultJsonProvider() {
+        try {
+            Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider");
+            client.register(MOXyJsonProvider);
+        } catch (Exception e) {
+            log.warn("This implementation of RestfulClient does require the class 'MOXyJsonProvider'"
+                    + " on the class-path."
+                    + " Are you missing a maven dependency?");
+        }
+    }
+
+    private void registerBasicAuthFilter() {
+        if(clientConfig.isUseBasicAuth()){
+            final Credentials credentials = Credentials.of(
+                    clientConfig.getRestfulAuthUser(),
+                    clientConfig.getRestfulAuthPassword());
+            client.register(BasicAuthFilter.of(credentials));
+        }
+    }
+
+    private void registerConversationFilters() {
+        if(clientConfig.isUseRequestDebugLogging()){
+            client.register(new ClientConversationLogger());
+        }
+        clientConfig.getClientConversationFilters().stream()
+        .filter(Objects::nonNull)
+        .forEach(client::register);
+    }
+
+    // -- HELPER
+
+    private String relativePathToUri(String path) {
+        final String baseUri = _Strings.suffix(clientConfig.getRestfulBase(), "/");
+        while(path.startsWith("/")) {
+            path = path.substring(1);
+        }
+        return baseUri + path;
+    }
+
+    private String toSuppressionLiteral(EnumSet<SuppressionType> suppressionTypes) {
+        final String suppressionSetLiteral = stream(suppressionTypes)
+                .map(SuppressionType::name)
+                .collect(Collectors.joining(","));
+
+        if(_Strings.isNotEmpty(suppressionSetLiteral)) {
+            return ";suppress=" + suppressionSetLiteral;
+        }
+
+        return "";
+    }
+
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientConfig.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientConfig.java
new file mode 100644
index 0000000000..01d439af5f
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientConfig.java
@@ -0,0 +1,60 @@
+/*
+ *  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.isis.viewer.restfulobjects.client;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.apache.isis.viewer.restfulobjects.client.log.ClientConversationFilter;
+
+import lombok.Data;
+
+/**
+ * @since 2.0 {@index}
+ */
+@XmlRootElement(name="restful-client-config")
+@XmlAccessorType(XmlAccessType.FIELD)
+@Data
+public class RestfulClientConfig {
+
+    @XmlElement(name="restfulBase")
+    private String restfulBase;
+
+    @XmlElement(name="useBasicAuth")
+    private boolean useBasicAuth;
+
+    @XmlElement(name="restfulAuthUser")
+    private String restfulAuthUser;
+
+    @XmlElement(name="restfulAuthPassword")
+    private String restfulAuthPassword;
+
+    @XmlElement(name="useRequestDebugLogging")
+    private boolean useRequestDebugLogging;
+
+    @XmlTransient
+    private List<ClientConversationFilter> clientConversationFilters = new ArrayList<>();
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientException.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientException.java
new file mode 100644
index 0000000000..efce5915ad
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/RestfulClientException.java
@@ -0,0 +1,43 @@
+/*
+ *  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.isis.viewer.restfulobjects.client;
+
+/**
+ * @since 2.0 {@index}
+ */
+public class RestfulClientException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public RestfulClientException() {
+    }
+
+    public RestfulClientException(final String message) {
+        super(message);
+    }
+
+    public RestfulClientException(final Throwable cause) {
+        super(cause);
+    }
+
+    public RestfulClientException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/auth/BasicAuthFilter.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/auth/BasicAuthFilter.java
new file mode 100644
index 0000000000..cb057ea34b
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/auth/BasicAuthFilter.java
@@ -0,0 +1,96 @@
+/*
+ *  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.isis.viewer.restfulobjects.client.auth;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import javax.annotation.Priority;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.isis.commons.internal.base._Strings;
+
+import lombok.NonNull;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Priority(100)
+public class BasicAuthFilter implements ClientRequestFilter {
+
+    /**
+     *
+     * @since 2.0
+     */
+    public static class Credentials {
+        final String user;
+        final String pass;
+        public static Credentials empty() {
+            return new Credentials("anonymous", null);
+        }
+        public static Credentials of(final String user, final String pass) {
+            if(_Strings.isNullOrEmpty(user)) {
+                return empty();
+            }
+            return new Credentials(user, pass);
+        }
+        private Credentials(final String user, final String pass) {
+            this.user = user;
+            this.pass = pass;
+        }
+        @Override
+        public String toString() {
+            return "" + user + ":" + pass;
+        }
+    }
+
+    public static BasicAuthFilter of(final Credentials credentials) {
+        BasicAuthFilter filter = new BasicAuthFilter();
+        filter.setCredentials(credentials);
+        return filter;
+    }
+
+    private @NonNull Credentials credentials = Credentials.empty();
+
+    public Credentials getCredentials() {
+        return credentials;
+    }
+
+    public void setCredentials(final Credentials credentials) {
+        this.credentials = credentials;
+    }
+
+    @Override
+    public void filter(final ClientRequestContext requestContext) throws IOException {
+        requestContext.getHeaders().add("Authorization", getAuthorizationValue());
+    }
+
+    // -- HELPER
+
+    private String getAuthorizationValue() {
+        try {
+            return "Basic " + DatatypeConverter.printBase64Binary(credentials.toString().getBytes("UTF-8"));
+        } catch (UnsupportedEncodingException ex) {
+            throw new IllegalStateException("Cannot encode with UTF-8", ex);
+        }
+    }
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationFilter.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationFilter.java
new file mode 100644
index 0000000000..7c238c36a9
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationFilter.java
@@ -0,0 +1,93 @@
+/*
+ *  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.isis.viewer.restfulobjects.client.log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import org.apache.isis.commons.internal.base._Bytes;
+
+import lombok.val;
+
+/**
+ * @since 2.0 {@index}
+ */
+public interface ClientConversationFilter
+extends ClientRequestFilter, ClientResponseFilter {
+
+    void onRequest(
+            String endpoint,
+            String method,
+            String acceptHeaderParsing,
+            Map<String, List<String>> headers,
+            String body);
+
+    void onResponse(int httpReturnCode, Map<String, List<String>> headers, String body);
+
+    @Override
+    default void filter(ClientRequestContext requestContext) throws IOException {
+        val endpoint = requestContext.getUri().toString();
+        val method = requestContext.getMethod();
+
+        Exception acceptableMediaTypeParsingFailure;
+        try {
+            @SuppressWarnings("unused")
+            final String acceptableMediaTypes = requestContext.getAcceptableMediaTypes().toString();
+            acceptableMediaTypeParsingFailure = null;
+        } catch (Exception e) {
+            acceptableMediaTypeParsingFailure = e;
+        }
+        final String acceptHeaderParsing = acceptableMediaTypeParsingFailure != null
+                ? "Failed to parse accept header, cause: " + acceptableMediaTypeParsingFailure.getMessage()
+                : "OK";
+
+        final String requestBody = "" + requestContext.getEntity();
+
+        onRequest(
+                endpoint, method, acceptHeaderParsing,
+                requestContext.getStringHeaders(),
+                requestBody);
+    }
+
+    @Override
+    default void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+
+        val inputStream = responseContext.getEntityStream();
+        final String responseBody;
+        if(inputStream!=null) {
+            val bytes = _Bytes.ofKeepOpen(inputStream);
+            responseBody = new String(bytes, StandardCharsets.UTF_8);
+            responseContext.setEntityStream(new ByteArrayInputStream(bytes));
+        } else {
+            responseBody = "null";
+        }
+
+        onResponse(responseContext.getStatusInfo().getStatusCode(), responseContext.getHeaders(), responseBody);
+    }
+
+
+}
diff --git a/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationLogger.java b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationLogger.java
new file mode 100644
index 0000000000..24a075c2f9
--- /dev/null
+++ b/viewers/restfulobjects/client/src/main/java/org/apache/isis/viewer/restfulobjects/client/log/ClientConversationLogger.java
@@ -0,0 +1,102 @@
+/*
+ *  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.isis.viewer.restfulobjects.client.log;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.isis.commons.internal.base._Strings;
+
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @since 2.0 {@index}
+ */
+@Log4j2
+public class ClientConversationLogger implements ClientConversationFilter {
+
+    @Override
+    public void onRequest(String endpoint, String method, String acceptHeaderParsing,
+            Map<String, List<String>> headers, String body) {
+
+        val headersAsText = headers.entrySet().stream()
+                .map(this::toKeyValueString)
+                .map(this::obscureAuthHeader)
+                .collect(Collectors.joining(",\n\t"));
+
+        val sb = new StringBuilder();
+        sb.append("\n")
+        .append("---------- JAX-RS REQUEST -------------\n")
+        .append("uri: ").append(endpoint).append("\n")
+        .append("method: ").append(method).append("\n")
+        .append("accept-header-parsing: ").append(acceptHeaderParsing).append("\n")
+        .append("headers: \n\t").append(headersAsText).append("\n")
+        .append("request-body: ").append(body).append("\n")
+        .append("----------------------------------------\n")
+        ;
+
+        log.info(sb.toString());
+    }
+
+    @Override
+    public void onResponse(int httpReturnCode, Map<String, List<String>> headers, String body) {
+        val headersAsText = headers.entrySet().stream()
+                .map(this::toKeyValueString)
+                .map(this::obscureAuthHeader)
+                .collect(Collectors.joining(",\n\t"));
+
+        val sb = new StringBuilder();
+        sb.append("\n")
+        .append("---------- JAX-RS RESPONSE -------------\n")
+        .append("http-return-code: \n\t").append(httpReturnCode).append("\n")
+        .append("headers: \n\t").append(headersAsText).append("\n")
+        .append("response-body: ").append(body).append("\n")
+        .append("----------------------------------------\n")
+        ;
+
+        log.info(sb.toString());
+    }
+
+    // -- HELPER
+
+    private final String basicAuthMagic = "Authorization: [Basic ";
+
+    private String toKeyValueString(Map.Entry<?, ?> entry) {
+        return "" + entry.getKey() + ": " + entry.getValue();
+    }
+
+    private String obscureAuthHeader(String keyValueLiteral) {
+        if(_Strings.isEmpty(keyValueLiteral)) {
+            return keyValueLiteral;
+        }
+        if(keyValueLiteral.startsWith(basicAuthMagic)) {
+
+            final String obscured = _Strings.padEnd(basicAuthMagic, keyValueLiteral.length() - 1, '*') + "]";
+            return obscured;
+
+        }
+        return keyValueLiteral;
+    }
+
+
+
+
+}


[isis] 01/02: ISIS-3000: relocate adoc

Posted by ah...@apache.org.
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

commit d71ff2b3407157bd1dbaa8ada62b8167007bdc48
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Apr 12 15:44:15 2022 +0200

    ISIS-3000: relocate adoc
---
 mappings/restclient/adoc/antora.yml                   | 19 -------------------
 mappings/restclient/adoc/modules/restclient/nav.adoc  |  4 ----
 .../adoc/modules/restclient/partials/module-nav.adoc  |  7 -------
 .../adoc/modules/ROOT/pages/client.adoc               |  0
 .../adoc/modules/ROOT/partials/module-nav.adoc        |  2 ++
 5 files changed, 2 insertions(+), 30 deletions(-)

diff --git a/mappings/restclient/adoc/antora.yml b/mappings/restclient/adoc/antora.yml
deleted file mode 100644
index ed780ecb0b..0000000000
--- a/mappings/restclient/adoc/antora.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-#  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.
-
-name: mappings
-version: latest
diff --git a/mappings/restclient/adoc/modules/restclient/nav.adoc b/mappings/restclient/adoc/modules/restclient/nav.adoc
deleted file mode 100644
index 64d61e0e49..0000000000
--- a/mappings/restclient/adoc/modules/restclient/nav.adoc
+++ /dev/null
@@ -1,4 +0,0 @@
-
-:Notice: 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 ag [...]
-
-include::vro:ROOT:partial$component-nav.adoc[]
diff --git a/mappings/restclient/adoc/modules/restclient/partials/module-nav.adoc b/mappings/restclient/adoc/modules/restclient/partials/module-nav.adoc
deleted file mode 100644
index 81a82543b3..0000000000
--- a/mappings/restclient/adoc/modules/restclient/partials/module-nav.adoc
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-* xref:mappings:restclient:about.adoc[REST Client]
-
-
-
diff --git a/mappings/restclient/adoc/modules/restclient/pages/about.adoc b/viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc
similarity index 100%
rename from mappings/restclient/adoc/modules/restclient/pages/about.adoc
rename to viewers/restfulobjects/adoc/modules/ROOT/pages/client.adoc
diff --git a/viewers/restfulobjects/adoc/modules/ROOT/partials/module-nav.adoc b/viewers/restfulobjects/adoc/modules/ROOT/partials/module-nav.adoc
index 773d9808ce..f7faf4b92e 100644
--- a/viewers/restfulobjects/adoc/modules/ROOT/partials/module-nav.adoc
+++ b/viewers/restfulobjects/adoc/modules/ROOT/partials/module-nav.adoc
@@ -8,6 +8,8 @@
 ** xref:vro:ROOT:content-negotiation/other-representations.adoc[Other Representations]
 * xref:vro:ROOT:security.adoc[Security]
 * xref:vro:ROOT:layout-resources.adoc[Layout Resources]
+* xref:vro:ROOT:client.adoc[Client]
 * xref:vro:ROOT:hints-and-tips.adoc[Hints-n-Tips]
 
 
+