1
16 package org.springframework.data.mapping.context;
17
18 import lombok.AccessLevel;
19 import lombok.RequiredArgsConstructor;
20 import lombok.ToString;
21 import lombok.Value;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.Set;
34 import java.util.function.Function;
35 import java.util.function.Predicate;
36 import java.util.stream.Collectors;
37 import java.util.stream.Stream;
38
39 import org.springframework.data.mapping.AssociationHandler;
40 import org.springframework.data.mapping.PersistentEntity;
41 import org.springframework.data.mapping.PersistentProperty;
42 import org.springframework.data.mapping.PersistentPropertyPath;
43 import org.springframework.data.mapping.PersistentPropertyPaths;
44 import org.springframework.data.mapping.PropertyHandler;
45 import org.springframework.data.mapping.PropertyPath;
46 import org.springframework.data.util.ClassTypeInformation;
47 import org.springframework.data.util.Pair;
48 import org.springframework.data.util.StreamUtils;
49 import org.springframework.data.util.TypeInformation;
50 import org.springframework.lang.Nullable;
51 import org.springframework.util.Assert;
52 import org.springframework.util.ConcurrentReferenceHashMap;
53 import org.springframework.util.StringUtils;
54
55
62 @RequiredArgsConstructor
63 class PersistentPropertyPathFactory<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>> {
64
65 private static final Predicate<PersistentProperty<? extends PersistentProperty<?>>> IS_ENTITY = it -> it.isEntity();
66
67 private final Map<TypeAndPath, PersistentPropertyPath<P>> propertyPaths = new ConcurrentReferenceHashMap<>();
68 private final MappingContext<E, P> context;
69
70
77 public PersistentPropertyPath<P> from(Class<?> type, String propertyPath) {
78
79 Assert.notNull(type, "Type must not be null!");
80 Assert.notNull(propertyPath, "Property path must not be null!");
81
82 return getPersistentPropertyPath(ClassTypeInformation.from(type), propertyPath);
83 }
84
85
92 public PersistentPropertyPath<P> from(TypeInformation<?> type, String propertyPath) {
93
94 Assert.notNull(type, "Type must not be null!");
95 Assert.notNull(propertyPath, "Property path must not be null!");
96
97 return getPersistentPropertyPath(type, propertyPath);
98 }
99
100
106 public PersistentPropertyPath<P> from(PropertyPath path) {
107
108 Assert.notNull(path, "Property path must not be null!");
109
110 return from(path.getOwningType(), path.toDotPath());
111 }
112
113
121 public <T> PersistentPropertyPaths<T, P> from(Class<T> type, Predicate<? super P> propertyFilter) {
122
123 Assert.notNull(type, "Type must not be null!");
124 Assert.notNull(propertyFilter, "Property filter must not be null!");
125
126 return from(ClassTypeInformation.from(type), propertyFilter);
127 }
128
129
138 public <T> PersistentPropertyPaths<T, P> from(Class<T> type, Predicate<? super P> propertyFilter,
139 Predicate<P> traversalGuard) {
140
141 Assert.notNull(type, "Type must not be null!");
142 Assert.notNull(propertyFilter, "Property filter must not be null!");
143 Assert.notNull(traversalGuard, "Traversal guard must not be null!");
144
145 return from(ClassTypeInformation.from(type), propertyFilter, traversalGuard);
146 }
147
148
156 public <T> PersistentPropertyPaths<T, P> from(TypeInformation<T> type, Predicate<? super P> propertyFilter) {
157 return from(type, propertyFilter, it -> !it.isAssociation());
158 }
159
160
169 public <T> PersistentPropertyPaths<T, P> from(TypeInformation<T> type, Predicate<? super P> propertyFilter,
170 Predicate<P> traversalGuard) {
171
172 Assert.notNull(type, "Type must not be null!");
173 Assert.notNull(propertyFilter, "Property filter must not be null!");
174 Assert.notNull(traversalGuard, "Traversal guard must not be null!");
175
176 return DefaultPersistentPropertyPaths.of(type,
177 from(type, propertyFilter, traversalGuard, DefaultPersistentPropertyPath.empty()));
178 }
179
180 private PersistentPropertyPath<P> getPersistentPropertyPath(TypeInformation<?> type, String propertyPath) {
181
182 return propertyPaths.computeIfAbsent(TypeAndPath.of(type, propertyPath),
183 it -> createPersistentPropertyPath(it.getPath(), it.getType()));
184 }
185
186
193 private PersistentPropertyPath<P> createPersistentPropertyPath(String propertyPath, TypeInformation<?> type) {
194
195 String trimmedPath = propertyPath.trim();
196
197 List<String> parts = trimmedPath.isEmpty()
198 ? Collections.emptyList()
199 : Arrays.asList(trimmedPath.split("\\."));
200
201 DefaultPersistentPropertyPath<P> path = DefaultPersistentPropertyPath.empty();
202 Iterator<String> iterator = parts.iterator();
203 E current = context.getRequiredPersistentEntity(type);
204
205 while (iterator.hasNext()) {
206
207 String segment = iterator.next();
208 final DefaultPersistentPropertyPath<P> currentPath = path;
209
210 Pair<DefaultPersistentPropertyPath<P>, E> pair = getPair(path, iterator, segment, current);
211
212 if (pair == null) {
213
214 String source = StringUtils.collectionToDelimitedString(parts, ".");
215
216 throw new InvalidPersistentPropertyPath(source, type, segment, currentPath);
217 }
218
219 path = pair.getFirst();
220 current = pair.getSecond();
221 }
222
223 return path;
224 }
225
226 @Nullable
227 private Pair<DefaultPersistentPropertyPath<P>, E> getPair(DefaultPersistentPropertyPath<P> path,
228 Iterator<String> iterator, String segment, E entity) {
229
230 P property = entity.getPersistentProperty(segment);
231
232 if (property == null) {
233 return null;
234 }
235
236 TypeInformation<?> type = property.getTypeInformation().getRequiredActualType();
237 return Pair.of(path.append(property), iterator.hasNext() ? context.getRequiredPersistentEntity(type) : entity);
238 }
239
240 private <T> Collection<PersistentPropertyPath<P>> from(TypeInformation<T> type, Predicate<? super P> filter,
241 Predicate<P> traversalGuard, DefaultPersistentPropertyPath<P> basePath) {
242
243 TypeInformation<?> actualType = type.getActualType();
244
245 if (actualType == null) {
246 return Collections.emptyList();
247 }
248
249 E entity = context.getRequiredPersistentEntity(actualType);
250 Set<PersistentPropertyPath<P>> properties = new HashSet<>();
251
252 PropertyHandler<P> propertyTester = persistentProperty -> {
253
254 TypeInformation<?> typeInformation = persistentProperty.getTypeInformation();
255 TypeInformation<?> actualPropertyType = typeInformation.getActualType();
256
257 if (basePath.containsPropertyOfType(actualPropertyType)) {
258 return;
259 }
260
261 DefaultPersistentPropertyPath<P> currentPath = basePath.append(persistentProperty);
262
263 if (filter.test(persistentProperty)) {
264 properties.add(currentPath);
265 }
266
267 if (traversalGuard.and(IS_ENTITY).test(persistentProperty)) {
268 properties.addAll(from(typeInformation, filter, traversalGuard, currentPath));
269 }
270 };
271
272 entity.doWithProperties(propertyTester);
273
274 AssociationHandler<P> handler = association -> propertyTester.doWithPersistentProperty(association.getInverse());
275 entity.doWithAssociations(handler);
276
277 return properties;
278 }
279
280 @Value(staticConstructor = "of")
281 static class TypeAndPath {
282
283 TypeInformation<?> type;
284 String path;
285 }
286
287 @ToString
288 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
289 static class DefaultPersistentPropertyPaths<T, P extends PersistentProperty<P>>
290 implements PersistentPropertyPaths<T, P> {
291
292 private static final Comparator<PersistentPropertyPath<? extends PersistentProperty<?>>> SHORTEST_PATH = Comparator
293 .comparingInt(PersistentPropertyPath::getLength);
294
295 private final TypeInformation<T> type;
296 private final Iterable<PersistentPropertyPath<P>> paths;
297
298
305 static <T, P extends PersistentProperty<P>> PersistentPropertyPaths<T, P> of(TypeInformation<T> type,
306 Collection<PersistentPropertyPath<P>> paths) {
307
308 List<PersistentPropertyPath<P>> sorted = new ArrayList<>(paths);
309
310 Collections.sort(sorted, SHORTEST_PATH.thenComparing(ShortestSegmentFirst.INSTANCE));
311
312 return new DefaultPersistentPropertyPaths<>(type, sorted);
313 }
314
315
319 @Override
320 public Optional<PersistentPropertyPath<P>> getFirst() {
321 return isEmpty() ? Optional.empty() : Optional.of(iterator().next());
322 }
323
324
328 @Override
329 public boolean contains(String path) {
330 return contains(PropertyPath.from(path, type));
331 }
332
333
337 @Override
338 public boolean contains(PropertyPath path) {
339
340 Assert.notNull(path, "PropertyPath must not be null!");
341
342 if (!path.getOwningType().equals(type)) {
343 return false;
344 }
345
346 String dotPath = path.toDotPath();
347
348 return stream().anyMatch(it -> dotPath.equals(it.toDotPath()));
349 }
350
351
355 @Override
356 public Iterator<PersistentPropertyPath<P>> iterator() {
357 return paths.iterator();
358 }
359
360
364 @Override
365 public PersistentPropertyPaths<T, P> dropPathIfSegmentMatches(Predicate<? super P> predicate) {
366
367 Assert.notNull(predicate, "Predicate must not be null!");
368
369 List<PersistentPropertyPath<P>> paths = this.stream()
370 .filter(it -> !it.stream().anyMatch(predicate))
371 .collect(Collectors.toList());
372
373 return paths.equals(this.paths) ? this : new DefaultPersistentPropertyPaths<>(type, paths);
374 }
375
376
383 private static enum ShortestSegmentFirst
384 implements Comparator<PersistentPropertyPath<? extends PersistentProperty<?>>> {
385
386 INSTANCE;
387
388 @Override
389 @SuppressWarnings("null")
390 public int compare(PersistentPropertyPath<?> left, PersistentPropertyPath<?> right) {
391
392 Function<PersistentProperty<?>, Integer> mapper = it -> it.getName().length();
393
394 Stream<Integer> leftNames = left.stream().map(mapper);
395 Stream<Integer> rightNames = right.stream().map(mapper);
396
397 return StreamUtils.zip(leftNames, rightNames, (l, r) -> l - r)
398 .filter(it -> it != 0)
399 .findFirst()
400 .orElse(0);
401 }
402 }
403 }
404 }
405