DATACMNS-1275 - Introduced MappingContext.findPersistentPropertyPaths(Class<?>, Predicate<P>).
MappingContext now exposes a method to detect all property paths pointing to properties matching a given predicate. Extracted PersistentPropertyPath creation into a dedicated factory class so that it can be tested individually. DefaultPersistentPropertyPath now exposes a ….containsPropertyOfType(…) to detect whether we've already processed a particular type in the path. Also applied a bit of Java 8 and Lombok polish. InvalidPersistentPropertyPath now collects suggested alternatives to create a better exception message. PersistentEntities now allows to map over a MappingContext and PersistentEntity that a given type is corresponding to. Streamable now exposes an ….isEmpty(). Removed references to equivalent methods implemented in subtypes.
This commit is contained in:
@@ -21,9 +21,11 @@ import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Graeme Rocher
|
||||
@@ -62,16 +64,6 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
|
||||
*/
|
||||
TypeInformation<?> getTypeInformation();
|
||||
|
||||
/**
|
||||
* Returns the {@link TypeInformation} if the property references a {@link PersistentEntity}. Will return
|
||||
* {@literal null} in case it refers to a simple type. Will return {@link Collection}'s component type or the
|
||||
* {@link Map}'s value type transparently.
|
||||
*
|
||||
* @deprecated Use getPersistentEntityTypes instead.
|
||||
*/
|
||||
@Deprecated
|
||||
Iterable<? extends TypeInformation<?>> getPersistentEntityType();
|
||||
|
||||
/**
|
||||
* Returns the {@link TypeInformation} if the property references a {@link PersistentEntity}. Will return
|
||||
* {@literal null} in case it refers to a simple type. Will return {@link Collection}'s component type or the
|
||||
@@ -79,9 +71,7 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
|
||||
return getPersistentEntityType();
|
||||
};
|
||||
Iterable<? extends TypeInformation<?>> getPersistentEntityTypes();
|
||||
|
||||
/**
|
||||
* Returns the getter method to access the property value if available. Might return {@literal null} in case there is
|
||||
@@ -328,4 +318,19 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
|
||||
* @return
|
||||
*/
|
||||
boolean usePropertyAccess();
|
||||
|
||||
/**
|
||||
* Returns whether the actual type of the property carries the given annotation.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
* @see #getActualType()
|
||||
*/
|
||||
default boolean hasActualTypeAnnotation(Class<? extends Annotation> annotationType) {
|
||||
|
||||
Assert.notNull(annotationType, "Annotation type must not be null!");
|
||||
|
||||
return AnnotatedElementUtils.hasAnnotation(getActualType(), annotationType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ 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
|
||||
@@ -40,6 +41,37 @@ public interface PersistentPropertyAccessor {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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!");
|
||||
|
||||
PersistentPropertyPath<? extends PersistentProperty<?>> parentPath = path.getParentPath();
|
||||
PersistentProperty<?> leafProperty = path.getRequiredLeafProperty();
|
||||
|
||||
Object parent = parentPath.isEmpty() ? 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, parentPath.getLeafProperty(), path.toDotPath(),
|
||||
getBean().getClass().getName()));
|
||||
}
|
||||
|
||||
PersistentPropertyAccessor accessor = leafProperty.getOwner().getPropertyAccessor(parent);
|
||||
|
||||
accessor.setProperty(leafProperty, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the given {@link PersistentProperty} of the underlying bean instance.
|
||||
*
|
||||
@@ -49,6 +81,43 @@ public interface PersistentPropertyAccessor {
|
||||
@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
|
||||
*/
|
||||
@Nullable
|
||||
default Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path) {
|
||||
|
||||
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 accessor = entity.getPropertyAccessor(current);
|
||||
|
||||
current = accessor.getProperty(property);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying bean.
|
||||
*
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping.context;
|
||||
package org.springframework.data.mapping;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -24,7 +24,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends Iterable<T> {
|
||||
public interface PersistentPropertyPath<P extends PersistentProperty<P>> extends Streamable<P> {
|
||||
|
||||
/**
|
||||
* Returns the dot based path notation using {@link PersistentProperty#getName()}.
|
||||
@@ -42,7 +42,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
String toDotPath(Converter<? super T, String> converter);
|
||||
String toDotPath(Converter<? super P, String> converter);
|
||||
|
||||
/**
|
||||
* Returns a {@link String} path with the given delimiter based on the {@link PersistentProperty#getName()}.
|
||||
@@ -62,7 +62,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
String toPath(String delimiter, Converter<? super T, String> converter);
|
||||
String toPath(String delimiter, Converter<? super P, String> converter);
|
||||
|
||||
/**
|
||||
* Returns the last property in the {@link PersistentPropertyPath}. So for {@code foo.bar} it will return the
|
||||
@@ -72,7 +72,18 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
T getLeafProperty();
|
||||
P getLeafProperty();
|
||||
|
||||
default P getRequiredLeafProperty() {
|
||||
|
||||
P property = getLeafProperty();
|
||||
|
||||
if (property == null) {
|
||||
throw new IllegalStateException("No leaf property found!");
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first property in the {@link PersistentPropertyPath}. So for {@code foo.bar} it will return the
|
||||
@@ -82,7 +93,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
T getBaseProperty();
|
||||
P getBaseProperty();
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link PersistentPropertyPath} is a base path of the current one. This means that the
|
||||
@@ -91,7 +102,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean isBasePathOf(PersistentPropertyPath<T> path);
|
||||
boolean isBasePathOf(PersistentPropertyPath<P> path);
|
||||
|
||||
/**
|
||||
* Returns the sub-path of the current one as if it was based on the given base path. So for a current path
|
||||
@@ -101,7 +112,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @param base must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
PersistentPropertyPath<T> getExtensionForBaseOf(PersistentPropertyPath<T> base);
|
||||
PersistentPropertyPath<P> getExtensionForBaseOf(PersistentPropertyPath<P> base);
|
||||
|
||||
/**
|
||||
* Returns the parent path of the current {@link PersistentPropertyPath}, i.e. the path without the leaf property.
|
||||
@@ -110,7 +121,7 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
PersistentPropertyPath<T> getParentPath();
|
||||
PersistentPropertyPath<P> getParentPath();
|
||||
|
||||
/**
|
||||
* Returns the length of the {@link PersistentPropertyPath}.
|
||||
@@ -118,12 +129,4 @@ public interface PersistentPropertyPath<T extends PersistentProperty<T>> extends
|
||||
* @return
|
||||
*/
|
||||
int getLength();
|
||||
|
||||
/**
|
||||
* Returns whether the path is empty.
|
||||
*
|
||||
* @return
|
||||
* @since 1.11
|
||||
*/
|
||||
boolean isEmpty();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2018 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.data.mapping;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
/**
|
||||
* A wrapper for a collection of {@link PersistentPropertyPath}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
* @soundtrack Stuart McCallum - North Star (City)
|
||||
*/
|
||||
public interface PersistentPropertyPaths<T, P extends PersistentProperty<P>>
|
||||
extends Streamable<PersistentPropertyPath<P>> {
|
||||
|
||||
/**
|
||||
* Returns the first {@link PersistentPropertyPath}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Optional<PersistentPropertyPath<P>> getFirst();
|
||||
|
||||
/**
|
||||
* Returns whether the given path is contained in the current {@link PersistentPropertyPaths}.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean contains(String path);
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link PropertyPath} is contained in the current {@link PersistentPropertyPaths}.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean contains(PropertyPath path);
|
||||
}
|
||||
@@ -18,23 +18,20 @@ package org.springframework.data.mapping.context;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -45,6 +42,8 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
|
||||
import org.springframework.data.mapping.model.MutablePersistentEntity;
|
||||
@@ -53,16 +52,13 @@ import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.FieldCallback;
|
||||
import org.springframework.util.ReflectionUtils.FieldFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and
|
||||
@@ -87,8 +83,8 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
|
||||
private final Optional<E> NONE = Optional.empty();
|
||||
private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
|
||||
private final Map<TypeAndProperties, PersistentPropertyPath<P>> propertyPaths = new ConcurrentReferenceHashMap<>();
|
||||
private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory = new ClassGeneratingPropertyAccessorFactory();
|
||||
private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
|
||||
|
||||
private @Nullable ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@@ -100,6 +96,10 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
private final Lock read = lock.readLock();
|
||||
private final Lock write = lock.writeLock();
|
||||
|
||||
protected AbstractMappingContext() {
|
||||
this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
|
||||
@@ -253,10 +253,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
*/
|
||||
@Override
|
||||
public PersistentPropertyPath<P> getPersistentPropertyPath(PropertyPath propertyPath) {
|
||||
|
||||
Assert.notNull(propertyPath, "Property path must not be null!");
|
||||
|
||||
return getPersistentPropertyPath(propertyPath.toDotPath(), propertyPath.getOwningType());
|
||||
return persistentPropertyPathFactory.from(propertyPath);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -265,11 +262,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
*/
|
||||
@Override
|
||||
public PersistentPropertyPath<P> getPersistentPropertyPath(String propertyPath, Class<?> type) {
|
||||
|
||||
Assert.notNull(propertyPath, "Property path must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return getPersistentPropertyPath(propertyPath, ClassTypeInformation.from(type));
|
||||
return persistentPropertyPathFactory.from(type, propertyPath);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -278,65 +271,36 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
*/
|
||||
@Override
|
||||
public PersistentPropertyPath<P> getPersistentPropertyPath(InvalidPersistentPropertyPath invalidPath) {
|
||||
return getPersistentPropertyPath(invalidPath.getResolvedPath(), invalidPath.getType());
|
||||
return persistentPropertyPathFactory.from(invalidPath.getType(), invalidPath.getResolvedPath());
|
||||
}
|
||||
|
||||
private PersistentPropertyPath<P> getPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.context.MappingContext#findPersistentPropertyPath(java.lang.Class, java.util.function.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public <T> PersistentPropertyPaths<T, P> findPersistentPropertyPaths(Class<T> type, Predicate<? super P> predicate) {
|
||||
|
||||
return propertyPaths.computeIfAbsent(TypeAndProperties.of(type, propertyPath),
|
||||
it -> createPersistentPropertyPath(it.getPath(), it.getType()));
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(predicate, "Selection predicate must not be null!");
|
||||
|
||||
return doFindPersistentPropertyPaths(type, predicate, it -> !it.isAssociation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PersistentPropertyPath} for the given parts and {@link TypeInformation}.
|
||||
*
|
||||
* @param propertyPath must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
* Actually looks up the {@link PersistentPropertyPaths} for the given type, selection predicate and traversal guard.
|
||||
* Primary purpose is to allow sub-types to alter the default traversal guard, e.g. used by
|
||||
* {@link #findPersistentPropertyPaths(Class, Predicate)}.
|
||||
*
|
||||
* @param type will never be {@literal null}.
|
||||
* @param predicate will never be {@literal null}.
|
||||
* @param traversalGuard will never be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @see #findPersistentPropertyPaths(Class, Predicate)
|
||||
*/
|
||||
private PersistentPropertyPath<P> createPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
|
||||
|
||||
List<String> parts = Arrays.asList(propertyPath.split("\\."));
|
||||
DefaultPersistentPropertyPath<P> path = DefaultPersistentPropertyPath.empty();
|
||||
Iterator<String> iterator = parts.iterator();
|
||||
E current = getRequiredPersistentEntity(type);
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
String segment = iterator.next();
|
||||
final DefaultPersistentPropertyPath<P> foo = path;
|
||||
final E bar = current;
|
||||
|
||||
Pair<DefaultPersistentPropertyPath<P>, E> pair = getPair(path, iterator, segment, current);
|
||||
|
||||
if (pair == null) {
|
||||
|
||||
String source = StringUtils.collectionToDelimitedString(parts, ".");
|
||||
String resolvedPath = foo.toDotPath();
|
||||
|
||||
throw new InvalidPersistentPropertyPath(source, type, segment, resolvedPath,
|
||||
String.format("No property %s found on %s!", segment, bar.getName()));
|
||||
}
|
||||
|
||||
path = pair.getFirst();
|
||||
current = pair.getSecond();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<DefaultPersistentPropertyPath<P>, E> getPair(DefaultPersistentPropertyPath<P> path,
|
||||
Iterator<String> iterator, String segment, E entity) {
|
||||
|
||||
P property = entity.getPersistentProperty(segment);
|
||||
|
||||
if (property == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeInformation<?> type = property.getTypeInformation().getRequiredActualType();
|
||||
return Pair.of(path.append(property), iterator.hasNext() ? getRequiredPersistentEntity(type) : entity);
|
||||
protected final <T> PersistentPropertyPaths<T, P> doFindPersistentPropertyPaths(Class<T> type,
|
||||
Predicate<? super P> predicate, Predicate<P> traversalGuard) {
|
||||
return persistentPropertyPathFactory.from(ClassTypeInformation.from(type), predicate, traversalGuard);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,13 +537,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
}
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
static class TypeAndProperties {
|
||||
|
||||
TypeInformation<?> type;
|
||||
String path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter rejecting static fields as well as artificially introduced ones. See
|
||||
* {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details.
|
||||
|
||||
@@ -15,13 +15,18 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.context;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -32,6 +37,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements PersistentPropertyPath<P> {
|
||||
|
||||
private static final Converter<PersistentProperty<?>, String> DEFAULT_CONVERTER = (source) -> source.getName();
|
||||
@@ -79,7 +85,7 @@ class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements
|
||||
Class<?> leafPropertyType = getLeafProperty().getActualType();
|
||||
|
||||
Assert.isTrue(property.getOwner().getType().equals(leafPropertyType),
|
||||
String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName()));
|
||||
() -> String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName()));
|
||||
|
||||
List<P> properties = new ArrayList<>(this.properties);
|
||||
properties.add(property);
|
||||
@@ -124,18 +130,12 @@ class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements
|
||||
Assert.hasText(delimiter, "Delimiter must not be null or empty!");
|
||||
Assert.notNull(converter, "Converter must not be null!");
|
||||
|
||||
List<String> result = new ArrayList<>();
|
||||
String result = properties.stream() //
|
||||
.map(converter::convert) //
|
||||
.filter(StringUtils::hasText) //
|
||||
.collect(Collectors.joining(delimiter));
|
||||
|
||||
for (P property : properties) {
|
||||
|
||||
String convert = converter.convert(property);
|
||||
|
||||
if (StringUtils.hasText(convert)) {
|
||||
result.add(convert);
|
||||
}
|
||||
}
|
||||
|
||||
return result.isEmpty() ? null : StringUtils.collectionToDelimitedString(result, delimiter);
|
||||
return result.isEmpty() ? null : result;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -214,11 +214,7 @@ class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements
|
||||
|
||||
int size = properties.size();
|
||||
|
||||
if (size <= 1) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new DefaultPersistentPropertyPath<>(properties.subList(0, size - 1));
|
||||
return size == 0 ? this : new DefaultPersistentPropertyPath<>(properties.subList(0, size - 1));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -237,41 +233,18 @@ class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements
|
||||
return properties.iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.context.PersistentPropertyPath#isEmpty()
|
||||
/**
|
||||
* Returns whether the current path contains a property of the given type.
|
||||
*
|
||||
* @param type can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return properties.isEmpty();
|
||||
}
|
||||
public boolean containsPropertyOfType(@Nullable TypeInformation<?> type) {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || !getClass().equals(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DefaultPersistentPropertyPath<?> that = (DefaultPersistentPropertyPath<?>) obj;
|
||||
|
||||
return this.properties.equals(that.properties);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return properties.hashCode();
|
||||
return type == null //
|
||||
? false //
|
||||
: properties.stream() //
|
||||
.anyMatch(property -> type.equals(property.getTypeInformation().getActualType()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,10 +15,18 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.PropertyMatches;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Exception to indicate a source path couldn't be resolved into a {@link PersistentPropertyPath} completely.
|
||||
@@ -29,6 +37,7 @@ import org.springframework.util.Assert;
|
||||
public class InvalidPersistentPropertyPath extends MappingException {
|
||||
|
||||
private static final long serialVersionUID = 2805815643641094488L;
|
||||
private static final String DEFAULT_MESSAGE = "No property '%s' found on %s! Did you mean: %s?";
|
||||
|
||||
private final String source, unresolvableSegment, resolvedPath;
|
||||
private final TypeInformation<?> type;
|
||||
@@ -41,10 +50,11 @@ public class InvalidPersistentPropertyPath extends MappingException {
|
||||
* @param resolvedPath
|
||||
* @param message must not be {@literal null} or empty.
|
||||
*/
|
||||
InvalidPersistentPropertyPath(String source, TypeInformation<?> type, String unresolvableSegment,
|
||||
@Nullable String resolvedPath, String message) {
|
||||
public InvalidPersistentPropertyPath(String source, TypeInformation<?> type, String unresolvableSegment,
|
||||
PersistentPropertyPath<? extends PersistentProperty<?>> resolvedPath) {
|
||||
|
||||
super(message);
|
||||
super(createMessage(resolvedPath.isEmpty() ? type : resolvedPath.getRequiredLeafProperty().getTypeInformation(),
|
||||
unresolvableSegment));
|
||||
|
||||
Assert.notNull(source, "Source property path must not be null!");
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
@@ -53,7 +63,7 @@ public class InvalidPersistentPropertyPath extends MappingException {
|
||||
this.source = source;
|
||||
this.type = type;
|
||||
this.unresolvableSegment = unresolvableSegment;
|
||||
this.resolvedPath = resolvedPath == null ? "" : resolvedPath;
|
||||
this.resolvedPath = toDotPathOrEmpty(resolvedPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,4 +101,32 @@ public class InvalidPersistentPropertyPath extends MappingException {
|
||||
public String getResolvedPath() {
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
private static String toDotPathOrEmpty(@Nullable PersistentPropertyPath<? extends PersistentProperty<?>> path) {
|
||||
|
||||
if (path == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String dotPath = path.toDotPath();
|
||||
|
||||
return dotPath == null ? "" : dotPath;
|
||||
}
|
||||
|
||||
private static String createMessage(TypeInformation<?> type, String unresolvableSegment) {
|
||||
|
||||
Set<String> potentialMatches = detectPotentialMatches(unresolvableSegment, type.getType());
|
||||
Object match = StringUtils.collectionToCommaDelimitedString(potentialMatches);
|
||||
|
||||
return String.format(DEFAULT_MESSAGE, unresolvableSegment, type.getType(), match);
|
||||
}
|
||||
|
||||
private static Set<String> detectPotentialMatches(String propertyName, Class<?> type) {
|
||||
|
||||
Set<String> result = new HashSet<>();
|
||||
result.addAll(Arrays.asList(PropertyMatches.forField(propertyName, type).getPossibleMatches()));
|
||||
result.addAll(Arrays.asList(PropertyMatches.forProperty(propertyName, type).getPossibleMatches()));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
package org.springframework.data.mapping.context;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -184,6 +187,19 @@ public interface MappingContext<E extends PersistentEntity<?, P>, P extends Pers
|
||||
@Deprecated
|
||||
PersistentPropertyPath<P> getPersistentPropertyPath(InvalidPersistentPropertyPath invalidPath);
|
||||
|
||||
/**
|
||||
* Returns all {@link PersistentPropertyPath}s pointing to properties on the given type that match the given
|
||||
* {@link Predicate}. In case of circular references the detection will stop at the property that references a type
|
||||
* that's already included in the path. Note, that is is a potentially expensive operation as results cannot be
|
||||
* cached.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param predicate must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> PersistentPropertyPaths<T, P> findPersistentPropertyPaths(Class<T> type, Predicate<? super P> predicate);
|
||||
|
||||
/**
|
||||
* Returns the {@link TypeInformation}s for all {@link PersistentEntity}s in the {@link MappingContext}.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mapping.context;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -33,7 +34,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class PersistentEntities implements Streamable<PersistentEntity<?, ? extends PersistentProperty<?>>> {
|
||||
|
||||
private final Streamable<? extends MappingContext<?, ?>> contexts;
|
||||
private final Streamable<? extends MappingContext<?, ? extends PersistentProperty<?>>> contexts;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntities} for the given {@link MappingContext}s.
|
||||
@@ -78,6 +79,26 @@ public class PersistentEntities implements Streamable<PersistentEntity<?, ? exte
|
||||
() -> new IllegalArgumentException(String.format("Couldn't find PersistentEntity for type %s!", type)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link BiFunction} on the given {@link MappingContext} and {@link PersistentEntity} based on the
|
||||
* given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param combiner must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> Optional<T> mapOnContext(Class<?> type,
|
||||
BiFunction<MappingContext<?, ? extends PersistentProperty<?>>, PersistentEntity<?, ?>, T> combiner) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(combiner, "Combining BiFunction must not be null!");
|
||||
|
||||
return contexts.stream() //
|
||||
.filter(it -> it.hasPersistentEntityFor(type)) //
|
||||
.map(it -> combiner.apply(it, it.getRequiredPersistentEntity(type))) //
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link TypeInformation} exposed by the registered {@link MappingContext}s.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright 2018 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.data.mapping.context;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.data.util.StreamUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A factory implementation to create {@link PersistentPropertyPath} instances in various ways.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
* @soundtrack Cypress Hill - Boom Biddy Bye Bye (Fugees Remix, Unreleased & Revamped)
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class PersistentPropertyPathFactory<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>> {
|
||||
|
||||
private static final Predicate<PersistentProperty<?>> IS_ENTITY = it -> it.isEntity();
|
||||
|
||||
private final Map<TypeAndPath, PersistentPropertyPath<P>> propertyPaths = new ConcurrentReferenceHashMap<>();
|
||||
private final MappingContext<E, P> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} for the given property path on the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyPath must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public PersistentPropertyPath<P> from(Class<?> type, String propertyPath) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(propertyPath, "Property path must not be null!");
|
||||
|
||||
return getPersistentPropertyPath(ClassTypeInformation.from(type), propertyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} for the given property path on the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyPath must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public PersistentPropertyPath<P> from(TypeInformation<?> type, String propertyPath) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(propertyPath, "Property path must not be null!");
|
||||
|
||||
return getPersistentPropertyPath(type, propertyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} for the given {@link PropertyPath}.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public PersistentPropertyPath<P> from(PropertyPath path) {
|
||||
|
||||
Assert.notNull(path, "Property path must not be null!");
|
||||
|
||||
return from(path.getOwningType(), path.toDotPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
|
||||
* matching it.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyFilter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> PersistentPropertyPaths<T, P> from(Class<T> type, Predicate<? super P> propertyFilter) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(propertyFilter, "Property filter must not be null!");
|
||||
|
||||
return from(ClassTypeInformation.from(type), propertyFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
|
||||
* matching it.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyFilter must not be {@literal null}.
|
||||
* @param traversalGuard must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> PersistentPropertyPaths<T, P> from(Class<T> type, Predicate<? super P> propertyFilter,
|
||||
Predicate<P> traversalGuard) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(propertyFilter, "Property filter must not be null!");
|
||||
Assert.notNull(traversalGuard, "Traversal guard must not be null!");
|
||||
|
||||
return from(ClassTypeInformation.from(type), propertyFilter, traversalGuard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
|
||||
* matching it.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyFilter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> PersistentPropertyPaths<T, P> from(TypeInformation<T> type, Predicate<? super P> propertyFilter) {
|
||||
return from(type, propertyFilter, it -> !it.isAssociation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
|
||||
* matching it.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param propertyFilter must not be {@literal null}.
|
||||
* @param traversalGuard must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T> PersistentPropertyPaths<T, P> from(TypeInformation<T> type, Predicate<? super P> propertyFilter,
|
||||
Predicate<P> traversalGuard) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(propertyFilter, "Property filter must not be null!");
|
||||
Assert.notNull(traversalGuard, "Traversal guard must not be null!");
|
||||
|
||||
return DefaultPersistentPropertyPaths.of(type,
|
||||
from(type, propertyFilter, traversalGuard, DefaultPersistentPropertyPath.empty()));
|
||||
}
|
||||
|
||||
private PersistentPropertyPath<P> getPersistentPropertyPath(TypeInformation<?> type, String propertyPath) {
|
||||
|
||||
return propertyPaths.computeIfAbsent(TypeAndPath.of(type, propertyPath),
|
||||
it -> createPersistentPropertyPath(it.getPath(), it.getType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PersistentPropertyPath} for the given parts and {@link TypeInformation}.
|
||||
*
|
||||
* @param propertyPath must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private PersistentPropertyPath<P> createPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
|
||||
|
||||
String trimmedPath = propertyPath.trim();
|
||||
|
||||
List<String> parts = trimmedPath.isEmpty() //
|
||||
? Collections.emptyList() //
|
||||
: Arrays.asList(trimmedPath.split("\\."));
|
||||
|
||||
DefaultPersistentPropertyPath<P> path = DefaultPersistentPropertyPath.empty();
|
||||
Iterator<String> iterator = parts.iterator();
|
||||
E current = context.getRequiredPersistentEntity(type);
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
String segment = iterator.next();
|
||||
final DefaultPersistentPropertyPath<P> currentPath = path;
|
||||
|
||||
Pair<DefaultPersistentPropertyPath<P>, E> pair = getPair(path, iterator, segment, current);
|
||||
|
||||
if (pair == null) {
|
||||
|
||||
String source = StringUtils.collectionToDelimitedString(parts, ".");
|
||||
|
||||
throw new InvalidPersistentPropertyPath(source, type, segment, currentPath);
|
||||
}
|
||||
|
||||
path = pair.getFirst();
|
||||
current = pair.getSecond();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Pair<DefaultPersistentPropertyPath<P>, E> getPair(DefaultPersistentPropertyPath<P> path,
|
||||
Iterator<String> iterator, String segment, E entity) {
|
||||
|
||||
P property = entity.getPersistentProperty(segment);
|
||||
|
||||
if (property == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeInformation<?> type = property.getTypeInformation().getRequiredActualType();
|
||||
return Pair.of(path.append(property), iterator.hasNext() ? context.getRequiredPersistentEntity(type) : entity);
|
||||
}
|
||||
|
||||
private <T> Collection<PersistentPropertyPath<P>> from(TypeInformation<T> type, Predicate<? super P> filter,
|
||||
Predicate<P> traversalGuard, DefaultPersistentPropertyPath<P> basePath) {
|
||||
|
||||
TypeInformation<?> actualType = type.getActualType();
|
||||
|
||||
if (actualType == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
E entity = context.getRequiredPersistentEntity(actualType);
|
||||
Set<PersistentPropertyPath<P>> properties = new HashSet<>();
|
||||
|
||||
PropertyHandler<P> propertyTester = persistentProperty -> {
|
||||
|
||||
TypeInformation<?> typeInformation = persistentProperty.getTypeInformation();
|
||||
TypeInformation<?> actualPropertyType = typeInformation.getActualType();
|
||||
|
||||
if (basePath.containsPropertyOfType(actualPropertyType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DefaultPersistentPropertyPath<P> currentPath = basePath.append(persistentProperty);
|
||||
|
||||
if (filter.test(persistentProperty)) {
|
||||
properties.add(currentPath);
|
||||
}
|
||||
|
||||
if (traversalGuard.and(IS_ENTITY).test(persistentProperty)) {
|
||||
properties.addAll(from(typeInformation, filter, traversalGuard, currentPath));
|
||||
}
|
||||
};
|
||||
|
||||
entity.doWithProperties(propertyTester);
|
||||
|
||||
AssociationHandler<P> handler = association -> propertyTester.doWithPersistentProperty(association.getInverse());
|
||||
entity.doWithAssociations(handler);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
static class TypeAndPath {
|
||||
|
||||
TypeInformation<?> type;
|
||||
String path;
|
||||
}
|
||||
|
||||
@ToString
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
static class DefaultPersistentPropertyPaths<T, P extends PersistentProperty<P>>
|
||||
implements PersistentPropertyPaths<T, P> {
|
||||
|
||||
private static final Comparator<PersistentPropertyPath<?>> SHORTEST_PATH = Comparator
|
||||
.comparingInt(PersistentPropertyPath::getLength);
|
||||
|
||||
private final TypeInformation<T> type;
|
||||
private final Iterable<PersistentPropertyPath<P>> paths;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultPersistentPropertyPaths} instance
|
||||
*
|
||||
* @param type
|
||||
* @param paths
|
||||
* @return
|
||||
*/
|
||||
static <T, P extends PersistentProperty<P>> PersistentPropertyPaths<T, P> of(TypeInformation<T> type,
|
||||
Collection<PersistentPropertyPath<P>> paths) {
|
||||
|
||||
List<PersistentPropertyPath<P>> sorted = new ArrayList<>(paths);
|
||||
|
||||
Collections.sort(sorted, SHORTEST_PATH.thenComparing(ShortestSegmentFirst.INSTANCE));
|
||||
|
||||
return new DefaultPersistentPropertyPaths<>(type, sorted);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.PersistentPropertyPaths#getFirst()
|
||||
*/
|
||||
@Override
|
||||
public Optional<PersistentPropertyPath<P>> getFirst() {
|
||||
return isEmpty() ? Optional.empty() : Optional.of(iterator().next());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.PersistentPropertyPaths#contains(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(String path) {
|
||||
return contains(PropertyPath.from(path, type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.PersistentPropertyPaths#contains(org.springframework.data.mapping.PropertyPath)
|
||||
*/
|
||||
@Override
|
||||
public boolean contains(PropertyPath path) {
|
||||
|
||||
Assert.notNull(path, "PropertyPath must not be null!");
|
||||
|
||||
if (!path.getOwningType().equals(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String dotPath = path.toDotPath();
|
||||
|
||||
return stream().anyMatch(it -> dotPath.equals(it.toDotPath()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<PersistentPropertyPath<P>> iterator() {
|
||||
return paths.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple {@link Comparator} to sort {@link PersistentPropertyPath} instances by their property segment's name
|
||||
* length.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
private static enum ShortestSegmentFirst
|
||||
implements Comparator<PersistentPropertyPath<? extends PersistentProperty<?>>> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public int compare(PersistentPropertyPath<?> left, PersistentPropertyPath<?> right) {
|
||||
|
||||
Function<PersistentProperty<?>, Integer> mapper = it -> it.getName().length();
|
||||
|
||||
Stream<Integer> leftNames = left.stream().map(mapper);
|
||||
Stream<Integer> rightNames = right.stream().map(mapper);
|
||||
|
||||
return StreamUtils.zip(leftNames, rightNames, (l, r) -> l - r) //
|
||||
.filter(it -> it != 0) //
|
||||
.findFirst() //
|
||||
.orElse(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,13 +130,12 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
return information;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.PersistentProperty#getPersistentEntityType()
|
||||
* @see org.springframework.data.mapping.PersistentProperty#getPersistentEntityTypes()
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public Iterable<? extends TypeInformation<?>> getPersistentEntityType() {
|
||||
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
|
||||
|
||||
if (!isEntity()) {
|
||||
return Collections.emptySet();
|
||||
|
||||
@@ -217,10 +217,9 @@ public class RepositoryComposition {
|
||||
*/
|
||||
public void validateImplementation() {
|
||||
|
||||
fragments.stream()
|
||||
.forEach(it -> it.getImplementation() //
|
||||
.orElseThrow(() -> new IllegalStateException(String.format("Fragment %s has no implementation.",
|
||||
ClassUtils.getQualifiedName(it.getSignatureContributor())))));
|
||||
fragments.stream().forEach(it -> it.getImplementation() //
|
||||
.orElseThrow(() -> new IllegalStateException(String.format("Fragment %s has no implementation.",
|
||||
ClassUtils.getQualifiedName(it.getSignatureContributor())))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,15 +319,6 @@ public class RepositoryComposition {
|
||||
return from(Stream.concat(left, right).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@literal true} if this {@link RepositoryFragments} contains no {@link RepositoryFragment fragments}.
|
||||
*
|
||||
* @return {@literal true} if this {@link RepositoryFragments} contains no {@link RepositoryFragment fragments}.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return fragments.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
@@ -338,14 +328,6 @@ public class RepositoryComposition {
|
||||
return fragments.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Stream} of {@link RepositoryFragment fragments}.
|
||||
*/
|
||||
@Override
|
||||
public Stream<RepositoryFragment<?>> stream() {
|
||||
return fragments.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Stream} of {@link Method methods}.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,9 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.Spliterators.AbstractSpliterator;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
@@ -107,4 +110,39 @@ public interface StreamUtils {
|
||||
public static <T> Stream<T> fromNullable(@Nullable T source) {
|
||||
return source == null ? Stream.empty() : Stream.of(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zips the given {@link Stream}s using the given {@link BiFunction}. The resulting {@link Stream} will have the
|
||||
* length of the shorter of the two, abbreviating the zipping when the shorter of the two {@link Stream}s is
|
||||
* exhausted.
|
||||
*
|
||||
* @param left must not be {@literal null}.
|
||||
* @param right must not be {@literal null}.
|
||||
* @param combiner must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
public static <L, R, T> Stream<T> zip(Stream<L> left, Stream<R> right, BiFunction<L, R, T> combiner) {
|
||||
|
||||
Assert.notNull(left, "Left stream must not be null!");
|
||||
Assert.notNull(right, "Right must not be null!");
|
||||
Assert.notNull(combiner, "Combiner must not be null!");
|
||||
|
||||
Spliterator<L> lefts = left.spliterator();
|
||||
Spliterator<R> rights = right.spliterator();
|
||||
|
||||
long size = Long.min(lefts.estimateSize(), rights.estimateSize());
|
||||
int characteristics = lefts.characteristics() & rights.characteristics();
|
||||
boolean parallel = left.isParallel() || right.isParallel();
|
||||
|
||||
return StreamSupport.stream(new AbstractSpliterator<T>(size, characteristics) {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("null")
|
||||
public boolean tryAdvance(Consumer<? super T> action) {
|
||||
return lefts.tryAdvance(left -> rights.tryAdvance(right -> action.accept(combiner.apply(left, right))));
|
||||
}
|
||||
|
||||
}, parallel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,13 @@ public interface Streamable<T> extends Iterable<T> {
|
||||
|
||||
return Streamable.of(() -> stream().filter(predicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link Streamable} is empty.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default boolean isEmpty() {
|
||||
return !iterator().hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user