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);
+
+ }
+}