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 2018/10/16 13:22:53 UTC

[isis] 02/02: ISIS-2006: adding a jax-rs request/response debug logging filter

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

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

commit 0d4c71b9841c661ca34dc4e2959fbc51835c543c
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Oct 16 12:51:36 2018 +0200

    ISIS-2006: adding a jax-rs request/response debug logging filter
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2006
---
 .../apache/isis/applib/client/RestfulClient.java   |  56 ++++++---
 .../isis/applib/client/RestfulClientConfig.java    |  42 ++++---
 .../isis/applib/client/auth/BasicAuthFilter.java   |   2 +
 .../applib/client/log/RestfulLoggingFilter.java    | 128 +++++++++++++++++++++
 .../isis/commons/internal/base/_Strings.java       |  10 ++
 .../internal/base/_Strings_KeyValuePair.java       |  26 +++++
 6 files changed, 233 insertions(+), 31 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
index 8009e25..6496db1 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClient.java
@@ -23,6 +23,7 @@ import static org.apache.isis.commons.internal.base._NullSafe.stream;
 import java.util.EnumSet;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 
 import javax.ws.rs.client.Client;
@@ -32,6 +33,7 @@ import javax.ws.rs.core.Response;
 
 import org.apache.isis.applib.client.auth.BasicAuthFilter;
 import org.apache.isis.applib.client.auth.BasicAuthFilter.Credentials;
+import org.apache.isis.applib.client.log.RestfulLoggingFilter;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.context._Context;
 
@@ -41,9 +43,11 @@ import org.apache.isis.commons.internal.context._Context;
 RestfulClientConfig clientConfig = new RestfulClientConfig();
 clientConfig.setRestfulBase("http://localhost:8080/helloworld/restful/");
 // setup basic-auth
