diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java index 69eb44b0f4..c5dec02b61 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java @@ -41,6 +41,8 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware private volatile SimpleTypeConverter delegate = new SimpleTypeConverter(); + private volatile boolean haveCalledDelegateGetDefaultEditor; + private volatile ConversionService conversionService; @@ -80,9 +82,9 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware return false; } if (!String.class.isAssignableFrom(sourceType)) { - return delegate.findCustomEditor(sourceType, null) != null || delegate.getDefaultEditor(sourceType) != null; + return delegate.findCustomEditor(sourceType, null) != null || this.getDefaultEditor(sourceType) != null; } - return delegate.findCustomEditor(targetType, null) != null || delegate.getDefaultEditor(targetType) != null; + return delegate.findCustomEditor(targetType, null) != null || this.getDefaultEditor(targetType) != null; } public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) { @@ -115,7 +117,7 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware if (!String.class.isAssignableFrom(sourceType.getType())) { PropertyEditor editor = delegate.findCustomEditor(sourceType.getType(), null); if (editor==null) { - editor = delegate.getDefaultEditor(sourceType.getType()); + editor = this.getDefaultEditor(sourceType.getType()); } if (editor != null) { // INT-1441 String text = null; @@ -132,4 +134,19 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware return delegate.convertIfNecessary(value, targetType.getType()); } + private PropertyEditor getDefaultEditor(Class sourceType) { + PropertyEditor defaultEditor; + if (this.haveCalledDelegateGetDefaultEditor) { + defaultEditor= delegate.getDefaultEditor(sourceType); + } + else { + synchronized(this) { + // not thread-safe - it builds the defaultEditors field in-place (SPR-10191) + defaultEditor= delegate.getDefaultEditor(sourceType); + } + this.haveCalledDelegateGetDefaultEditor = true; + } + return defaultEditor; + } + } \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java index a67f80db8e..d60f0b175a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java @@ -1,20 +1,46 @@ -/** +/* + * 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. + * 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.integration.util; import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; @@ -26,6 +52,7 @@ import org.springframework.integration.message.GenericMessage; /** * @author Oleg Zhurakousky + * @author Gary Russell * */ public class BeanFactoryTypeConverterTests { @@ -111,4 +138,42 @@ public class BeanFactoryTypeConverterTests { Object object = new Object(); assertEquals("foo", typeConverter.convertValue(object, TypeDescriptor.valueOf(Object.class), TypeDescriptor.valueOf(String.class))); } + + @Test + public void initialConcurrency() throws Exception { + ConversionService conversionService = mock(ConversionService.class); // can convert nothing so we drop down to P.E.s + final BeanFactoryTypeConverter beanFactoryTypeConverter = new BeanFactoryTypeConverter(conversionService); + ConfigurableBeanFactory beanFactory = mock(ConfigurableBeanFactory.class); + SimpleTypeConverter typeConverter = spy(new SimpleTypeConverter()); + when(beanFactory.getTypeConverter()).thenReturn(typeConverter); + final AtomicBoolean inGetDefaultEditor = new AtomicBoolean(); + final AtomicBoolean concurrentlyInGetDefaultEditor = new AtomicBoolean(); + final AtomicInteger count = new AtomicInteger(); + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + count.incrementAndGet(); + Thread.sleep(500); + concurrentlyInGetDefaultEditor.set(inGetDefaultEditor.getAndSet(true)); + Thread.sleep(500); + inGetDefaultEditor.set(false); + return invocation.callRealMethod(); + } + }).when(typeConverter).getDefaultEditor(UUID.class); + beanFactoryTypeConverter.setBeanFactory(beanFactory); + final TypeDescriptor sourceType = TypeDescriptor.valueOf(UUID.class); + final TypeDescriptor targetType = TypeDescriptor.valueOf(String.class); + ExecutorService exec = Executors.newFixedThreadPool(2); + Runnable test = new Runnable() { + public void run() { + beanFactoryTypeConverter.canConvert(sourceType, targetType); + beanFactoryTypeConverter.convertValue(UUID.randomUUID(), sourceType, targetType); + } + }; + exec.execute(test); + exec.execute(test); + exec.shutdown(); + assertTrue(exec.awaitTermination(10, TimeUnit.SECONDS)); + assertEquals(4, count.get()); + assertFalse(concurrentlyInGetDefaultEditor.get()); + } }