Remove deprecations in PersistentPropertyAccessor.
Related issue: #2813.
This commit is contained in:
@@ -17,7 +17,6 @@ package org.springframework.data.mapping;
|
||||
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Domain service to allow accessing and setting {@link PersistentProperty}s of an entity. Usually obtained through
|
||||
@@ -46,63 +45,6 @@ public interface PersistentPropertyAccessor<T> {
|
||||
*/
|
||||
void setProperty(PersistentProperty<?> property, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Sets the given value for the {@link PersistentProperty} pointed to by the given {@link PersistentPropertyPath}. The
|
||||
* lookup of intermediate values must not yield {@literal null}.
|
||||
*
|
||||
* @param path must not be {@literal null} or empty.
|
||||
* @param value can be {@literal null}.
|
||||
* @since 2.1
|
||||
* @deprecated since 2.3, use {@link PersistentPropertyPathAccessor#setProperty(PersistentPropertyPath, Object)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
default void setProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path, @Nullable Object value) {
|
||||
|
||||
Assert.notNull(path, "PersistentPropertyPath must not be null");
|
||||
Assert.isTrue(!path.isEmpty(), "PersistentPropertyPath must not be empty");
|
||||
|
||||
PersistentProperty<? extends PersistentProperty<?>> leafProperty = path.getLeafProperty();
|
||||
PersistentPropertyPath<? extends PersistentProperty<?>> parentPath = path.getParentPath();
|
||||
|
||||
if (parentPath != null) {
|
||||
|
||||
PersistentProperty<? extends PersistentProperty<?>> parentProperty = parentPath.getLeafProperty();
|
||||
|
||||
if (parentProperty.isCollectionLike() || parentProperty.isMap()) {
|
||||
throw new MappingException(
|
||||
"Cannot traverse collection or map intermediate %s".formatted(parentPath.toDotPath()));
|
||||
}
|
||||
}
|
||||
|
||||
Object parent = parentPath == null ? getBean() : getProperty(parentPath);
|
||||
|
||||
if (parent == null) {
|
||||
|
||||
String nullIntermediateMessage = "Cannot lookup property %s on null intermediate; Original path was: %s on %s";
|
||||
|
||||
throw new MappingException(
|
||||
String.format(nullIntermediateMessage, path.getParentPath(), path.toDotPath(),
|
||||
getBean().getClass().getName()));
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor<?> accessor = parent == getBean() //
|
||||
? this //
|
||||
: leafProperty.getOwner().getPropertyAccessor(parent);
|
||||
|
||||
accessor.setProperty(leafProperty, value);
|
||||
|
||||
if (parentPath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object bean = accessor.getBean();
|
||||
|
||||
if (bean != parent) {
|
||||
setProperty(parentPath, bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the given {@link PersistentProperty} of the underlying bean instance.
|
||||
*
|
||||
@@ -112,65 +54,6 @@ public interface PersistentPropertyAccessor<T> {
|
||||
@Nullable
|
||||
Object getProperty(PersistentProperty<?> property);
|
||||
|
||||
/**
|
||||
* Return the value pointed to by the given {@link PersistentPropertyPath}. If the given path is empty, the wrapped
|
||||
* bean is returned.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
* @deprecated since 2.3, use {@link PersistentPropertyPathAccessor#getProperty(PersistentPropertyPath)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
default Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path) {
|
||||
return getProperty(path, new TraversalContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value pointed to by the given {@link PersistentPropertyPath}. If the given path is empty, the wrapped
|
||||
* bean is returned. On each path segment value lookup, the resulting value is post-processed by handlers registered
|
||||
* on the given {@link TraversalContext} context. This can be used to unwrap container types that are encountered
|
||||
* during the traversal.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.2
|
||||
* @deprecated since 2.3, use
|
||||
* {@link PersistentPropertyPathAccessor#getProperty(PersistentPropertyPath, org.springframework.data.mapping.AccessOptions.GetOptions)}
|
||||
* instead.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
default Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path, TraversalContext context) {
|
||||
|
||||
Object bean = getBean();
|
||||
Object current = bean;
|
||||
|
||||
if (path.isEmpty()) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
for (PersistentProperty<?> property : path) {
|
||||
|
||||
if (current == null) {
|
||||
|
||||
String nullIntermediateMessage = "Cannot lookup property %s on null intermediate; Original path was: %s on %s";
|
||||
|
||||
throw new MappingException(
|
||||
String.format(nullIntermediateMessage, property, path.toDotPath(), bean.getClass().getName()));
|
||||
}
|
||||
|
||||
PersistentEntity<?, ? extends PersistentProperty<?>> entity = property.getOwner();
|
||||
PersistentPropertyAccessor<Object> accessor = entity.getPropertyAccessor(current);
|
||||
|
||||
current = context.postProcess(property, accessor.getProperty(property));
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying bean. The actual instance may change between
|
||||
* {@link #setProperty(PersistentProperty, Object)} calls.
|
||||
|
||||
@@ -37,10 +37,7 @@ public interface PersistentPropertyPathAccessor<T> extends PersistentPropertyAcc
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings("deprecation")
|
||||
default Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path) {
|
||||
return PersistentPropertyAccessor.super.getProperty(path);
|
||||
}
|
||||
Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path);
|
||||
|
||||
/**
|
||||
* Return the value pointed to by the given {@link PersistentPropertyPath}. If the given path is empty, the wrapped
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A context object for lookups of values for {@link PersistentPropertyPaths} via a {@link PersistentPropertyAccessor}.
|
||||
* It allows to register functions to post-process the objects returned for a particular property, so that the
|
||||
* subsequent traversal would rather use the processed object. This is especially helpful if you need to traverse paths
|
||||
* that contain {@link Collection}s and {@link Map} that usually need indices and keys to reasonably traverse nested
|
||||
* properties.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 2.2
|
||||
* @soundtrack 8-Bit Misfits - Crash Into Me (Dave Matthews Band cover)
|
||||
*/
|
||||
public class TraversalContext {
|
||||
|
||||
private Map<PersistentProperty<?>, Function<Object, Object>> handlers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers a {@link Function} to post-process values for the given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public TraversalContext registerHandler(PersistentProperty<?> property, Function<Object, Object> handler) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null");
|
||||
Assert.notNull(handler, "Handler must not be null");
|
||||
|
||||
handlers.put(property, handler);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Function} to handle {@link Collection} values for the given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TraversalContext registerCollectionHandler(PersistentProperty<?> property,
|
||||
Function<? super Collection<?>, Object> handler) {
|
||||
return registerHandler(property, Collection.class, (Function<Object, Object>) handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Function} to handle {@link List} values for the given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TraversalContext registerListHandler(PersistentProperty<?> property,
|
||||
Function<? super List<?>, Object> handler) {
|
||||
return registerHandler(property, List.class, (Function<Object, Object>) handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Function} to handle {@link Set} values for the given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TraversalContext registerSetHandler(PersistentProperty<?> property, Function<? super Set<?>, Object> handler) {
|
||||
return registerHandler(property, Set.class, (Function<Object, Object>) handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link Function} to handle {@link Map} values for the given property.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public TraversalContext registerMapHandler(PersistentProperty<?> property,
|
||||
Function<? super Map<?, ?>, Object> handler) {
|
||||
return registerHandler(property, Map.class, (Function<Object, Object>) handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link Function} to post-process values obtained for the given {@link PersistentProperty} for
|
||||
* the given type.
|
||||
*
|
||||
* @param <T> the type of the value to handle.
|
||||
* @param property must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param handler must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> TraversalContext registerHandler(PersistentProperty<?> property, Class<T> type,
|
||||
Function<? super T, Object> handler) {
|
||||
|
||||
Assert.isTrue(type.isAssignableFrom(property.getType()), () -> String
|
||||
.format("Cannot register a property handler for %s on a property of type %s", type, property.getType()));
|
||||
|
||||
Function<Object, T> caster = (Function<Object, T>) it -> type.cast(it);
|
||||
|
||||
return registerHandler(property, caster.andThen(handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processes the value obtained for the given {@link PersistentProperty} using the registered handler.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @return the post-processed value or the value itself if no handlers registered.
|
||||
*/
|
||||
@Nullable
|
||||
Object postProcess(PersistentProperty<?> property, @Nullable Object value) {
|
||||
|
||||
Function<Object, Object> handler = handlers.get(property);
|
||||
|
||||
return handler == null ? value : handler.apply(value);
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,13 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.mapping.context.SampleMappingContext;
|
||||
import org.springframework.data.mapping.context.SamplePersistentProperty;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
|
||||
/**
|
||||
@@ -58,68 +47,6 @@ public class PersistentPropertyAccessorUnitTests {
|
||||
this.path = context.getPersistentPropertyPath(path, Order.class);
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1275
|
||||
public void looksUpValueForPropertyPath() {
|
||||
|
||||
var order = new Order(new Customer("Dave"));
|
||||
|
||||
setUp(order, "customer.firstname");
|
||||
|
||||
assertThat(accessor.getProperty(path)).isEqualTo("Dave");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1275
|
||||
public void setsPropertyOnNestedPath() {
|
||||
|
||||
var customer = new Customer("Dave");
|
||||
var order = new Order(customer);
|
||||
|
||||
setUp(order, "customer.firstname");
|
||||
|
||||
accessor.setProperty(path, "Oliver August");
|
||||
|
||||
assertThat(customer.firstname).isEqualTo("Oliver August");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1275
|
||||
public void rejectsIntermediateNullValuesForRead() {
|
||||
|
||||
setUp(new Order(null), "customer.firstname");
|
||||
|
||||
assertThatExceptionOfType(MappingException.class)//
|
||||
.isThrownBy(() -> accessor.getProperty(path));
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1275
|
||||
public void rejectsIntermediateNullValuesForWrite() {
|
||||
|
||||
setUp(new Order(null), "customer.firstname");
|
||||
|
||||
assertThatExceptionOfType(MappingException.class)//
|
||||
.isThrownBy(() -> accessor.setProperty(path, "Oliver August"));
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1322
|
||||
public void correctlyReplacesObjectInstancesWhenSettingPropertyPathOnImmutableObjects() {
|
||||
|
||||
PersistentEntity<Object, SamplePersistentProperty> entity = context.getPersistentEntity(Outer.class);
|
||||
var path = context.getPersistentPropertyPath("immutable.value",
|
||||
entity.getType());
|
||||
|
||||
var immutable = new NestedImmutable("foo");
|
||||
var outer = new Outer(immutable);
|
||||
|
||||
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(outer);
|
||||
accessor.setProperty(path, "bar");
|
||||
|
||||
var result = accessor.getBean();
|
||||
|
||||
assertThat(result).isInstanceOfSatisfying(Outer.class, it -> {
|
||||
assertThat(it.immutable).isNotSameAs(immutable);
|
||||
assertThat(it).isNotSameAs(outer);
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1377
|
||||
public void shouldConvertToPropertyPathLeafType() {
|
||||
|
||||
@@ -137,53 +64,6 @@ public class PersistentPropertyAccessorUnitTests {
|
||||
assertThat(convertingAccessor.getBean().getCustomer().getFirstname()).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1555
|
||||
public void usesTraversalContextToTraverseCollections() {
|
||||
|
||||
var withContext = WithContext.builder() //
|
||||
.collection(Collections.singleton("value")) //
|
||||
.list(Collections.singletonList("value")) //
|
||||
.set(Collections.singleton("value")) //
|
||||
.map(Collections.singletonMap("key", "value")) //
|
||||
.string(" value ") //
|
||||
.build();
|
||||
|
||||
var collectionHelper = Spec.of("collection",
|
||||
(context, property) -> context.registerCollectionHandler(property, it -> it.iterator().next()));
|
||||
var listHelper = Spec.of("list", (context, property) -> context.registerListHandler(property, it -> it.get(0)));
|
||||
var setHelper = Spec.of("set",
|
||||
(context, property) -> context.registerSetHandler(property, it -> it.iterator().next()));
|
||||
var mapHelper = Spec.of("map", (context, property) -> context.registerMapHandler(property, it -> it.get("key")));
|
||||
var stringHelper = Spec.of("string",
|
||||
(context, property) -> context.registerHandler(property, String.class, it -> it.trim()));
|
||||
|
||||
Stream.of(collectionHelper, listHelper, setHelper, mapHelper, stringHelper).forEach(it -> {
|
||||
|
||||
PersistentEntity<Object, SamplePersistentProperty> entity = context.getPersistentEntity(WithContext.class);
|
||||
PersistentProperty<?> property = entity.getRequiredPersistentProperty(it.name);
|
||||
var accessor = entity.getPropertyAccessor(withContext);
|
||||
|
||||
var traversalContext = it.registrar.apply(new TraversalContext(), property);
|
||||
|
||||
var propertyPath = context.getPersistentPropertyPath(it.name,
|
||||
WithContext.class);
|
||||
|
||||
assertThat(accessor.getProperty(propertyPath, traversalContext)).isEqualTo("value");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1555
|
||||
public void traversalContextRejectsInvalidPropertyHandler() {
|
||||
|
||||
PersistentEntity<Object, SamplePersistentProperty> entity = context.getPersistentEntity(WithContext.class);
|
||||
PersistentProperty<?> property = entity.getRequiredPersistentProperty("collection");
|
||||
|
||||
var traversal = new TraversalContext();
|
||||
|
||||
assertThatIllegalArgumentException() //
|
||||
.isThrownBy(() -> traversal.registerHandler(property, Map.class, Function.identity()));
|
||||
}
|
||||
|
||||
@Value
|
||||
static class Order {
|
||||
Customer customer;
|
||||
@@ -208,23 +88,4 @@ public class PersistentPropertyAccessorUnitTests {
|
||||
static class Outer {
|
||||
NestedImmutable immutable;
|
||||
}
|
||||
|
||||
// DATACMNS-1555
|
||||
|
||||
@Builder
|
||||
static class WithContext {
|
||||
|
||||
Collection<String> collection;
|
||||
List<String> list;
|
||||
Set<String> set;
|
||||
Map<String, String> map;
|
||||
String string;
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
static class Spec {
|
||||
|
||||
String name;
|
||||
BiFunction<TraversalContext, PersistentProperty<?>, TraversalContext> registrar;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user