FELIX-4548 : Implement the missing errors registration. Apply modified patch from Thomas Baier. Traversing the exception hierarchy by using the exception object (instread of the class name)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1672326 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
index c675987..d3ea3b1 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/dispatch/Dispatcher.java
@@ -166,8 +166,8 @@
                     code != SC_PARTIAL_CONTENT &&
                     code >= SC_OK)
                 {
-                    final String exceptionType = (String)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
-                    final ServletHandler errorHandler = handlerRegistry.getErrorsHandler(request.getRequestURI(), this.serviceId, code, exceptionType);
+                    final Throwable exception = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+                    final ServletHandler errorHandler = handlerRegistry.getErrorsHandler(request.getRequestURI(), this.serviceId, code, exception);
 
                     if ( errorHandler != null )
                     {
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ErrorsMapping.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ErrorsMapping.java
index b319c52..a41ce46 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ErrorsMapping.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/ErrorsMapping.java
@@ -94,15 +94,51 @@
         this.exceptionsMap.clear();
     }
 
+    /**
+     * Get the servlet handling the error
+     * @param exception Optional exception
+     * @param errorCode Error code
+     * @return The servlet handling the error or {@code null}
+     */
+    public ServletHandler get(final Throwable exception, final int errorCode)
+    {
+        ServletHandler errorHandler = this.get(exception);
+        if (errorHandler != null)
+        {
+            return errorHandler;
+        }
 
-    public ServletHandler get(int errorCode)
+        return get(errorCode);
+    }
+
+    private ServletHandler get(final int errorCode)
     {
         return this.errorCodesMap.get(errorCode);
     }
 
-    public ServletHandler get(String exception)
+    private ServletHandler get(final Throwable exception)
     {
-        return this.exceptionsMap.get(exception);
+        if (exception == null)
+        {
+            return null;
+        }
+
+        ServletHandler servletHandler = null;
+        Class<?> throwableClass = exception.getClass();
+        while ( servletHandler == null && throwableClass != null )
+        {
+            servletHandler = this.exceptionsMap.get(throwableClass.getName());
+            if ( servletHandler == null )
+            {
+                throwableClass = throwableClass.getSuperclass();
+                if ( !Throwable.class.isAssignableFrom(throwableClass) )
+                {
+                    throwableClass = null;
+                }
+            }
+
+        }
+        return servletHandler;
     }
 
 
diff --git a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
index 02f68ea..8fa9788 100644
--- a/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
+++ b/http/base/src/main/java/org/apache/felix/http/base/internal/handler/HandlerRegistry.java
@@ -208,7 +208,7 @@
         return null;
     }
 
-    public ServletHandler getErrorsHandler(String requestURI, Long serviceId, int code, String exceptionType)
+    public ServletHandler getErrorsHandler(String requestURI, Long serviceId, int code, Throwable exception)
     {
         ErrorsMapping errorsMapping = getErrorsMapping(requestURI, serviceId);
         if (errorsMapping == null)
@@ -216,14 +216,7 @@
             return null;
         }
 
