Browse Source

Remove deprecations in PersistentPropertyAccessor.

Related issue: #2813.
pull/2823/head
Oliver Drotbohm 3 years ago
parent
commit
55e14952c8
No known key found for this signature in database
GPG Key ID: C25FBFA0DA493A1D
  1. 117
      src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java
  2. 5
      src/main/java/org/springframework/data/mapping/PersistentPropertyPathAccessor.java
  3. 146
      src/main/java/org/springframework/data/mapping/TraversalContext.java
  4. 139
      src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java

117
src/main/java/org/springframework/data/mapping/PersistentPropertyAccessor.java

@ -17,7 +17,6 @@ package org.springframework.data.mapping; @@ -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> { @@ -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> { @@ -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.

5
src/main/java/org/springframework/data/mapping/PersistentPropertyPathAccessor.java

@ -37,10 +37,7 @@ public interface PersistentPropertyPathAccessor<T> extends PersistentPropertyAcc @@ -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

146
src/main/java/org/springframework/data/mapping/TraversalContext.java

@ -1,146 +0,0 @@ @@ -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);
}
}

139
src/test/java/org/springframework/data/mapping/PersistentPropertyAccessorUnitTests.java

@ -19,24 +19,13 @@ import static org.assertj.core.api.Assertions.*; @@ -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 { @@ -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 { @@ -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 { @@ -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;
}
}

Loading…
Cancel
Save