From 9c09a0a0375364a2bfe5f3e2c768d298c41e6719 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 23 Apr 2013 13:53:09 +0200 Subject: [PATCH] Consistent Map/Set ordering Use LinkedHashMaps/Sets wherever exposed to users, and code tests defensively in terms of expected Map/Set ordering. Otherwise, there'll be runtime order differences between JDK 7 and JDK 8 due to internal HashMap/Set implementation differences. Issue: SPR-9639 --- .../xml/XmlBeanDefinitionReaderTests.java | 31 ++++---- .../org/springframework/ui/ModelMapTests.java | 27 ++++--- .../validation/DataBinderTests.java | 5 +- .../spel/testresources/Inventor.java | 5 +- .../web/HttpMediaTypeException.java | 11 ++- .../HttpMediaTypeNotSupportedException.java | 6 +- ...ttpRequestMethodNotSupportedException.java | 10 +-- .../AbstractHandlerExceptionResolver.java | 8 +- .../SimpleMappingExceptionResolver.java | 5 +- .../SimpleMappingExceptionResolverTests.java | 5 +- .../AbstractHandlerExceptionResolver.java | 6 +- .../SimpleMappingExceptionResolver.java | 68 +++++++++------- .../RequestMappingInfoHandlerMapping.java | 22 +++--- .../web/servlet/view/AbstractView.java | 10 +-- .../SimpleMappingExceptionResolverTests.java | 4 +- .../servlet/view/document/PdfViewTests.java | 79 ------------------- ...edViewTest.java => AtomFeedViewTests.java} | 20 +++-- ...eedViewTest.java => RssFeedViewTests.java} | 18 +++-- 18 files changed, 145 insertions(+), 195 deletions(-) delete mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/view/document/PdfViewTests.java rename spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/{AtomFeedViewTest.java => AtomFeedViewTests.java} (89%) rename spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/{RssFeedViewTest.java => RssFeedViewTests.java} (91%) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java index c5da99f0e4..04f39e6909 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/xml/XmlBeanDefinitionReaderTests.java @@ -29,7 +29,7 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.tests.sample.beans.TestBean; - +import org.springframework.util.ObjectUtils; /** * @author Rick Evans @@ -38,13 +38,13 @@ import org.springframework.tests.sample.beans.TestBean; public class XmlBeanDefinitionReaderTests extends TestCase { public void testSetParserClassSunnyDay() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(DefaultBeanDefinitionDocumentReader.class); } public void testSetParserClassToNull() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(null); fail("Should have thrown IllegalArgumentException (null parserClass)"); } @@ -54,7 +54,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { public void testSetParserClassToUnsupportedParserType() throws Exception { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); new XmlBeanDefinitionReader(registry).setDocumentReaderClass(String.class); fail("Should have thrown IllegalArgumentException (unsupported parserClass)"); } @@ -64,7 +64,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { public void testWithOpenInputStream() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml")); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); fail("Should have thrown BeanDefinitionStoreException (can't determine validation mode)"); @@ -74,7 +74,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { } public void testWithOpenInputStreamAndExplicitValidationMode() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml")); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_DTD); @@ -83,14 +83,14 @@ public class XmlBeanDefinitionReaderTests extends TestCase { } public void testWithImport() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("import.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); } public void testWithWildcardImport() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("importPattern.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); @@ -98,7 +98,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { public void testWithInputSource() { try { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); InputSource resource = new InputSource(getClass().getResourceAsStream("test.xml")); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); fail("Should have thrown BeanDefinitionStoreException (can't determine validation mode)"); @@ -108,7 +108,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { } public void testWithInputSourceAndExplicitValidationMode() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); InputSource resource = new InputSource(getClass().getResourceAsStream("test.xml")); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_DTD); @@ -117,7 +117,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { } public void testWithFreshInputStream() { - SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();; + SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); Resource resource = new ClassPathResource("test.xml", getClass()); new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource); testBeanDefinitions(registry); @@ -133,9 +133,10 @@ public class XmlBeanDefinitionReaderTests extends TestCase { assertEquals(TestBean.class.getName(), registry.getBeanDefinition("rod").getBeanClassName()); assertEquals(TestBean.class.getName(), registry.getBeanDefinition("aliased").getBeanClassName()); assertTrue(registry.isAlias("youralias")); - assertEquals(2, registry.getAliases("aliased").length); - assertEquals("myalias", registry.getAliases("aliased")[0]); - assertEquals("youralias", registry.getAliases("aliased")[1]); + String[] aliases = registry.getAliases("aliased"); + assertEquals(2, aliases.length); + assertTrue(ObjectUtils.containsElement(aliases, "myalias")); + assertTrue(ObjectUtils.containsElement(aliases, "youralias")); } public void testDtdValidationAutodetect() throws Exception { @@ -147,7 +148,7 @@ public class XmlBeanDefinitionReaderTests extends TestCase { } private void doTestValidation(String resourceName) throws Exception { - DefaultListableBeanFactory factory = new DefaultListableBeanFactory();; + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); Resource resource = new ClassPathResource(resourceName, getClass()); new XmlBeanDefinitionReader(factory).loadBeanDefinitions(resource); TestBean bean = (TestBean) factory.getBean("testBean"); diff --git a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java index 7b159f3b3a..11ce2b5ce3 100644 --- a/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java +++ b/spring-context/src/test/java/org/springframework/ui/ModelMapTests.java @@ -16,26 +16,26 @@ package org.springframework.ui; -import static org.junit.Assert.*; - import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.junit.Test; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.tests.sample.beans.TestBean; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import static org.junit.Assert.*; + /** * @author Rick Evans * @author Juergen Hoeller @@ -226,12 +226,12 @@ public final class ModelMapTests { public void testAopCglibProxy() throws Exception { ModelMap map = new ModelMap(); ProxyFactory factory = new ProxyFactory(); - Date date = new Date(); - factory.setTarget(date); + SomeInnerClass val = new SomeInnerClass(); + factory.setTarget(val); factory.setProxyTargetClass(true); map.addAttribute(factory.getProxy()); - assertTrue(map.containsKey("date")); - assertEquals(date, map.get("date")); + assertTrue(map.containsKey("someInnerClass")); + assertEquals(val, map.get("someInnerClass")); } @Test @@ -288,11 +288,20 @@ public final class ModelMapTests { } - private static class SomeInnerClass { + public static class SomeInnerClass { + + public boolean equals(Object obj) { + return (obj instanceof SomeInnerClass); + } + + @Override + public int hashCode() { + return SomeInnerClass.class.hashCode(); + } } - private static class UKInnerClass { + public static class UKInnerClass { } } diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java index 77fc0dee24..567716bf23 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java @@ -56,6 +56,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.Formatter; import org.springframework.format.number.NumberFormatter; import org.springframework.format.support.FormattingConversionService; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -614,8 +615,8 @@ public class DataBinderTests extends TestCase { assertNull(rod.getSomeMap().get("key4")); String[] disallowedFields = binder.getBindingResult().getSuppressedFields(); assertEquals(2, disallowedFields.length); - assertEquals("someMap[key3]", disallowedFields[0]); - assertEquals("someMap[key4]", disallowedFields[1]); + assertTrue(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")); + assertTrue(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")); } /** diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java index 87f63a3914..efc60ba973 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/testresources/Inventor.java @@ -3,6 +3,7 @@ package org.springframework.expression.spel.testresources; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -30,8 +31,8 @@ public class Inventor { private boolean accessedThroughGetSet; public List listOfInteger = new ArrayList(); public List booleanList = new ArrayList(); - public Map mapOfStringToBoolean = new HashMap(); - public Map mapOfNumbersUpToTen = new HashMap(); + public Map mapOfStringToBoolean = new LinkedHashMap(); + public Map mapOfNumbersUpToTen = new LinkedHashMap(); public List listOfNumbersUpToTen = new ArrayList(); public List listOneFive = new ArrayList(); public String[] stringArrayOfThreeItems = new String[]{"1","2","3"}; diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java index f41a2b8f92..eb3fcc3719 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package org.springframework.web; -import java.util.List; import java.util.Collections; +import java.util.List; import javax.servlet.ServletException; import org.springframework.http.MediaType; @@ -33,6 +33,7 @@ public abstract class HttpMediaTypeException extends ServletException { private final List supportedMediaTypes; + /** * Create a new HttpMediaTypeException. * @param message the exception message @@ -48,13 +49,15 @@ public abstract class HttpMediaTypeException extends ServletException { */ protected HttpMediaTypeException(String message, List supportedMediaTypes) { super(message); - this.supportedMediaTypes = supportedMediaTypes; + this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes); } + /** * Return the list of supported media types. */ public List getSupportedMediaTypes() { - return supportedMediaTypes; + return this.supportedMediaTypes; } + } diff --git a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java index ed820c7043..93bc99972a 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpMediaTypeNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException { private final MediaType contentType; + /** * Create a new HttpMediaTypeNotSupportedException. * @param message the exception message @@ -61,11 +62,12 @@ public class HttpMediaTypeNotSupportedException extends HttpMediaTypeException { this.contentType = contentType; } + /** * Return the HTTP request content type method that caused the failure. */ public MediaType getContentType() { - return contentType; + return this.contentType; } } diff --git a/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java b/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java index a70b37386c..44096bb583 100644 --- a/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java +++ b/spring-web/src/main/java/org/springframework/web/HttpRequestMethodNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,9 @@ package org.springframework.web; import java.util.Collection; -import java.util.HashSet; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; - import javax.servlet.ServletException; import org.springframework.http.HttpMethod; @@ -105,11 +105,11 @@ public class HttpRequestMethodNotSupportedException extends ServletException { * Return the actually supported HTTP methods, if known, as {@link HttpMethod} instances. */ public Set getSupportedHttpMethods() { - Set supportedMethods = new HashSet(); + Set supportedMethods = new LinkedHashSet(); for (String value : this.supportedMethods) { supportedMethods.add(HttpMethod.valueOf(value)); } - return supportedMethods; + return Collections.unmodifiableSet(supportedMethods); } } diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/AbstractHandlerExceptionResolver.java index 64365dfa48..74534798f4 100644 --- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/AbstractHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,7 +48,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti private int order = Ordered.LOWEST_PRECEDENCE; - private Set mappedHandlers; + private Set mappedHandlers; private Class[] mappedHandlerClasses; @@ -74,7 +74,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti * error view will be used as fallback for all exceptions; any further * HandlerExceptionResolvers in the chain will be ignored in this case. */ - public void setMappedHandlers(Set mappedHandlers) { + public void setMappedHandlers(Set mappedHandlers) { this.mappedHandlers = mappedHandlers; } @@ -145,7 +145,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti /** * Check whether this resolver is supposed to apply to the given handler. *

The default implementation checks against the specified mapped handlers - * and handler classes, if any, and alspo checks the window state (according + * and handler classes, if any, and also checks the window state (according * to the "renderWhenMinimize" property). * @param request current portlet request * @param handler the executed handler, or {@code null} if none chosen at the diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolver.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolver.java index 5025a024e0..8065b75386 100644 --- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolver.java +++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -159,7 +159,8 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) { String exceptionMapping = (String) names.nextElement(); int depth = getDepth(exceptionMapping, ex); - if (depth >= 0 && depth < deepest) { + if (depth >= 0 && (depth < deepest || (depth == deepest && + dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { deepest = depth; dominantMapping = exceptionMapping; viewName = exceptionMappings.getProperty(exceptionMapping); diff --git a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolverTests.java b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolverTests.java index e00e74589a..1632f5074a 100644 --- a/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolverTests.java +++ b/spring-webmvc-portlet/src/test/java/org/springframework/web/portlet/handler/SimpleMappingExceptionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package org.springframework.web.portlet.handler; import java.util.Collections; import java.util.Properties; - import javax.portlet.WindowState; import junit.framework.TestCase; @@ -206,7 +205,7 @@ public class SimpleMappingExceptionResolverTests extends TestCase { exceptionResolver.setMappedHandlers(Collections.singleton(handler1)); exceptionResolver.setExceptionMappings(props); ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, oddException); - assertEquals("error", mav.getViewName()); + assertEquals("another-error", mav.getViewName()); } public void testTwoMappingsThrowOddExceptionUseLongExceptionMapping() { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index fdef487e48..330c187870 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti private int order = Ordered.LOWEST_PRECEDENCE; - private Set mappedHandlers; + private Set mappedHandlers; private Class[] mappedHandlerClasses; @@ -76,7 +76,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti * as fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be * ignored in this case. */ - public void setMappedHandlers(Set mappedHandlers) { + public void setMappedHandlers(Set mappedHandlers) { this.mappedHandlers = mappedHandlers; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java index 82f1979856..8ca6708dca 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,12 @@ import org.springframework.web.servlet.ModelAndView; import org.springframework.web.util.WebUtils; /** - * {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation that allows for mapping exception - * class names to view names, either for a set of given handlers or for all handlers in the DispatcherServlet. + * {@link org.springframework.web.servlet.HandlerExceptionResolver} implementation + * that allows for mapping exception class names to view names, either for a set of + * given handlers or for all handlers in the DispatcherServlet. * - *

Error views are analogous to error page JSPs, but can be used with any kind of exception including any checked - * one, with fine-granular mappings for specific handlers. + *

Error views are analogous to error page JSPs, but can be used with any kind of + * exception including any checked one, with fine-granular mappings for specific handlers. * * @author Juergen Hoeller * @author Arjen Poutsma @@ -87,18 +88,20 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso } /** - * Set the name of the default error view. This view will be returned if no specific mapping was found.

Default is - * none. + * Set the name of the default error view. + * This view will be returned if no specific mapping was found. + *

Default is none. */ public void setDefaultErrorView(String defaultErrorView) { this.defaultErrorView = defaultErrorView; } /** - * Set the HTTP status code that this exception resolver will apply for a given resolved error view. Keys are - * view names; values are status codes. - *

Note that this error code will only get applied in case of a top-level request. It will not be set for an include - * request, since the HTTP status cannot be modified from within an include. + * Set the HTTP status code that this exception resolver will apply for a given + * resolved error view. Keys are view names; values are status codes. + *

Note that this error code will only get applied in case of a top-level request. + * It will not be set for an include request, since the HTTP status cannot be modified + * from within an include. *

If not specified, the default status code will be applied. * @see #setDefaultStatusCode(int) */ @@ -127,12 +130,13 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso } /** - * Set the default HTTP status code that this exception resolver will apply if it resolves an error view and if there - * is no status code mapping defined. - *

Note that this error code will only get applied in case of a top-level request. It will not be set for an - * include request, since the HTTP status cannot be modified from within an include. - *

If not specified, no status code will be applied, either leaving this to the controller or view, or keeping - * the servlet engine's default of 200 (OK). + * Set the default HTTP status code that this exception resolver will apply + * if it resolves an error view and if there is no status code mapping defined. + *

Note that this error code will only get applied in case of a top-level request. + * It will not be set for an include request, since the HTTP status cannot be modified + * from within an include. + *

If not specified, no status code will be applied, either leaving this to the + * controller or view, or keeping the servlet engine's default of 200 (OK). * @param defaultStatusCode HTTP status code value, for example 500 * ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 ({@link HttpServletResponse#SC_NOT_FOUND}) * @see #setStatusCodes(Properties) @@ -142,31 +146,34 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso } /** - * Set the name of the model attribute as which the exception should be exposed. Default is "exception".

This can be - * either set to a different attribute name or to {@code null} for not exposing an exception attribute at all. + * Set the name of the model attribute as which the exception should be exposed. + * Default is "exception". + *

This can be either set to a different attribute name or to {@code null} + * for not exposing an exception attribute at all. * @see #DEFAULT_EXCEPTION_ATTRIBUTE */ public void setExceptionAttribute(String exceptionAttribute) { this.exceptionAttribute = exceptionAttribute; } + /** - * Actually resolve the given exception that got thrown during on handler execution, returning a ModelAndView that - * represents a specific error page if appropriate.

May be overridden in subclasses, in order to apply specific - * exception checks. Note that this template method will be invoked after checking whether this resolved applies - * ("mappedHandlers" etc), so an implementation may simply proceed with its actual exception handling. + * Actually resolve the given exception that got thrown during on handler execution, + * returning a ModelAndView that represents a specific error page if appropriate. + *

May be overridden in subclasses, in order to apply specific exception checks. + * Note that this template method will be invoked after checking whether this + * resolved applies ("mappedHandlers" etc), so an implementation may simply proceed + * with its actual exception handling. * @param request current HTTP request * @param response current HTTP response - * @param handler the executed handler, or {@code null} if none chosen at the time of the exception (for example, - * if multipart resolution failed) + * @param handler the executed handler, or {@code null} if none chosen at the time + * of the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding ModelAndView to forward to, or {@code null} for default processing */ @Override - protected ModelAndView doResolveException(HttpServletRequest request, - HttpServletResponse response, - Object handler, - Exception ex) { + protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) { // Expose ModelAndView for chosen error view. String viewName = determineViewName(ex, request); @@ -231,7 +238,8 @@ public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionReso for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) { String exceptionMapping = (String) names.nextElement(); int depth = getDepth(exceptionMapping, ex); - if (depth >= 0 && depth < deepest) { + if (depth >= 0 && (depth < deepest || (depth == deepest && + dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) { deepest = depth; dominantMapping = exceptionMapping; viewName = exceptionMappings.getProperty(exceptionMapping); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index 89fcb4e9e1..07ad5dce9d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -20,10 +20,10 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -86,7 +86,6 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe /** * Expose URI template variables, matrix variables, and producible media types in the request. - * * @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE * @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE * @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE @@ -149,19 +148,16 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe /** * Iterate all RequestMappingInfos once again, look if any match by URL at * least and raise exceptions accordingly. - * - * @throws HttpRequestMethodNotSupportedException - * if there are matches by URL but not by HTTP method - * @throws HttpMediaTypeNotAcceptableException - * if there are matches by URL but not by consumable media types - * @throws HttpMediaTypeNotAcceptableException - * if there are matches by URL but not by producible media types + * @throws HttpRequestMethodNotSupportedException if there are matches by URL + * but not by HTTP method + * @throws HttpMediaTypeNotAcceptableException if there are matches by URL + * but not by consumable/producible media types */ @Override protected HandlerMethod handleNoMatch(Set requestMappingInfos, String lookupPath, HttpServletRequest request) throws ServletException { - Set allowedMethods = new HashSet(6); + Set allowedMethods = new LinkedHashSet(4); Set patternMatches = new HashSet(); Set patternAndMethodMatches = new HashSet(); @@ -193,12 +189,12 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe if (patternAndMethodMatches.isEmpty()) { consumableMediaTypes = getConsumableMediaTypes(request, patternMatches); - producibleMediaTypes = getProdicubleMediaTypes(request, patternMatches); + producibleMediaTypes = getProducibleMediaTypes(request, patternMatches); paramConditions = getRequestParams(request, patternMatches); } else { consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches); - producibleMediaTypes = getProdicubleMediaTypes(request, patternAndMethodMatches); + producibleMediaTypes = getProducibleMediaTypes(request, patternAndMethodMatches); paramConditions = getRequestParams(request, patternAndMethodMatches); } @@ -236,7 +232,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe return result; } - private Set getProdicubleMediaTypes(HttpServletRequest request, Set partialMatches) { + private Set getProducibleMediaTypes(HttpServletRequest request, Set partialMatches) { Set result = new HashSet(); for (RequestMappingInfo partialMatch : partialMatches) { if (partialMatch.getProducesCondition().getMatchingCondition(request) == null) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractView.java index 5ec4806647..fc17d9bd5e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/AbstractView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,10 @@ package org.springframework.web.servlet.view; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; - import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -70,7 +69,7 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement private String requestContextAttribute; /** Map of static attributes, keyed by attribute name (String) */ - private final Map staticAttributes = new HashMap(); + private final Map staticAttributes = new LinkedHashMap(); /** Whether or not the view should add path variables in the model */ private boolean exposePathVariables = true; @@ -269,6 +268,7 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement * Dynamic values take precedence over static attributes. */ protected Map createMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) { @SuppressWarnings("unchecked") Map pathVars = this.exposePathVariables ? @@ -278,7 +278,7 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement int size = this.staticAttributes.size(); size += (model != null) ? model.size() : 0; size += (pathVars != null) ? pathVars.size() : 0; - Map mergedModel = new HashMap(size); + Map mergedModel = new LinkedHashMap(size); mergedModel.putAll(this.staticAttributes); if (pathVars != null) { mergedModel.putAll(pathVars); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolverTests.java index b673752ecf..a931bab954 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/SimpleMappingExceptionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -249,7 +249,7 @@ public class SimpleMappingExceptionResolverTests { exceptionResolver.setMappedHandlers(Collections.singleton(handler1)); exceptionResolver.setExceptionMappings(props); ModelAndView mav = exceptionResolver.resolveException(request, response, handler1, oddException); - assertEquals("error", mav.getViewName()); + assertEquals("another-error", mav.getViewName()); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/document/PdfViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/document/PdfViewTests.java deleted file mode 100644 index 933b8e0129..0000000000 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/document/PdfViewTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-2012 the original author or authors. - * - * 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.springframework.web.servlet.view.document; - -import java.io.ByteArrayOutputStream; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.lowagie.text.Document; -import com.lowagie.text.PageSize; -import com.lowagie.text.Paragraph; -import com.lowagie.text.pdf.PdfWriter; -import junit.framework.TestCase; - -import org.springframework.mock.web.test.MockHttpServletRequest; -import org.springframework.mock.web.test.MockHttpServletResponse; - -/** - * @author Alef Arendsen - * @author Juergen Hoeller - */ -public class PdfViewTests extends TestCase { - - public void testPdf() throws Exception { - final String text = "this should be in the PDF"; - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - AbstractPdfView pdfView = new AbstractPdfView() { - @Override - protected void buildPdfDocument(Map model, Document document, PdfWriter writer, - HttpServletRequest request, HttpServletResponse response) throws Exception { - document.add(new Paragraph(text)); - } - }; - - pdfView.render(new HashMap(), request, response); - byte[] pdfContent = response.getContentAsByteArray(); - assertEquals("correct response content type", "application/pdf", response.getContentType()); - assertEquals("correct response content length", pdfContent.length, response.getContentLength()); - - // rebuild iText document for comparison - Document document = new Document(PageSize.A4); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PdfWriter writer = PdfWriter.getInstance(document, baos); - writer.setViewerPreferences(PdfWriter.AllowPrinting | PdfWriter.PageLayoutSinglePage); - document.open(); - document.add(new Paragraph(text)); - document.close(); - byte[] baosContent = baos.toByteArray(); - assertEquals("correct size", pdfContent.length, baosContent.length); - - int diffCount = 0; - for (int i = 0; i < pdfContent.length; i++) { - if (pdfContent[i] != baosContent[i]) { - diffCount++; - } - } - assertTrue("difference only in encryption", diffCount < 70); - } - -} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTest.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTests.java similarity index 89% rename from spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTest.java rename to spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTests.java index 3827ebc2a9..e2236eb281 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTest.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/AtomFeedViewTests.java @@ -1,5 +1,5 @@ /* - * Copyright ${YEAR} the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.web.servlet.view.feed; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -27,16 +27,20 @@ import javax.servlet.http.HttpServletResponse; import com.sun.syndication.feed.atom.Content; import com.sun.syndication.feed.atom.Entry; import com.sun.syndication.feed.atom.Feed; -import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; -import static org.custommonkey.xmlunit.XMLUnit.setIgnoreWhitespace; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; -public class AtomFeedViewTest { +import static org.custommonkey.xmlunit.XMLAssert.*; +import static org.custommonkey.xmlunit.XMLUnit.*; +import static org.junit.Assert.assertEquals; + +/** + * @author Arjen Poutsma + */ +public class AtomFeedViewTests { private AbstractAtomFeedView view; @@ -51,9 +55,9 @@ public class AtomFeedViewTest { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - Map model = new HashMap(); - model.put("1", "This is entry 1"); + Map model = new LinkedHashMap(); model.put("2", "This is entry 2"); + model.put("1", "This is entry 1"); view.render(model, request, response); assertEquals("Invalid content-type", "application/atom+xml", response.getContentType()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTest.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTests.java similarity index 91% rename from spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTest.java rename to spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTests.java index a662993bd8..6569263f0a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTest.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/feed/RssFeedViewTests.java @@ -17,8 +17,8 @@ package org.springframework.web.servlet.view.feed; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -27,16 +27,20 @@ import javax.servlet.http.HttpServletResponse; import com.sun.syndication.feed.rss.Channel; import com.sun.syndication.feed.rss.Description; import com.sun.syndication.feed.rss.Item; -import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; -import static org.custommonkey.xmlunit.XMLUnit.setIgnoreWhitespace; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; -public class RssFeedViewTest { +import static org.custommonkey.xmlunit.XMLAssert.*; +import static org.custommonkey.xmlunit.XMLUnit.*; +import static org.junit.Assert.assertEquals; + +/** + * @author Arjen Poutsma + */ +public class RssFeedViewTests { private AbstractRssFeedView view; @@ -52,9 +56,9 @@ public class RssFeedViewTest { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - Map model = new HashMap(); - model.put("1", "This is entry 1"); + Map model = new LinkedHashMap(); model.put("2", "This is entry 2"); + model.put("1", "This is entry 1"); view.render(model, request, response); assertEquals("Invalid content-type", "application/rss+xml", response.getContentType());