You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by ka...@apache.org on 2012/02/06 23:45:09 UTC

svn commit: r1241232 - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry5/ main/java/org/apache/tapestry5/internal/services/ test/java/org/apache/tapestry5/internal/services/

Author: kaosko
Date: Mon Feb  6 22:45:09 2012
New Revision: 1241232

URL: http://svn.apache.org/viewvc?rev=1241232&view=rev
Log:
Incomplete - issue TAP5-1833: Merge functionality of Tynamo.org's
tapestry-exceptionpage module with the built-in ExceptionHandler

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ContextAwareException.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ExceptionHandlerAssistant.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ContextAwareException.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ContextAwareException.java?rev=1241232&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ContextAwareException.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ContextAwareException.java Mon Feb  6 22:45:09 2012
@@ -0,0 +1,5 @@
+package org.apache.tapestry5;
+
+public interface ContextAwareException {
+	Object[] getContext();
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ExceptionHandlerAssistant.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ExceptionHandlerAssistant.java?rev=1241232&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ExceptionHandlerAssistant.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ExceptionHandlerAssistant.java Mon Feb  6 22:45:09 2012
@@ -0,0 +1,8 @@
+package org.apache.tapestry5;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface ExceptionHandlerAssistant {
+	public Object handleRequestException(Throwable exception, List<Object> exceptionContext) throws IOException;
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java?rev=1241232&r1=1241231&r2=1241232&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandler.java Mon Feb  6 22:45:09 2012
@@ -1,87 +1,220 @@
-// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.internal.services;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.tapestry5.SymbolConstants;
-import org.apache.tapestry5.internal.structure.Page;
-import org.apache.tapestry5.ioc.annotations.Symbol;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.services.ExceptionReporter;
-import org.apache.tapestry5.services.RequestExceptionHandler;
-import org.apache.tapestry5.services.Response;
-import org.slf4j.Logger;
-
-/**
- * Default implementation of {@link RequestExceptionHandler} that displays the standard ExceptionReport page. The page
- * must implement the {@link ExceptionReporter} interface.
- */
-public class DefaultRequestExceptionHandler implements RequestExceptionHandler
-{
-    private final RequestPageCache pageCache;
-
-    private final PageResponseRenderer renderer;
-
-    private final Logger logger;
-
-    private final String pageName;
-
-    private final Response response;
-
-    public DefaultRequestExceptionHandler(RequestPageCache pageCache, PageResponseRenderer renderer, Logger logger,
-
-    @Symbol(SymbolConstants.EXCEPTION_REPORT_PAGE)
-    String pageName,
-
-    Response response)
-    {
-        this.pageCache = pageCache;
-        this.renderer = renderer;
-        this.logger = logger;
-        this.pageName = pageName;
-        this.response = response;
-    }
-
-    public void handleRequestException(Throwable exception) throws IOException
-    {
-        logger.error(String.format("Processing of request failed with uncaught exception: %s", exception), exception);
-
-        // TAP5-233: Make sure the client knows that an error occurred.
-
-        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-
-        String rawMessage = InternalUtils.toMessage(exception);
-
-        // Encode it compatibly with the JavaScript escape() function.
-
-        String encoded = URLEncoder.encode(rawMessage, "UTF-8").replace("+", "%20");
-
-        response.setHeader("X-Tapestry-ErrorMessage", encoded);
-
-        Page page = pageCache.get(pageName);
-
-        ExceptionReporter rootComponent = (ExceptionReporter) page.getRootComponent();
-
-        // Let the page set up for the new exception.
-
-        rootComponent.reportException(exception);
-
-        renderer.renderPageResponse(page);
-    }
-}
+// Copyright 2006, 2008, 2010, 2011 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.services;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tapestry5.ContextAwareException;
+import org.apache.tapestry5.ExceptionHandlerAssistant;
+import org.apache.tapestry5.Link;
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.internal.structure.Page;
+import org.apache.tapestry5.ioc.ServiceResources;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.ioc.internal.OperationException;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.runtime.ComponentEventException;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.ExceptionReporter;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.RequestExceptionHandler;
+import org.apache.tapestry5.services.Response;
+import org.slf4j.Logger;
+
+/**
+ * Default implementation of {@link RequestExceptionHandler} that displays the standard ExceptionReport page. The page
+ * must implement the {@link ExceptionReporter} interface.
+ */
+public class DefaultRequestExceptionHandler implements RequestExceptionHandler
+{
+    private final RequestPageCache pageCache;
+
+    private final PageResponseRenderer renderer;
+
+    private final Logger logger;
+
+    private final String pageName;
+
+    private final Request request;
+
+    private final Response response;
+    
+    private final ServiceResources serviceResources;
+    
+    private final ComponentClassResolver componentClassResolver;
+    
+    private final LinkSource linkSource;
+    
+    // should be Class<? extends Throwable>, Object but it's not allowed to configure subtypes
+    private final Map<Class, Object> configuration;
+    
+    private final Map<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant> handlerAssistants = Collections
+            .synchronizedMap(new HashMap<Class<ExceptionHandlerAssistant>, ExceptionHandlerAssistant>());
+
+    @SuppressWarnings("rawtypes")
+    public DefaultRequestExceptionHandler(RequestPageCache pageCache, PageResponseRenderer renderer, Logger logger,
+
+    @Symbol(SymbolConstants.EXCEPTION_REPORT_PAGE)
+    String pageName,
+
+    Request request, Response response, ServiceResources serviceResources, ComponentClassResolver componentClassResolver, LinkSource linkSource, Map<Class, Object> configuration)
+    {
+        this.pageCache = pageCache;
+        this.renderer = renderer;
+        this.logger = logger;
+        this.pageName = pageName;
+        this.request = request;
+        this.response = response;
+        this.serviceResources = serviceResources;
+        this.componentClassResolver = componentClassResolver;
+        this.linkSource = linkSource;
+        
+        for (Entry<Class, Object> entry : configuration.entrySet()) {
+            if (!Throwable.class.isAssignableFrom(entry.getKey()))
+                throw new IllegalArgumentException(Throwable.class.getName() + " is the only allowable key type but " + entry.getKey().getName()
+                        + " was contributed");
+        }
+        this.configuration = configuration;
+    }
+
+    public void handleRequestException(Throwable exception) throws IOException
+    {
+        Throwable cause = exception;
+
+        // Depending on where the error was thrown, there could be several levels of wrappers..
+        // For exceptions in component operations, it's OperationException -> ComponentEventException -> <Target>Exception
+
+        // Throw away the wrapped exceptions first
+        while (cause instanceof OperationException || cause instanceof ComponentEventException) {
+            if (cause.getCause() == null) break;
+            cause = cause.getCause();
+        }
+
+        Class<?> causeClass = cause.getClass();
+        if (!configuration.containsKey(causeClass)) {
+            // try at most two level of superclasses before delegating back to the default exception handler
+            causeClass = causeClass.getSuperclass();
+            if (causeClass == null || !configuration.containsKey(causeClass)) {
+                causeClass = causeClass.getSuperclass();
+                if (causeClass == null || !configuration.containsKey(causeClass)) {
+                    renderException(exception);
+                    return;
+                }
+            }
+        }
+
+        Object[] exceptionContext = formExceptionContext(cause);
+        Object value = configuration.get(causeClass);
+        Object page = null;
+        ExceptionHandlerAssistant assistant = null;
+        if (value instanceof ExceptionHandlerAssistant) assistant = (ExceptionHandlerAssistant) value;
+        else if (!(value instanceof Class)) {
+            renderException(exception);
+            return;
+        } else if (ExceptionHandlerAssistant.class.isAssignableFrom((Class) value)) {
+            @SuppressWarnings("unchecked")
+            Class<ExceptionHandlerAssistant> handlerType = (Class<ExceptionHandlerAssistant>) value;
+            assistant = handlerAssistants.get(handlerType);
+            if (assistant == null) {
+                assistant = (ExceptionHandlerAssistant) serviceResources.autobuild(handlerType);
+                handlerAssistants.put(handlerType, assistant);
+            }
+        }
+
+        // the assistant may handle the exception directly or return a page
+        if (assistant != null) {
+            // in case the assistant changes the context
+            List context = Arrays.asList(exceptionContext);
+            page = assistant.handleRequestException(exception, context);
+            exceptionContext = context.toArray();
+        }
+        if (page == null) return;
+
+        exceptionContext = new Object[0];
+
+        try {
+            if (page instanceof Class) page = componentClassResolver.resolvePageClassNameToPageName(((Class) page).getName());
+            Link link = page instanceof Link ? (Link) page : linkSource.createPageRenderLink(page.toString(), false, exceptionContext);
+            if (request.isXHR()) {
+                OutputStream os = response.getOutputStream("application/json;charset=UTF-8");
+                os.write(("{\"redirectURL\":\"" + link.toAbsoluteURI() + "\"}").getBytes());
+                os.close();
+            } else response.sendRedirect(link);
+        }
+        // This could throw exceptions if this is already a render request, but it's
+        // user's responsibility not to abuse the mechanism
+        catch (Exception e) {
+            // Nothing to do but delegate
+            renderException(exception);
+        }
+    }
+    
+    private void renderException(Throwable exception) throws IOException
+    {
+        logger.error(String.format("Processing of request failed with uncaught exception: %s", exception), exception);
+
+        // TAP5-233: Make sure the client knows that an error occurred.
+
+        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+        String rawMessage = InternalUtils.toMessage(exception);
+
+        // Encode it compatibly with the JavaScript escape() function.
+
+        String encoded = URLEncoder.encode(rawMessage, "UTF-8").replace("+", "%20");
+
+        response.setHeader("X-Tapestry-ErrorMessage", encoded);
+
+        Page page = pageCache.get(pageName);
+
+        ExceptionReporter rootComponent = (ExceptionReporter) page.getRootComponent();
+
+        // Let the page set up for the new exception.
+
+        rootComponent.reportException(exception);
+
+        renderer.renderPageResponse(page);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected Object[] formExceptionContext(Throwable exception) {
+        if (exception instanceof ContextAwareException) return ((ContextAwareException) exception).getContext();
+
+        Class exceptionClass = exception.getClass();
+        // pick the first class in the hierarchy that's not anonymous, probably no reason check for array types
+        while ("".equals(exceptionClass.getSimpleName()))
+            exceptionClass = exceptionClass.getSuperclass();
+
+        // check if exception type is plain runtimeException - yes, we really want the test to be this way
+        if (exceptionClass.isAssignableFrom(RuntimeException.class))
+            return exception.getMessage() == null ? new Object[0] : new Object[] { exception.getMessage().toLowerCase() };
+
+        // otherwise, form the context from the exception type name
+        String exceptionType = exceptionClass.getSimpleName();
+        if (exceptionType.endsWith("Exception")) exceptionType = exceptionType.substring(0, exceptionType.length() - 9);
+        return new Object[] { exceptionType.toLowerCase() };
+    }
+    
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java?rev=1241232&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DefaultRequestExceptionHandlerTest.java Mon Feb  6 22:45:09 2012
@@ -0,0 +1,62 @@
+package org.apache.tapestry5.internal.services;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.apache.tapestry5.ContextAwareException;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("serial")
+public class DefaultRequestExceptionHandlerTest {
+	private DefaultRequestExceptionHandler contextFormer = new DefaultRequestExceptionHandler(null, null, null, null, null, null,
+			null, null, null, null);
+
+	private static class MyContextAwareException extends Throwable implements ContextAwareException {
+		private Object[] context;
+
+		public MyContextAwareException(Object[] context) {
+			this.context = context;
+		}
+
+		public Object[] getContext() {
+			return context;
+		}
+
+	}
+
+	@Test
+	public void noContextWhenExceptionDoesntContainMessage() {
+		Object[] context = contextFormer.formExceptionContext(new RuntimeException() {
+		});
+		assertEquals(context.length, 0);
+	}
+
+	@Test
+	public void contextIsExceptionMessage() {
+		Object[] context = contextFormer.formExceptionContext(new RuntimeException() {
+			public String getMessage() {
+				return "HelloWorld";
+			}
+		});
+		assertEquals(context.length, 1);
+		assertTrue("helloworld".equals(context[0]));
+	}
+
+	@Test
+	public void contextIsExceptionType() {
+		Object[] context = contextFormer.formExceptionContext(new IllegalArgumentException("Value not allowed"));
+		assertEquals(context.length, 1);
+		assertTrue(context[0] instanceof String);
+		assertTrue("illegalargument".equals(context[0]));
+	}
+
+	@Test
+	public void contextIsProvidedByContextAwareException() {
+		Object[] sourceContext = new Object[] { new Integer(10), this };
+
+		Object[] context = contextFormer.formExceptionContext(new MyContextAwareException(sourceContext) {
+		});
+		assertEquals(context, sourceContext);
+
+	}
+}