-clientConfig.setUseBasicAuth(true);
+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>
@@ -125,7 +129,9 @@ if(digest.isSuccess()) {
  */
 public class RestfulClient {
     
-    public static String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=urn:org.apache.isis/v1";
+    private static final Logger LOG = Logger.getLogger(RestfulClient.class.getName());
+    
+    public static String DEFAULT_RESPONSE_CONTENT_TYPE = "application/json;profile=\"urn:org.apache.isis/v1\"";
 
     private RestfulClientConfig clientConfig;
     private Client client;
@@ -140,22 +146,11 @@ public class RestfulClient {
         this.clientConfig = clientConfig;
         client = ClientBuilder.newClient();
         
-        if(clientConfig.isUseBasicAuth()){
-            final Credentials credentials = Credentials.of(
-                    clientConfig.getRestfulAuthUser(), 
-                    clientConfig.getRestfulAuthPassword());
-            client.register(BasicAuthFilter.of(credentials));
-        }
-        
-        try {
-            Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider");
-            client.register(MOXyJsonProvider);
-        } catch (Exception e) {
-            // this is just provided for convenience
-        }
-        
+        registerDefaultJsonProvider();
+        registerBasicAuthFilter();
+        registerRequestDebugLoggingFilter();
     }
-    
+
     public RestfulClientConfig getConfig() {
         return clientConfig;
     }
@@ -210,6 +205,33 @@ public class RestfulClient {
         return completableFuture;
     }
     
+    // -- FILTER
+    
+    private void registerDefaultJsonProvider() {
+        try {
+            Class<?> MOXyJsonProvider = _Context.loadClass("org.eclipse.persistence.jaxb.rs.MOXyJsonProvider");
+            client.register(MOXyJsonProvider);
+        } catch (Exception e) {
+            LOG.warning("This implementaion 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 registerRequestDebugLoggingFilter() {
+        if(clientConfig.isUseRequestDebugLogging()){
+            client.register(new RestfulLoggingFilter());
+        }
+    }
+    
     // -- HELPER
     
     private String relativePathToUri(String path) {
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
index c0578b1..cdd9b84 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/RestfulClientConfig.java
@@ -32,23 +32,23 @@ import javax.xml.bind.annotation.XmlRootElement;
 public class RestfulClientConfig {
 
     // --
-    
+
     @XmlElement(name="restfulBase") 
     private String restfulBase;
-    
+
     public String getRestfulBase() {
         return restfulBase;
     }
-    
+
     public void setRestfulBase(String restfulBase) {
         this.restfulBase = restfulBase;
     }
-    
+
     // --
-    
+
     @XmlElement(name="useBasicAuth") 
     private boolean useBasicAuth;
-    
+
     public boolean isUseBasicAuth() {
         return useBasicAuth;
     }
@@ -56,32 +56,46 @@ public class RestfulClientConfig {
     public void setUseBasicAuth(boolean useBasicAuth) {
         this.useBasicAuth = useBasicAuth;
     }
-    
+
     // --
 
     @XmlElement(name="restfulAuthUser")
     private String restfulAuthUser;
-    
+
     public String getRestfulAuthUser() {
         return restfulAuthUser;
     }
-    
+
     public void setRestfulAuthUser(String restfulAuthUser) {
         this.restfulAuthUser = restfulAuthUser;
     }
-    
+
     // --
 
     @XmlElement(name="restfulAuthPassword")
     private String restfulAuthPassword;
-    
+
     public String getRestfulAuthPassword() {
         return restfulAuthPassword;
     }
-    
+
     public void setRestfulAuthPassword(String restfulAuthPassword) {
         this.restfulAuthPassword = restfulAuthPassword;
     }
-    
-    
+
+    // --
+
+    @XmlElement(name="useRequestDebugLogging") 
+    private boolean useRequestDebugLogging;
+
+    public boolean isUseRequestDebugLogging() {
+        return useRequestDebugLogging;
+    }
+
+    public void setUseRequestDebugLogging(boolean useRequestDebugLogging) {
+        this.useRequestDebugLogging = useRequestDebugLogging;
+    }
+
+    // --
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
index 3f4f39e..201e35b 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/auth/BasicAuthFilter.java
@@ -23,6 +23,7 @@ import static org.apache.isis.commons.internal.base._With.requires;
 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;
@@ -33,6 +34,7 @@ import org.apache.isis.commons.internal.base._Strings;
  * 
  * @since 2.0.0-M2
  */
+@Priority(100)
 public class BasicAuthFilter implements ClientRequestFilter {
 
     /**
diff --git a/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java b/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java
new file mode 100644
index 0000000..8ea45f1
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/client/log/RestfulLoggingFilter.java
@@ -0,0 +1,128 @@
+/*
+ *  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.applib.client.log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.annotation.Priority;
+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._Strings;
+
+/**
+ * 
+ * @since 2.0.0-M2
+ */
+@Priority(999)
+public class RestfulLoggingFilter implements ClientRequestFilter, ClientResponseFilter{
+    private static final Logger LOG = Logger.getLogger(RestfulLoggingFilter.class.getName());
+
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException {
+        final String endpoint = requestContext.getUri().toString();
+        final String 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 headers = requestContext.getStringHeaders().entrySet().stream()
+                .map(entry->entry.toString())
+                .map(this::obscureAuthHeader)
+                .collect(Collectors.joining(",\n\t"));
+        
+        final String requestBody = requestContext.getEntity().toString();
+        
+        final StringBuilder 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(headers).append("\n")
+        .append("request-body: ").append(requestBody).append("\n")
+        .append("----------------------------------------\n")
+        ;
+        
+        LOG.log(Level.INFO, sb.toString());
+    }
+    
+    @Override
+    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+        
+        final InputStream inputStream = responseContext.getEntityStream();
+        final String responseBody;
+        if(inputStream!=null) {
+            responseBody = _Strings.read(responseContext.getEntityStream(), StandardCharsets.UTF_8);
+            responseContext.setEntityStream(new ByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)));    
+        } else {
+            responseBody = "null";
+        }
+        
+        final StringBuilder sb = new StringBuilder();
+        sb.append("\n")
+        .append("---------- JAX-RS RESPONSE -------------\n")
+        .append("response-body: ").append(responseBody).append("\n")
+        .append("----------------------------------------\n")
+        ;
+        
+        LOG.log(Level.INFO, sb.toString());
+        
+    }
+       
+    // -- HELPER
+    
+    private final String basicAuthMagic = "Authorization=[Basic "; 
+    
+    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;
+    }
+
+
+    
+    
+    
+    
+}
\ No newline at end of file
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
index 38fd902..8596840 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings.java
@@ -28,6 +28,7 @@ import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Scanner;
 import java.util.Spliterator;
 import java.util.Spliterators;
@@ -76,6 +77,15 @@ public final class _Strings {
         return _Strings_KeyValuePair.of(key, value);
     }
     
+    /**
+     * Parses a string assumed to be of the form <kbd>key=value</kbd> into its parts.
+     *
+     * @return a non-empty Optional, if (and only if) the {@code keyValueLiteral} does contain at least one '='
+     */
+    public static Optional<KeyValuePair> parseKeyValuePair(@Nullable String keyValueLiteral) {
+        return _Strings_KeyValuePair.parse(keyValueLiteral);
+    }
+    
     // -- FILLING
     
     public static String of(int length, char c) {
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
index 7696a48..3fa619a 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_Strings_KeyValuePair.java
@@ -21,6 +21,8 @@ package org.apache.isis.commons.internal.base;
 
 import static org.apache.isis.commons.internal.exceptions._Exceptions.notImplemented;
 
+import java.util.Optional;
+
 import org.apache.isis.commons.internal.base._Strings.KeyValuePair;
 
 /**
@@ -56,5 +58,29 @@ final class _Strings_KeyValuePair implements _Strings.KeyValuePair {
     public String setValue(String value) {
         throw notImplemented();
     }
+    
+    /**
+     * Parses a string assumed to be of the form <kbd>key=value</kbd> into its parts.
+     *
+     * @return a non-empty Optional, if (and only if) the {@code keyValueLiteral} does contain at least one '='
+     */
+    public static Optional<KeyValuePair> parse(String keyValueLiteral) {
+        
+        if(_Strings.isNullOrEmpty(keyValueLiteral)) {
+            return Optional.empty();
+        }
+        
+        final int equalsIndex = keyValueLiteral.indexOf('=');
+        if (equalsIndex == -1) {
+            return Optional.empty();
+        }
+
+        String aKey = keyValueLiteral.substring(0, equalsIndex);
+        String aValue = equalsIndex == keyValueLiteral.length() - 1 
+                ? "" 
+                        : keyValueLiteral.substring(equalsIndex + 1);
+
+        return Optional.of(of(aKey, aValue));
+    }
 
 }