diff --git a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index f04bdd5ac3..02a0902f87 100644 --- a/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -30,6 +30,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -62,6 +64,7 @@ import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.FieldCallback; import org.springframework.util.ReflectionUtils.FieldFilter; @@ -87,6 +90,8 @@ public abstract class AbstractMappingContext, P extends PersistentProperty

> implements MappingContext, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean { + private static final Logger LOGGER = LoggerFactory.getLogger(MappingContext.class); + private final Optional NONE = Optional.empty(); private final Map, Optional> persistentEntities = new HashMap<>(); private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory; @@ -550,6 +555,10 @@ private void createAndRegisterProperty(Property input) { return; } + if (isKotlinOverride(property, input)) { + return; + } + entity.addPersistentProperty(property); if (property.isAssociation()) { @@ -562,6 +571,38 @@ private void createAndRegisterProperty(Property input) { property.getPersistentEntityTypes().forEach(AbstractMappingContext.this::addPersistentEntity); } + + private boolean isKotlinOverride(P property, Property input) { + + if (!KotlinDetector.isKotlinPresent() || !input.getField().isPresent()) { + return false; + } + + Field field = input.getField().get(); + if (!KotlinDetector.isKotlinType(field.getDeclaringClass())) { + return false; + } + + for (P existingProperty : entity) { + + if (!property.getName().equals(existingProperty.getName())) { + continue; + } + + if (field.getDeclaringClass() != entity.getType() + && ClassUtils.isAssignable(field.getDeclaringClass(), entity.getType())) { + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(String.format("Skipping '%s.%s' property declaration shadowed by '%s %s' in '%s'. ", + field.getDeclaringClass().getName(), property.getName(), property.getType().getSimpleName(), + property.getName(), entity.getType().getSimpleName())); + } + return true; + } + } + + return false; + } } /** diff --git a/src/test/java/org/springframework/data/mapping/KotlinModelTypes.kt b/src/test/java/org/springframework/data/mapping/KotlinModelTypes.kt new file mode 100644 index 0000000000..23ddc2f026 --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/KotlinModelTypes.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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 + * + * https://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.data.mapping + +open class ShadowedPropertyType { + open val shadowedProperty: Int = 1 +} + +class ShadowingPropertyType : ShadowedPropertyType() { + override var shadowedProperty: Int = 10 +} + +open class ShadowedPropertyTypeWithCtor(open val shadowedProperty: Int) + +class ShadowingPropertyTypeWithCtor(val someValue: String, override var shadowedProperty: Int = 1) : ShadowedPropertyTypeWithCtor(shadowedProperty) + + diff --git a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java index d49ed04f7d..4b780a6d19 100755 --- a/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java @@ -41,9 +41,15 @@ import org.springframework.data.annotation.Id; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.ShadowedPropertyType; +import org.springframework.data.mapping.ShadowedPropertyTypeWithCtor; +import org.springframework.data.mapping.ShadowingPropertyType; +import org.springframework.data.mapping.ShadowingPropertyTypeWithCtor; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.StreamUtils; import org.springframework.data.util.TypeInformation; /** @@ -52,6 +58,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch + * @author Christoph Stobl */ class AbstractMappingContextUnitTests { @@ -216,6 +223,37 @@ void cleansUpCacheForRuntimeException() { .isThrownBy(() -> context.getPersistentEntity(Unsupported.class)); } + @Test // DATACMNS-1509 + public void shouldIgnoreKotlinOverrideCtorPropertyInSuperClass() { + + BasicPersistentEntity entity = context + .getPersistentEntity(ClassTypeInformation.from(ShadowingPropertyTypeWithCtor.class)); + entity.doWithProperties((PropertyHandler) property -> { + assertThat(property.getField().getDeclaringClass()).isNotEqualTo(ShadowedPropertyTypeWithCtor.class); + }); + } + + @Test // DATACMNS-1509 + public void shouldIgnoreKotlinOverridePropertyInSuperClass() { + + BasicPersistentEntity entity = context + .getPersistentEntity(ClassTypeInformation.from(ShadowingPropertyType.class)); + entity.doWithProperties((PropertyHandler) property -> { + assertThat(property.getField().getDeclaringClass()).isNotEqualTo(ShadowedPropertyType.class); + }); + } + + @Test // DATACMNS-1509 + public void shouldStillIncludeNonKotlinShadowedPropertyInSuperClass() { + + BasicPersistentEntity entity = context + .getPersistentEntity(ClassTypeInformation.from(ShadowingProperty.class)); + + assertThat(StreamUtils.createStreamFromIterator(entity.iterator()) + .filter(it -> it.getField().getDeclaringClass().equals(ShadowedProperty.class)).findFirst() // + ).isNotEmpty(); + } + private static void assertHasEntityFor(Class type, SampleMappingContext context, boolean expected) { boolean found = false; @@ -306,4 +344,37 @@ public void verify() { }; } } + + static class ShadowedProperty { + + private final String value; + + ShadowedProperty(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + static class ShadowingProperty extends ShadowedProperty { + + private String value; + + ShadowingProperty(String value) { + super(value); + this.value = value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String getValue() { + return value; + } + } + }