spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.6k stars 38.13k forks source link

CollectionsToCollectionsConverter no longer handles java.util.Collections$EmptyList -> java.util.Collections$EmptyList [SPR-7293] #11952

Closed spring-projects-issues closed 12 years ago

spring-projects-issues commented 14 years ago

Jamie Goodfellow opened SPR-7293 and commented

Prior to 3.0.3, this class was capable of converting from a java.util.Collections$EmptyList to java.util.Collections$EmptyList. Starting from 3.0.3, this operation results in the exception: java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList

The following Test case was verified to pass in 3.0.2 and fail in 3.0.3:

package org.springframework.core.convert.support;

import java.util.Collections; import java.util.List;

import org.junit.Test; import org.springframework.core.convert.TypeDescriptor;

public class CollectionToCollectionConverterTest {

@Test
public void testCollectionsEmptyList() throws Exception {
    CollectionToCollectionConverter converter = new CollectionToCollectionConverter(new GenericConversionService());

    TypeDescriptor type = new TypeDescriptor(getClass().getField("list"), Class.forName("java.util.Collections$EmptyList"));

    converter.convert(list, type, type);
}

public List list = Collections.emptyList();

}

This specific situation is a problem because Spring Security uses this mechanism to convert the filterChainMap in the filterChainProxy, and elements with filters="none" set the filter chain to Collections.EMPTY_LIST. The Spring Security stack trace showing this error is:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.filterChainProxy': Initialization of bean failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.util.LinkedHashMap' to required type 'java.util.Map' for property 'filterChainMap'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "{/resources/=[], /ui/error/session=[], /favicon.ico=[], /=[org.springframework.security.web.context.SecurityContextPersistenceFilter@a30589, org.springframework.security.web.authentication.logout.LogoutFilter@c07930, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@544b02, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@87286, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@96d92e, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@18c5b4f, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@1beb7ec, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1cfb56, org.springframework.security.web.session.SessionManagementFilter@b8f952, org.springframework.security.web.access.ExceptionTranslationFilter@1f88953, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1cbf6bb, org.springframework.security.web.authentication.switchuser.SwitchUserFilter@1b3a74d]}" from type 'java.util.LinkedHashMap' to type 'java.util.Map'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "[]" from type 'java.util.Collections$EmptyList' to type 'java.util.Collections$EmptyList'; nested exception is java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:527) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:574) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425) at com.truecontext.common.test.web.XmlWebApplicationContextLoader.loadContext(XmlWebApplicationContextLoader.java:47) at com.truecontext.common.test.web.XmlWebApplicationContextLoader.loadContext(XmlWebApplicationContextLoader.java:1) at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:280) at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:304) ... 24 more Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.util.LinkedHashMap' to required type 'java.util.Map' for property 'filterChainMap'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "{/resources/=[], /ui/error/session=[], /favicon.ico=[], /=[org.springframework.security.web.context.SecurityContextPersistenceFilter@a30589, org.springframework.security.web.authentication.logout.LogoutFilter@c07930, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@544b02, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@87286, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@96d92e, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@18c5b4f, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@1beb7ec, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1cfb56, org.springframework.security.web.session.SessionManagementFilter@b8f952, org.springframework.security.web.access.ExceptionTranslationFilter@1f88953, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1cbf6bb, org.springframework.security.web.authentication.switchuser.SwitchUserFilter@1b3a74d]}" from type 'java.util.LinkedHashMap' to type 'java.util.Map'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "[]" from type 'java.util.Collections$EmptyList' to type 'java.util.Collections$EmptyList'; nested exception is java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:457) at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:499) at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:493) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1363) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1322) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1076) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517) ... 36 more Caused by: org.springframework.core.convert.ConversionFailedException: Unable to convert value "{/resources/*=[], /ui/error/session=[], /favicon.ico=[], /**=[org.springframework.security.web.context.SecurityContextPersistenceFilter@a30589, org.springframework.security.web.authentication.logout.LogoutFilter@c07930, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@544b02, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@87286, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@96d92e, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@18c5b4f, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@1beb7ec, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1cfb56, org.springframework.security.web.session.SessionManagementFilter@b8f952, org.springframework.security.web.access.ExceptionTranslationFilter@1f88953, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1cbf6bb, org.springframework.security.web.authentication.switchuser.SwitchUserFilter@1b3a74d]}" from type 'java.util.LinkedHashMap' to type 'java.util.Map'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "[]" from type 'java.util.Collections$EmptyList' to type 'java.util.Collections$EmptyList'; nested exception is java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:40) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:183) at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:141) at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:447) ... 42 more Caused by: org.springframework.core.convert.ConversionFailedException: Unable to convert value "[]" from type 'java.util.Collections$EmptyList' to type 'java.util.Collections$EmptyList'; nested exception is java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:40) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:183) at org.springframework.core.convert.support.MapToMapConverter.convert(MapToMapConverter.java:68) at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37) ... 45 more Caused by: java.lang.IllegalArgumentException: Could not instantiate Collection type: java.util.Collections$EmptyList at org.springframework.core.CollectionFactory.createCollection(CollectionFactory.java:256) at org.springframework.core.convert.support.CollectionToCollectionConverter.convert(CollectionToCollectionConverter.java:61) at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37) ... 48 more

