1
16 package org.springframework.data.mapping.model;
17
18 import lombok.NonNull;
19 import lombok.RequiredArgsConstructor;
20
21 import java.io.Serializable;
22 import java.lang.annotation.Annotation;
23 import java.util.*;
24 import java.util.stream.Collectors;
25
26 import org.springframework.core.annotation.AnnotatedElementUtils;
27 import org.springframework.data.annotation.Immutable;
28 import org.springframework.data.annotation.TypeAlias;
29 import org.springframework.data.domain.Persistable;
30 import org.springframework.data.mapping.*;
31 import org.springframework.data.spel.EvaluationContextProvider;
32 import org.springframework.data.support.IsNewStrategy;
33 import org.springframework.data.support.PersistableIsNewStrategy;
34 import org.springframework.data.util.Lazy;
35 import org.springframework.data.util.TypeInformation;
36 import org.springframework.expression.EvaluationContext;
37 import org.springframework.lang.Nullable;
38 import org.springframework.util.Assert;
39 import org.springframework.util.CollectionUtils;
40 import org.springframework.util.ConcurrentReferenceHashMap;
41 import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
42 import org.springframework.util.MultiValueMap;
43 import org.springframework.util.StringUtils;
44
45
55 public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implements MutablePersistentEntity<T, P> {
56
57 private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!";
58
59 private final @Nullable PreferredConstructor<T, P> constructor;
60 private final TypeInformation<T> information;
61 private final List<P> properties;
62 private final List<P> persistentPropertiesCache;
63 private final @Nullable Comparator<P> comparator;
64 private final Set<Association<P>> associations;
65
66 private final Map<String, P> propertyCache;
67 private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
68 private final MultiValueMap<Class<? extends Annotation>, P> propertyAnnotationCache;
69
70 private @Nullable P idProperty;
71 private @Nullable P versionProperty;
72 private PersistentPropertyAccessorFactory propertyAccessorFactory;
73 private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
74
75 private final Lazy<Alias> typeAlias;
76 private final Lazy<IsNewStrategy> isNewStrategy;
77 private final Lazy<Boolean> isImmutable;
78 private final Lazy<Boolean> requiresPropertyPopulation;
79
80
85 public BasicPersistentEntity(TypeInformation<T> information) {
86 this(information, null);
87 }
88
89
97 public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparator<P> comparator) {
98
99 Assert.notNull(information, "Information must not be null!");
100
101 this.information = information;
102 this.properties = new ArrayList<>();
103 this.persistentPropertiesCache = new ArrayList<>();
104 this.comparator = comparator;
105 this.constructor = PreferredConstructorDiscoverer.discover(this);
106 this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
107
108 this.propertyCache = new HashMap<>(16, 1f);
109 this.annotationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
110 this.propertyAnnotationCache = CollectionUtils
111 .toMultiValueMap(new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK));
112 this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE;
113 this.typeAlias = Lazy.of(() -> getAliasFromAnnotation(getType()));
114 this.isNewStrategy = Lazy.of(() -> Persistable.class.isAssignableFrom(information.getType())
115 ? PersistableIsNewStrategy.INSTANCE
116 : getFallbackIsNewStrategy());
117
118 this.isImmutable = Lazy.of(() -> isAnnotationPresent(Immutable.class));
119 this.requiresPropertyPopulation = Lazy.of(() -> !isImmutable() && properties.stream()
120 .anyMatch(it -> !(isConstructorArgument(it) || it.isTransient())));
121 }
122
123
127 @Nullable
128 public PreferredConstructor<T, P> getPersistenceConstructor() {
129 return constructor;
130 }
131
132
136 public boolean isConstructorArgument(PersistentProperty<?> property) {
137 return constructor != null && constructor.isConstructorParameter(property);
138 }
139
140
144 public boolean isIdProperty(PersistentProperty<?> property) {
145 return idProperty != null && idProperty.equals(property);
146 }
147
148
152 public boolean isVersionProperty(PersistentProperty<?> property) {
153 return versionProperty != null && versionProperty.equals(property);
154 }
155
156
160 public String getName() {
161 return getType().getName();
162 }
163
164
168 @Nullable
169 public P getIdProperty() {
170 return idProperty;
171 }
172
173
177 @Nullable
178 public P getVersionProperty() {
179 return versionProperty;
180 }
181
182
186 public boolean hasIdProperty() {
187 return idProperty != null;
188 }
189
190
194 public boolean hasVersionProperty() {
195 return versionProperty != null;
196 }
197
198
202 public void addPersistentProperty(P property) {
203
204 Assert.notNull(property, "Property must not be null!");
205
206 if (properties.contains(property)) {
207 return;
208 }
209
210 properties.add(property);
211
212 if (!property.isTransient() && !property.isAssociation()) {
213 persistentPropertiesCache.add(property);
214 }
215
216 propertyCache.computeIfAbsent(property.getName(), key -> property);
217
218 P candidate = returnPropertyIfBetterIdPropertyCandidateOrNull(property);
219
220 if (candidate != null) {
221 this.idProperty = candidate;
222 }
223
224 if (property.isVersionProperty()) {
225
226 P versionProperty = this.versionProperty;
227
228 if (versionProperty != null) {
229
230 throw new MappingException(
231 String.format(
232 "Attempt to add version property %s but already have property %s registered "
233 + "as version. Check your mapping configuration!",
234 property.getField(), versionProperty.getField()));
235 }
236
237 this.versionProperty = property;
238 }
239 }
240
241
245 @Override
246 public void setEvaluationContextProvider(EvaluationContextProvider provider) {
247 this.evaluationContextProvider = provider;
248 }
249
250
256 @Nullable
257 protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) {
258
259 if (!property.isIdProperty()) {
260 return null;
261 }
262
263 P idProperty = this.idProperty;
264
265 if (idProperty != null) {
266 throw new MappingException(String.format("Attempt to add id property %s but already have property %s registered "
267 + "as id. Check your mapping configuration!", property.getField(), idProperty.getField()));
268 }
269
270 return property;
271 }
272
273
277 public void addAssociation(Association<P> association) {
278
279 Assert.notNull(association, "Association must not be null!");
280
281 associations.add(association);
282 }
283
284
288 @Override
289 @Nullable
290 public P getPersistentProperty(String name) {
291 return propertyCache.get(name);
292 }
293
294
298 @Override
299 public Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType) {
300
301 Assert.notNull(annotationType, "Annotation type must not be null!");
302 return propertyAnnotationCache.computeIfAbsent(annotationType, this::doFindPersistentProperty);
303 }
304
305 private List<P> doFindPersistentProperty(Class<? extends Annotation> annotationType) {
306
307 List<P> annotatedProperties = properties.stream()
308 .filter(it -> it.isAnnotationPresent(annotationType))
309 .collect(Collectors.toList());
310
311 if (!annotatedProperties.isEmpty()) {
312 return annotatedProperties;
313 }
314
315 return associations.stream()
316 .map(Association::getInverse)
317 .filter(it -> it.isAnnotationPresent(annotationType)).collect(Collectors.toList());
318 }
319
320
324 public Class<T> getType() {
325 return information.getType();
326 }
327
328
332 public Alias getTypeAlias() {
333 return typeAlias.get();
334 }
335
336
340 public TypeInformation<T> getTypeInformation() {
341 return information;
342 }
343
344
348 public void doWithProperties(PropertyHandler<P> handler) {
349
350 Assert.notNull(handler, "PropertyHandler must not be null!");
351
352 for (P property : persistentPropertiesCache) {
353 handler.doWithPersistentProperty(property);
354 }
355 }
356
357
361 @Override
362 public void doWithProperties(SimplePropertyHandler handler) {
363
364 Assert.notNull(handler, "Handler must not be null!");
365
366 for (PersistentProperty<?> property : persistentPropertiesCache) {
367 handler.doWithPersistentProperty(property);
368 }
369 }
370
371
375 public void doWithAssociations(AssociationHandler<P> handler) {
376
377 Assert.notNull(handler, "Handler must not be null!");
378
379 for (Association<P> association : associations) {
380 handler.doWithAssociation(association);
381 }
382 }
383
384
388 public void doWithAssociations(SimpleAssociationHandler handler) {
389
390 Assert.notNull(handler, "Handler must not be null!");
391
392 for (Association<? extends PersistentProperty<?>> association : associations) {
393 handler.doWithAssociation(association);
394 }
395 }
396
397
401 @Nullable
402 @Override
403 public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
404 return doFindAnnotation(annotationType).orElse(null);
405 }
406
407
411 @Override
412 public <A extends Annotation> boolean isAnnotationPresent(Class<A> annotationType) {
413 return doFindAnnotation(annotationType).isPresent();
414 }
415
416 @SuppressWarnings("unchecked")
417 private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
418
419 return (Optional<A>) annotationCache.computeIfAbsent(annotationType,
420 it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), it)));
421 }
422
423
427 public void verify() {
428
429 if (comparator != null) {
430 properties.sort(comparator);
431 persistentPropertiesCache.sort(comparator);
432 }
433 }
434
435
439 @Override
440 public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
441 this.propertyAccessorFactory = factory;
442 }
443
444
448 @Override
449 public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
450
451 verifyBeanType(bean);
452
453 return propertyAccessorFactory.getPropertyAccessor(this, bean);
454 }
455
456
460 @Override
461 public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
462 return new SimplePersistentPropertyPathAccessor<>(getPropertyAccessor(bean));
463 }
464
465
469 @Override
470 public IdentifierAccessor getIdentifierAccessor(Object bean) {
471
472 verifyBeanType(bean);
473
474 if (Persistable.class.isAssignableFrom(getType())) {
475 return new PersistableIdentifierAccessor((Persistable<?>) bean);
476 }
477
478 return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean);
479 }
480
481
485 @Override
486 public boolean isNew(Object bean) {
487
488 verifyBeanType(bean);
489
490 return isNewStrategy.get().isNew(bean);
491 }
492
493
497 @Override
498 public boolean isImmutable() {
499 return isImmutable.get();
500 }
501
502
506 @Override
507 public boolean requiresPropertyPopulation() {
508 return requiresPropertyPopulation.get();
509 }
510
511
515 @Override
516 public Iterator<P> iterator() {
517
518 Iterator<P> iterator = properties.iterator();
519
520 return new Iterator<P>() {
521
522 @Override
523 public boolean hasNext() {
524 return iterator.hasNext();
525 }
526
527 @Override
528 public P next() {
529 return iterator.next();
530 }
531 };
532 }
533
534 protected EvaluationContext getEvaluationContext(Object rootObject) {
535 return evaluationContextProvider.getEvaluationContext(rootObject);
536 }
537
538
546 protected IsNewStrategy getFallbackIsNewStrategy() {
547 return PersistentEntityIsNewStrategy.of(this);
548 }
549
550
555 private final void verifyBeanType(Object bean) {
556
557 Assert.notNull(bean, "Target bean must not be null!");
558 Assert.isInstanceOf(getType(), bean,
559 () -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
560 }
561
562
568 private static Alias getAliasFromAnnotation(Class<?> type) {
569
570 Optional<String> typeAliasValue = Optional
571 .ofNullable(AnnotatedElementUtils.findMergedAnnotation(type, TypeAlias.class))
572 .map(TypeAlias::value)
573 .filter(StringUtils::hasText);
574
575 return Alias.ofNullable(typeAliasValue.orElse(null));
576 }
577
578
584 private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor {
585
586 public AbsentIdentifierAccessor(Object target) {
587 super(target);
588 }
589
590
594 @Override
595 @Nullable
596 public Object getIdentifier() {
597 return null;
598 }
599 }
600
601
606 @RequiredArgsConstructor
607 private static final class AssociationComparator<P extends PersistentProperty<P>>
608 implements Comparator<Association<P>>, Serializable {
609
610 private static final long serialVersionUID = 4508054194886854513L;
611 private final @NonNull Comparator<P> delegate;
612
613
617 public int compare(@Nullable Association<P> left, @Nullable Association<P> right) {
618
619 if (left == null) {
620 throw new IllegalArgumentException("Left argument must not be null!");
621 }
622
623 if (right == null) {
624 throw new IllegalArgumentException("Right argument must not be null!");
625 }
626
627 return delegate.compare(left.getInverse(), right.getInverse());
628 }
629 }
630 }
631