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

enorman pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlets-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new e680d90  SLING-10021 The DefaultErrorHandlerServlet should consider 
the 'Accept' header (#14)
e680d90 is described below

commit e680d900b31edd68cf49ed7ec84a21ff76ad5097
Author: Eric Norman <[email protected]>
AuthorDate: Tue Dec 29 19:06:12 2020 -0800

    SLING-10021 The DefaultErrorHandlerServlet should consider the 'Accept' 
header (#14)
    
    SLING-10021 The DefaultErrorHandlerServlet should consider the 'Accept' 
header
---
 .gitignore                                         |   1 +
 bnd.bnd                                            |   4 +-
 pom.xml                                            |  60 +++++-
 .../defaults/DefaultErrorHandlerServlet.java       |  89 ++++++++-
 .../defaults/DefaultErrorHandlerServletTest.java   | 212 +++++++++++++++++++++
 .../internal/helper/ScriptSelection2Test.java      |   2 +-
 .../resolver/it/ServletResolverTestSupport.java    |   1 +
 7 files changed, 365 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0eefb1f..0a3f3d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ maven-eclipse.xml
 .DS_Store
 jcr.log
 atlassian-ide-plugin.xml
+dependency-reduced-pom.xml
diff --git a/bnd.bnd b/bnd.bnd
index 05a4426..dab71c1 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -1,12 +1,14 @@
 Import-Package:\
   !org.apache.sling.engine.impl.*,\
+  !org.apache.sling.servlets.post.impl.helper.*,\
   *
 
 Provide-Capability:\
   
osgi.extender;osgi.extender="org.apache.sling.servlets.resolver";version:Version="1.1"
 
 -includeresource:\
-  
@org.apache.sling.engine-*.jar!/org/apache/sling/engine/impl/request/SlingRequestPathInfo*
+  
@org.apache.sling.engine-*.jar!/org/apache/sling/engine/impl/request/SlingRequestPathInfo*,\
+  
@org.apache.sling.servlets.post-*.jar!/org/apache/sling/servlets/post/impl/helper/MediaRangeList*
 
 -plugin:\
   org.apache.sling.bnd.plugin.headers.parameters.remove.Plugin;\
diff --git a/pom.xml b/pom.xml
index 60ce509..ce13245 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,10 @@
                                     
<pattern>org.apache.sling.engine.impl.request</pattern>
                                     
<shadedPattern>org.apache.sling.servlets.resolver.internal.engine</shadedPattern>
                                 </relocation>
+                                <relocation>
+                                    
<pattern>org.apache.sling.servlets.post.impl.helper</pattern>
+                                    
<shadedPattern>org.apache.sling.servlets.resolver.internal.helper</shadedPattern>
+                                </relocation>
                             </relocations>
                         </configuration>
                     </execution>
@@ -206,6 +210,12 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-json_1.1_spec</artifactId>
+            <version>1.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
             <version>2.23.1-SNAPSHOT</version>
@@ -254,6 +264,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.servlets.post</artifactId>
+            <version>2.3.16</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
             <version>2.2.0</version>
             <scope>provided</scope>
@@ -298,8 +314,26 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.jcr.resource</artifactId>
+            <version>2.9.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.core</artifactId>
+            <version>2.4.16</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.sling-mock.core</artifactId>
+            <version>2.5.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.sling-mock.junit4</artifactId>
-            <version>2.4.0</version>
+            <version>2.5.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -318,6 +352,12 @@
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-spi</artifactId>
+            <version>${org.ops4j.pax.exam.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam</artifactId>
             <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
@@ -377,6 +417,24 @@
             <version>2.2.0</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.johnzon</artifactId>
+            <version>1.2.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+            <version>2.13.1</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-atinject_1.0_spec</artifactId>
+            <version>1.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServlet.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServlet.java
index 0fe9199..d85b636 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServlet.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServlet.java
@@ -20,18 +20,23 @@ package 
org.apache.sling.servlets.resolver.internal.defaults;
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
 import javax.servlet.GenericServlet;
 import javax.servlet.Servlet;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.request.RequestProgressTracker;
 import org.apache.sling.api.request.ResponseUtil;
+import org.apache.sling.servlets.post.impl.helper.MediaRangeList;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Component;
 import org.slf4j.Logger;
@@ -51,6 +56,8 @@ import org.slf4j.LoggerFactory;
             "sling.servlet.prefix=-1"
     })
 public class DefaultErrorHandlerServlet extends GenericServlet {
+    private static final String JSON_CONTENT_TYPE = "application/json";
+    private static final String HTML_CONTENT_TYPE = "text/html";
 
     /** default log */
     private final Logger log = 
LoggerFactory.getLogger(DefaultErrorHandlerServlet.class);
@@ -73,6 +80,21 @@ public class DefaultErrorHandlerServlet extends 
GenericServlet {
             statusMessage = statusToString(statusCode);
         }
 
+        //properly consider the 'Accept' header conditions to decide whether 
to send json or html back 
+        if (req instanceof HttpServletRequest && 
+                JSON_CONTENT_TYPE.equals(new 
MediaRangeList((HttpServletRequest)req).prefer(HTML_CONTENT_TYPE, 
JSON_CONTENT_TYPE))) {
+            renderJson(req, res, statusMessage, requestUri, servletName, 
statusCode);
+        } else {
+            //default to HTML rendering
+            renderHtml(req, res, statusMessage, requestUri, servletName, 
statusCode);
+        }
+    }
+
+    /**
+     * Render the error as html
+     */
+    protected void renderHtml(ServletRequest req, ServletResponse res, String 
statusMessage, String requestUri,
+            String servletName, int statusCode) throws IOException {
         // start the response message
         final PrintWriter pw = sendIntro((HttpServletResponse) res, statusCode,
             statusMessage, requestUri, servletName);
@@ -108,6 +130,71 @@ public class DefaultErrorHandlerServlet extends 
GenericServlet {
     }
 
     /**
+     * Render the error as json
+     */
+    protected void renderJson(ServletRequest req, ServletResponse res, String 
statusMessage, String requestUri,
+            String servletName, int statusCode) throws IOException {
+        HttpServletResponse response = (HttpServletResponse)res;
+        if (!response.isCommitted()) {
+            response.reset();
+            response.setStatus(statusCode);
+            response.setContentType(JSON_CONTENT_TYPE);
+            response.setCharacterEncoding("UTF-8");
+        } else {
+            // Response already committed: don't change status, but report
+            // the error inline and warn about that
+            log.warn("Response already committed, unable to change status, 
output might not be well formed");
+        }
+        
+        // send the error as JSON
+        try (JsonGenerator jsonGenerator = 
Json.createGenerator(res.getWriter())) {
+            jsonGenerator.writeStartObject();
+            jsonGenerator.write("status", statusCode);
+            
+            String msg = 
(String)req.getAttribute(SlingConstants.ERROR_MESSAGE);
+            if (msg != null && !msg.isEmpty()) {
+                jsonGenerator.write("message", statusMessage);
+            }
+
+            if (requestUri != null && !requestUri.isEmpty()) {
+                jsonGenerator.write("requestUri", requestUri);
+            }
+            
+            if (servletName != null && !servletName.isEmpty()) {
+                jsonGenerator.write("servletName", servletName);
+            }
+
+            String exceptionType = 
(String)req.getAttribute(SlingConstants.ERROR_EXCEPTION_TYPE);
+            if (exceptionType != null && !exceptionType.isEmpty()) {
+                jsonGenerator.write("exceptionType", exceptionType);
+            }
+
+            // dump the stack trace
+            if (req.getAttribute(SlingConstants.ERROR_EXCEPTION) instanceof 
Throwable) {
+                final Throwable throwable = (Throwable) 
req.getAttribute(SlingConstants.ERROR_EXCEPTION);
+                try (StringWriter sw = new StringWriter();
+                        PrintWriter pw = new PrintWriter(sw)) {
+                    printStackTrace(pw, throwable);
+                    jsonGenerator.write("exception", sw.toString());
+                }
+            }
+
+            // dump the request progress tracker
+            if (req instanceof SlingHttpServletRequest) {
+                // dump the request progress tracker
+                final RequestProgressTracker tracker = 
((SlingHttpServletRequest)req).getRequestProgressTracker();
+                StringWriter strWriter = new StringWriter();
+                try (PrintWriter progressWriter = new PrintWriter(strWriter)) {
+                    tracker.dump(progressWriter);
+                }
+                jsonGenerator.write("requestProgress", strWriter.toString());
+            }
+
+            jsonGenerator.writeEnd();
+        }
+    }
+
+    /**
      * Print the stack trace for the root exception if the throwable is a
      * {@link ServletException}. If this does not contain an exception,
      * the throwable itself is printed.
@@ -154,7 +241,7 @@ public class DefaultErrorHandlerServlet extends 
GenericServlet {
 
             response.reset();
             response.setStatus(statusCode);
-            response.setContentType("text/html");
+            response.setContentType(HTML_CONTENT_TYPE);
             response.setCharacterEncoding("UTF-8");
 
             pw = response.getWriter();
diff --git 
a/src/test/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServletTest.java
 
b/src/test/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServletTest.java
new file mode 100644
index 0000000..ba54f8f
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/servlets/resolver/internal/defaults/DefaultErrorHandlerServletTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.sling.servlets.resolver.internal.defaults;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.regex.Pattern;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.commons.testing.sling.MockSlingHttpServletRequest;
+import org.apache.sling.commons.testing.sling.MockSlingHttpServletResponse;
+import org.junit.Test;
+
+/**
+ * SLING-10021 test 'Accept' content-type handling in the default error 
handler servlet
+ */
+public class DefaultErrorHandlerServletTest {
+
+    @Test
+    public void testJsonErrorResponse() throws IOException, ServletException {
+        // mock a request that accepts a json response
+        MockSlingHttpServletRequest req = new 
MockErrorSlingHttpServletRequest("application/json,*/*;q=0.9");
+        MockSlingHttpServletResponse res = new 
MockErrorSlingHttpServletResponse(false);
+
+        DefaultErrorHandlerServlet errorServlet = new 
DefaultErrorHandlerServlet();
+        errorServlet.init(new MockServletConfig());
+        errorServlet.service(req, res);
+
+        // verify we got json back
+        assertEquals("application/json", res.getContentType());
+        String responseOutput = res.getOutput().toString();
+
+        // check the json content matches what would be sent from the 
DefaultErrorHandlingServlet
+        try (Reader reader = new StringReader(responseOutput);
+                JsonReader jsonReader = Json.createReader(reader)) {
+            JsonObject jsonObj = jsonReader.readObject();
+            assertEquals(500, jsonObj.getInt("status"));
+            assertEquals("/testuri", jsonObj.getString("requestUri"));
+            assertEquals("org.apache.sling.test.ServletName", 
jsonObj.getString("servletName"));
+            assertEquals("Test Error Message", jsonObj.getString("message"));
+            assertTrue(jsonObj.getString("exception").contains("Test 
Exception"));
+            assertEquals(Exception.class.getName(), 
jsonObj.getString("exceptionType"));
+        }
+
+    }
+
+    @Test
+    public void testHtmlErrorResponse() throws IOException, ServletException {
+        // mock a request that accepts an html response
+        MockSlingHttpServletRequest req = new 
MockErrorSlingHttpServletRequest("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
+        MockSlingHttpServletResponse res = new 
MockErrorSlingHttpServletResponse(false);
+
+        DefaultErrorHandlerServlet errorServlet = new 
DefaultErrorHandlerServlet();
+        errorServlet.init(new MockServletConfig());
+        errorServlet.service(req, res);
+
+        // verify we got json back
+        assertEquals("text/html", res.getContentType());
+        String responseOutput = res.getOutput().toString();
+
+        // check the html content matches what would be sent from the 
DefaultErrorHandlingServlet
+        Pattern regex = Pattern.compile("The requested URL \\/testuri resulted 
in an error in org.apache.sling.test.ServletName\\.", Pattern.MULTILINE);
+        assertTrue("Expected error message", 
regex.matcher(responseOutput).find());
+        assertTrue(responseOutput.contains("Test Exception"));
+    }
+
+    /**
+     * Mock impl to simulate enough of a servlet context to satisfy what is 
used
+     * by DefaultErrorHandlerServlet
+      */
+    private static final class MockServletConfig implements 
javax.servlet.ServletConfig {
+
+        @Override
+        public String getServletName() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ServletContext getServletContext() {
+            return new org.apache.sling.servlethelpers.MockServletContext() {
+                @Override
+                public String getServerInfo() {
+                    return "Test Server Info";
+                }
+            };
+        }
+
+        @Override
+        public String getInitParameter(String name) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Enumeration<String> getInitParameterNames() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    /**
+     * Mock impl to simulate an error response
+     */
+    private static final class MockErrorSlingHttpServletResponse extends 
MockSlingHttpServletResponse {
+
+        private PrintWriter writer;
+        private StringWriter strWriter;
+        private boolean committed;
+
+        public MockErrorSlingHttpServletResponse(boolean committed) {
+            super();
+            this.committed = committed;
+        }
+
+        @Override
+        public boolean isCommitted() {
+            return this.committed;
+        }
+
+        @Override
+        public void reset() {
+            //no-op
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException {
+            // the super MockWriter throws away the buffer during
+            //   the flush() calls, so we can't use it to verify
+            //   content in the response.
+            if (this.writer == null) {
+                strWriter = new StringWriter();
+                this.strWriter.getBuffer();
+                this.writer = new PrintWriter(strWriter);
+            }
+            return this.writer;
+        }
+
+        @Override
+        public StringBuffer getOutput() {
+            return strWriter.getBuffer();
+        }
+
+    }
+
+    /**
+     * Mock impl to simulate an error request
+     */
+    private static final class MockErrorSlingHttpServletRequest extends 
MockSlingHttpServletRequest {
+        private String accept;
+
+        private MockErrorSlingHttpServletRequest(String accept) {
+            super(null, null, null, null, null);
+            this.accept = accept;
+        }
+
+        @Override
+        public String getHeader(String name) {
+            if ("Accept".equals(name)) {
+                return accept;
+            }
+            return super.getHeader(name);
+        }
+
+        @Override
+        public Object getAttribute(String name) {
+            if (SlingConstants.ERROR_STATUS.equals(name)) {
+                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+            } else if (SlingConstants.ERROR_MESSAGE.equals(name)) {
+                return "Test Error Message";
+            } else if (SlingConstants.ERROR_REQUEST_URI.equals(name)) {
+                return "/testuri";
+            } else if (SlingConstants.ERROR_SERVLET_NAME.equals(name)) {
+                return "org.apache.sling.test.ServletName";
+            } else if (SlingConstants.ERROR_EXCEPTION.equals(name)) {
+                return new Exception("Test Exception");
+            } else if (SlingConstants.ERROR_EXCEPTION_TYPE.equals(name)) {
+                return Exception.class.getName();
+            }
+            return super.getAttribute(name);
+        }
+
+    }
+
+
+}
diff --git 
a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelection2Test.java
 
b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelection2Test.java
index f66a4c8..81a7f23 100644
--- 
a/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelection2Test.java
+++ 
b/src/test/java/org/apache/sling/servlets/resolver/internal/helper/ScriptSelection2Test.java
@@ -30,7 +30,7 @@ import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.jcr.resource.JcrResourceConstants;
+import org.apache.sling.jcr.resource.api.JcrResourceConstants;
 import org.apache.sling.testing.mock.sling.junit.SlingContext;
 import org.apache.sling.testing.mock.sling.servlet.MockRequestPathInfo;
 import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest;
diff --git 
a/src/test/java/org/apache/sling/servlets/resolver/it/ServletResolverTestSupport.java
 
b/src/test/java/org/apache/sling/servlets/resolver/it/ServletResolverTestSupport.java
index 046ca84..f2d20da 100644
--- 
a/src/test/java/org/apache/sling/servlets/resolver/it/ServletResolverTestSupport.java
+++ 
b/src/test/java/org/apache/sling/servlets/resolver/it/ServletResolverTestSupport.java
@@ -78,6 +78,7 @@ public class ServletResolverTestSupport extends TestSupport {
         versionResolver.setVersionFromProject("org.apache.sling", 
"org.apache.sling.resourceresolver");
         versionResolver.setVersionFromProject("org.apache.sling", 
"org.apache.sling.scripting.api");
         versionResolver.setVersionFromProject("org.apache.sling", 
"org.apache.sling.scripting.core");
+        versionResolver.setVersionFromProject("org.apache.sling", 
"org.apache.sling.commons.johnzon");
         return options(
             composite(
                 when(vmOpt != null).useOptions(

Reply via email to