diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 027917954..52b6edb99 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -32,6 +32,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEventPublisher; @@ -51,6 +53,9 @@ import org.springframework.validation.Validator; /** * Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and * {@link PersistentProperty}. + *

+ * The implementation uses a {@link ReentrantReadWriteLock} to make sure {@link PersistentEntity} are completely + * populated before accessing them from outside. * * @param E the concrete {@link PersistentEntity} type the {@link MappingContext} implementation creates * @param P the concrete {@link PersistentProperty} type the {@link MappingContext} implementation creates @@ -70,6 +75,10 @@ public abstract class AbstractMappingContext getPersistentEntities() { - return persistentEntities.values(); + try { + read.lock(); + return persistentEntities.values(); + } finally { + read.unlock(); + } } /* @@ -132,10 +146,16 @@ public abstract class AbstractMappingContext type) { - E entity = persistentEntities.get(type); + try { + read.lock(); + E entity = persistentEntities.get(type); - if (entity != null) { - return entity; + if (entity != null) { + return entity; + } + + } finally { + read.unlock(); } if (strict) { @@ -144,7 +164,7 @@ public abstract class AbstractMappingContext type = typeInformation.getType(); try { + + write.lock(); + final E entity = createPersistentEntity(typeInformation); // Eagerly cache the entity as we might have to find it during recursive lookups. @@ -260,8 +283,11 @@ public abstract class AbstractMappingContext> { + + @Test + public void foo() throws InterruptedException { + + final DummyMappingContext context = new DummyMappingContext(); + + Thread a = new Thread(new Runnable() { + public void run() { + context.getPersistentEntity(Person.class); + } + }); + + Thread b = new Thread(new Runnable() { + + public void run() { + + PersistentEntity entity = context.getPersistentEntity(Person.class); + + entity.doWithProperties(new PropertyHandler() { + public void doWithPersistentProperty(T persistentProperty) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + } + }); + + a.start(); + Thread.sleep(2800); + b.start(); + + a.join(); + b.join(); + } + + class DummyMappingContext extends AbstractMappingContext, T> { + + @Override + @SuppressWarnings("unchecked") + protected BasicPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + return (BasicPersistentEntity) new BasicPersistentEntity(typeInformation); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected T createPersistentProperty(final Field field, final PropertyDescriptor descriptor, + final BasicPersistentEntity owner, final SimpleTypeHolder simpleTypeHolder) { + + PersistentProperty prop = mock(PersistentProperty.class); + + when(prop.getTypeInformation()).thenReturn((TypeInformation) owner.getTypeInformation()); + when(prop.getName()).thenReturn(field.getName()); + + try { + Thread.sleep(800); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + return (T) prop; + } + } + + class Person { + + String firstname; + String lastname; + String email; + } +}