I believe this used to work because a check was made to see if the source was assignable to the target type. If so, the source was returned. I would either put this logic back in, or in the CollectionsFactory, specially handle the Collections$EmptyList list type.


Affects: 3.0.3, 3.0.6, 3.1 RC2

Referenced from: commits https://github.com/spring-projects/spring-framework/commit/7eeb654eec3ac63c7a8a5df7ef279561bccff1e0, https://github.com/spring-projects/spring-framework/commit/e0d922d35202fdb9f309f2aeccd8293b09943ebd, https://github.com/spring-projects/spring-framework/commit/27b04036a9e6e7434bf3ef0942b417aa16b90574

Backported to: 3.0.7

3 votes, 8 watchers

spring-projects-issues commented 14 years ago

Burkhard Graves commented

Hi Jamie, I ran into the same problem - do you have a 'quick workaround' for the the not working 'filters="none"'?

spring-projects-issues commented 14 years ago

Jamie Goodfellow commented

I made a copy of org.springframework.core.CollectionFactory and put it in my own JAR, and updated the createCollection method to be:

public static Collection createCollection(Class<?> collectionType, int initialCapacity) {
    if (collectionType.isInterface()) {
        if (List.class.equals(collectionType)) {
            return new ArrayList(initialCapacity);
        }
        else if (SortedSet.class.equals(collectionType) || collectionType.equals(navigableSetClass)) {
            return new TreeSet();
        }
        else if (Set.class.equals(collectionType) || Collection.class.equals(collectionType)) {
            return new LinkedHashSet(initialCapacity);
        }
        else {
            throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
        }
    }
    else {
        if (!Collection.class.isAssignableFrom(collectionType)) {
            throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
        }
        else if(emptyListClass != null && emptyListClass.equals(collectionType)) {
            return Collections.emptyList();
        }
        try {
            return (Collection) collectionType.newInstance();
        }
        catch (Exception ex) {
            throw new IllegalArgumentException("Could not instantiate Collection type: " + collectionType.getName());
        }
    }
}

Where emptyListClass is defined in the static constructor at the top: emptyListClass = Collections.emptyList().getClass();

This class overrides the Spring version in the class loader.

Alternatively, you could change your filters="none" to filters="permitAll" (which would allow everything through, but still apply filters), or you could stick with Spring 3.0.2. If anyone has any other/better workarounds, let me know!

spring-projects-issues commented 14 years ago

Burkhard Graves commented

Hi Jamie, thanks for your help! I'll stick with 3.0.2 until 3.0.4 comes out - won't take long, I guess... ;-)

spring-projects-issues commented 14 years ago

Juergen Hoeller commented

Fixed for 3.0.4 - will be available in tonight's 3.0.4 snapshot. Please give it an early try and let us know whether it works for you again!

Juergen

spring-projects-issues commented 13 years ago

Burkhard Graves commented

Broken again in 3.0.6.

Regards Burkhard

spring-projects-issues commented 13 years ago

Chris Beams commented

Juergen - updated the affects and fix versions here so that this gets re-evaluated for RC1