1 /*
2  * Copyright 2018-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

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 /**
56  * A factory implementation to create {@link PersistentPropertyPath} instances in various ways.
57  * 
58  * @author Oliver Gierke
59  * @since 2.1
60  * @soundtrack Cypress Hill - Boom Biddy Bye Bye (Fugees Remix, Unreleased & Revamped)
61  */

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     /**
71      * Creates a new {@link PersistentPropertyPath} for the given property path on the given type.
72      * 
73      * @param type must not be {@literal null}.
74      * @param propertyPath must not be {@literal null}.
75      * @return
76      */

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     /**
86      * Creates a new {@link PersistentPropertyPath} for the given property path on the given type.
87      * 
88      * @param type must not be {@literal null}.
89      * @param propertyPath must not be {@literal null}.
90      * @return
91      */

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     /**
101      * Creates a new {@link PersistentPropertyPath} for the given {@link PropertyPath}.
102      * 
103      * @param path must not be {@literal null}.
104      * @return
105      */

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     /**
114      * Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
115      * matching it.
116      * 
117      * @param type must not be {@literal null}.
118      * @param propertyFilter must not be {@literal null}.
119      * @return
120      */

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     /**
130      * Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
131      * matching it.
132      * 
133      * @param type must not be {@literal null}.
134      * @param propertyFilter must not be {@literal null}.
135      * @param traversalGuard must not be {@literal null}.
136      * @return
137      */

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     /**
149      * Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
150      * matching it.
151      * 
152      * @param type must not be {@literal null}.
153      * @param propertyFilter must not be {@literal null}.
154      * @return
155      */

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     /**
161      * Creates a new {@link PersistentPropertyPath} based on a given type and {@link Predicate} to select properties
162      * matching it.
163      * 
164      * @param type must not be {@literal null}.
165      * @param propertyFilter must not be {@literal null}.
166      * @param traversalGuard must not be {@literal null}.
167      * @return
168      */

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     /**
187      * Creates a {@link PersistentPropertyPath} for the given parts and {@link TypeInformation}.
188      *
189      * @param propertyPath must not be {@literal null}.
190      * @param type must not be {@literal null}.
191      * @return
192      */

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         /**
299          * Creates a new {@link DefaultPersistentPropertyPaths} instance
300          * 
301          * @param type
302          * @param paths
303          * @return
304          */

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         /* 
316          * (non-Javadoc)
317          * @see org.springframework.data.mapping.PersistentPropertyPaths#getFirst()
318          */

319         @Override
320         public Optional<PersistentPropertyPath<P>> getFirst() {
321             return isEmpty() ? Optional.empty() : Optional.of(iterator().next());
322         }
323
324         /* 
325          * (non-Javadoc)
326          * @see org.springframework.data.mapping.PersistentPropertyPaths#contains(java.lang.String)
327          */

328         @Override
329         public boolean contains(String path) {
330             return contains(PropertyPath.from(path, type));
331         }
332
333         /* 
334          * (non-Javadoc)
335          * @see org.springframework.data.mapping.PersistentPropertyPaths#contains(org.springframework.data.mapping.PropertyPath)
336          */

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         /* 
352          * (non-Javadoc)
353          * @see java.lang.Iterable#iterator()
354          */

355         @Override
356         public Iterator<PersistentPropertyPath<P>> iterator() {
357             return paths.iterator();
358         }
359
360         /*
361          * (non-Javadoc)
362          * @see org.springframework.data.mapping.PersistentPropertyPaths#dropPathIfSegmentMatches(java.util.function.Predicate)
363          */

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         /**
377          * Simple {@link Comparator} to sort {@link PersistentPropertyPath} instances by their property segment's name
378          * length.
379          * 
380          * @author Oliver Gierke
381          * @since 2.1
382          */

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