-        // TODO check exception hierarchy
-        ServletHandler errorHandler = errorsMapping.get(exceptionType);
-        if (errorHandler != null)
-        {
-            return errorHandler;
-        }
-
-        return errorsMapping.get(code);
+        return errorsMapping.get(exception, code);
     }
 
     private ErrorsMapping getErrorsMapping(final String requestURI, final Long serviceId)
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/ErrorPageTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/ErrorPageTest.java
new file mode 100644
index 0000000..dc06295
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/ErrorPageTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.felix.http.itest;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertTrue;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_ERROR_PAGE;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.context.ServletContextHelper;
+
+@RunWith(JUnit4TestRunner.class)
+public class ErrorPageTest extends BaseIntegrationTest
+{
+    private List<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>();
+
+    private CountDownLatch initLatch;
+    private CountDownLatch destroyLatch;
+
+    public void setupLatches(int count)
+    {
+        initLatch = new CountDownLatch(count);
+        destroyLatch = new CountDownLatch(count);
+    }
+
+    public void setupErrorServlet(final Integer errorCode,
+        final Class<? extends RuntimeException> exceptionType,
+        String context) throws Exception
+    {
+        Dictionary<String, Object> servletProps = new Hashtable<String, Object>();
+        servletProps.put(HTTP_WHITEBOARD_SERVLET_NAME, "servlet");
+        servletProps.put(HTTP_WHITEBOARD_SERVLET_PATTERN, asList("/test"));
+        if (context != null)
+        {
+            servletProps.put(HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=" + context + ")");
+        }
+
+        TestServlet servletWithErrorCode = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws IOException
+            {
+                if (errorCode != null)
+                {
+                    resp.sendError(errorCode);
+                }
+
+                if (exceptionType != null)
+                {
+                    RuntimeException exception;
+                    try
+                    {
+                        exception = exceptionType.newInstance();
+                    }
+                    catch (Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                    throw exception;
+                }
+            }
+        };
+
+        registrations.add(m_context.registerService(Servlet.class.getName(), servletWithErrorCode, servletProps));
+    }
+
+    public void setupErrorPage(final Integer errorCode,
+        final Class<? extends Throwable> exceptionType,
+        final String name,
+        String context) throws Exception
+    {
+        TestServlet errorPage = new TestServlet(initLatch, destroyLatch)
+        {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws IOException
+            {
+                resp.getWriter().print(name);
+                resp.flushBuffer();
+            }
+        };
+
+        List<String> errors = new ArrayList<String>();
+        if (errorCode != null)
+        {
+            errors.add(errorCode.toString());
+        }
+        if (exceptionType != null)
+        {
+            errors.add(exceptionType.getName());
+        }
+
+        Dictionary<String, Object> errorPageProps = new Hashtable<String, Object>();
+        errorPageProps.put(HTTP_WHITEBOARD_SERVLET_NAME, name);
+        errorPageProps.put(HTTP_WHITEBOARD_SERVLET_ERROR_PAGE, errors);
+        if (context != null)
+        {
+            errorPageProps.put(HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HTTP_WHITEBOARD_CONTEXT_NAME + "=" + context + ")");
+        }
+
+        registrations.add(m_context.registerService(Servlet.class.getName(), errorPage, errorPageProps));
+    }
+
+    private void registerContext(String name, String path) throws InterruptedException
+    {
+        Dictionary<String, ?> properties = createDictionary(
+                HTTP_WHITEBOARD_CONTEXT_NAME, name,
+                HTTP_WHITEBOARD_CONTEXT_PATH, path);
+
+        ServletContextHelper servletContextHelper = new ServletContextHelper(m_context.getBundle()){
+            // test
+        };
+        registrations.add(m_context.registerService(ServletContextHelper.class.getName(), servletContextHelper, properties));
+
+        Thread.sleep(500);
+    }
+
+    @After
+    public void unregisterServices() throws InterruptedException
+    {
+        for (ServiceRegistration<?> serviceRegistration : registrations)
+        {
+            serviceRegistration.unregister();
+        }
+
+        assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+        Thread.sleep(500);
+    }
+
+    @Test
+    public void errorPageForErrorCodeIsSent() throws Exception
+    {
+        setupLatches(2);
+        setupErrorServlet(501, null, null);
+        setupErrorPage(501, null, "Error page", null);
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(501, "Error page", createURL("/test"));
+    }
+
+    @Test
+    public void errorPageForExceptionIsSent() throws Exception
+    {
+        setupLatches(2);
+        setupErrorServlet(null, NullPointerException.class, null);
+        setupErrorPage(null, NullPointerException.class, "Error page", null);
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(500, "Error page", createURL("/test"));
+    }
+
+    @Test
+    public void errorPageForParentExceptionIsSent() throws Exception
+    {
+        setupLatches(2);
+        setupErrorServlet(null, NullPointerException.class, null);
+        setupErrorPage(null, RuntimeException.class, "Error page", null);
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(500, "Error page", createURL("/test"));
+    }
+
+    @Test
+    public void errorPageForExceptionIsPreferedOverErrorCode() throws Exception
+    {
+        setupLatches(3);
+        setupErrorServlet(null, NullPointerException.class, null);
+        setupErrorPage(500, null, "Error page 2", null);
+        setupErrorPage(null, NullPointerException.class, "Error page 1", null);
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(500, "Error page 1", createURL("/test"));
+    }
+
+    @Test
+    public void errorPageIsHandledPerContext() throws Exception
+    {
+        registerContext("context1", "/one");
+        registerContext("context2", "/two");
+
+        setupLatches(3);
+        setupErrorServlet(501, null, "context1");
+        setupErrorPage(501, null, "Error page 1", "context2");
+        setupErrorPage(501, null, "Error page 2", "context1");
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(501, "Error page 2", createURL("/one/test"));
+    }
+
+    @Test
+    public void errorPageIsShadowedByHigherRankingPage() throws Exception
+    {
+        registerContext("context1", "/one");
+        registerContext("context2", "/two");
+
+        // Shadowed error page is not initialized
+        setupLatches(2);
+        setupErrorServlet(501, null, "context1");
+        setupErrorPage(501, null, "Error page 1", "context1");
+        setupErrorPage(501, null, "Error page 2", "context1");
+        assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+        assertContent(501, "Error page 1", createURL("/one/test"));
+    }
+}